mirror of
https://git.savannah.gnu.org/git/tar.git
synced 2026-08-16 22:36:05 +00:00
Get rid of create_dir
It turns out that make_directories can do what we need (create all directories in a path) if we append a dummy "." component, as it creates directories up to and excluding the last component of the path. Also, do not avoid using delay_set_stat on the newly created directories. This matches the rest of the code and avoids potentially leaving the newly-created directories with too open permissions. This requires some workarounds to cope with apply_nonancestor_delayed_set_stat and mark_metadata_set problems: do it as in the rest of the code - apply the stats before proceeding with extraction of anything else.
This commit is contained in:
committed by
Paul Eggert
parent
325b899214
commit
1980e032af
+1
-1
@@ -558,7 +558,7 @@ void verify_volume (void);
|
||||
extern dev_t root_device;
|
||||
|
||||
void extr_init (void);
|
||||
bool create_dir (char const *file_name);
|
||||
int make_directories (char *file_name, bool *interdir_made);
|
||||
void extract_archive (void);
|
||||
void extract_finish (void);
|
||||
bool rename_directory (char *src, char *dst);
|
||||
|
||||
+22
-76
@@ -216,8 +216,6 @@ static Hash_table *delayed_link_table;
|
||||
static struct delayed_link *delayed_link_head;
|
||||
static struct delayed_link **delayed_link_tail = &delayed_link_head;
|
||||
|
||||
static bool one_top_level_prepare = false;
|
||||
|
||||
struct string_list
|
||||
{
|
||||
struct string_list *next;
|
||||
@@ -776,7 +774,7 @@ fixup_delayed_set_stat (char const *src, char const *dst)
|
||||
create all required directories. Return zero if all the required
|
||||
directories were created, nonzero (issuing a diagnostic) otherwise.
|
||||
Set *INTERDIR_MADE (unless NULL) if at least one directory was created. */
|
||||
static int
|
||||
int
|
||||
make_directories (char *file_name, bool *interdir_made)
|
||||
{
|
||||
char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
|
||||
@@ -817,10 +815,9 @@ make_directories (char *file_name, bool *interdir_made)
|
||||
/* Create a struct delayed_set_stat even if
|
||||
mode == desired_mode, because
|
||||
repair_delayed_set_stat may need to update the struct. */
|
||||
if (! one_top_level_prepare)
|
||||
delay_set_stat (file_name,
|
||||
NULL, mode & ~ current_umask, MODE_RWX,
|
||||
desired_mode, AT_SYMLINK_NOFOLLOW);
|
||||
delay_set_stat (file_name,
|
||||
NULL, mode & ~ current_umask, MODE_RWX,
|
||||
desired_mode, AT_SYMLINK_NOFOLLOW);
|
||||
if (interdir_made)
|
||||
*interdir_made = true;
|
||||
print_for_mkdir (file_name, desired_mode);
|
||||
@@ -1132,73 +1129,6 @@ safe_dir_mode (struct stat const *st)
|
||||
| (we_are_root ? 0 : MODE_WXUSR));
|
||||
}
|
||||
|
||||
/* Trimmed version of extract_dir, to create a dir that is not in the
|
||||
archive, including parents. Should behave like extract_dir when
|
||||
NO_OVERWRITE_DIR_OLD_FILES is set in order to avoid changing existing
|
||||
paths if they are in the way.
|
||||
*/
|
||||
bool
|
||||
create_dir (char const *file_name)
|
||||
{
|
||||
int status;
|
||||
mode_t mode;
|
||||
bool interdir_made = false;
|
||||
/* exists only to avoid passing a const pointer to make_directories */
|
||||
char *unconst_file_name;
|
||||
|
||||
mode = MODE_RWX & ~ newdir_umask;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
struct fdbase f = fdbase (file_name);
|
||||
status = f.fd == BADFD ? -1 : mkdirat (f.fd, f.base, mode);
|
||||
if (status == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (errno == EEXIST)
|
||||
{
|
||||
struct stat st;
|
||||
st.st_mode = 0;
|
||||
|
||||
if (is_directory_link (file_name, &st))
|
||||
return true;
|
||||
|
||||
if ((st.st_mode != 0 && fstatat_flags == 0)
|
||||
|| deref_stat (file_name, &st) == 0)
|
||||
{
|
||||
if (S_ISDIR (st.st_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
errno = EEXIST;
|
||||
break;
|
||||
}
|
||||
else if (errno != ENOENT || interdir_made)
|
||||
{
|
||||
/* The error is not due to missing parent, or we already
|
||||
tried to make the parent directories and succeeded, so
|
||||
there must be another problem. No point in retrying. */
|
||||
break;
|
||||
}
|
||||
unconst_file_name = xstrdup (file_name);
|
||||
if (make_directories (unconst_file_name, &interdir_made) == 0)
|
||||
{
|
||||
free (unconst_file_name);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
free (unconst_file_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mkdir_error (file_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Extractor functions for various member types */
|
||||
|
||||
static bool
|
||||
@@ -1992,9 +1922,25 @@ extract_archive (void)
|
||||
if (one_top_level_dir)
|
||||
{
|
||||
/* Create one_top_level dir if it does not exist. */
|
||||
one_top_level_prepare = true;
|
||||
chdir_do (chdir_current, true);
|
||||
one_top_level_prepare = false;
|
||||
/* Flush delayed stat to mirror the code above that does it
|
||||
before extracting a new entry. Creating the one_top_level
|
||||
dir may have created new delayed_set_stat interdir
|
||||
entries, so repeat the operation. Ideally this should not
|
||||
be needed, but the newly-created interdir entries have
|
||||
st_dev/st_ino uninitialized, which would be a problem if
|
||||
there is a "." entry afterwards:
|
||||
apply_nonancestor_delayed_set_stat would use the
|
||||
uninitialized values. Ideally, st_dev/st_ino would be
|
||||
initialized by mark_metadata_set, but this one does not
|
||||
take chdir into account, so it stats a wrong file. */
|
||||
if (!delay_directory_restore_option)
|
||||
{
|
||||
idx_t dir = chdir_current;
|
||||
apply_nonancestor_delayed_set_stat (current_stat_info.file_name,
|
||||
false);
|
||||
chdir_do (dir, false);
|
||||
}
|
||||
}
|
||||
if (fun (current_stat_info.file_name, typeflag))
|
||||
ok = true;
|
||||
|
||||
+12
-1
@@ -1120,16 +1120,27 @@ chdir_do (idx_t i, bool create)
|
||||
{
|
||||
if (create)
|
||||
{
|
||||
char *dir_with_dot;
|
||||
struct open_how saved_open_searchdir_how = open_searchdir_how;
|
||||
/* Don't use O_BENEATH during creation of the
|
||||
directory. The one-top-level directory is
|
||||
allowed to be given as an absolute path. */
|
||||
open_searchdir_how.resolve = 0;
|
||||
if (create_dir (curr->name))
|
||||
/* Append a dot. make_directories creates
|
||||
directories up to and excluding the last
|
||||
component of the path. So, in order to create
|
||||
"a/b", we need to pass "a/b/." to it. */
|
||||
{
|
||||
namebuf_t nbuf = namebuf_create (curr->name);
|
||||
namebuf_add_dir (nbuf, ".");
|
||||
dir_with_dot = namebuf_finish (nbuf);
|
||||
}
|
||||
if (make_directories (dir_with_dot, NULL) == 0)
|
||||
/* Directory created, retry */
|
||||
fd = openat (chdir_fd, curr->name,
|
||||
open_searchdir_how.flags & ~O_NOFOLLOW);
|
||||
open_searchdir_how = saved_open_searchdir_how;
|
||||
free (dir_with_dot);
|
||||
/* Either the creation or open failed */
|
||||
if (fd < 0)
|
||||
open_fatal (curr->name);
|
||||
|
||||
Reference in New Issue
Block a user