re-mark 1.29b-2 as not yet uploaded (merge madness!)
[debian/tar] / src / sparse.c
index 9fde5080d5304a32253fd67d72e1a99a0c69bf56..4e784015e722ad39a69f5b2c2e7975d3ade361ad 100644 (file)
@@ -1,6 +1,6 @@
 /* Functions for dealing with sparse files
 
-   Copyright (C) 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+   Copyright 2003-2007, 2010, 2013-2016 Free Software Foundation, Inc.
 
    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
@@ -13,8 +13,7 @@
    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.  */
+   with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <system.h>
 #include <inttostr.h>
@@ -209,52 +208,51 @@ sparse_add_map (struct tar_stat_info *st, struct sp_array const *sp)
   st->sparse_map_avail = avail + 1;
 }
 
-/* Scan the sparse file and create its map */
+/* Scan the sparse file byte-by-byte and create its map. */
 static bool
-sparse_scan_file (struct tar_sparse_file *file)
+sparse_scan_file_raw (struct tar_sparse_file *file)
 {
   struct tar_stat_info *st = file->stat_info;
   int fd = file->fd;
   char buffer[BLOCKSIZE];
-  size_t count;
+  size_t count = 0;
   off_t offset = 0;
   struct sp_array sp = {0, 0};
 
-  if (!lseek_or_error (file, 0))
-    return false;
-
   st->archive_file_size = 0;
-  
+
   if (!tar_sparse_scan (file, scan_begin, NULL))
     return false;
 
-  while ((count = safe_read (fd, buffer, sizeof buffer)) != 0
-        && count != SAFE_READ_ERROR)
+  while ((count = blocking_read (fd, buffer, sizeof buffer)) != 0
+         && count != SAFE_READ_ERROR)
     {
       /* Analyze the block.  */
       if (zero_block_p (buffer, count))
-       {
-         if (sp.numbytes)
-           {
-             sparse_add_map (st, &sp);
-             sp.numbytes = 0;
-             if (!tar_sparse_scan (file, scan_block, NULL))
-               return false;
-           }
-       }
+        {
+          if (sp.numbytes)
+            {
+              sparse_add_map (st, &sp);
+              sp.numbytes = 0;
+              if (!tar_sparse_scan (file, scan_block, NULL))
+                return false;
+            }
+        }
       else
-       {
-         if (sp.numbytes == 0)
-           sp.offset = offset;
-         sp.numbytes += count;
-         st->archive_file_size += count;
-         if (!tar_sparse_scan (file, scan_block, buffer))
-           return false;
-       }
+        {
+          if (sp.numbytes == 0)
+            sp.offset = offset;
+          sp.numbytes += count;
+          st->archive_file_size += count;
+          if (!tar_sparse_scan (file, scan_block, buffer))
+            return false;
+        }
 
       offset += count;
     }
 
+  /* save one more sparse segment of length 0 to indicate that
+     the file ends with a hole */
   if (sp.numbytes == 0)
     sp.offset = offset;
 
@@ -263,6 +261,114 @@ sparse_scan_file (struct tar_sparse_file *file)
   return tar_sparse_scan (file, scan_end, NULL);
 }
 
