]> Dogcows Code - chaz/tar/blobdiff - src/extract.c
Fix restoring of directory timestamps from
[chaz/tar] / src / extract.c
index d835813424f524ac4aac58de27834efa687eec4e..c89ebd01e74c4a1acb7abffd0b6fabc3f0e83892 100644 (file)
 static bool we_are_root;       /* true if our effective uid == 0 */
 static mode_t newdir_umask;    /* umask when creating new directories */
 static mode_t current_umask;   /* current umask (which is set to 0 if -p) */
+static bool directories_first;  /* Directory members precede non-directory
+                                  ones in the archive. This is detected for
+                                  incremental archives only. This variable
+                                  helps correctly restore directory
+                                  timestamps */
 
 /* Status of the permissions of a file that we are extracting.  */
 enum permstatus
@@ -57,7 +62,13 @@ enum permstatus
 struct delayed_set_stat
   {
     struct delayed_set_stat *next;
-    struct stat stat_info;
+    dev_t dev;
+    ino_t ino;
+    mode_t mode;
+    uid_t uid;
+    gid_t gid;
+    struct timespec atime;
+    struct timespec mtime;
     size_t file_name_len;
     mode_t invert_permissions;
     enum permstatus permstatus;
@@ -76,7 +87,7 @@ struct delayed_link
     /* The device, inode number and last-modified time of the placeholder.  */
     dev_t dev;
     ino_t ino;
-    time_t mtime;
+    struct timespec mtime;
 
     /* True if the link is symbolic.  */
     bool is_symlink;
@@ -200,29 +211,29 @@ check_time (char const *file_name, struct timespec t)
   if (t.tv_sec <= 0)
     WARN ((0, 0, _("%s: implausibly old time stamp %s"),
           file_name, tartime (t, true)));
-  else if (timespec_lt (start_time, t))
+  else if (timespec_cmp (start_time, t) < 0)
     {
       struct timespec now;
       gettime (&now);
-      if (timespec_lt (now, t))
+      if (timespec_cmp (now, t) < 0)
        {
-         unsigned long int ds = t.tv_sec - now.tv_sec;
-         int dns = t.tv_nsec - now.tv_nsec;
-         char dnsbuf[sizeof ".FFFFFFFFF"];
-         if (dns < 0)
+         char buf[TIMESPEC_STRSIZE_BOUND];
+         struct timespec diff;
+         diff.tv_sec = t.tv_sec - now.tv_sec;
+         diff.tv_nsec = t.tv_nsec - now.tv_nsec;
+         if (diff.tv_nsec < 0)
            {
-             dns += 1000000000;
-             ds--;
+             diff.tv_nsec += BILLION;
+             diff.tv_sec--;
            }
-         code_ns_fraction (dns, dnsbuf);
-         WARN ((0, 0, _("%s: time stamp %s is %lu%s s in the future"),
-                file_name, tartime (t, true), ds, dnsbuf));
+         WARN ((0, 0, _("%s: time stamp %s is %s s in the future"),
+                file_name, tartime (t, true), code_timespec (diff, buf)));
        }
     }
 }
 
 /* Restore stat attributes (owner, group, mode and times) for
-   FILE_NAME, using information given in *STAT_INFO.
+   FILE_NAME, using information given in *ST.
    If CUR_INFO is nonzero, *CUR_INFO is the
    file's currernt status.
    If not restoring permissions, invert the
@@ -237,7 +248,7 @@ check_time (char const *file_name, struct timespec t)
 
 static void
 set_stat (char const *file_name,
-         struct stat const *stat_info,
+         struct tar_stat_info const *st,
          struct stat const *cur_info,
          mode_t invert_permissions, enum permstatus permstatus,
          char typeflag)
@@ -256,8 +267,11 @@ set_stat (char const *file_name,
          /* FIXME: incremental_option should set ctime too, but how?  */
 
          struct timespec ts[2];
-         ts[0] = incremental_option ? get_stat_atime (stat_info) : start_time;
-         ts[1] = get_stat_mtime (stat_info);
+         if (incremental_option)
+           ts[0] = st->atime;
+         else
+           ts[0] = start_time;
+         ts[1] = st->mtime;
 
          if (utimens (file_name, ts) != 0)
            utime_error (file_name);
