bug-gnu-utils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

tar fails to extract hard linked files


From: Bruno Haible
Subject: tar fails to extract hard linked files
Date: Mon, 13 Jan 2003 13:49:34 +0100 (CET)

Hi,

Attempting to unpack a tar archive containing hard linked files on a
file system that doesn't support hard links gives an error, and doesn't
create the affected files.

This is particularly annoying because

  1) On some OSes, like BeOS, the filesystem doesn't support hard links.
     Thus the only way to deal with such a tar file on this OS is to
     transfer it to a different OS, unpack it there, undo the hard links
     using file copies, re-pack the thing, transfer it back to BeOS, and
     unpack it finally. Quite cumbersome.

  2) tar creates archives with hard links by default. Thus the guy who
     creates an archive is not aware that his archive will not unpack under
     certain OSes.

Note that the same situation can also occur on Solaris, if it contains
hardlinked files from /usr/lib and /usr/local - since these two directories
might be on the same mounted filesystem on the machine that creates the
archive but on different filesystems on the machine that extracts it.

Here is a patch which changes tar's behaviour to creates a regula file when
hard linking fails - by copying the previously extracted file. It still
gives an error message, as before, and still gives a nonzero error status,
as before. But the result is much more useful than before.


2003-01-11  Bruno Haible  <address@hidden>

        * extract.c (copy_reg): New function.
        (extract_archive): Call it.

diff -r -c3 tar-1.13.25.orig/src/extract.c tar-1.13.25/src/extract.c
*** tar-1.13.25.orig/src/extract.c      Mon Sep 24 20:55:17 2001
--- tar-1.13.25/src/extract.c   Sat Jan 11 23:17:02 2003
***************
*** 433,438 ****
--- 433,569 ----
    return 1;
  }
  
+ /* Copy a regular file from SRC_PATH to DST_PATH, preserving permissions
+    and modification date.  Return 0 if successful, -1 if an error occurred.  
*/
+ 
+ static int
+ copy_reg (const char *src_path, const char *dst_path)
+ {
+   char *buf;
+   int buf_size;
+   int dest_desc;
+   int source_desc;
+   int n_read;
+   struct stat src_sb;
+   struct stat dst_sb;
+   int return_val = 0;
+   off_t n_read_total = 0;
+ 
+   source_desc = open (src_path, O_RDONLY);
+   if (source_desc < 0)
+     {
+       /* If SRC_PATH doesn't exist, then chances are good that the
+        user did something like this `cp --backup foo foo': and foo
+        existed to start with, but copy_internal renamed DST_PATH
+        with the backup suffix, thus also renaming SRC_PATH.  */
+       if (errno == ENOENT)
+       error (0, 0, _("`%s' and `%s' are the same file"),
+              src_path, dst_path);
+       else
+       error (0, errno, "%s", src_path);
+ 
+       return -1;
+     }
+ 
+   /* Find out file mode and owner.  */
+ 
+   if (fstat (source_desc, &src_sb))
+     {
+       error (0, errno, "%s", src_path);
+       return_val = -1;
+       goto ret2;
+     }
+ 
+   /* Create the new regular file with small permissions initially,
+      to not create a security hole.  */
+ 
+   dest_desc = open (dst_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | 
S_IWUSR);
+   if (dest_desc < 0)
+     {
+       error (0, errno, _("cannot create regular file `%s'"), dst_path);
+       return_val = -1;
+       goto ret2;
+     }
+ 
+   /* Find out the optimal buffer size.  */
+ 
+   if (fstat (dest_desc, &dst_sb))
+     {
+       error (0, errno, "%s", dst_path);
+       return_val = -1;
+       goto ret;
+     }
+ 
+   buf_size = ST_BLKSIZE (dst_sb);
+   buf = (char *) alloca (buf_size);
+ 
+   for (;;)
+     {
+       n_read = read (source_desc, buf, buf_size);
+       if (n_read < 0)
+       {
+ #ifdef EINTR
+         if (errno == EINTR)
+           continue;
+ #endif
+         error (0, errno, "%s", src_path);
+         return_val = -1;
+         goto ret;
+       }
+       if (n_read == 0)
+       break;
+ 
+       n_read_total += n_read;
+ 
+       if (full_write (dest_desc, buf, n_read) < 0)
+       {
+         error (0, errno, "%s", dst_path);
+         return_val = -1;
+         goto ret;
+       }
+     }
+ 
+   if (dst_sb.st_uid != src_sb.st_uid || dst_sb.st_gid != src_sb.st_gid)
+     if (chown (dst_path, src_sb.st_uid, src_sb.st_gid))
+       {
+       error (0, errno, _("preserving ownership for %s"), dst_path);
+       return_val = -1;
+       goto ret;
+       }
+ 
+   if (dst_sb.st_mode != src_sb.st_mode)
+     if (chmod (dst_path, src_sb.st_mode))
+       {
+       error (0, errno, _("preserving permissions for %s"), dst_path);
+       return_val = -1;
+       goto ret;
+       }
+ 
+ ret:
+   if (close (dest_desc) < 0)
+     {
+       error (0, errno, "%s", dst_path);
+       return_val = -1;
+     }
+ ret2:
+   if (close (source_desc) < 0)
+     {
+       error (0, errno, "%s", src_path);
+       return_val = -1;
+     }
+ 
+   if (return_val == 0)
+     {
+       struct utimbuf ut;
+ 
+       ut.actime = src_sb.st_atime;
+       ut.modtime = src_sb.st_mtime;
+       utime (dst_path, &ut);
+     }
+ 
+   return return_val;
+ }
+ 
  /* 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
***************
*** 1054,1059 ****
--- 1185,1198 ----
            && st1.st_dev == st2.st_dev
            && st1.st_ino == st2.st_ino)
          break;
+ 
+       if (copy_reg (current_link_name, CURRENT_FILE_NAME) == 0)
+         {
+           WARN ((0, e, _("%s: Cannot link to %s, plain copy used instead"),
+                  quotearg_colon (CURRENT_FILE_NAME),
+                  quote (current_link_name)));
+           break;
+         }
  
        link_error (current_link_name, CURRENT_FILE_NAME);
        if (backup_option)




reply via email to

[Prev in Thread] Current Thread [Next in Thread]