]> Dogcows Code - chaz/tar/blobdiff - src/extract.c
import from gnulib
[chaz/tar] / src / extract.c
index 58a55886ff92e1581203214b0084ec687a121ac6..2a3f9bf944bcc33ac0a6b4671cda76a38680b664 100644 (file)
@@ -1,5 +1,8 @@
 /* Extract files from a tar archive.
-   Copyright 1988, 92,93,94,96,97,98, 1999 Free Software Foundation, Inc.
+
+   Copyright 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000,
+   2001 Free Software Foundation, Inc.
+
    Written by John Gilmore, on 1985-11-19.
 
    This program is free software; you can redistribute it and/or modify it
@@ -31,7 +34,7 @@ struct utimbuf
 
 #include "common.h"
 
-static int we_are_root;                /* true if our effective uid == 0 */
+int 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) */
 
@@ -50,9 +53,14 @@ enum permstatus
 };
 
 /* List of directories whose statuses we need to extract after we've
-   finished extracting their subsidiary files.  The head of the list
-   has the longest name; each non-head element in the list is an
-   ancestor (in the directory hierarchy) of the preceding element.  */
+   finished extracting their subsidiary files.  If you consider each
+   contiguous subsequence of elements of the form [D]?[^D]*, where [D]
+   represents an element where AFTER_SYMLINKS is nonzero and [^D]
+   represents an element where AFTER_SYMLINKS is zero, then the head
+   of the subsequence has the longest name, and each non-head element
+   in the prefix is an ancestor (in the directory hierarchy) of the
+   preceding element.  */
+
 struct delayed_set_stat
   {
     struct delayed_set_stat *next;
@@ -60,22 +68,51 @@ struct delayed_set_stat
     size_t file_name_len;
     mode_t invert_permissions;
     enum permstatus permstatus;
+    bool after_symlinks;
     char file_name[1];
   };
 
 static struct delayed_set_stat *delayed_set_stat_head;
 
