]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
Get rid of create_dir
authorPavel Cahyna <pcahyna@redhat.com>
Mon, 22 Jun 2026 15:18:02 +0000 (17:18 +0200)
committerPaul Eggert <eggert@cs.ucla.edu>
Thu, 30 Jul 2026 06:12:46 +0000 (23:12 -0700)
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.

src/common.h
src/extract.c
src/misc.c

index 84d54114c3840361bde808ee903115248bb05593..2a153611e0490cd6b168b928df26266c2ce2ff0c 100644 (file)
@@ -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);
index 90cc2310f8b6cfd6873af21430047a023fa2df90..eb4010a85600c89a34c85df8b84521df8eef1b0c 100644 (file)
@@ -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;
index bdb458a34ef40ed6c0bafde3a412726b58c181e2..df77323269a99a75fcf0692d467ff5283c295378 100644 (file)
@@ -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);