From: Pavel Cahyna Date: Mon, 22 Jun 2026 15:18:02 +0000 (+0200) Subject: Get rid of create_dir X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1980e032afe60c5fe0e5df573457885ece4e69d5;p=thirdparty%2Ftar.git 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. --- diff --git a/src/common.h b/src/common.h index 84d54114..2a153611 100644 --- a/src/common.h +++ b/src/common.h @@ -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); diff --git a/src/extract.c b/src/extract.c index 90cc2310..eb4010a8 100644 --- a/src/extract.c +++ b/src/extract.c @@ -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; diff --git a/src/misc.c b/src/misc.c index bdb458a3..df773232 100644 --- a/src/misc.c +++ b/src/misc.c @@ -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);