-/*--------------------------.
-| Set up to extract files.  |
-`--------------------------*/
+/* List of symbolic links whose creation we have delayed.  */
+struct delayed_symlink
+  {
+    /* The next delayed symbolic link in the list.  */
+    struct delayed_symlink *next;
+
+    /* The device, inode number and last-modified time of the placeholder.  */
+    dev_t dev;
+    ino_t ino;
+    time_t mtime;
+
+    /* The desired owner and group of the symbolic link.  */
+    uid_t uid;
+    gid_t gid;
 
+    /* A list of sources for this symlink.  The sources are all to be
+       hard-linked together.  */
+    struct string_list *sources;
+
+    /* The desired target of the desired link.  */
+    char target[1];
+  };
+
+static struct delayed_symlink *delayed_symlink_head;
+
+struct string_list
+  {
+    struct string_list *next;
+    char string[1];
+  };
+
+/*  Set up to extract files.  */
 void
 extr_init (void)
 {
   we_are_root = geteuid () == 0;
   same_permissions_option += we_are_root;
   same_owner_option += we_are_root;
-  xalloc_fail_func = apply_delayed_set_stat;
+  xalloc_fail_func = extract_finish;
 
   /* Option -p clears the kernel umask, so it does not affect proper
      restoration of file permissions.  New intermediate directories will
@@ -92,12 +129,14 @@ extr_init (void)
 }
 
 /* If restoring permissions, restore the mode for FILE_NAME from
-   information given in *STAT_INFO; otherwise invert the
+   information given in *STAT_INFO (where *CURRENT_STAT_INFO gives
+   the current status if CURRENT_STAT_INFO is nonzero); otherwise invert the
    INVERT_PERMISSIONS bits from the file's current permissions.
    PERMSTATUS specifies the status of the file's permissions.
    TYPEFLAG specifies the type of the file.  */
 static void
-set_mode (char *file_name, struct stat *stat_info,
+set_mode (char const *file_name, struct stat const *stat_info,
+         struct stat const *current_stat_info,
          mode_t invert_permissions, enum permstatus permstatus,
          char typeflag)
 {
@@ -129,20 +168,36 @@ set_mode (char *file_name, struct stat *stat_info,
         that we created, so there's no point optimizing this code for
         other cases.  */
       struct stat st;
-      if (stat (file_name, &st) != 0)
+      if (! current_stat_info)
        {
-         stat_error (file_name);
-         return;
+         if (stat (file_name, &st) != 0)
+           {
+             stat_error (file_name);
+             return;
+           }
+         current_stat_info = &st;
        }
-      mode = st.st_mode ^ invert_permissions;
+      mode = current_stat_info->st_mode ^ invert_permissions;
     }
 
   if (chmod (file_name, mode) != 0)
     chmod_error_details (file_name, mode);
 }
 
+/* Check time after successfully setting FILE_NAME's time stamp to T.  */
+static void
+check_time (char const *file_name, time_t t)
+{
+  time_t now;
+  if (start_time < t && (now = time (0)) < t)
+    WARN ((0, 0, _("%s: time stamp %s is %lu s in the future"),
+          file_name, tartime (t), (unsigned long) (t - now)));
+}
+
 /* Restore stat attributes (owner, group, mode and times) for
    FILE_NAME, using information given in *STAT_INFO.
+   If CURRENT_STAT_INFO is nonzero, *CURRENT_STAT_INFO is the
+   file's currernt status.
    If not restoring permissions, invert the
    INVERT_PERMISSIONS bits from the file's current permissions.
    PERMSTATUS specifies the status of the file's permissions.
@@ -154,7 +209,8 @@ set_mode (char *file_name, struct stat *stat_info,
    punt for the rest.  Sigh!  */
 
 static void
-set_stat (char *file_name, struct stat *stat_info,
+set_stat (char const *file_name, struct stat const *stat_info,
+         struct stat const *current_stat_info,
          mode_t invert_permissions, enum permstatus permstatus,
          char typeflag)
 {
@@ -182,13 +238,18 @@ set_stat (char *file_name, struct stat *stat_info,
 
          if (utime (file_name, &utimbuf) < 0)
            utime_error (file_name);
+         else
+           {
+             check_time (file_name, stat_info->st_atime);
+             check_time (file_name, stat_info->st_mtime);
+           }
        }
 
       /* Some systems allow non-root users to give files away.  Once this
         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,
+      set_mode (file_name, stat_info, current_stat_info,
                invert_permissions, permstatus, typeflag);
     }
 
@@ -217,7 +278,7 @@ set_stat (char *file_name, struct stat *stat_info,
             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,
+           set_mode (file_name, stat_info, 0,
                      invert_permissions, permstatus, typeflag);
        }
     }
@@ -234,20 +295,23 @@ delay_set_stat (char const *file_name, struct stat const *stat_info,
                mode_t invert_permissions, enum permstatus permstatus)
 {
   size_t file_name_len = strlen (file_name);
-  struct delayed_set_stat *data = xmalloc (sizeof *data + file_name_len);
+  struct delayed_set_stat *data =
+    xmalloc (offsetof (struct delayed_set_stat, file_name)
+            + file_name_len + 1);
   data->file_name_len = file_name_len;
   strcpy (data->file_name, file_name);
   data->invert_permissions = invert_permissions;
   data->permstatus = permstatus;
+  data->after_symlinks = 0;
   data->stat_info = *stat_info;
   data->next = delayed_set_stat_head;
   delayed_set_stat_head = data;
 }
 
 /* Update the delayed_set_stat info for an intermediate directory
-   created on the path to DIR_NAME.  The intermediate directory
-   turned out to be the same as this directory, due to ".." or
-   symbolic links.  *DIR_STAT_INFO is the status of the directory.  */
+   created on the path to DIR_NAME.  The intermediate directory turned
+   out to be the same as this directory, e.g. due to ".." or symbolic
+   links.  *DIR_STAT_INFO is the status of the directory.  */
 static void
 repair_delayed_set_stat (char const *dir_name,
                         struct stat const *dir_stat_info)
@@ -277,37 +341,37 @@ repair_delayed_set_stat (char const *dir_name,
          quotearg_colon (dir_name)));
 }
 
-/*-----------------------------------------------------------------------.
-| After a file/link/symlink/directory creation has failed, see if it's  |
-| because some required directory was not present, and if so, create all |
-| required directories.  Return non-zero if a directory was created.    |
-`-----------------------------------------------------------------------*/
-
+/* After a file/link/symlink/directory creation has failed, see if
+   it's because some required directory was not present, and if so,
+   create all required directories.  Return non-zero if a directory
+   was created.  */
 static int
 make_directories (char *file_name)
 {
+  char *cursor0 = file_name + FILESYSTEM_PREFIX_LEN (file_name);
   char *cursor;                        /* points into path */
   int did_something = 0;       /* did we do anything yet? */
-  int saved_errno = errno;     /* remember caller's errno */
   int mode;
   int invert_permissions;
   int status;
 
-  for (cursor = strchr (file_name, '/');
-       cursor;
-       cursor = strchr (cursor + 1, '/'))
+  
+  for (cursor = cursor0; *cursor; cursor++)
     {
+      if (! ISSLASH (*cursor))
+       continue;
+
       /* Avoid mkdir of empty string, if leading or double '/'.  */
 
-      if (cursor == file_name || cursor[-1] == '/')
+      if (cursor == cursor0 || ISSLASH (cursor[-1]))
        continue;
 
       /* Avoid mkdir where last part of path is "." or "..".  */
 
       if (cursor[-1] == '.'
-         && (cursor == file_name + 1 || cursor[-2] == '/'
+         && (cursor == cursor0 + 1 || ISSLASH (cursor[-2])
              || (cursor[-2] == '.'
-                 && (cursor == file_name + 2 || cursor[-3] == '/'))))
+                 && (cursor == cursor0 + 2 || ISSLASH (cursor[-3])))))
        continue;
 
       *cursor = '\0';          /* truncate the path there */
@@ -346,7 +410,6 @@ make_directories (char *file_name)
       break;
     }
 
-  errno = saved_errno;
   return did_something;                /* tell them to retry if we made one */
 }
 