+static bool
+sparse_scan_file_wholesparse (struct tar_sparse_file *file)
+{
+  struct tar_stat_info *st = file->stat_info;
+  struct sp_array sp = {0, 0};
+
+  /* Note that this function is called only for truly sparse files of size >= 1
+     block size (checked via ST_IS_SPARSE before).  See the thread
+     http://www.mail-archive.com/bug-tar@gnu.org/msg04209.html for more info */
+  if (ST_NBLOCKS (st->stat) == 0)
+    {
+      st->archive_file_size = 0;
+      sp.offset = st->stat.st_size;
+      sparse_add_map (st, &sp);
+      return true;
+    }
+
+  return false;
+}
+
+#ifdef SEEK_HOLE
+/* Try to engage SEEK_HOLE/SEEK_DATA feature. */
+static bool
+sparse_scan_file_seek (struct tar_sparse_file *file)
+{
+  struct tar_stat_info *st = file->stat_info;
+  int fd = file->fd;
+  struct sp_array sp = {0, 0};
+  off_t offset = 0;
+  off_t data_offset;
+  off_t hole_offset;
+
+  st->archive_file_size = 0;
+
+  for (;;)
+    {
+      /* locate first chunk of data */
+      data_offset = lseek (fd, offset, SEEK_DATA);
+
+      if (data_offset == (off_t)-1)
+        /* ENXIO == EOF; error otherwise */
+        {
+          if (errno == ENXIO)
+            {
+              /* file ends with hole, add one more empty chunk of data */
+              sp.numbytes = 0;
+              sp.offset = st->stat.st_size;
+              sparse_add_map (st, &sp);
+              return true;
+            }
+          return false;
+        }
+
+      hole_offset = lseek (fd, data_offset, SEEK_HOLE);
+
+      /* according to specs, if FS does not fully support
+        SEEK_DATA/SEEK_HOLE it may just implement kind of "wrapper" around
+        classic lseek() call.  We must detect it here and try to use other
+        hole-detection methods. */
+      if (offset == 0 /* first loop */
+          && data_offset == 0
+          && hole_offset == st->stat.st_size)
+        {
+          lseek (fd, 0, SEEK_SET);
+          return false;
+        }
+
+      sp.offset = data_offset;
+      sp.numbytes = hole_offset - data_offset;
+      sparse_add_map (st, &sp);
+
+      st->archive_file_size += sp.numbytes;
+      offset = hole_offset;
+    }
+
+  return true;
+}
+#endif
+
+static bool
+sparse_scan_file (struct tar_sparse_file *file)
+{
+  /* always check for completely sparse files */
+  if (sparse_scan_file_wholesparse (file))
+    return true;
+
+  switch (hole_detection)
+    {
+    case HOLE_DETECTION_DEFAULT:
+    case HOLE_DETECTION_SEEK:
+#ifdef SEEK_HOLE
+      if (sparse_scan_file_seek (file))
+        return true;
+#else
+      if (hole_detection == HOLE_DETECTION_SEEK)
+       WARN((0, 0,
+             _("\"seek\" hole detection is not supported, using \"raw\".")));
+      /* fall back to "raw" for this and all other files */
+      hole_detection = HOLE_DETECTION_RAW;
+#endif
+    case HOLE_DETECTION_RAW:
+      if (sparse_scan_file_raw (file))
+       return true;
+    }
+  
+  return false;
+}
+
 static struct tar_sparse_optab const oldgnu_optab;
 static struct tar_sparse_optab const star_optab;
 static struct tar_sparse_optab const pax_optab;
@@ -324,7 +430,6 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i)
       memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read);
       bytes_left -= bytes_read;
       file->dumped_size += bytes_read;
-      mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
       set_next_block_after (blk);
     }
 
@@ -334,7 +439,7 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i)
 static bool
 sparse_extract_region (struct tar_sparse_file *file, size_t i)
 {
-  size_t write_size;
+  off_t write_size;
 
   if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
     return false;
@@ -358,7 +463,7 @@ sparse_extract_region (struct tar_sparse_file *file, size_t i)
          return false;
        }
       set_next_block_after (blk);
-      count = full_write (file->fd, blk->buffer, wrbytes);
+      count = blocking_write (file->fd, blk->buffer, wrbytes);
       write_size -= count;
       file->dumped_size += count;
       mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
@@ -398,10 +503,11 @@ sparse_dump_file (int fd, struct tar_stat_info *st)
        {
          size_t i;
 
-         mv_begin (file.stat_info);
+         mv_begin_write (file.stat_info->file_name,
+                         file.stat_info->stat.st_size,
+                         file.stat_info->archive_file_size - file.dumped_size);
          for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
            rc = tar_sparse_dump_region (&file, i);
-         mv_end ();
        }
     }
 
