]> Dogcows Code - chaz/tar/blobdiff - src/extract.c
Fix --keep-old-files option.
[chaz/tar] / src / extract.c
index 0d23d4ab55ec9027b45bfd5ae3dff676ef223e3f..6c3849274e1281e72f9334e5a6e4f288e5f33022 100644 (file)
@@ -110,12 +110,15 @@ struct delayed_link
     /* The next delayed link in the list.  */
     struct delayed_link *next;
 
-    /* The device, inode number and ctime of the placeholder.  Use
-       ctime, not mtime, to make false matches less likely if some
-       other process removes the placeholder.  */
+    /* The device, inode number and birthtime of the placeholder.
+       birthtime.tv_nsec is negative if the birthtime is not available.
+       Don't use mtime as this would allow for false matches if some
+       other process removes the placeholder.  Don't use ctime as
+       this would cause race conditions and other screwups, e.g.,
+       when restoring hard-linked symlinks.  */
     dev_t dev;
     ino_t ino;
-    struct timespec ctime;
+    struct timespec birthtime;
 
     /* True if the link is symbolic.  */
     bool is_symlink;
@@ -609,6 +612,18 @@ maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
   switch (e)
     {
     case ELOOP:
+
+      /* With open ("symlink", O_NOFOLLOW|...), POSIX says errno == ELOOP,
+        but some operating systems do not conform to the standard.  */
+#ifdef EFTYPE
+      /* NetBSD uses errno == EFTYPE; see <http://gnats.netbsd.org/43154>.  */
+    case EFTYPE:
+#endif
+      /* FreeBSD 8.1 uses errno == EMLINK.  */
+    case EMLINK:
+      /* Tru64 5.1B uses errno == ENOTSUP.  */
+    case ENOTSUP:
+
       if (! regular
          || old_files_option != OVERWRITE_OLD_FILES || dereference_option)
        break;
@@ -627,9 +642,14 @@ maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
 
       switch (old_files_option)
        {
-       case KEEP_OLD_FILES:
+       case SKIP_OLD_FILES:
+         WARNOPT (WARN_EXISTING_FILE,
+                  (0, 0, _("%s: skipping existing file"), file_name));
          return RECOVER_SKIP;
 
+       case KEEP_OLD_FILES:
+         return RECOVER_NO;
+         
        case KEEP_NEWER_FILES:
          if (file_newer_p (file_name, stp, &current_stat_info))
            break;
@@ -777,7 +797,7 @@ extract_dir (char *file_name, int typeflag)
 
   for (;;)
     {
-      status = mkdir (file_name, mode);
+      status = mkdirat (chdir_fd, file_name, mode);
       if (status == 0)
        {
          current_mode = mode & ~ current_umask;
@@ -864,6 +884,20 @@ open_output_file (char const *file_name, int typeflag, mode_t mode,
        }
     }
 
+  /* If O_NOFOLLOW is needed but does not work, check for a symlink
+     separately.  There's a race condition, but that cannot be avoided
+     on hosts lacking O_NOFOLLOW.  */
+  if (! O_NOFOLLOW && overwriting_old_files && ! dereference_option)
+    {
+      struct stat st;
+      if (fstatat (chdir_fd, file_name, &st, AT_SYMLINK_NOFOLLOW) == 0
+         && S_ISLNK (st.st_mode))
+       {
+         errno = ELOOP;
+         return -1;
+       }
+    }
+
   fd = openat (chdir_fd, file_name, openflag, mode);
   if (0 <= fd)
     {
@@ -1050,7 +1084,7 @@ create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made)
       delayed_link_head = p;
       p->dev = st.st_dev;
       p->ino = st.st_ino;
-      p->ctime = get_stat_ctime (&st);
+      p->birthtime = get_stat_birthtime (&st);
       p->is_symlink = is_symlink;
       if (is_symlink)
        {
@@ -1108,7 +1142,8 @@ extract_link (char *file_name, int typeflag)
              if (ds->change_dir == chdir_current
                  && ds->dev == st1.st_dev
                  && ds->ino == st1.st_ino
-                 && timespec_cmp (ds->ctime, get_stat_ctime (&st1)) == 0)
+                 && (timespec_cmp (ds->birthtime, get_stat_birthtime (&st1))
+                     == 0))
                {
                  struct string_list *p =  xmalloc (offsetof (struct string_list, string)
                                                    + strlen (file_name) + 1);
@@ -1191,7 +1226,7 @@ static int
 extract_node (char *file_name, int typeflag)
 {
   bool interdir_made = false;
-  mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX
+  mode_t mode = (current_stat_info.stat.st_mode & (MODE_RWX | S_IFBLK | S_IFCHR)
                 & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0));
 
   while (mknodat (chdir_fd, file_name, mode, current_stat_info.stat.st_rdev)
@@ -1474,7 +1509,7 @@ apply_delayed_links (void)
          if (fstatat (chdir_fd, source, &st, AT_SYMLINK_NOFOLLOW) == 0
              && st.st_dev == ds->dev
              && st.st_ino == ds->ino
-             && timespec_cmp (get_stat_ctime (&st), ds->ctime) == 0)
+             && timespec_cmp (get_stat_birthtime (&st), ds->birthtime) == 0)
            {
              /* Unlink the placeholder, then create a hard link if possible,
                 a symbolic link otherwise.  */
This page took 0.022661 seconds and 4 git commands to generate.