@@ -370,13 +433,10 @@ prepare_to_extract (char const *file_name)
   return 1;
 }
 
-/*--------------------------------------------------------------------.
-| Attempt repairing what went wrong with the extraction.  Delete an   |
-| already existing file or create missing intermediate directories.   |
-| Return nonzero if we somewhat increased our chances at a successful |
-| extraction.  errno is properly restored on zero return.            |
-`--------------------------------------------------------------------*/
-
+/* Attempt repairing what went wrong with the extraction.  Delete an
+   already existing file or create missing intermediate directories.
+   Return nonzero if we somewhat increased our chances at a successful
+   extraction.  errno is properly restored on zero return.  */
 static int
 maybe_recoverable (char *file_name, int *interdir_made)
 {
@@ -394,6 +454,7 @@ maybe_recoverable (char *file_name, int *interdir_made)
          return 0;
 
        case DEFAULT_OLD_FILES:
+       case OVERWRITE_OLD_DIRS:
        case OVERWRITE_OLD_FILES:
          {
            int r = remove_any_file (file_name, 0);
@@ -405,7 +466,10 @@ maybe_recoverable (char *file_name, int *interdir_made)
     case ENOENT:
       /* Attempt creating missing intermediate directories.  */
       if (! make_directories (file_name))
-       return 0;
+       {
+         errno = ENOENT;
+         return 0;
+       }
       *interdir_made = 1;
       return 1;
 
@@ -416,21 +480,17 @@ maybe_recoverable (char *file_name, int *interdir_made)
     }
 }
 
-/*---.
-| ?  |
-`---*/
-
 static void
 extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name)
 {
   int sparse_ind = 0;
-  size_t written;
-  ssize_t count;
 
   /* assuming sizeleft is initially totalsize */
 
   while (*sizeleft > 0)
     {
+      size_t written;
+      size_t count;
       union block *data_block = find_next_block ();
       if (! data_block)
        {
@@ -446,10 +506,13 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name)
       while (written > BLOCKSIZE)
        {
          count = full_write (fd, data_block->buffer, BLOCKSIZE);
-         if (count < 0)
-           write_error (name);
          written -= count;
          *sizeleft -= count;
+         if (count != BLOCKSIZE)
+           {
+             write_error_details (name, count, BLOCKSIZE);
+             return;
+           }
          set_next_block_after (data_block);
          data_block = find_next_block ();
          if (! data_block)
@@ -460,46 +523,90 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name)
        }
 
       count = full_write (fd, data_block->buffer, written);
+      *sizeleft -= count;
 
-      if (count < 0)
-       write_error (name);
-      else if (count != written)
+      if (count != written)
        {
          write_error_details (name, count, written);
-         skip_file (*sizeleft);
+         return;
        }
 
-      written -= count;
-      *sizeleft -= count;
       set_next_block_after (data_block);
     }