@@ -272,7 +286,7 @@ set_stat (char const *file_name,
         done, it is not possible anymore to change file permissions, so we
         have to set permissions prior to possibly giving files away.  */
 
-      set_mode (file_name, stat_info, cur_info,
+      set_mode (file_name, &st->stat, cur_info,
                invert_permissions, permstatus, typeflag);
     }
 
@@ -286,48 +300,66 @@ set_stat (char const *file_name,
       if (typeflag == SYMTYPE)
        {
 #if HAVE_LCHOWN
-         if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
+         if (lchown (file_name, st->stat.st_uid, st->stat.st_gid) < 0)
            chown_error_details (file_name,
-                                stat_info->st_uid, stat_info->st_gid);
+                                st->stat.st_uid, st->stat.st_gid);
 #endif
        }
       else
        {
-         if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
+         if (chown (file_name, st->stat.st_uid, st->stat.st_gid) < 0)
            chown_error_details (file_name,
-                                stat_info->st_uid, stat_info->st_gid);
+                                st->stat.st_uid, st->stat.st_gid);
 
          /* On a few systems, and in particular, those allowing to give files
             away, changing the owner or group destroys the suid or sgid bits.
             So let's attempt setting these bits once more.  */
-         if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX))
-           set_mode (file_name, stat_info, 0,
+         if (st->stat.st_mode & (S_ISUID | S_ISGID | S_ISVTX))
+           set_mode (file_name, &st->stat, 0,
                      invert_permissions, permstatus, typeflag);
        }
     }
 }
 
 /* Remember to restore stat attributes (owner, group, mode and times)
-   for the directory FILE_NAME, using information given in *STAT_INFO,
+   for the directory FILE_NAME, using information given in *ST,
    once we stop extracting files into that directory.
    If not restoring permissions, remember to invert the
    INVERT_PERMISSIONS bits from the file's current permissions.
-   PERMSTATUS specifies the status of the file's permissions.  */
+   PERMSTATUS specifies the status of the file's permissions.
+
+   NOTICE: this works only if the archive has usual member order, i.e.
+   directory, then the files in that directory. Incremental archive have
+   somewhat reversed order: first go subdirectories, then all other
+   members. To help cope with this case the variable directories_first
+   is set by prepare_to_extract.
+
+   If an archive was explicitely created so that its member order is
+   reversed, some directory timestamps can be restored incorrectly,
+   e.g.:
+       tar --no-recursion -cf archive dir dir/subdir dir/subdir/file
+*/
 static void
-delay_set_stat (char const *file_name, struct stat const *stat_info,
+delay_set_stat (char const *file_name, struct tar_stat_info const *st,
                mode_t invert_permissions, enum permstatus permstatus)
 {
   size_t file_name_len = strlen (file_name);
   struct delayed_set_stat *data =
     xmalloc (offsetof (struct delayed_set_stat, file_name)
             + file_name_len + 1);
+  data->next = delayed_set_stat_head;
+  data->dev = st->stat.st_dev;
+  data->ino = st->stat.st_ino;
+  data->mode = st->stat.st_mode;
+  data->uid = st->stat.st_uid;
+  data->gid = st->stat.st_gid;
+  data->atime = st->atime;
+  data->mtime = st->mtime;
   data->file_name_len = file_name_len;
-  strcpy (data->file_name, file_name);
   data->invert_permissions = invert_permissions;
   data->permstatus = permstatus;
   data->after_links = 0;
-  data->stat_info = *stat_info;
-  data->next = delayed_set_stat_head;
+  strcpy (data->file_name, file_name);
   delayed_set_stat_head = data;
 }
 
@@ -352,7 +384,13 @@ repair_delayed_set_stat (char const *dir,
       if (st.st_dev == dir_stat_info->st_dev
          && st.st_ino == dir_stat_info->st_ino)
        {
-         data->stat_info = current_stat_info.stat;
+         data->dev = current_stat_info.stat.st_dev;
+         data->ino = current_stat_info.stat.st_ino;
+         data->mode = current_stat_info.stat.st_mode;
+         data->uid = current_stat_info.stat.st_uid;
+         data->gid = current_stat_info.stat.st_gid;
+         data->atime = current_stat_info.atime;
+         data->mtime = current_stat_info.mtime;
          data->invert_permissions =
            (MODE_RWX & (current_stat_info.stat.st_mode ^ st.st_mode));
          data->permstatus = ARCHIVED_PERMSTATUS;
@@ -408,7 +446,7 @@ make_directories (char *file_name)
             invert_permissions is zero, because
             repair_delayed_set_stat may need to update the struct.  */
          delay_set_stat (file_name,
-                         &current_stat_info.stat /* ignored */,
+                         &current_stat_info /* ignored */,
                          invert_permissions, INTERDIR_PERMSTATUS);
 
          print_for_mkdir (file_name, cursor - file_name, mode);
@@ -448,7 +486,7 @@ file_newer_p (const char *file_name, struct tar_stat_info *tar_stat)
       return errno != ENOENT;
     }
   if (!S_ISDIR (st.st_mode)
-      && st.st_mtime >= tar_stat->stat.st_mtime)
+      && tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (&st)) <= 0)
     {
       return true;
     }
