]> Dogcows Code - chaz/tar/blobdiff - src/create.c
Update copyright years.
[chaz/tar] / src / create.c
index e137325484e83efd8dd819722e64bda87e1e04fc..24920db37b967b99ea803d1418c7cb02141100cd 100644 (file)
@@ -1,23 +1,24 @@
 /* Create a tar archive.
 
-   Copyright (C) 1985, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
-   2003, 2004, 2005, 2006, 2007, 2009, 2010 Free Software Foundation, Inc.
+   Copyright 1985, 1992-1994, 1996-1997, 1999-2001, 2003-2007,
+   2009-2010, 2012-2014 Free Software Foundation, Inc.
 
-   Written by John Gilmore, on 1985-08-25.
+   This file is part of GNU tar.
 
-   This program is free software; you can redistribute it and/or modify it
-   under the terms of the GNU General Public License as published by the
-   Free Software Foundation; either version 3, or (at your option) any later
-   version.
+   GNU tar is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
 
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
-   Public License for more details.
+   GNU tar is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
-   You should have received a copy of the GNU General Public License along
-   with this program; if not, write to the Free Software Foundation, Inc.,
-   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+   Written by John Gilmore, on 1985-08-25.  */
 
 #include <system.h>
 
 #include "common.h"
 #include <hash.h>
 