-
-  free (sparsearray);
 }
 
-/*----------------------------------.
-| Extract a file from the archive.  |
-`----------------------------------*/
+/* Fix the statuses of all directories whose statuses need fixing, and
+   which are not ancestors of FILE_NAME.  If AFTER_SYMLINKS is
+   nonzero, do this for all such directories; otherwise, stop at the
+   first directory that is marked to be fixed up only after delayed
+   symlinks are applied.  */
+static void
+apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks)
+{
+  size_t file_name_len = strlen (file_name);
+  bool check_for_renamed_directories = 0;
+
+  while (delayed_set_stat_head)
+    {
+      struct delayed_set_stat *data = delayed_set_stat_head;
+      bool skip_this_one = 0;
+      struct stat st;
+      struct stat const *current_stat_info = 0;
+
+      check_for_renamed_directories |= data->after_symlinks;
+
+      if (after_symlinks < data->after_symlinks
+         || (data->file_name_len < file_name_len
+             && file_name[data->file_name_len]
+             && (ISSLASH (file_name[data->file_name_len])
+                 || ISSLASH (file_name[data->file_name_len - 1]))
+             && memcmp (file_name, data->file_name, data->file_name_len) == 0))
+       break;
+
+      if (check_for_renamed_directories)
+       {
+         current_stat_info = &st;
+         if (stat (data->file_name, &st) != 0)
+           {
+             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)))
+           {
+             ERROR ((0, 0,
+                     _("%s: Directory renamed before its status could be extracted"),
+                     quotearg_colon (data->file_name)));
+             skip_this_one = 1;
+           }
+       }
+
+      if (! skip_this_one)
+       set_stat (data->file_name, &data->stat_info, current_stat_info,
+                 data->invert_permissions, data->permstatus, DIRTYPE);
+
+      delayed_set_stat_head = data->next;
+      free (data);
+    }
+}
 
+/* Extract a file from the archive.  */
 void
 extract_archive (void)
 {
   union block *data_block;
   int fd;
   int status;
-  ssize_t sstatus;
+  size_t count;
   size_t name_length;
   size_t written;
   int openflag;
   mode_t mode;
   off_t size;
-  int skipcrud;
+  size_t skipcrud;
   int counter;
   int interdir_made = 0;
   char typeflag;
-#if 0
-  int sparse_ind = 0;
-#endif
   union block *exhdr;
 
 #define CURRENT_FILE_NAME (skipcrud + current_file_name)
@@ -509,13 +616,11 @@ extract_archive (void)
 
   if (interactive_option && !confirm ("extract", current_file_name))
     {
-      if (current_header->oldgnu_header.isextended)
-       skip_extended_headers ();
-      skip_file (current_stat.st_size);
+      skip_member ();
       return;
     }
 
-  /* Print the block from `current_header' and `current_stat'.  */
+  /* Print the block from current_header and current_stat.  */
 
   if (verbose_option)
     print_header ();
@@ -525,29 +630,33 @@ extract_archive (void)
   skipcrud = 0;
   if (! absolute_names_option)
     {
-      while (CURRENT_FILE_NAME[0] == '/')
+      if (contains_dot_dot (CURRENT_FILE_NAME))
+       {
+         ERROR ((0, 0, _("%s: Member name contains `..'"),
+                 quotearg_colon (CURRENT_FILE_NAME)));
+         skip_member ();
+         return;
+       }
+
+      skipcrud = FILESYSTEM_PREFIX_LEN (current_file_name);
+      while (ISSLASH (CURRENT_FILE_NAME[0]))
+       skipcrud++;
+
+      if (skipcrud)
        {
          static int warned_once;
          
          if (!warned_once)
            {
              warned_once = 1;
-             WARN ((0, 0, _("Removing leading `/' from member names")));
+             WARN ((0, 0, _("Removing leading `%.*s' from member names"),
+                    (int) skipcrud, current_file_name));
            }
-         skipcrud++;           /* force relative path */
-       }
-
-      if (contains_dot_dot (CURRENT_FILE_NAME))
-       {
-         ERROR ((0, 0, _("%s: Member name contains `..'"),
-                 quotearg_colon (CURRENT_FILE_NAME)));
-         if (current_header->oldgnu_header.isextended)
-           skip_extended_headers ();
-         skip_file (current_stat.st_size);
-         return;
        }
     }
 
+  apply_nonancestor_delayed_set_stat (CURRENT_FILE_NAME, 0);
+
   /* Take a safety backup of a previously existing file.  */
 
   if (backup_option && !to_stdout_option)
@@ -556,9 +665,7 @@ extract_archive (void)
        int e = errno;
        ERROR ((0, e, _("%s: Was unable to backup this file"),
                quotearg_colon (CURRENT_FILE_NAME)));
-       if (current_header->oldgnu_header.isextended)
-         skip_extended_headers ();
-       skip_file (current_stat.st_size);
+       skip_member ();
        return;
       }
 
