]> Dogcows Code - chaz/tar/blobdiff - src/sparse.c
Update copyright years.
[chaz/tar] / src / sparse.c
index fb77aefdf483a9a2333d6711010663810020d67e..6a976763419e78a5d5041fe3a01046e787dccdc8 100644 (file)
@@ -1,10 +1,10 @@
 /* Functions for dealing with sparse files
 
-   Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
+   Copyright 2003-2007, 2010, 2013-2014 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
-   Free Software Foundation; either version 2, or (at your option) any later
+   Free Software Foundation; either version 3, or (at your option) any later
    version.
 
    This program is distributed in the hope that it will be useful, but
    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>
 #include <quotearg.h>
 #include "common.h"
 
 struct tar_sparse_file;
+static bool sparse_select_optab (struct tar_sparse_file *file);
 
 enum sparse_scan_state
   {
@@ -47,47 +48,47 @@ struct tar_sparse_file
 {
   int fd;                           /* File descriptor */
   bool seekable;                    /* Is fd seekable? */
-  size_t offset;                    /* Current offset in fd if seekable==false.
+  off_t offset;                     /* Current offset in fd if seekable==false.
                                       Otherwise unused */
-  size_t dumped_size;               /* Number of bytes actually written
+  off_t dumped_size;                /* Number of bytes actually written
                                       to the archive */
   struct tar_stat_info *stat_info;  /* Information about the file */
-  struct tar_sparse_optab *optab;
+  struct tar_sparse_optab const *optab; /* Operation table */
   void *closure;                    /* Any additional data optab calls might
-                                      reqiure */
+                                      require */
 };
 
 /* Dump zeros to file->fd until offset is reached. It is used instead of
    lseek if the output file is not seekable */
-static long
+static bool
 dump_zeros (struct tar_sparse_file *file, off_t offset)
 {
-  char buf[BLOCKSIZE];
-  
-  if (offset - file->offset < 0)
+  static char const zero_buf[BLOCKSIZE];
+
+  if (offset < file->offset)
     {
       errno = EINVAL;
-      return -1;
+      return false;
     }
 
-  memset (buf, 0, sizeof buf);
   while (file->offset < offset)
     {
-      size_t size = offset - file->offset;
-      size_t wrbytes;
-      
-      if (size > sizeof buf)
-       size = sizeof buf;
-      wrbytes = write (file->fd, buf, size);
+      size_t size = (BLOCKSIZE < offset - file->offset
+                    ? BLOCKSIZE
+                    : offset - file->offset);
+      ssize_t wrbytes;
+
+      wrbytes = write (file->fd, zero_buf, size);
       if (wrbytes <= 0)
        {
          if (wrbytes == 0)
            errno = EINVAL;
-         return -1;
+         return false;
        }
       file->offset += wrbytes;
     }
-  return file->offset;
+
+  return true;
 }
 
 static bool
@@ -101,9 +102,14 @@ tar_sparse_member_p (struct tar_sparse_file *file)
 static bool
 tar_sparse_init (struct tar_sparse_file *file)
 {
-  file->dumped_size = 0;
+  memset (file, 0, sizeof *file);
+
+  if (!sparse_select_optab (file))
+    return false;
+
   if (file->optab->init)
     return file->optab->init (file);
+
   return true;
 }
 