+/* Error number to use when an impostor is discovered.
+   Pretend the impostor isn't there.  */
+enum { IMPOSTOR_ERRNO = ENOENT };
+
 struct link
   {
     dev_t dev;
@@ -72,13 +77,13 @@ exclusion_tag_warning (const char *dirname, const char *tagname,
 }
 
 enum exclusion_tag_type
-check_exclusion_tags (int fd, char const **tag_file_name)
+check_exclusion_tags (struct tar_stat_info const *st, char const **tag_file_name)
 {
   struct exclusion_tag *tag;
 
   for (tag = exclusion_tags; tag; tag = tag->next)
     {
-      int tagfd = openat (fd, tag->name, open_read_flags);
+      int tagfd = subfile_open (st, tag->name, open_read_flags);
       if (0 <= tagfd)
        {
          bool satisfied = !tag->predicate || tag->predicate (tagfd);
@@ -507,12 +512,11 @@ start_private_header (const char *name, size_t size, time_t t)
   tar_name_copy_str (header->header.name, name, NAME_FIELD_SIZE);
   OFF_TO_CHARS (size, header->header.size);
 
-  TIME_TO_CHARS (t, header->header.mtime);
+  TIME_TO_CHARS (t < 0 ? 0 : min (t, MAX_OCTAL_VAL (header->header.mtime)),
+                header->header.mtime);
   MODE_TO_CHARS (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, header->header.mode);
-  UID_TO_CHARS (getuid (), header->header.uid);
-  GID_TO_CHARS (getgid (), header->header.gid);
-  MAJOR_TO_CHARS (0, header->header.devmajor);
-  MINOR_TO_CHARS (0, header->header.devminor);
+  UID_TO_CHARS (0, header->header.uid);
+  GID_TO_CHARS (0, header->header.gid);
   strncpy (header->header.magic, TMAGIC, TMAGLEN);
   strncpy (header->header.version, TVERSION, TVERSLEN);
   return header;
@@ -530,11 +534,6 @@ write_short_name (struct tar_stat_info *st)
   return header;
 }
 
-#define FILL(field,byte) do {            \
-  memset(field, byte, sizeof(field)-1);  \
-  (field)[sizeof(field)-1] = 0;          \
-} while (0)
-
 /* Write a GNUTYPE_LONGLINK or GNUTYPE_LONGNAME block.  */
 static void
 write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
@@ -544,13 +543,7 @@ write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
   union block *header;
   char *tmpname;
 
-  header = start_private_header ("././@LongLink", size, time (NULL));
-  FILL (header->header.mtime, '0');
-  FILL (header->header.mode, '0');
-  FILL (header->header.uid, '0');
-  FILL (header->header.gid, '0');
-  FILL (header->header.devmajor, 0);
-  FILL (header->header.devminor, 0);
+  header = start_private_header ("././@LongLink", size, 0);
   uid_to_uname (0, &tmpname);
   UNAME_TO_CHARS (tmpname, header->header.uname);
   free (tmpname);
@@ -558,7 +551,8 @@ write_gnu_long_link (struct tar_stat_info *st, const char *p, char type)
   GNAME_TO_CHARS (tmpname, header->header.gname);
   free (tmpname);
 
-  strcpy (header->header.magic, OLDGNU_MAGIC);
+  strcpy (header->buffer + offsetof (struct posix_header, magic),
+         OLDGNU_MAGIC);
   header->header.typeflag = type;
   finish_header (st, header, -1);
 
@@ -706,7 +700,7 @@ write_extended (bool global, struct tar_stat_info *st, union block *old_header)
     {
       type = XGLTYPE;
       p = xheader_ghdr_name ();
-      time (&t);
+      t = start_time.tv_sec;
     }
   else
     {
@@ -780,9 +774,9 @@ start_header (struct tar_stat_info *st)
        . . . . . . . . .   9 = Omron UNIOS-B 4.3BSD 1.60Beta
 
             . = works
-            # = ``impossible file type''
+            # = "impossible file type"
 
-     The following mask for old archive removes the `#'s in column 4
+     The following mask for old archive removes the '#'s in column 4
      above, thus making GNU tar both a universal donor and a universal
      acceptor for Paul's test.  */
 
@@ -895,7 +889,8 @@ start_header (struct tar_stat_info *st)
     case OLDGNU_FORMAT:
     case GNU_FORMAT:   /*FIXME?*/
       /* Overwrite header->header.magic and header.version in one blow.  */
-      strcpy (header->header.magic, OLDGNU_MAGIC);
+      strcpy (header->buffer + offsetof (struct posix_header, magic),
+             OLDGNU_MAGIC);
       break;
 
     case POSIX_FORMAT:
@@ -914,8 +909,15 @@ start_header (struct tar_stat_info *st)
     }
   else
     {
-      uid_to_uname (st->stat.st_uid, &st->uname);
-      gid_to_gname (st->stat.st_gid, &st->gname);
+      if (owner_name_option)
+       st->uname = xstrdup (owner_name_option);
+      else
+       uid_to_uname (st->stat.st_uid, &st->uname);
+
+      if (group_name_option)
+       st->gname = xstrdup (group_name_option);
+      else
+       gid_to_gname (st->stat.st_gid, &st->gname);
 
       if (archive_format == POSIX_FORMAT
          && (strlen (st->uname) > UNAME_FIELD_SIZE
@@ -930,6 +932,30 @@ start_header (struct tar_stat_info *st)
       GNAME_TO_CHARS (st->gname, header->header.gname);
     }
 
+  if (archive_format == POSIX_FORMAT)
+    {
+      if (acls_option > 0)
+        {
+          if (st->acls_a_ptr)
+            xheader_store ("SCHILY.acl.access", st, NULL);
+          if (st->acls_d_ptr)
+            xheader_store ("SCHILY.acl.default", st, NULL);
+        }
+      if ((selinux_context_option > 0) && st->cntx_name)
+        xheader_store ("RHT.security.selinux", st, NULL);
+      if (xattrs_option > 0)
+        {
+          size_t scan_xattr = 0;
+          struct xattr_array *xattr_map = st->xattr_map;
+
+          while (scan_xattr < st->xattr_map_size)
+            {
+              xheader_store (xattr_map[scan_xattr].xkey, st, &scan_xattr);
+              ++scan_xattr;
+            }
+        }
+    }
+
   return header;
 }
 
@@ -1038,7 +1064,7 @@ dump_regular_file (int fd, struct tar_stat_info *st)
            memset (blk->buffer + size_left, 0, BLOCKSIZE - count);
        }
 
-      count = (fd < 0) ? bufsize : safe_read (fd, blk->buffer, bufsize);
+      count = (fd <= 0) ? bufsize : blocking_read (fd, blk->buffer, bufsize);
       if (count == SAFE_READ_ERROR)
        {
          read_diag_details (st->orig_file_name,
@@ -1159,7 +1185,7 @@ dump_dir0 (struct tar_stat_info *st, char const *directory)
       char *name_buf;
       size_t name_size;
 
-      switch (check_exclusion_tags (st->fd, &tag_file_name))
+      switch (check_exclusion_tags (st, &tag_file_name))
        {
        case exclusion_tag_all:
          /* Handled in dump_file0 */
@@ -1224,21 +1250,54 @@ ensure_slash (char **pstr)
   (*pstr)[len] = '\0';
 }
 
+/* If we just ran out of file descriptors, release a file descriptor
+   in the directory chain somewhere leading from DIR->parent->parent
+   up through the root.  Return true if successful, false (preserving
+   errno == EMFILE) otherwise.
+
+   Do not release DIR's file descriptor, or DIR's parent, as other
+   code assumes that they work.  On some operating systems, another
+   process can claim file descriptor resources as we release them, and
+   some calls or their emulations require multiple file descriptors,
+   so callers should not give up if a single release doesn't work.  */
+
 static bool
-dump_dir (struct tar_stat_info *st)
+open_failure_recover (struct tar_stat_info const *dir)
 {
-  char *directory = 0;
-  int dupfd = dup (st->fd);
-  if (0 <= dupfd)
+  if (errno == EMFILE && dir && dir->parent)
     {
-      directory = fdsavedir (dupfd);
-      if (! directory)
-       {
-         int e = errno;
-         close (dupfd);
-         errno = e;
-       }
+      struct tar_stat_info *p;
+      for (p = dir->parent->parent; p; p = p->parent)
+       if (0 < p->fd && (! p->parent || p->parent->fd <= 0))
+         {
+           tar_stat_close (p);
+           return true;
+         }
+      errno = EMFILE;
     }
+
+  return false;
+}
+
+/* Return the directory entries of ST, in a dynamically allocated buffer,
+   each entry followed by '\0' and the last followed by an extra '\0'.
+   Return null on failure, setting errno.  */
+char *
+get_directory_entries (struct tar_stat_info *st)
+{
+  while (! (st->dirstream = fdopendir (st->fd)))
+    if (! open_failure_recover (st))
+      return 0;
+  return streamsavedir (st->dirstream);
+}
+
+/* Dump the directory ST.  Return true if successful, false (emitting
+   diagnostics) otherwise.  Get ST's entries, recurse through its
+   subdirectories, and clean up file descriptors afterwards.  */
+static bool
+dump_dir (struct tar_stat_info *st)
+{
+  char *directory = get_directory_entries (st);
   if (! directory)
     {
       savedir_diag (st->orig_file_name);
@@ -1247,6 +1306,7 @@ dump_dir (struct tar_stat_info *st)
 
   dump_dir0 (st, directory);
 
+  restore_parent_fd (st);
   free (directory);
   return true;
 }
@@ -1307,21 +1367,20 @@ create_archive (void)
                    {
                      if (! st.orig_file_name)
                        {
-                         st.orig_file_name = xstrdup (p->name);
-                         st.fd = open (st.orig_file_name,
-                                       ((open_read_flags - O_RDONLY
-                                         + O_SEARCH)
-                                        | O_DIRECTORY));
-                         if (st.fd < 0)
+                         int fd = openat (chdir_fd, p->name,
+                                          open_searchdir_flags);
+                         if (fd < 0)
                            {
                              open_diag (p->name);
                              break;
                            }
-                         if (fstat (st.fd, &st.stat) != 0)
+                         st.fd = fd;
+                         if (fstat (fd, &st.stat) != 0)
                            {
                              stat_diag (p->name);
                              break;
                            }
+                         st.orig_file_name = xstrdup (p->name);
                        }
                      if (buffer_size < plen + qlen)
                        {
@@ -1492,6 +1551,75 @@ check_links (void)
     }
 }
 
+/* Assuming DIR is the working directory, open FILE, using FLAGS to
+   control the open.  A null DIR means to use ".".  If we are low on
+   file descriptors, try to release one or more from DIR's parents to
+   reuse it.  */
+int
+subfile_open (struct tar_stat_info const *dir, char const *file, int flags)
+{
+  int fd;
+
+  static bool initialized;
+  if (! initialized)
+    {
+      /* Initialize any tables that might be needed when file
+        descriptors are exhausted, and whose initialization might
+        require a file descriptor.  This includes the system message
+        catalog and tar's message catalog.  */
+      initialized = true;
+      strerror (ENOENT);
+      gettext ("");
+    }
+
+  while ((fd = openat (dir ? dir->fd : chdir_fd, file, flags)) < 0
+        && open_failure_recover (dir))
+    continue;
+  return fd;
+}
+
+/* Restore the file descriptor for ST->parent, if it was temporarily
+   closed to conserve file descriptors.  On failure, set the file
+   descriptor to the negative of the corresponding errno value.  Call
+   this every time a subdirectory is ascended from.  */
+void
+restore_parent_fd (struct tar_stat_info const *st)
+{
+  struct tar_stat_info *parent = st->parent;
+  if (parent && ! parent->fd)
+    {
+      int parentfd = openat (st->fd, "..", open_searchdir_flags);
+      struct stat parentstat;
+
+      if (parentfd < 0)
+       parentfd = - errno;
+      else if (! (fstat (parentfd, &parentstat) == 0
+                 && parent->stat.st_ino == parentstat.st_ino
+                 && parent->stat.st_dev == parentstat.st_dev))
+       {
+         close (parentfd);
+         parentfd = IMPOSTOR_ERRNO;
+       }
+
+      if (parentfd < 0)
+       {
+         int origfd = openat (chdir_fd, parent->orig_file_name,
+                              open_searchdir_flags);
+         if (0 <= origfd)
+           {
+             if (fstat (parentfd, &parentstat) == 0
+                 && parent->stat.st_ino == parentstat.st_ino
+                 && parent->stat.st_dev == parentstat.st_dev)
+               parentfd = origfd;
+             else
+               close (origfd);
+           }
+       }
+
+      parent->fd = parentfd;
+    }
+}
+
 /* Dump a single file, recursing on directories.  ST is the file's
    status info, NAME its name relative to the parent directory, and P
    its full name (which may be relative to the working directory).  */
@@ -1506,12 +1634,12 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
   char type;
   off_t original_size;
   struct timespec original_ctime;
-  struct timespec restore_times[2];
   off_t block_ordinal = -1;
-  int fd = -1;
+  int fd = 0;
   bool is_dir;
-  bool top_level = ! st->parent;
-  int parentfd = top_level ? AT_FDCWD : st->parent->fd;
+  struct tar_stat_info const *parent = st->parent;
+  bool top_level = ! parent;
+  int parentfd = top_level ? chdir_fd : parent->fd;
   void (*diag) (char const *) = 0;
 
   if (interactive_option && !confirm ("add", p))
@@ -1523,15 +1651,24 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
 
   transform_name (&st->file_name, XFORM_REGFILE);
 
-  if (fstatat (parentfd, name, &st->stat, fstatat_flags) != 0)
+  if (parentfd < 0 && ! top_level)
+    {
+      errno = - parentfd;
+      diag = open_diag;
+    }
+  else if (fstatat (parentfd, name, &st->stat, fstatat_flags) != 0)
     diag = stat_diag;
   else if (file_dumpable_p (&st->stat))
     {
-      fd = st->fd = openat (parentfd, name, open_read_flags);
+      fd = subfile_open (parent, name, open_read_flags);
       if (fd < 0)
        diag = open_diag;
-      else if (fstat (fd, &st->stat) != 0)
-       diag = stat_diag;
+      else
+       {
+         st->fd = fd;
+         if (fstat (fd, &st->stat) != 0)
+           diag = stat_diag;
+       }
     }
   if (diag)
     {
@@ -1540,8 +1677,8 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
     }
 
   st->archive_file_size = original_size = st->stat.st_size;
-  st->atime = restore_times[0] = get_stat_atime (&st->stat);
-  st->mtime = restore_times[1] = get_stat_mtime (&st->stat);
+  st->atime = get_stat_atime (&st->stat);
+  st->mtime = get_stat_mtime (&st->stat);
   st->ctime = original_ctime = get_stat_ctime (&st->stat);
 
 #ifdef S_ISHIDDEN
@@ -1561,9 +1698,9 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
      put in the archive.
 
      This check is omitted if incremental_option is set *and* the
-     requested file is not explicitely listed in the command line. */
+     requested file is not explicitly listed in the command line.  */
 
-  if (!(incremental_option && !is_individual_file (p))
+  if (! (incremental_option && ! top_level)
       && !S_ISDIR (st->stat.st_mode)
       && OLDER_TAR_STAT_TIME (*st, m)
       && (!after_date_option || OLDER_TAR_STAT_TIME (*st, c)))
@@ -1594,13 +1731,17 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
       bool ok;
       struct stat final_stat;
 
+      xattrs_acls_get (parentfd, name, st, 0, !is_dir);
+      xattrs_selinux_get (parentfd, name, st, fd);
+      xattrs_xattrs_get (parentfd, name, st, fd);
+
       if (is_dir)
        {
          const char *tag_file_name;
          ensure_slash (&st->orig_file_name);
          ensure_slash (&st->file_name);
 
-         if (check_exclusion_tags (fd, &tag_file_name) == exclusion_tag_all)
+         if (check_exclusion_tags (st, &tag_file_name) == exclusion_tag_all)
            {
              exclusion_tag_warning (st->orig_file_name, tag_file_name,
                                     _("directory not dumped"));
@@ -1608,12 +1749,15 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
            }
 
          ok = dump_dir (st);
+
+         fd = st->fd;
+         parentfd = top_level ? chdir_fd : parent->fd;
        }
       else
        {
          enum dump_status status;
 
-         if (fd != -1 && sparse_option && ST_IS_SPARSE (st->stat))
+         if (fd && sparse_option && ST_IS_SPARSE (st->stat))
            {
              status = sparse_dump_file (fd, st);
              if (status == dump_status_not_implemented)
@@ -1641,14 +1785,26 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
 
       if (ok)
        {
-         if ((fd < 0
-              ? fstatat (parentfd, name, &final_stat, fstatat_flags)
-              : fstat (fd, &final_stat))
-             != 0)
+         if (fd < 0)
            {
-             file_removed_diag (p, top_level, stat_diag);
+             errno = - fd;
              ok = false;
            }
+         else if (fd == 0)
+           {
+             if (parentfd < 0 && ! top_level)
+               {
+                 errno = - parentfd;
+                 ok = false;
+               }
+             else
+               ok = fstatat (parentfd, name, &final_stat, fstatat_flags) == 0;
+           }
+         else
+           ok = fstat (fd, &final_stat) == 0;
+
+         if (! ok)
+           file_removed_diag (p, top_level, stat_diag);
        }
 
       if (ok)
@@ -1665,20 +1821,12 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
              set_exit_status (TAREXIT_DIFFERS);
            }
          else if (atime_preserve_option == replace_atime_preserve
-                  && set_file_atime (fd, p, restore_times) != 0)
+                  && fd && (is_dir || original_size != 0)
+                  && set_file_atime (fd, parentfd, name, st->atime) != 0)
            utime_error (p);
        }
 
-      if (0 < fd)
-       {
-         if (close (fd) != 0)
-           {
-             close_diag (p);
-             ok = false;
-           }
-         st->fd = 0;
-       }
-
+      ok &= tar_stat_close (st);
       if (ok && remove_files_option)
        queue_deferred_unlink (p, is_dir);
 
@@ -1705,6 +1853,9 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
       if (NAME_FIELD_SIZE - (archive_format == OLDGNU_FORMAT) < size)
        write_long_link (st);
 
+      xattrs_selinux_get (parentfd, name, st, 0);
+      xattrs_xattrs_get (parentfd, name, st, 0);
+
       block_ordinal = current_block_ordinal ();
       st->stat.st_size = 0;    /* force 0 size on symlink */
       header = start_header (st);
@@ -1723,11 +1874,26 @@ dump_file0 (struct tar_stat_info *st, char const *name, char const *p)
     }
 #endif
   else if (S_ISCHR (st->stat.st_mode))
-    type = CHRTYPE;
+    {
+      type = CHRTYPE;
+      xattrs_acls_get (parentfd, name, st, 0, true);
+      xattrs_selinux_get (parentfd, name, st, 0);
+      xattrs_xattrs_get (parentfd, name, st, 0);
+    }
   else if (S_ISBLK (st->stat.st_mode))
-    type = BLKTYPE;
+    {
+      type = BLKTYPE;
+      xattrs_acls_get (parentfd, name, st, 0, true);
+      xattrs_selinux_get (parentfd, name, st, 0);
+      xattrs_xattrs_get (parentfd, name, st, 0);
+    }
   else if (S_ISFIFO (st->stat.st_mode))
-    type = FIFOTYPE;
+    {
+      type = FIFOTYPE;
+      xattrs_acls_get (parentfd, name, st, 0, true);
+      xattrs_selinux_get (parentfd, name, st, 0);
+      xattrs_xattrs_get (parentfd, name, st, 0);
+    }
   else if (S_ISSOCK (st->stat.st_mode))
     {
       WARNOPT (WARN_FILE_IGNORED,
This page took 0.03748 seconds and 4 git commands to generate.