@@ -651,7 +758,8 @@ extract_archive (void)
         suffix means a directory.  */
 
       name_length = strlen (CURRENT_FILE_NAME);
-      if (name_length && CURRENT_FILE_NAME[name_length - 1] == '/')
+      if (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length
+         && CURRENT_FILE_NAME[name_length - 1] == '/')
        goto really_dir;
 
       /* FIXME: deal with protection issues.  */
@@ -671,9 +779,7 @@ extract_archive (void)
 
       if (! prepare_to_extract (CURRENT_FILE_NAME))
        {
-         if (current_header->oldgnu_header.isextended)
-           skip_extended_headers ();
-         skip_file (current_stat.st_size);
+         skip_member ();
          if (backup_option)
            undo_last_backup ();
          break;
@@ -710,9 +816,7 @@ extract_archive (void)
            goto again_file;
 
          open_error (CURRENT_FILE_NAME);
-         if (current_header->oldgnu_header.isextended)
-           skip_extended_headers ();
-         skip_file (current_stat.st_size);
+         skip_member ();
          if (backup_option)
            undo_last_backup ();
          break;
@@ -735,11 +839,10 @@ extract_archive (void)
          memcpy (name, CURRENT_FILE_NAME, name_length_bis);
          size = current_stat.st_size;
          extract_sparse_file (fd, &size, current_stat.st_size, name);
+         free (sparsearray);
        }
       else
-       for (size = current_stat.st_size;
-            size > 0;
-            size -= written)
+       for (size = current_stat.st_size; size > 0; )
          {
            if (multi_volume_option)
              {
@@ -764,21 +867,20 @@ extract_archive (void)
            if (written > size)
              written = size;
            errno = 0;
-           sstatus = full_write (fd, data_block->buffer, written);
+           count = full_write (fd, data_block->buffer, written);
+           size -= count;
 
            set_next_block_after ((union block *)
                                  (data_block->buffer + written - 1));
-           if (sstatus == written)
-             continue;
-
-           /* Error in writing to file.  Print it, skip to next file in
-              archive.  */
-
-           write_error_details (CURRENT_FILE_NAME, sstatus, written);
-           skip_file (size - written);
-           break;              /* still do the close, mod time, chmod, etc */
+           if (count != written)
+             {
+               write_error_details (CURRENT_FILE_NAME, count, written);
+               break;
+             }
          }
 
+      skip_file (size);
+
       if (multi_volume_option)
        assign_string (&save_name, 0);
 
@@ -796,7 +898,7 @@ extract_archive (void)
            undo_last_backup ();
        }
 
