]> Dogcows Code - chaz/tar/commitdiff
Implement sparse format versioning. Implement new
authorSergey Poznyakoff <gray@gnu.org.ua>
Fri, 23 Jun 2006 15:21:48 +0000 (15:21 +0000)
committerSergey Poznyakoff <gray@gnu.org.ua>
Fri, 23 Jun 2006 15:21:48 +0000 (15:21 +0000)
version (1.0) of PAX sparse format.
(pax_sparse_member_p): Fix condition
(pax_dump_header): A dispatcher function
(pax_dump_header_0,pax_dump_header_1): New functions.
(pax_optab): Update
(oldgnu_dump_header): Minor fix: make sure
sparse_header.isextended is set before calling
set_next_block_after

src/sparse.c

index a8192beda8ea5f89a7d5d93d935b381317716787..799a00fc0bc93256b78c1e1427ad4237defb588e 100644 (file)
@@ -1,6 +1,6 @@
 /* Functions for dealing with sparse files
 
-   Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
+   Copyright (C) 2003, 2004, 2005, 2006 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
@@ -740,11 +740,9 @@ 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;
 }
@@ -843,16 +841,16 @@ static struct tar_sparse_optab const 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
-   GNU.sparse.map       Map of non-null data chunks. A string consisting
-                       of comma-separated values "offset,size[,offset,size]..."
-
-   Tar versions 1.14-1.15.1 instead of the latter used:
-   
    repeat numblocks time
      GNU.sparse.offset    Offset of the next data block
      GNU.sparse.numbytes  Size of the next data block
@@ -860,26 +858,73 @@ static struct tar_sparse_optab const star_optab = {
 
    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. However,
+   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 new keyword GNU.sparse.map was introduced
-   in tar 1.15.2. Some people might still need the 1.14 way of handling
-   sparse files for the compatibility reasons: it can be achieved by
-   specifying `--pax-option delete=GNU.sparse.map' in the command line.
+   
+   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
 
-   See FIXME-1.14-1.15.1-1.20, below.
+   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->sparse_map_avail > 0;
+  return file->stat_info->sparse_map_avail > 0
+          || file->stat_info->sparse_major > 0;
 }
 
 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;
@@ -892,13 +937,8 @@ pax_dump_header (struct tar_sparse_file *file)
   xheader_store ("GNU.sparse.size", file->stat_info, NULL);
   xheader_store ("GNU.sparse.numblocks", file->stat_info, NULL);
   
-  /* FIXME-1.14-1.15.1-1.20: See the comment above.
-     Starting with 1.17 this should display a warning about POSIX-incompatible
-     keywords being generated. In 1.20, the true branch of the if block below
-     will be removed and GNU.sparse.map will be marked in xhdr_tab as
-     protected. */
-  
-  if (xheader_keyword_deleted_p ("GNU.sparse.map"))
+  if (xheader_keyword_deleted_p ("GNU.sparse.map")
+      || tar_sparse_minor == 0)
     {
       for (i = 0; i < file->stat_info->sparse_map_avail; i++)
        {
@@ -936,13 +976,198 @@ pax_dump_header (struct tar_sparse_file *file)
   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 *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;
+  
+  /* 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);
+  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 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;
+  
+  if (file->stat_info->sparse_major == 0)
+    pax_dump_header_0 (file);
+  else
+    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;
+  
+  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);                               \
+          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);
+      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 (size_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.028193 seconds and 4 git commands to generate.