]> git.ipfire.org Git - thirdparty/tar.git/commitdiff
Fix savannah bug #63123
authorSergey Poznyakoff <gray@gnu.org>
Sat, 22 Oct 2022 09:06:45 +0000 (12:06 +0300)
committerSergey Poznyakoff <gray@gnu.org>
Sat, 22 Oct 2022 16:59:04 +0000 (19:59 +0300)
The bug was introduced by commit 79d1ac38c1, which didn't take into
account all the consequences of returning RECOVER_OK on EEXIST, in
particular interactions with the delayed_set_stat logic.

The commit 79d1ac38c1 is reverted (the bug it was intended to fix
was actually fixed by 79a442d7b0).  Instead:

* src/extract.c (maybe_recoverable): Don't call maybe_recoverable
if EEXIST is reported when UNLINK_FIRST_OLD_FILES option is set.

src/extract.c

index 78de47f56000f4514aeb53d352be65743b01790e..37ab29564d2122c5e351eff3fa249a2d523dd16e 100644 (file)
@@ -679,9 +679,10 @@ fixup_delayed_set_stat (char const *src, char const *dst)
 
 /* After a file/link/directory creation has failed due to ENOENT,
    create all required directories.  Return zero if all the required
-   directories were created, nonzero (issuing a diagnostic) otherwise.  */
+   directories were created, nonzero (issuing a diagnostic) otherwise.
+   Set *INTERDIR_MADE if at least one directory was created.  */
 static int
-make_directories (char *file_name)
+make_directories (char *file_name, bool *interdir_made)
 {
   char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
   char *cursor;                        /* points into the file name */
@@ -725,6 +726,7 @@ make_directories (char *file_name)
                          desired_mode, AT_SYMLINK_NOFOLLOW);
 
          print_for_mkdir (file_name, cursor - file_name, desired_mode);
+         *interdir_made = true;
          parent_end = NULL;
        }
       else
@@ -879,12 +881,9 @@ maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
       FALLTHROUGH;
 
     case ENOENT:
-      /* Attempt creating missing intermediate directories.  */
-      if (make_directories (file_name) == 0)
-       {
-         *interdir_made = true;
-         return RECOVER_OK;
-       }
+      /* Attempt creating missing intermediate directories. */
+      if (make_directories (file_name, interdir_made) == 0)
+       return RECOVER_OK;
       break;
 
     default:
@@ -1072,61 +1071,69 @@ extract_dir (char *file_name, int typeflag)
          break;
        }
 
-      if (errno == EEXIST
-         && (interdir_made
+      if (errno == EEXIST)
+       {
+         if (interdir_made
              || keep_directory_symlink_option
              || old_files_option == NO_OVERWRITE_DIR_OLD_FILES
              || old_files_option == DEFAULT_OLD_FILES
-             || old_files_option == OVERWRITE_OLD_FILES))
-       {
-         struct stat st;
-         st.st_mode = 0;
-
-         if (keep_directory_symlink_option
-             && is_directory_link (file_name, &st))
-           return 0;
-
-         if ((st.st_mode != 0 && fstatat_flags == 0)
-             || deref_stat (file_name, &st) == 0)
+             || old_files_option == OVERWRITE_OLD_FILES)
            {
-             current_mode = st.st_mode;
-             current_mode_mask = ALL_MODE_BITS;
+             struct stat st;
+             st.st_mode = 0;
+
+             if (keep_directory_symlink_option
+                 && is_directory_link (file_name, &st))
+               return 0;
 
-             if (S_ISDIR (current_mode))
+             if ((st.st_mode != 0 && fstatat_flags == 0)
+                 || deref_stat (file_name, &st) == 0)
                {
-                 if (interdir_made)
-                   {
-                     repair_delayed_set_stat (file_name, &st);
-                     return 0;
-                   }
-                 else if (old_files_option == NO_OVERWRITE_DIR_OLD_FILES)
+                 current_mode = st.st_mode;
+                 current_mode_mask = ALL_MODE_BITS;
+
+                 if (S_ISDIR (current_mode))
                    {
-                     /* Temporarily change the directory mode to a safe
-                        value, to be able to create files in it, should
-                        the need be.
-                     */
-                     mode = safe_dir_mode (&st);
-                     status = fd_chmod(-1, file_name, mode,
-                                       AT_SYMLINK_NOFOLLOW, DIRTYPE);
-                     if (status == 0)
+                     if (interdir_made)
                        {
-                         /* Store the actual directory mode, to be restored
-                            later.
-                         */
-                         current_stat_info.stat = st;
-                         current_mode = mode & ~ current_umask;
-                         current_mode_mask = MODE_RWX;
-                         atflag = AT_SYMLINK_NOFOLLOW;
-                         break;
+                         repair_delayed_set_stat (file_name, &st);
+                         return 0;
                        }
-                     else
+                     else if (old_files_option == NO_OVERWRITE_DIR_OLD_FILES)
                        {
-                         chmod_error_details (file_name, mode);
+                         /* Temporarily change the directory mode to a safe
+                            value, to be able to create files in it, should
+                            the need be.
+                         */
+                         mode = safe_dir_mode (&st);
+                         status = fd_chmod (-1, file_name, mode,
+                                            AT_SYMLINK_NOFOLLOW, DIRTYPE);
+                         if (status == 0)
+                           {
+                             /* Store the actual directory mode, to be restored
+                                later.
+                             */
+                             current_stat_info.stat = st;
+                             current_mode = mode & ~ current_umask;
+                             current_mode_mask = MODE_RWX;
+                             atflag = AT_SYMLINK_NOFOLLOW;
+                             break;
+                           }
+                         else
+                           {
+                             chmod_error_details (file_name, mode);
+                           }
                        }
+                     break;
                    }
-                 break;
                }
            }
+         else if (old_files_option == UNLINK_FIRST_OLD_FILES)
+           {
+             status = 0;
+             break;
+           }
+         
          errno = EEXIST;
        }
 
@@ -1978,11 +1985,12 @@ rename_directory (char *src, char *dst)
   else
     {
       int e = errno;
+      bool interdir_made;
 
       switch (e)
        {
        case ENOENT:
-         if (make_directories (dst) == 0)
+         if (make_directories (dst, &interdir_made) == 0)
            {
              if (renameat (chdir_fd, src, chdir_fd, dst) == 0)
                return true;