-      set_stat (CURRENT_FILE_NAME, &current_stat, 0,
+      set_stat (CURRENT_FILE_NAME, &current_stat, 0, 0,
                (old_files_option == OVERWRITE_OLD_FILES
                 ? UNKNOWN_PERMSTATUS
                 : ARCHIVED_PERMSTATUS),
@@ -808,30 +910,90 @@ extract_archive (void)
       if (! prepare_to_extract (CURRENT_FILE_NAME))
        break;
 
-      while (status = symlink (current_link_name, CURRENT_FILE_NAME),
-            status != 0)
-       if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
-         break;
-
-      if (status == 0)
-
-       /* Setting the attributes of symbolic links might, on some systems,
-          change the pointed to file, instead of the symbolic link itself.
-          At least some of these systems have a lchown call, and the
-          set_stat routine knows about this.    */
-
-       set_stat (CURRENT_FILE_NAME, &current_stat, 0,
-                 ARCHIVED_PERMSTATUS, typeflag);
+      if (absolute_names_option
+         || ! (ISSLASH (current_link_name
+                        [FILESYSTEM_PREFIX_LEN (current_link_name)])
+               || contains_dot_dot (current_link_name)))
+       {
+         while (status = symlink (current_link_name, CURRENT_FILE_NAME),
+                status != 0)
+           if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+             break;
 
+         if (status == 0)
+           set_stat (CURRENT_FILE_NAME, &current_stat, 0, 0, 0, SYMTYPE);
+         else
+           symlink_error (current_link_name, CURRENT_FILE_NAME);
+       }
       else
        {
-         int e = errno;
-         ERROR ((0, e, _("%s: Cannot create symlink to %s"),
-                 quotearg_colon (CURRENT_FILE_NAME),
-                 quote (current_link_name)));
-         if (backup_option)
-           undo_last_backup ();
+         /* This symbolic link is potentially dangerous.  Don't
+            create it now; instead, create a placeholder file, which
+            will be replaced after other extraction is done.  */
+         struct stat st;
+
+         while (fd = open (CURRENT_FILE_NAME, O_WRONLY | O_CREAT | O_EXCL, 0),
+                fd < 0)
+           if (! maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
+             break;
+
+         status = -1;
+         if (fd < 0)
+           open_error (CURRENT_FILE_NAME);
+         else if (fstat (fd, &st) != 0)
+           {
+             stat_error (CURRENT_FILE_NAME);
+             close (fd);
+           }
+         else if (close (fd) != 0)
+           close_error (CURRENT_FILE_NAME);
+         else
+           {
+             struct delayed_set_stat *h;
+             struct delayed_symlink *p =
+               xmalloc (offsetof (struct delayed_symlink, target)
+                        + strlen (current_link_name) + 1);
+             p->next = delayed_symlink_head;
+             delayed_symlink_head = p;
+             p->dev = st.st_dev;
+             p->ino = st.st_ino;
+             p->mtime = st.st_mtime;
+             p->uid = current_stat.st_uid;
+             p->gid = current_stat.st_gid;
+             p->sources = xmalloc (offsetof (struct string_list, string)
+                                   + strlen (CURRENT_FILE_NAME) + 1);
+             p->sources->next = 0;
+             strcpy (p->sources->string, CURRENT_FILE_NAME);
+             strcpy (p->target, current_link_name);
+
+             h = delayed_set_stat_head;
+             if (h && ! h->after_symlinks
+                 && strncmp (CURRENT_FILE_NAME, h->file_name, h->file_name_len) == 0
+                 && ISSLASH (CURRENT_FILE_NAME[h->file_name_len])
+                 && (base_name (CURRENT_FILE_NAME)
+                     == CURRENT_FILE_NAME + h->file_name_len + 1))
+               {
+                 do
+                   {
+                     h->after_symlinks = 1;
+
+                     if (stat (h->file_name, &st) != 0)
+                       stat_error (h->file_name);
+                     else
+                       {
+                         h->stat_info.st_dev = st.st_dev;
+                         h->stat_info.st_ino = st.st_ino;
+                       }
+                   }
+                 while ((h = h->next) && ! h->after_symlinks);
+               }
+
+             status = 0;
+           }
        }
+  
+      if (status != 0 && backup_option)
+       undo_last_backup ();
       break;
 
 #else
@@ -863,7 +1025,24 @@ extract_archive (void)
        status = link (current_link_name, CURRENT_FILE_NAME);
 
        if (status == 0)
-         break;
+         {
+           struct delayed_symlink *ds = delayed_symlink_head;
+           if (ds && stat (current_link_name, &st1) == 0)
+             for (; ds; ds = ds->next)
+               if (ds->dev == st1.st_dev
+                   && ds->ino == st1.st_ino
+                   && ds->mtime == st1.st_mtime)
+                 {
+                   struct string_list *p = 
+                     xmalloc (offsetof (struct string_list, string)
+                              + strlen (CURRENT_FILE_NAME) + 1);
+                   strcpy (p->string, CURRENT_FILE_NAME);
+                   p->next = ds->sources;
+                   ds->sources = p;
+                   break;
+                 }
+           break;
+         }
        if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
          goto again_link;
 
@@ -876,9 +1055,7 @@ extract_archive (void)
            && st1.st_ino == st2.st_ino)
          break;
 
-       ERROR ((0, e, _("%s: Cannot link to %s"),
-               quotearg_colon (CURRENT_FILE_NAME),
-               quote (current_link_name)));
+       link_error (current_link_name, CURRENT_FILE_NAME);
        if (backup_option)
          undo_last_backup ();
       }
@@ -911,7 +1088,7 @@ extract_archive (void)
            undo_last_backup ();
          break;
        };