@@ -508,13 +614,13 @@ check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
 static bool
 check_data_region (struct tar_sparse_file *file, size_t i)
 {
-  size_t size_left;
+  off_t size_left;
 
   if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
     return false;
   size_left = file->stat_info->sparse_map[i].numbytes;
   mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
-      
+
   while (size_left > 0)
     {
       size_t bytes_read;
@@ -564,9 +670,9 @@ sparse_diff_file (int fd, struct tar_stat_info *st)
   file.stat_info = st;
   file.fd = fd;
   file.seekable = true; /* File *must* be seekable for compare to work */
-  
+
   rc = tar_sparse_decode_header (&file);
-  mv_begin (st);
+  mv_begin_read (st);
   for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
     {
       rc = check_sparse_region (&file,
@@ -579,7 +685,7 @@ sparse_diff_file (int fd, struct tar_stat_info *st)
   if (!rc)
     skip_file (file.stat_info->archive_file_size - file.dumped_size);
   mv_end ();
-  
+
   tar_sparse_done (&file);
   return rc;
 }
@@ -588,18 +694,18 @@ sparse_diff_file (int fd, struct tar_stat_info *st)
 /* Old GNU Format. The sparse file information is stored in the
    oldgnu_header in the following manner:
 
-   The header is marked with type 'S'. Its `size' field contains
+   The header is marked with type 'S'. Its 'size' field contains
    the cumulative size of all non-empty blocks of the file. The
-   actual file size is stored in `realsize' member of oldgnu_header.
+   actual file size is stored in 'realsize' member of oldgnu_header.
 
-   The map of the file is stored in a list of `struct sparse'.
+   The map of the file is stored in a list of 'struct sparse'.
    Each struct contains offset to the block of data and its
    size (both as octal numbers). The first file header contains
    at most 4 such structs (SPARSES_IN_OLDGNU_HEADER). If the map
-   contains more structs, then the field `isextended' of the main
-   header is set to 1 (binary) and the `struct sparse_header'
+   contains more structs, then the field 'isextended' of the main
+   header is set to 1 (binary) and the 'struct sparse_header'
    header follows, containing at most 21 following structs
-   (SPARSES_IN_SPARSE_HEADER). If more structs follow, `isextended'
+   (SPARSES_IN_SPARSE_HEADER). If more structs follow, 'isextended'
    field of the extended header is set and next  next extension header
    follows, etc... */
 
@@ -625,8 +731,9 @@ oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
   if (s->numbytes[0] == '\0')
     return add_finish;
   sp.offset = OFF_FROM_HEADER (s->offset);
-  sp.numbytes = SIZE_FROM_HEADER (s->numbytes);
-  if (sp.offset < 0
+  sp.numbytes = OFF_FROM_HEADER (s->numbytes);
+  if (sp.offset < 0 || sp.numbytes < 0
+      || INT_ADD_OVERFLOW (sp.offset, sp.numbytes)
       || file->stat_info->stat.st_size < sp.offset + sp.numbytes
       || file->stat_info->archive_file_size < 0)
     return add_fail;
@@ -640,10 +747,10 @@ oldgnu_fixup_header (struct tar_sparse_file *file)
 {
   /* NOTE! st_size was initialized from the header
      which actually contains archived size. The following fixes it */
+  off_t realsize = OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
   file->stat_info->archive_file_size = file->stat_info->stat.st_size;
-  file->stat_info->stat.st_size =
-    OFF_FROM_HEADER (current_header->oldgnu_header.realsize);
-  return true;
+  file->stat_info->stat.st_size = max (0, realsize);
+  return 0 <= realsize;
 }
 
 /* Convert old GNU format sparse data to internal representation */
@@ -695,8 +802,8 @@ oldgnu_store_sparse_info (struct tar_sparse_file *file, size_t *pindex,
     {
       OFF_TO_CHARS (file->stat_info->sparse_map[*pindex].offset,
                    sp->offset);
-      SIZE_TO_CHARS (file->stat_info->sparse_map[*pindex].numbytes,
-                    sp->numbytes);
+      OFF_TO_CHARS (file->stat_info->sparse_map[*pindex].numbytes,
+                   sp->numbytes);
     }
 }
 
@@ -764,10 +871,10 @@ star_fixup_header (struct tar_sparse_file *file)
 {
   /* NOTE! st_size was initialized from the header
      which actually contains archived size. The following fixes it */
+  off_t realsize = OFF_FROM_HEADER (current_header->star_in_header.realsize);
   file->stat_info->archive_file_size = file->stat_info->stat.st_size;
-  file->stat_info->stat.st_size =
-            OFF_FROM_HEADER (current_header->star_in_header.realsize);
-  return true;
+  file->stat_info->stat.st_size = max (0, realsize);
+  return 0 <= realsize;
 }
 
 /* Convert STAR format sparse data to internal representation */
@@ -807,6 +914,7 @@ star_get_sparse_info (struct tar_sparse_file *file)
       set_next_block_after (h);
       for (i = 0; i < SPARSES_IN_STAR_EXT_HEADER && rc == add_ok; i++)
        rc = oldgnu_add_sparse (file, &h->star_ext_header.sp[i]);
+      file->dumped_size += BLOCKSIZE;
     }
 
   if (rc == add_fail)
@@ -851,13 +959,13 @@ static struct tar_sparse_optab const star_optab = {
    instances of GNU.sparse.offset/GNU.sparse.numbytes variables, whereas
    POSIX requires the latest occurrence of the variable to override all
    previous occurrences.
-   
+
    To avoid this incompatibility two following versions were introduced.
 
    * 0.1
 
    Used by tar 1.15.2 -- 1.15.91 (alpha releases).
-   
+
    The sparse file map is stored in
    x header:
 
@@ -875,18 +983,18 @@ static struct tar_sparse_optab const star_optab = {
    Starting from this version, the exact sparse format version is specified
    explicitely in the header using the following variables:
 
-   GNU.sparse.major     Major version 
+   GNU.sparse.major     Major version
    GNU.sparse.minor     Minor version
 
    X header keeps the following variables:
-   
+
    GNU.sparse.name      Real file name of the sparse file
    GNU.sparse.realsize  Real size of the stored file (corresponds to the old
                         GNU.sparse.size variable)
 
    The name field of the ustar header is constructed using the pattern
    "%d/GNUSparseFile.%p/%f".
-   
+
    The sparse map itself is stored in the file data block, preceding the actual
    file data. It consists of a series of octal numbers of arbitrary length,
    delimited by newlines. The map is padded with nulls to the nearest block
@@ -915,6 +1023,18 @@ pax_sparse_member_p (struct tar_sparse_file *file)
           || file->stat_info->sparse_major > 0;
 }
 
+/* Start a header that uses the effective (shrunken) file size.  */
+static union block *
+pax_start_header (struct tar_stat_info *st)
+{
+  off_t realsize = st->stat.st_size;
+  union block *blk;
+  st->stat.st_size = st->archive_file_size;
+  blk = start_header (st);
+  st->stat.st_size = realsize;
+  return blk;
+}
+
 static bool
 pax_dump_header_0 (struct tar_sparse_file *file)
 {
@@ -924,11 +1044,11 @@ pax_dump_header_0 (struct tar_sparse_file *file)
   char nbuf[UINTMAX_STRSIZE_BOUND];
   struct sp_array *map = file->stat_info->sparse_map;
   char *save_file_name = NULL;
-  
+
   /* Store the real file size */
   xheader_store ("GNU.sparse.size", file->stat_info, NULL);
   xheader_store ("GNU.sparse.numblocks", file->stat_info, NULL);
-  
+
   if (xheader_keyword_deleted_p ("GNU.sparse.map")
       || tar_sparse_minor == 0)
     {
@@ -964,9 +1084,7 @@ pax_dump_header_0 (struct tar_sparse_file *file)
          return false;
        }
     }
-  blk = start_header (file->stat_info);
-  /* Store the effective (shrunken) file size */
-  OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size);
+  blk = pax_start_header (file->stat_info);
   finish_header (file->stat_info, blk, block_ordinal);
   if (save_file_name)
     {
@@ -991,7 +1109,7 @@ pax_dump_header_1 (struct tar_sparse_file *file)
 #define COPY_STRING(b,dst,src) do                \
  {                                               \
    char *endp = b->buffer + BLOCKSIZE;           \
-   char *srcp = src;                             \
+   char const *srcp = src;                       \
    while (*srcp)                                 \
      {                                           \
        if (dst == endp)                          \
@@ -1003,7 +1121,7 @@ pax_dump_header_1 (struct tar_sparse_file *file)
         }                                       \
        *dst++ = *srcp++;                         \
      }                                           \
-   } while (0)                       
+   } while (0)
 
   /* Compute stored file size */
   p = umaxtostr (file->stat_info->sparse_map_avail, nbuf);
@@ -1018,19 +1136,20 @@ pax_dump_header_1 (struct tar_sparse_file *file)
   size = (size + BLOCKSIZE - 1) / BLOCKSIZE;
   file->stat_info->archive_file_size += size * BLOCKSIZE;
   file->dumped_size += size * BLOCKSIZE;
-  
+
   /* Store sparse file identification */
   xheader_store ("GNU.sparse.major", file->stat_info, NULL);
   xheader_store ("GNU.sparse.minor", file->stat_info, NULL);
   xheader_store ("GNU.sparse.name", file->stat_info, NULL);
   xheader_store ("GNU.sparse.realsize", file->stat_info, NULL);
-  
-  file->stat_info->file_name = xheader_format_name (file->stat_info,
-                                           "%d/GNUSparseFile.%p/%f", 0);
 
-  blk = start_header (file->stat_info);
-  /* Store the effective (shrunken) file size */
-  OFF_TO_CHARS (file->stat_info->archive_file_size, blk->header.size);
+  file->stat_info->file_name =
+    xheader_format_name (file->stat_info, "%d/GNUSparseFile.%p/%f", 0);
+  /* Make sure the created header name is shorter than NAME_FIELD_SIZE: */
+  if (strlen (file->stat_info->file_name) > NAME_FIELD_SIZE)
+    file->stat_info->file_name[NAME_FIELD_SIZE] = 0;
+
+  blk = pax_start_header (file->stat_info);
   finish_header (file->stat_info, blk, block_ordinal);
   free (file->stat_info->file_name);
   file->stat_info->file_name = save_file_name;
@@ -1072,12 +1191,13 @@ decode_num (uintmax_t *num, char const *arg, uintmax_t maxval)
 
   if (!ISDIGIT (*arg))
     return false;
-  
+
+  errno = 0;
   u = strtoumax (arg, &arg_lim, 10);
 
   if (! (u <= maxval && errno != ERANGE) || *arg_lim)
     return false;
-  
+
   *num = u;
   return true;
 }
@@ -1117,7 +1237,7 @@ pax_decode_header (struct tar_sparse_file *file)
      }                                                             \
    while (*dst++ != '\n');                                         \
    dst[-1] = 0;                                                    \