@@ -551,8 +589,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
              stat_error (data->file_name);
              skip_this_one = 1;
            }
-         else if (! (st.st_dev == data->stat_info.st_dev
-                     && (st.st_ino == data->stat_info.st_ino)))
+         else if (! (st.st_dev == data->dev && st.st_ino == data->ino))
            {
              ERROR ((0, 0,
                      _("%s: Directory renamed before its status could be extracted"),
@@ -562,8 +599,16 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
        }
 
       if (! skip_this_one)
-       set_stat (data->file_name, &data->stat_info, cur_info,
-                 data->invert_permissions, data->permstatus, DIRTYPE);
+       {
+         struct tar_stat_info st;
+         st.stat.st_mode = data->mode;
+         st.stat.st_uid = data->uid;
+         st.stat.st_gid = data->gid;
+         st.atime = data->atime;
+         st.mtime = data->mtime;
+         set_stat (data->file_name, &st, cur_info,
+                   data->invert_permissions, data->permstatus, DIRTYPE);
+       }
 
       delayed_set_stat_head = data->next;
       free (data);
@@ -587,7 +632,8 @@ extract_dir (char *file_name, int typeflag)
   else if (typeflag == GNUTYPE_DUMPDIR)
     skip_member ();
 
-  mode = (current_stat_info.stat.st_mode | (we_are_root ? 0 : MODE_WXUSR)) & MODE_RWX;
+  mode = (current_stat_info.stat.st_mode |
+          (we_are_root ? 0 : MODE_WXUSR)) & MODE_RWX;
 
   while ((status = mkdir (file_name, mode)))
     {
@@ -627,7 +673,7 @@ extract_dir (char *file_name, int typeflag)
   if (status == 0
       || old_files_option == DEFAULT_OLD_FILES
       || old_files_option == OVERWRITE_OLD_FILES)
-    delay_set_stat (file_name, &current_stat_info.stat,
+    delay_set_stat (file_name, &current_stat_info,
                    MODE_RWX & (mode ^ current_stat_info.stat.st_mode),
                    (status == 0
                     ? ARCHIVED_PERMSTATUS
@@ -711,37 +757,33 @@ extract_file (char *file_name, int typeflag)
        }
     }
 
+  mv_begin (&current_stat_info);
   if (current_stat_info.is_sparse)
     sparse_extract_file (fd, &current_stat_info, &size);
   else
     for (size = current_stat_info.stat.st_size; size > 0; )
       {
-       if (multi_volume_option)
-         {
-           assign_string (&save_name, current_stat_info.orig_file_name);
-           save_totsize = current_stat_info.stat.st_size;
-           save_sizeleft = size;
-         }
-
+       mv_size_left (size);
+       
        /* Locate data, determine max length writeable, write it,
           block that we have used the data, then check if the write
           worked.  */
-
+       
        data_block = find_next_block ();
        if (! data_block)
          {
            ERROR ((0, 0, _("Unexpected EOF in archive")));
            break;              /* FIXME: What happens, then?  */
          }
-
+       
        written = available_space_after (data_block);
-
+       
        if (written > size)
          written = size;
        errno = 0;
        count = full_write (fd, data_block->buffer, written);
        size -= written;
-
+       
        set_next_block_after ((union block *)
                              (data_block->buffer + written - 1));
        if (count != written)
@@ -755,9 +797,8 @@ extract_file (char *file_name, int typeflag)
 
   skip_file (size);
 
-  if (multi_volume_option)
-    assign_string (&save_name, 0);
-
+  mv_end ();
+  
   /* If writing to stdout, don't try to do anything to the filename;
      it doesn't exist, or we don't want to touch it anyway.  */
 
@@ -771,7 +812,7 @@ extract_file (char *file_name, int typeflag)
   if (to_command_option)
     sys_wait_command ();
   else
-    set_stat (file_name, &current_stat_info.stat, 0, 0,
+    set_stat (file_name, &current_stat_info, NULL, 0,
              (old_files_option == OVERWRITE_OLD_FILES ?
               UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS),
              typeflag);
@@ -815,7 +856,7 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made)
       delayed_link_head = p;
       p->dev = st.st_dev;
       p->ino = st.st_ino;
-      p->mtime = st.st_mtime;
+      p->mtime = get_stat_mtime (&st);
       p->is_symlink = is_symlink;
       if (is_symlink)
        {
@@ -842,8 +883,8 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made)
                stat_error (h->file_name);
              else
                {
-                 h->stat_info.st_dev = st.st_dev;
-                 h->stat_info.st_ino = st.st_ino;
+                 h->dev = st.st_dev;
+                 h->ino = st.st_ino;
                }
            }
          while ((h = h->next) && ! h->after_links);
@@ -879,7 +920,7 @@ extract_link (char *file_name, int typeflag)
            for (; ds; ds = ds->next)
              if (ds->dev == st1.st_dev
                  && ds->ino == st1.st_ino
-                 && ds->mtime == st1.st_mtime)
+                 && timespec_cmp (ds->mtime, get_stat_mtime (&st1)) == 0)
                {
                  struct string_list *p =  xmalloc (offsetof (struct string_list, string)
                                                    + strlen (file_name) + 1);
@@ -926,7 +967,7 @@ extract_symlink (char *file_name, int typeflag)
       break;
 
   if (status == 0)
-    set_stat (file_name, &current_stat_info.stat, 0, 0, 0, SYMTYPE);
+    set_stat (file_name, &current_stat_info, NULL, 0, 0, SYMTYPE);
   else
     symlink_error (current_stat_info.link_name, file_name);
   return status;
@@ -958,7 +999,8 @@ extract_node (char *file_name, int typeflag)
   if (status != 0)
     mknod_error (file_name);
   else
-    set_stat (file_name, &current_stat_info.stat, 0, 0, ARCHIVED_PERMSTATUS, typeflag);
+    set_stat (file_name, &current_stat_info, NULL, 0,
+             ARCHIVED_PERMSTATUS, typeflag);
   return status;
 }
 #endif
@@ -975,7 +1017,7 @@ extract_fifo (char *file_name, int typeflag)
       break;
 
   if (status == 0)
-    set_stat (file_name, &current_stat_info.stat, NULL, 0,
+    set_stat (file_name, &current_stat_info, NULL, 0,
              ARCHIVED_PERMSTATUS, typeflag);
   else
     mkfifo_error (file_name);
@@ -1065,6 +1107,8 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun)
     case DIRTYPE:
     case GNUTYPE_DUMPDIR:
       *fun = extract_dir;
+      if (current_stat_info.dumpdir)
+       directories_first = true;
       break;
 
     case GNUTYPE_VOLHDR:
@@ -1156,7 +1200,8 @@ extract_archive (void)
                                  false, absolute_names_option);
   if (strip_name_components)
     {
-      size_t prefix_len = stripped_prefix_len (file_name, strip_name_components);
+      size_t prefix_len = stripped_prefix_len (file_name,
+                                              strip_name_components);
       if (prefix_len == (size_t) -1)
        {
          skip_member ();
@@ -1165,8 +1210,12 @@ extract_archive (void)
       file_name += prefix_len;
     }
 
-  apply_nonancestor_delayed_set_stat (file_name, 0);
-
+  /* Restore stats for all non-ancestor directories, unless
+     it is an incremental archive.
+     (see NOTICE in the comment to delay_set_stat above) */
+  if (!directories_first)
+    apply_nonancestor_delayed_set_stat (file_name, 0);
+      
   /* Take a safety backup of a previously existing file.  */
 
   if (backup_option)
@@ -1180,7 +1229,6 @@ extract_archive (void)
       }
 
   /* Extract the archive entry according to its type.  */
-
   /* KLUDGE */
   typeflag = sparse_member_p (&current_stat_info) ?
                   GNUTYPE_SPARSE : current_header->header.typeflag;
@@ -1217,7 +1265,7 @@ apply_delayed_links (void)
          if (lstat (source, &st) == 0
              && st.st_dev == ds->dev
              && st.st_ino == ds->ino
-             && st.st_mtime == ds->mtime)
+             && timespec_cmp (get_stat_mtime (&st), ds->mtime) == 0)
            {
              /* Unlink the placeholder, then create a hard link if possible,
                 a symbolic link otherwise.  */
@@ -1234,10 +1282,11 @@ apply_delayed_links (void)
                symlink_error (ds->target, source);
              else
                {
+                 struct tar_stat_info st1;
+                 st1.stat.st_uid = ds->uid;
+                 st1.stat.st_gid = ds->gid;
+                 set_stat (source, &st1, NULL, 0, 0, SYMTYPE);
                  valid_source = source;
-                 st.st_uid = ds->uid;
-                 st.st_gid = ds->gid;
-                 set_stat (source, &st, 0, 0, 0, SYMTYPE);
                }
            }
        }
This page took 0.033175 seconds and 4 git commands to generate.