-      set_stat (CURRENT_FILE_NAME, &current_stat, 0,
+      set_stat (CURRENT_FILE_NAME, &current_stat, 0, 0,
                ARCHIVED_PERMSTATUS, typeflag);
       break;
 #endif
@@ -927,7 +1104,7 @@ extract_archive (void)
          break;
 
       if (status == 0)
-       set_stat (CURRENT_FILE_NAME, &current_stat, 0,
+       set_stat (CURRENT_FILE_NAME, &current_stat, 0, 0,
                  ARCHIVED_PERMSTATUS, typeflag);
       else
        {
@@ -943,19 +1120,11 @@ extract_archive (void)
       name_length = strlen (CURRENT_FILE_NAME);
 
     really_dir:
-      /* Remove trailing "/" and "/.", unless that would result in the
-        empty string.  */
-      for (;;)
-       {
-         if (1 < name_length && CURRENT_FILE_NAME[name_length - 1] == '/')
-           CURRENT_FILE_NAME[--name_length] = '\0';
-         else if (2 < name_length
-                  && CURRENT_FILE_NAME[name_length - 1] == '.'
-                  && CURRENT_FILE_NAME[name_length - 2] == '/')
-           CURRENT_FILE_NAME[name_length -= 2] = '\0';
-         else
-           break;
-       }
+      /* Remove any redundant trailing "/"s.  */
+      while (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length
+            && CURRENT_FILE_NAME[name_length - 1] == '/')
+       name_length--;
+      CURRENT_FILE_NAME[name_length] = '\0';
 
       if (incremental_option)
        {
@@ -965,7 +1134,7 @@ extract_archive (void)
          gnu_restore (skipcrud);
        }
       else if (typeflag == GNUTYPE_DUMPDIR)
-       skip_file (current_stat.st_size);
+       skip_member ();
 
       if (! prepare_to_extract (CURRENT_FILE_NAME))
        break;
@@ -976,25 +1145,35 @@ extract_archive (void)
 
     again_dir:
       status = mkdir (CURRENT_FILE_NAME, mode);
+
       if (status != 0)
        {
-         if (errno == EEXIST && interdir_made
-             && contains_dot_dot (CURRENT_FILE_NAME))
+         if (errno == EEXIST
+             && (interdir_made
+                 || old_files_option == OVERWRITE_OLD_DIRS
+                 || old_files_option == OVERWRITE_OLD_FILES))
            {
-             int e = errno;
              struct stat st;
              if (stat (CURRENT_FILE_NAME, &st) == 0)
                {
-                 repair_delayed_set_stat (CURRENT_FILE_NAME, &st);
-                 break;
+                 if (interdir_made)
+                   {
+                     repair_delayed_set_stat (CURRENT_FILE_NAME, &st);
+                     break;
+                   }
+                 if (S_ISDIR (st.st_mode))
+                   {
+                     mode = st.st_mode & ~ current_umask;
+                     goto directory_exists;
+                   }
                }
-             e = errno;
+             errno = EEXIST;
            }
        
          if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made))
            goto again_dir;
 
-         if (errno != EEXIST || old_files_option == KEEP_OLD_FILES)
+         if (errno != EEXIST)
            {
              mkdir_error (CURRENT_FILE_NAME);
              if (backup_option)
@@ -1003,10 +1182,12 @@ extract_archive (void)
            }
        }
 
+    directory_exists:
       if (status == 0
+         || old_files_option == OVERWRITE_OLD_DIRS
          || old_files_option == OVERWRITE_OLD_FILES)
        delay_set_stat (CURRENT_FILE_NAME, &current_stat,
-                       mode & ~ current_stat.st_mode,
+                       MODE_RWX & (mode ^ current_stat.st_mode),
                        (status == 0
                         ? ARCHIVED_PERMSTATUS
                         : UNKNOWN_PERMSTATUS));
@@ -1025,7 +1206,7 @@ extract_archive (void)
       ERROR ((0, 0,
              _("%s: Cannot extract -- file is continued from another volume"),
              quotearg_colon (current_file_name)));
-      skip_file (current_stat.st_size);
+      skip_member ();
       if (backup_option)
        undo_last_backup ();
       break;
@@ -1033,7 +1214,7 @@ extract_archive (void)
     case GNUTYPE_LONGNAME:
     case GNUTYPE_LONGLINK:
       ERROR ((0, 0, _("Visible long name error")));
-      skip_file (current_stat.st_size);
+      skip_member ();
       if (backup_option)
        undo_last_backup ();
       break;
@@ -1048,38 +1229,85 @@ extract_archive (void)
 #undef CURRENT_FILE_NAME
 }
 
