]> Dogcows Code - chaz/tar/blobdiff - src/extract.c
(base_name): Remove decl, as system.h now declares it.
[chaz/tar] / src / extract.c
index 8cabb52cf9704b9df47b2abe5859aa0ce40010b1..d1401b32dcee13b0bf458550349a213d0f040908 100644 (file)
@@ -1,5 +1,8 @@
 /* Extract files from a tar archive.
-   Copyright 1988, 92,93,94,96,97,98,99, 2000 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
@@ -18,7 +21,6 @@
 
 #include "system.h"
 #include <quotearg.h>
-#include <time.h>
 
 #if HAVE_UTIME_H
 # include <utime.h>
@@ -72,8 +74,7 @@ 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 symbolic link.  */
+    /* The device, inode number and last-modified time of the placeholder.  */
     dev_t dev;
     ino_t ino;
     time_t mtime;
@@ -82,13 +83,22 @@ struct delayed_symlink
     uid_t uid;
     gid_t gid;
 
-    /* The location and desired target of the desired link, as two
-       adjacent character strings, both null-terminated. */
-    char names[1];
+    /* 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)
@@ -270,7 +280,8 @@ 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);
   data->file_name_len = file_name_len;
   strcpy (data->file_name, file_name);
   data->invert_permissions = invert_permissions;
@@ -282,9 +293,8 @@ delay_set_stat (char const *file_name, struct stat const *stat_info,
 
 /* 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, e.g. due trailing slash or
-   ".." or symbolic links.  *DIR_STAT_INFO is the status of the
-   directory.  */
+   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)
@@ -321,27 +331,30 @@ repair_delayed_set_stat (char const *dir_name,
 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 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 */
@@ -453,13 +466,13 @@ 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)
        {
@@ -475,10 +488,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)
@@ -489,21 +505,16 @@ 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);
 }
 
 /* Fix the statuses of all directories whose statuses need fixing, and
@@ -518,8 +529,8 @@ apply_nonancestor_delayed_set_stat (char const *file_name)
       struct delayed_set_stat *data = delayed_set_stat_head;
       if (data->file_name_len < file_name_len
          && file_name[data->file_name_len]
-         && (file_name[data->file_name_len] == '/'
-             || file_name[data->file_name_len - 1] == '/')
+         && (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;
       delayed_set_stat_head = data->next;
@@ -536,13 +547,13 @@ 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;
@@ -569,24 +580,28 @@ 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)));
-         skip_member ();
-         return;
        }
     }
 
@@ -693,7 +708,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.  */
@@ -773,11 +789,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)
              {
@@ -802,21 +817,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);
 
@@ -847,8 +861,9 @@ extract_archive (void)
        break;
 
       if (absolute_names_option
-         || (current_link_name[0] != '/'
-             && ! contains_dot_dot (current_link_name)))
+         || ! (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)
@@ -887,7 +902,8 @@ extract_archive (void)
              size_t filelen = strlen (CURRENT_FILE_NAME);
              size_t linklen = strlen (current_link_name);
              struct delayed_symlink *p =
-               xmalloc (sizeof *p + filelen + linklen + 1);
+               xmalloc (offsetof (struct delayed_symlink, target)
+                        + linklen + 1);
              p->next = delayed_symlink_head;
              delayed_symlink_head = p;
              p->dev = st.st_dev;
@@ -895,8 +911,11 @@ extract_archive (void)
              p->mtime = st.st_mtime;
              p->uid = current_stat.st_uid;
              p->gid = current_stat.st_gid;
-             memcpy (p->names, CURRENT_FILE_NAME, filelen + 1);
-             memcpy (p->names + filelen + 1, current_link_name, linklen + 1);
+             p->sources = xmalloc (offsetof (struct string_list, string)
+                                   + filelen + 1);
+             p->sources->next = 0;
+             memcpy (p->sources->string, CURRENT_FILE_NAME, filelen + 1);
+             memcpy (p->target, current_link_name, linklen + 1);
              status = 0;
            }
        }
@@ -934,7 +953,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;
 
@@ -947,9 +983,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 ();
       }
@@ -1014,6 +1048,12 @@ extract_archive (void)
       name_length = strlen (CURRENT_FILE_NAME);
 
     really_dir:
+      /* 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)
        {
          /* Read the entry and delete files that aren't listed in the
@@ -1033,15 +1073,25 @@ extract_archive (void)
 
     again_dir:
       status = mkdir (CURRENT_FILE_NAME, mode);
+
       if (status != 0)
        {
-         if (errno == EEXIST && interdir_made)
+         if (errno == EEXIST
+             && (interdir_made || old_files_option == OVERWRITE_OLD_FILES))
            {
              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;
+                   }
                }
              errno = EEXIST;
            }
@@ -1049,7 +1099,7 @@ extract_archive (void)
          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)
@@ -1058,10 +1108,11 @@ extract_archive (void)
            }
        }
 
+    directory_exists:
       if (status == 0
          || 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));
@@ -1107,40 +1158,56 @@ extract_archive (void)
 static void
 apply_delayed_symlinks (void)
 {
-  struct delayed_symlink *p;
-  struct delayed_symlink *next;
+  struct delayed_symlink *ds;
 
-  for (p = delayed_symlink_head; p; p = next)
+  for (ds = delayed_symlink_head; ds; )
     {
-      char const *file = p->names;
-      struct stat st;
+      struct string_list *sources = ds->sources;
+      char const *valid_source = 0;
 
-      /* Before doing anything, make sure the placeholder file is still
-        there.  If the placeholder isn't there, don't worry about it, as
-        it may have been removed by a later extraction.  */
-      if (lstat (file, &st) == 0
-         && st.st_dev == p->dev
-         && st.st_ino == p->ino
-         && st.st_mtime == p->mtime)
+      for (sources = ds->sources; sources; sources = sources->next)
        {
-         if (unlink (file) != 0)
-           unlink_error (file);
-         else
+         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)
            {
-             char const *contents = file + strlen (file) + 1;
-             if (symlink (contents, file) != 0)
-               symlink_error (contents, file);
+             /* 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
                {
-                 st.st_uid = p->uid;
-                 st.st_gid = p->gid;
-                 set_stat (file, &st, 0, 0, SYMTYPE);
+                 valid_source = source;
+                 st.st_uid = ds->uid;
+                 st.st_gid = ds->gid;
+                 set_stat (source, &st, 0, 0, SYMTYPE);
                }
            }
        }
 
-      next = p->next;
-      free (p);
+      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;
This page took 0.033652 seconds and 4 git commands to generate.