- } while (0)                       
+ } while (0)
 
       set_next_block_after (current_header);
       file->dumped_size += BLOCKSIZE;
@@ -1126,7 +1246,7 @@ pax_decode_header (struct tar_sparse_file *file)
       COPY_BUF (blk,nbuf,p);
       if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
        {
-         ERROR ((0, 0, _("%s: malformed sparse archive member"), 
+         ERROR ((0, 0, _("%s: malformed sparse archive member"),
                  file->stat_info->orig_file_name));
          return false;
        }
@@ -1137,19 +1257,19 @@ pax_decode_header (struct tar_sparse_file *file)
       for (i = 0; i < file->stat_info->sparse_map_size; i++)
        {
          struct sp_array sp;
-         
+
          COPY_BUF (blk,nbuf,p);
          if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
            {
-             ERROR ((0, 0, _("%s: malformed sparse archive member"), 
+             ERROR ((0, 0, _("%s: malformed sparse archive member"),
                      file->stat_info->orig_file_name));
              return false;
            }
          sp.offset = u;
          COPY_BUF (blk,nbuf,p);
-         if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
+         if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
            {
-             ERROR ((0, 0, _("%s: malformed sparse archive member"), 
+             ERROR ((0, 0, _("%s: malformed sparse archive member"),
                      file->stat_info->orig_file_name));
              return false;
            }
@@ -1158,7 +1278,7 @@ pax_decode_header (struct tar_sparse_file *file)
        }
       set_next_block_after (blk);
     }
-  
+
   return true;
 }
 
@@ -1168,7 +1288,7 @@ static struct tar_sparse_optab const pax_optab = {
   pax_sparse_member_p,
   pax_dump_header,
   NULL,
-  pax_decode_header,  
+  pax_decode_header,
   NULL,  /* No scan_block function */
   sparse_dump_region,
   sparse_extract_region,