@@ -168,14 +174,9 @@ tar_sparse_fixup_header (struct tar_sparse_file *file)
 static bool
 lseek_or_error (struct tar_sparse_file *file, off_t offset)
 {
-  off_t off;
-
-  if (file->seekable)
-    off = lseek (file->fd, offset, SEEK_SET);
-  else
-    off = dump_zeros (file, offset);
-  
-  if (off < 0)
+  if (file->seekable
+      ? lseek (file->fd, offset, SEEK_SET) < 0
+      : ! dump_zeros (file, offset))
     {
       seek_diag_details (file->stat_info->orig_file_name, offset);
       return false;
@@ -187,7 +188,7 @@ lseek_or_error (struct tar_sparse_file *file, off_t offset)
    it's made *entirely* of zeros, returning a 0 the instant it finds
    something that is a nonzero, i.e., useful data.  */
 static bool
-zero_block_p (char *buffer, size_t size)
+zero_block_p (char const *buffer, size_t size)
 {
   while (size--)
     if (*buffer++)
@@ -195,88 +196,77 @@ zero_block_p (char *buffer, size_t size)
   return true;
 }
 
-#define clear_block(p) memset (p, 0, BLOCKSIZE);
-
-#define SPARSES_INIT_COUNT SPARSES_IN_SPARSE_HEADER
-
 static void
-sparse_add_map (struct tar_sparse_file *file, struct sp_array *sp)
+sparse_add_map (struct tar_stat_info *st, struct sp_array const *sp)
 {
-  if (file->stat_info->sparse_map == NULL)
-    {
-      file->stat_info->sparse_map =
-       xmalloc (SPARSES_INIT_COUNT * sizeof file->stat_info->sparse_map[0]);
-      file->stat_info->sparse_map_size = SPARSES_INIT_COUNT;
-    }
-  else if (file->stat_info->sparse_map_avail == file->stat_info->sparse_map_size)
-    {
-      file->stat_info->sparse_map_size *= 2;
-      file->stat_info->sparse_map =
-       xrealloc (file->stat_info->sparse_map,
-                 file->stat_info->sparse_map_size
-                 * sizeof file->stat_info->sparse_map[0]);
-    }
-  file->stat_info->sparse_map[file->stat_info->sparse_map_avail++] = *sp;
+  struct sp_array *sparse_map = st->sparse_map;
+  size_t avail = st->sparse_map_avail;
+  if (avail == st->sparse_map_size)
+    st->sparse_map = sparse_map =
+      x2nrealloc (sparse_map, &st->sparse_map_size, sizeof *sparse_map);
+  sparse_map[avail] = *sp;
+  st->sparse_map_avail = avail + 1;
 }
 
 /* Scan the sparse file and create its map */
 static bool
 sparse_scan_file (struct tar_sparse_file *file)
 {
-  static char buffer[BLOCKSIZE];
-  size_t count;
+  struct tar_stat_info *st = file->stat_info;
+  int fd = file->fd;
+  char buffer[BLOCKSIZE];
+  size_t count = 0;
   off_t offset = 0;
   struct sp_array sp = {0, 0};
 
-  if (!lseek_or_error (file, 0))
-    return false;
-  clear_block (buffer);
-
-  file->stat_info->sparse_map_avail = 0;
-  file->stat_info->archive_file_size = 0;
+  st->archive_file_size = 0;
 
-  if (!tar_sparse_scan (file, scan_begin, NULL))
-    return false;
-
-  while ((count = safe_read (file->fd, buffer, sizeof buffer)) != 0
-        && count != SAFE_READ_ERROR)
+  if (ST_NBLOCKS (st->stat) == 0)
+    offset = st->stat.st_size;
+  else
     {
-      /* Analize the block */
-      if (zero_block_p (buffer, count))
+      if (!tar_sparse_scan (file, scan_begin, NULL))
+       return false;
+
+      while ((count = blocking_read (fd, buffer, sizeof buffer)) != 0
+            && count != SAFE_READ_ERROR)
        {
-         if (sp.numbytes)
+         /* 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;
+               }
+           }
+         else
            {
-             sparse_add_map (file, &sp);
-             sp.numbytes = 0;
-             if (!tar_sparse_scan (file, scan_block, NULL))
+             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;
            }
-       }
-      else
-       {
-         if (sp.numbytes == 0)
-           sp.offset = offset;
-         sp.numbytes += count;
-         file->stat_info->archive_file_size += count;
-         if (!tar_sparse_scan (file, scan_block, buffer))
-           return false;
-       }
 
-      offset += count;
-      clear_block (buffer);
+         offset += count;
+       }
     }
 
   if (sp.numbytes == 0)
     sp.offset = offset;
 
-  sparse_add_map (file, &sp);
-  file->stat_info->archive_file_size += count;
+  sparse_add_map (st, &sp);
+  st->archive_file_size += count;
   return tar_sparse_scan (file, scan_end, NULL);
 }
 
-static struct tar_sparse_optab oldgnu_optab;
-static struct tar_sparse_optab star_optab;
-static struct tar_sparse_optab pax_optab;
+static struct tar_sparse_optab const oldgnu_optab;
+static struct tar_sparse_optab const star_optab;
+static struct tar_sparse_optab const pax_optab;
 
 static bool
 sparse_select_optab (struct tar_sparse_file *file)
@@ -321,18 +311,18 @@ sparse_dump_region (struct tar_sparse_file *file, size_t i)
       size_t bytes_read;
 
       blk = find_next_block ();
-      memset (blk->buffer, 0, BLOCKSIZE);
       bytes_read = safe_read (file->fd, blk->buffer, bufsize);
       if (bytes_read == SAFE_READ_ERROR)
        {
           read_diag_details (file->stat_info->orig_file_name,
-                            file->stat_info->sparse_map[i].offset
-                                + file->stat_info->sparse_map[i].numbytes
-                                - bytes_left,
-                    bufsize);
+                            (file->stat_info->sparse_map[i].offset
+                             + file->stat_info->sparse_map[i].numbytes
+                             - bytes_left),
+                            bufsize);
          return false;
        }
 
+      memset (blk->buffer + bytes_read, 0, BLOCKSIZE - bytes_read);
       bytes_left -= bytes_read;
       file->dumped_size += bytes_read;
       set_next_block_after (blk);
@@ -344,7 +334,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;
@@ -368,9 +358,10 @@ 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);
       file->offset += count;
       if (count != wrbytes)
        {
@@ -391,14 +382,12 @@ sparse_dump_file (int fd, struct tar_stat_info *st)
   bool rc;
   struct tar_sparse_file file;
 
+  if (!tar_sparse_init (&file))
+    return dump_status_not_implemented;
+
   file.stat_info = st;
   file.fd = fd;
   file.seekable = true; /* File *must* be seekable for dump to work */
-  file.offset = 0;
-  
-  if (!sparse_select_optab (&file)
-      || !tar_sparse_init (&file))
-    return dump_status_not_implemented;
 
   rc = sparse_scan_file (&file);
   if (rc && file.optab->dump_region)
@@ -409,30 +398,24 @@ sparse_dump_file (int fd, struct tar_stat_info *st)
        {
          size_t i;
 
+         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);
        }
     }
 
-  pad_archive(file.stat_info->archive_file_size - file.dumped_size);
+  pad_archive (file.stat_info->archive_file_size - file.dumped_size);
   return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
 }
 
-/* Returns true if the file represented by stat is a sparse one */
-bool
-sparse_file_p (struct tar_stat_info *st)
-{
-  return (ST_NBLOCKS (st->stat)
-         < (st->stat.st_size / ST_NBLOCKSIZE
-            + (st->stat.st_size % ST_NBLOCKSIZE != 0)));
-}
-
 bool
 sparse_member_p (struct tar_stat_info *st)
 {
   struct tar_sparse_file file;
 
-  if (!sparse_select_optab (&file))
+  if (!tar_sparse_init (&file))
     return false;
   file.stat_info = st;
   return tar_sparse_member_p (&file);
@@ -443,7 +426,7 @@ sparse_fixup_header (struct tar_stat_info *st)
 {
   struct tar_sparse_file file;
 
-  if (!sparse_select_optab (&file))
+  if (!tar_sparse_init (&file))
     return false;
   file.stat_info = st;
   return tar_sparse_fixup_header (&file);
@@ -456,14 +439,13 @@ sparse_extract_file (int fd, struct tar_stat_info *st, off_t *size)
   struct tar_sparse_file file;
   size_t i;
 
+  if (!tar_sparse_init (&file))
+    return dump_status_not_implemented;
+
   file.stat_info = st;
   file.fd = fd;
   file.seekable = lseek (fd, 0, SEEK_SET) == 0;
   file.offset = 0;
-  
-  if (!sparse_select_optab (&file)
-      || !tar_sparse_init (&file))
-    return dump_status_not_implemented;
 
   rc = tar_sparse_decode_header (&file);
   for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
@@ -478,21 +460,18 @@ sparse_skip_file (struct tar_stat_info *st)
   bool rc = true;
   struct tar_sparse_file file;
 
+  if (!tar_sparse_init (&file))
+    return dump_status_not_implemented;
+
   file.stat_info = st;
   file.fd = -1;
 
-  if (!sparse_select_optab (&file)
-      || !tar_sparse_init (&file))
-    return dump_status_not_implemented;
-
   rc = tar_sparse_decode_header (&file);
-  skip_file (file.stat_info->archive_file_size);
+  skip_file (file.stat_info->archive_file_size - file.dumped_size);
   return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
 }
 
 \f
-static char diff_buffer[BLOCKSIZE];
-
 static bool
 check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
 {
@@ -502,11 +481,9 @@ check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
   while (beg < end)
     {
       size_t bytes_read;
-      size_t rdsize = end - beg;
+      size_t rdsize = BLOCKSIZE < end - beg ? BLOCKSIZE : end - beg;
+      char diff_buffer[BLOCKSIZE];
 
-      if (rdsize > BLOCKSIZE)
-       rdsize = BLOCKSIZE;
-      clear_block (diff_buffer);
       bytes_read = safe_read (file->fd, diff_buffer, rdsize);
       if (bytes_read == SAFE_READ_ERROR)
        {
@@ -517,8 +494,10 @@ check_sparse_region (struct tar_sparse_file *file, off_t beg, off_t end)
        }
       if (!zero_block_p (diff_buffer, bytes_read))
        {
+         char begbuf[INT_BUFSIZE_BOUND (off_t)];
          report_difference (file->stat_info,
-                            _("File fragment at %lu is not a hole"), beg);
+                            _("File fragment at %s is not a hole"),
+                            offtostr (beg, begbuf));
          return false;
        }
 
@@ -530,15 +509,18 @@ 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;
       size_t rdsize = (size_left > BLOCKSIZE) ? BLOCKSIZE : size_left;
+      char diff_buffer[BLOCKSIZE];
 
       union block *blk = find_next_block ();
       if (!blk)
@@ -551,14 +533,15 @@ check_data_region (struct tar_sparse_file *file, size_t i)
       if (bytes_read == SAFE_READ_ERROR)
        {
           read_diag_details (file->stat_info->orig_file_name,
-                            file->stat_info->sparse_map[i].offset
-                                + file->stat_info->sparse_map[i].numbytes
-                                - size_left,
+                            (file->stat_info->sparse_map[i].offset
+                             + file->stat_info->sparse_map[i].numbytes
+                             - size_left),
                             rdsize);
          return false;
        }
       file->dumped_size += bytes_read;
       size_left -= bytes_read;
+      mv_size_left (file->stat_info->archive_file_size - file->dumped_size);
       if (memcmp (blk->buffer, diff_buffer, rdsize))
        {
          report_difference (file->stat_info, _("Contents differ"));
@@ -576,25 +559,27 @@ sparse_diff_file (int fd, struct tar_stat_info *st)
   size_t i;
   off_t offset = 0;
 
+  if (!tar_sparse_init (&file))
+    return dump_status_not_implemented;
+
   file.stat_info = st;
   file.fd = fd;
-
-  if (!sparse_select_optab (&file)
-      || !tar_sparse_init (&file))
-    return dump_status_not_implemented;
+  file.seekable = true; /* File *must* be seekable for compare to work */
 
   rc = tar_sparse_decode_header (&file);
+  mv_begin_read (st);
   for (i = 0; rc && i < file.stat_info->sparse_map_avail; i++)
     {
       rc = check_sparse_region (&file,
                                offset, file.stat_info->sparse_map[i].offset)
-       && check_data_region (&file, i);
+           && check_data_region (&file, i);
       offset = file.stat_info->sparse_map[i].offset
                + file.stat_info->sparse_map[i].numbytes;
     }
 
   if (!rc)
     skip_file (file.stat_info->archive_file_size - file.dumped_size);
+  mv_end ();
 
   tar_sparse_done (&file);
   return rc;
@@ -604,18 +589,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... */
 
@@ -641,13 +626,14 @@ 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;
 
-  sparse_add_map (file, &sp);
+  sparse_add_map (file->stat_info, &sp);
   return add_ok;
 }
 
@@ -656,10 +642,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 */
@@ -669,7 +655,7 @@ oldgnu_get_sparse_info (struct tar_sparse_file *file)
   size_t i;
   union block *h = current_header;
   int ext_p;
-  static enum oldgnu_add_status rc;
+  enum oldgnu_add_status rc;
 
   file->stat_info->sparse_map_avail = 0;
   for (i = 0; i < SPARSES_IN_OLDGNU_HEADER; i++)
@@ -711,8 +697,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);
     }
 }
 
@@ -747,16 +733,14 @@ oldgnu_dump_header (struct tar_sparse_file *file)
       oldgnu_store_sparse_info (file, &i,
                                blk->sparse_header.sp,
                                SPARSES_IN_SPARSE_HEADER);
-      set_next_block_after (blk);
       if (i < file->stat_info->sparse_map_avail)
        blk->sparse_header.isextended = 1;
-      else
-       break;
+      set_next_block_after (blk);
     }
   return true;
 }
 