-/* Fix the status of all directories whose statuses need fixing.  */
-void
-apply_delayed_set_stat (void)
+/* Extract the symbolic links whose final extraction were delayed.  */
+static void
+apply_delayed_symlinks (void)
 {
-  apply_nonancestor_delayed_set_stat ("");
+  struct delayed_symlink *ds;
+
+  for (ds = delayed_symlink_head; ds; )
+    {
+      struct string_list *sources = ds->sources;
+      char const *valid_source = 0;
+
+      for (sources = ds->sources; sources; sources = sources->next)
+       {
+         char const *source = sources->string;
+         struct stat st;
+
+         /* Make sure the placeholder file is still there.  If not,
+            don't create a symlink, as the placeholder was probably
+            removed by a later extraction.  */
+         if (lstat (source, &st) == 0
+             && st.st_dev == ds->dev
+             && st.st_ino == ds->ino
+             && st.st_mtime == ds->mtime)
+           {
+             /* Unlink the placeholder, then create a hard link if possible,
+                a symbolic link otherwise.  */
+             if (unlink (source) != 0)
+               unlink_error (source);
+             else if (valid_source && link (valid_source, source) == 0)
+               ;
+             else if (symlink (ds->target, source) != 0)
+               symlink_error (ds->target, source);
+             else
+               {
+                 valid_source = source;
+                 st.st_uid = ds->uid;
+                 st.st_gid = ds->gid;
+                 set_stat (source, &st, 0, 0, 0, SYMTYPE);
+               }
+           }
+       }
+
+      for (sources = ds->sources; sources; )
+       {
+         struct string_list *next = sources->next;
+         free (sources);
+         sources = next;
+       }
+
+      {
+       struct delayed_symlink *next = ds->next;
+       free (ds);
+       ds = next;
+      }
+    }
+
+  delayed_symlink_head = 0;
 }
 
-/* Fix the statuses of all directories whose statuses need fixing, and
-   which are not ancestors of FILE_NAME.  */
+/* Finish the extraction of an archive.  */
 void
-apply_nonancestor_delayed_set_stat (char const *file_name)
+extract_finish (void)
 {
-  size_t file_name_len = strlen (file_name);
+  /* First, fix the status of ordinary directories that need fixing.  */
+  apply_nonancestor_delayed_set_stat ("", 0);
 
-  while (delayed_set_stat_head)
-    {
-      struct delayed_set_stat *data = delayed_set_stat_head;
-      if (data->file_name_len < file_name_len
-         && file_name[data->file_name_len] == '/'
-         && memcmp (file_name, data->file_name, data->file_name_len) == 0)
-       break;
-      delayed_set_stat_head = data->next;
-      set_stat (data->file_name, &data->stat_info,
-               data->invert_permissions, data->permstatus, DIRTYPE);
-      free (data);
-    }
+  /* Then, apply delayed symlinks, so that they don't affect delayed
+     directory status-setting for ordinary directories.  */
+  apply_delayed_symlinks ();
+
+  /* Finally, fix the status of directories that are ancestors
+     of delayed symlinks.  */
+  apply_nonancestor_delayed_set_stat ("", 1);
 }
 
 void
 fatal_exit (void)
 {
-  apply_delayed_set_stat ();
+  extract_finish ();
   error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now"));
   abort ();
 }
This page took 0.052216 seconds and 4 git commands to generate.