-static struct tar_sparse_optab oldgnu_optab = {
+static struct tar_sparse_optab const oldgnu_optab = {
   NULL,  /* No init function */
   NULL,  /* No done function */
   oldgnu_sparse_member_p,
@@ -782,10 +766,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 */
@@ -795,7 +779,7 @@ star_get_sparse_info (struct tar_sparse_file *file)
   size_t i;
   union block *h = current_header;
   int ext_p;
-  static enum oldgnu_add_status rc;
+  enum oldgnu_add_status rc = add_ok;
 
   file->stat_info->sparse_map_avail = 0;
 
@@ -825,6 +809,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)
@@ -837,7 +822,7 @@ star_get_sparse_info (struct tar_sparse_file *file)
 }
 
 
-static struct tar_sparse_optab star_optab = {
+static struct tar_sparse_optab const star_optab = {
   NULL,  /* No init function */
   NULL,  /* No done function */
   star_sparse_member_p,
@@ -850,8 +835,12 @@ static struct tar_sparse_optab star_optab = {
 };
 
 \f
-/* GNU PAX sparse file format. The sparse file map is stored in
-   x header:
+/* GNU PAX sparse file format. There are several versions:
+
+   * 0.0
+
+   The initial version of sparse format used by tar 1.14-1.15.1.
+   The sparse file map is stored in x header:
 
    GNU.sparse.size      Real size of the stored file
    GNU.sparse.numblocks Number of blocks in the sparse map
@@ -859,46 +848,343 @@ static struct tar_sparse_optab star_optab = {
      GNU.sparse.offset    Offset of the next data block
      GNU.sparse.numbytes  Size of the next data block
    end repeat
+
+   This has been reported as conflicting with the POSIX specs. The reason is
+   that offsets and sizes of non-zero data blocks were stored in multiple
+   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:
+
+   GNU.sparse.size      Real size of the stored file
+   GNU.sparse.numblocks Number of blocks in the sparse map
+   GNU.sparse.map       Map of non-null data chunks. A string consisting
+                       of comma-separated values "offset,size[,offset,size]..."
+
+   The resulting GNU.sparse.map string can be *very* long. While POSIX does not
+   impose any limit on the length of a x header variable, this can confuse some
+   tars.
+
+   * 1.0
+
+   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.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
+   boundary.
+
+   The first number gives the number of entries in the map. Following are map
+   entries, each one consisting of two numbers giving the offset and size of
+   the data block it describes.
+
+   The format is designed in such a way that non-posix aware tars and tars not
+   supporting GNU.sparse.* keywords will extract each sparse file in its
+   condensed form with the file map attached and will place it into a separate
+   directory. Then, using a simple program it would be possible to expand the
+   file to its original form even without GNU tar.
+
+   Bu default, v.1.0 archives are created. To use other formats,
+   --sparse-version option is provided. Additionally, v.0.0 can be obtained
+   by deleting GNU.sparse.map from 0.1 format: --sparse-version 0.1
+   --pax-option delete=GNU.sparse.map
 */
 
 static bool
 pax_sparse_member_p (struct tar_sparse_file *file)
 {
-  return file->stat_info->archive_file_size != file->stat_info->stat.st_size;
+  return file->stat_info->sparse_map_avail > 0
+          || 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 (struct tar_sparse_file *file)
+pax_dump_header_0 (struct tar_sparse_file *file)
 {
   off_t block_ordinal = current_block_ordinal ();
   union block *blk;
   size_t i;
+  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);
-  for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+
+  if (xheader_keyword_deleted_p ("GNU.sparse.map")
+      || tar_sparse_minor == 0)
     {
-      xheader_store ("GNU.sparse.offset", file->stat_info, &i);
-      xheader_store ("GNU.sparse.numbytes", file->stat_info, &i);
+      for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+       {
+         xheader_store ("GNU.sparse.offset", file->stat_info, &i);
+         xheader_store ("GNU.sparse.numbytes", file->stat_info, &i);
+       }
     }
+  else
+    {
+      xheader_store ("GNU.sparse.name", file->stat_info, NULL);
+      save_file_name = file->stat_info->file_name;
+      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);
+      xheader_string_begin (&file->stat_info->xhdr);
+      for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+       {
+         if (i)
+           xheader_string_add (&file->stat_info->xhdr, ",");
+         xheader_string_add (&file->stat_info->xhdr,
+                             umaxtostr (map[i].offset, nbuf));
+         xheader_string_add (&file->stat_info->xhdr, ",");
+         xheader_string_add (&file->stat_info->xhdr,
+                             umaxtostr (map[i].numbytes, nbuf));
+       }
+      if (!xheader_string_end (&file->stat_info->xhdr,
+                              "GNU.sparse.map"))
+       {
+         free (file->stat_info->file_name);
+         file->stat_info->file_name = save_file_name;
+         return false;
+       }
+    }
+  blk = pax_start_header (file->stat_info);
+  finish_header (file->stat_info, blk, block_ordinal);
+  if (save_file_name)
+    {
+      free (file->stat_info->file_name);
+      file->stat_info->file_name = save_file_name;
+    }
+  return true;
+}
+
+static bool
+pax_dump_header_1 (struct tar_sparse_file *file)
+{
+  off_t block_ordinal = current_block_ordinal ();
+  union block *blk;
+  char *p, *q;
+  size_t i;
+  char nbuf[UINTMAX_STRSIZE_BOUND];
+  off_t size = 0;
+  struct sp_array *map = file->stat_info->sparse_map;
+  char *save_file_name = file->stat_info->file_name;
+
+#define COPY_STRING(b,dst,src) do                \
+ {                                               \
+   char *endp = b->buffer + BLOCKSIZE;           \
+   char const *srcp = src;                       \
+   while (*srcp)                                 \
+     {                                           \
+       if (dst == endp)                          \
+        {                                       \
+          set_next_block_after (b);             \
+          b = find_next_block ();               \
+           dst = b->buffer;                      \
+          endp = b->buffer + BLOCKSIZE;         \
+        }                                       \
+       *dst++ = *srcp++;                         \
+     }                                           \
+   } while (0)
+
+  /* Compute stored file size */
+  p = umaxtostr (file->stat_info->sparse_map_avail, nbuf);
+  size += strlen (p) + 1;
+  for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+    {
+      p = umaxtostr (map[i].offset, nbuf);
+      size += strlen (p) + 1;
+      p = umaxtostr (map[i].numbytes, nbuf);
+      size += strlen (p) + 1;
+    }
+  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);
+  /* 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;
+
+  blk = find_next_block ();
+  q = blk->buffer;
+  p = umaxtostr (file->stat_info->sparse_map_avail, nbuf);
+  COPY_STRING (blk, q, p);
+  COPY_STRING (blk, q, "\n");
+  for (i = 0; i < file->stat_info->sparse_map_avail; i++)
+    {
+      p = umaxtostr (map[i].offset, nbuf);
+      COPY_STRING (blk, q, p);
+      COPY_STRING (blk, q, "\n");
+      p = umaxtostr (map[i].numbytes, nbuf);
+      COPY_STRING (blk, q, p);
+      COPY_STRING (blk, q, "\n");
+    }
+  memset (q, 0, BLOCKSIZE - (q - blk->buffer));
+  set_next_block_after (blk);
   return true;
 }
 
-static struct tar_sparse_optab pax_optab = {
+static bool
+pax_dump_header (struct tar_sparse_file *file)
+{
+  file->stat_info->sparse_major = tar_sparse_major;
+  file->stat_info->sparse_minor = tar_sparse_minor;
+
+  return (file->stat_info->sparse_major == 0) ?
+           pax_dump_header_0 (file) : pax_dump_header_1 (file);
+}
+
+static bool
+decode_num (uintmax_t *num, char const *arg, uintmax_t maxval)
+{
+  uintmax_t u;
+  char *arg_lim;
+
+  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;
+}
+
+static bool
+pax_decode_header (struct tar_sparse_file *file)
+{
+  if (file->stat_info->sparse_major > 0)
+    {
+      uintmax_t u;
+      char nbuf[UINTMAX_STRSIZE_BOUND];
+      union block *blk;
+      char *p;
+      size_t i;
+
+#define COPY_BUF(b,buf,src) do                                     \
+ {                                                                 \
+   char *endp = b->buffer + BLOCKSIZE;                             \
+   char *dst = buf;                                                \
+   do                                                              \
+     {                                                             \
+       if (dst == buf + UINTMAX_STRSIZE_BOUND -1)                  \
+         {                                                         \
+           ERROR ((0, 0, _("%s: numeric overflow in sparse archive member"), \
+                 file->stat_info->orig_file_name));               \
+           return false;                                           \
+         }                                                         \
+       if (src == endp)                                            \
+        {                                                         \
+          set_next_block_after (b);                               \
+           file->dumped_size += BLOCKSIZE;                         \
+           b = find_next_block ();                                 \
+           src = b->buffer;                                        \
+          endp = b->buffer + BLOCKSIZE;                           \
+        }                                                         \
+       *dst = *src++;                                              \
+     }                                                             \
+   while (*dst++ != '\n');                                         \
+   dst[-1] = 0;                                                    \
+ } while (0)
+
+      set_next_block_after (current_header);
+      file->dumped_size += BLOCKSIZE;
+      blk = find_next_block ();
+      p = blk->buffer;
+      COPY_BUF (blk,nbuf,p);
+      if (!decode_num (&u, nbuf, TYPE_MAXIMUM (size_t)))
+       {
+         ERROR ((0, 0, _("%s: malformed sparse archive member"),
+                 file->stat_info->orig_file_name));
+         return false;
+       }
+      file->stat_info->sparse_map_size = u;
+      file->stat_info->sparse_map = xcalloc (file->stat_info->sparse_map_size,
+                                            sizeof (*file->stat_info->sparse_map));
+      file->stat_info->sparse_map_avail = 0;
+      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"),
+                     file->stat_info->orig_file_name));
+             return false;
+           }
+         sp.offset = u;
+         COPY_BUF (blk,nbuf,p);
+         if (!decode_num (&u, nbuf, TYPE_MAXIMUM (off_t)))
+           {
+             ERROR ((0, 0, _("%s: malformed sparse archive member"),
+                     file->stat_info->orig_file_name));
+             return false;
+           }
+         sp.numbytes = u;
+         sparse_add_map (file->stat_info, &sp);
+       }
+      set_next_block_after (blk);
+    }
+
+  return true;
+}
+
+static struct tar_sparse_optab const pax_optab = {
   NULL,  /* No init function */
   NULL,  /* No done function */
   pax_sparse_member_p,
   pax_dump_header,
-  NULL,  /* No decode_header function */
-  NULL,  /* No fixup_header function */
+  NULL,
+  pax_decode_header,
   NULL,  /* No scan_block function */
   sparse_dump_region,
   sparse_extract_region,
 };
-
This page took 0.04941 seconds and 4 git commands to generate.