]> Dogcows Code - chaz/tar/blobdiff - src/sparse.c
Extract sparse files even if the output fd is not seekable.
[chaz/tar] / src / sparse.c
index 4f936dc6faed959aa93a20f6d74df4cd821543df..b43f3d78ad4a7b0e30752617d829135dc420c62f 100644 (file)
@@ -1,6 +1,6 @@
-/* Functions for dealing with sparse files 
+/* Functions for dealing with sparse files
 
-   Copyright (C) 2003 Free Software Foundation, Inc.
+   Copyright (C) 2003, 2004, 2005 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
@@ -16,7 +16,7 @@
    with this program; if not, write to the Free Software Foundation, Inc.,
    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
 
-#include "system.h"
+#include <system.h>
 #include <quotearg.h>
 #include "common.h"
 
@@ -33,17 +33,22 @@ struct tar_sparse_optab
 {
   bool (*init) (struct tar_sparse_file *);
   bool (*done) (struct tar_sparse_file *);
+  bool (*sparse_member_p) (struct tar_sparse_file *);
   bool (*dump_header) (struct tar_sparse_file *);
+  bool (*fixup_header) (struct tar_sparse_file *);
   bool (*decode_header) (struct tar_sparse_file *);
   bool (*scan_block) (struct tar_sparse_file *, enum sparse_scan_state,
                      void *);
-  bool (*dump_region) (struct tar_sparse_file *, size_t index);
-  bool (*extract_region) (struct tar_sparse_file *, size_t index);
+  bool (*dump_region) (struct tar_sparse_file *, size_t);
+  bool (*extract_region) (struct tar_sparse_file *, size_t);
 };
 
 struct tar_sparse_file
 {
   int fd;                           /* File descriptor */
+  bool seekable;                    /* Is fd seekable? */
+  size_t offset;                    /* Current offset in fd if seekable==false.
+                                      Otherwise unused */
   size_t dumped_size;               /* Number of bytes actually written
                                       to the archive */
   struct tar_stat_info *stat_info;  /* Information about the file */
@@ -52,6 +57,47 @@ struct tar_sparse_file
                                       reqiure */
 };
 
+/* Dump zeros to file->fd until offset is reached. It is used instead of
+   lseek if the output file is not seekable */
+static long
+dump_zeros (struct tar_sparse_file *file, off_t offset)
+{
+  char buf[BLOCKSIZE];
+  
+  if (offset - file->offset < 0)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  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);
+      if (wrbytes <= 0)
+       {
+         if (wrbytes == 0)
+           errno = EINVAL;
+         return -1;
+       }
+      file->offset += wrbytes;
+    }
+  return file->offset;
+}
+
+static bool
+tar_sparse_member_p (struct tar_sparse_file *file)
+{
+  if (file->optab->sparse_member_p)
+    return file->optab->sparse_member_p (file);
+  return false;
+}
+
 static bool
 tar_sparse_init (struct tar_sparse_file *file)
 {
@@ -79,18 +125,18 @@ tar_sparse_scan (struct tar_sparse_file *file, enum sparse_scan_state state,
 }
 
 static bool
-tar_sparse_dump_region (struct tar_sparse_file *file, size_t index)
+tar_sparse_dump_region (struct tar_sparse_file *file, size_t i)
 {
   if (file->optab->dump_region)
-    return file->optab->dump_region (file, index);
+    return file->optab->dump_region (file, i);
   return false;
 }
 
 static bool
-tar_sparse_extract_region (struct tar_sparse_file *file, size_t index)
+tar_sparse_extract_region (struct tar_sparse_file *file, size_t i)
 {
   if (file->optab->extract_region)
-    return file->optab->extract_region (file, index);
+    return file->optab->extract_region (file, i);
   return false;
 }
 
@@ -107,14 +153,29 @@ tar_sparse_decode_header (struct tar_sparse_file *file)
 {
   if (file->optab->decode_header)
     return file->optab->decode_header (file);
-  return false;
+  return true;
+}
+
+static bool
+tar_sparse_fixup_header (struct tar_sparse_file *file)
+{
+  if (file->optab->fixup_header)
+    return file->optab->fixup_header (file);
+  return true;
 }
 
 \f
 static bool
-lseek_or_error (struct tar_sparse_file *file, off_t offset, int whence)
+lseek_or_error (struct tar_sparse_file *file, off_t offset)
 {
-  if (lseek (file->fd, offset, whence) < 0)
+  off_t off;
+
+  if (file->seekable)
+    off = lseek (file->fd, offset, SEEK_SET);
+  else
+    off = dump_zeros (file, offset);
+  
+  if (off < 0)
     {
       seek_diag_details (file->stat_info->orig_file_name, offset);
       return false;
@@ -167,17 +228,18 @@ sparse_scan_file (struct tar_sparse_file *file)
   size_t offset = 0;
   struct sp_array sp = {0, 0};
 
-  if (!lseek_or_error (file, 0, SEEK_SET))
+  if (!lseek_or_error (file, 0))
     return false;
   clear_block (buffer);
 
-  file->stat_info->sparse_map_size = 0;
+  file->stat_info->sparse_map_avail = 0;
   file->stat_info->archive_file_size = 0;
-  
+
   if (!tar_sparse_scan (file, scan_begin, NULL))
     return false;
 
-  while ((count = safe_read (file->fd, buffer, sizeof buffer)) > 0)
+  while ((count = safe_read (file->fd, buffer, sizeof buffer)) != 0
+        && count != SAFE_READ_ERROR)
     {
       /* Analize the block */
       if (zero_block_p (buffer, count))
@@ -199,27 +261,27 @@ sparse_scan_file (struct tar_sparse_file *file)
          if (!tar_sparse_scan (file, scan_block, buffer))
            return false;
        }
-      
+
       offset += count;
       clear_block (buffer);
     }
-      
+
   if (sp.numbytes == 0)
-    {
-      sp.offset = offset - 1;
-      sp.numbytes = 1;
-    }
+    sp.offset = offset;
+
   sparse_add_map (file, &sp);
   file->stat_info->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 bool
 sparse_select_optab (struct tar_sparse_file *file)
 {
-  switch (archive_format)
+  switch (current_format == DEFAULT_FORMAT ? archive_format : current_format)
     {
     case V7_FORMAT:
     case USTAR_FORMAT:
@@ -231,39 +293,41 @@ sparse_select_optab (struct tar_sparse_file *file)
       break;
 
     case POSIX_FORMAT:
+      file->optab = &pax_optab;
+      break;
+
     case STAR_FORMAT:
-      /* FIXME: Add methods */
-      return false;
+      file->optab = &star_optab;
+      break;
 
     default:
-      break;
+      return false;
     }
   return true;
 }
 
 static bool
-sparse_dump_region (struct tar_sparse_file *file, size_t index)
+sparse_dump_region (struct tar_sparse_file *file, size_t i)
 {
   union block *blk;
-  off_t bytes_left = file->stat_info->sparse_map[index].numbytes;
-  
-  if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset,
-                      SEEK_SET))
+  off_t bytes_left = file->stat_info->sparse_map[i].numbytes;
+
+  if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
     return false;
 
-  do
+  while (bytes_left > 0)
     {
       size_t bufsize = (bytes_left > BLOCKSIZE) ? BLOCKSIZE : bytes_left;
-      off_t bytes_read;
-      
+      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 < 0)
+      if (bytes_read == SAFE_READ_ERROR)
        {
           read_diag_details (file->stat_info->orig_file_name,
-                            file->stat_info->sparse_map[index].offset
-                                + file->stat_info->sparse_map[index].numbytes
+                            file->stat_info->sparse_map[i].offset
+                                + file->stat_info->sparse_map[i].numbytes
                                 - bytes_left,
                     bufsize);
          return false;
@@ -273,20 +337,27 @@ sparse_dump_region (struct tar_sparse_file *file, size_t index)
       file->dumped_size += bytes_read;
       set_next_block_after (blk);
     }
-  while (bytes_left > 0);
+
   return true;
 }
 
 static bool
-sparse_extract_region (struct tar_sparse_file *file, size_t index)
+sparse_extract_region (struct tar_sparse_file *file, size_t i)
 {
   size_t write_size;
-  
-  if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset,
-                      SEEK_SET))
+
+  if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
     return false;
-  write_size = file->stat_info->sparse_map[index].numbytes;
-  while (write_size > 0)
+
+  write_size = file->stat_info->sparse_map[i].numbytes;
+
+  if (write_size == 0)
+    {
+      /* Last block of the file is a hole */
+      if (file->seekable && sys_truncate (file->fd))
+       truncate_warn (file->stat_info->orig_file_name);
+    }
+  else while (write_size > 0)
     {
       size_t count;
       size_t wrbytes = (write_size > BLOCKSIZE) ? BLOCKSIZE : write_size;
@@ -300,6 +371,7 @@ sparse_extract_region (struct tar_sparse_file *file, size_t index)
       count = full_write (file->fd, blk->buffer, wrbytes);
       write_size -= count;
       file->dumped_size += count;
+      file->offset += count;
       if (count != wrbytes)
        {
          write_error_details (file->stat_info->orig_file_name,
@@ -314,14 +386,16 @@ sparse_extract_region (struct tar_sparse_file *file, size_t index)
 
 /* Interface functions */
 enum dump_status
-sparse_dump_file (int fd, struct tar_stat_info *stat)
+sparse_dump_file (int fd, struct tar_stat_info *st)
 {
   bool rc;
   struct tar_sparse_file file;
 
-  file.stat_info = stat;
+  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;
@@ -346,23 +420,47 @@ sparse_dump_file (int fd, struct tar_stat_info *stat)
 
 /* Returns true if the file represented by stat is a sparse one */
 bool
-sparse_file_p (struct tar_stat_info *stat)
+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))
+    return false;
+  file.stat_info = st;
+  return tar_sparse_member_p (&file);
+}
+
+bool
+sparse_fixup_header (struct tar_stat_info *st)
 {
-  return (ST_NBLOCKS (stat->stat)
-         < (stat->stat.st_size / ST_NBLOCKSIZE
-            + (stat->stat.st_size % ST_NBLOCKSIZE != 0)));
+  struct tar_sparse_file file;
+
+  if (!sparse_select_optab (&file))
+    return false;
+  file.stat_info = st;
+  return tar_sparse_fixup_header (&file);
 }
 
 enum dump_status
-sparse_extract_file (int fd, struct tar_stat_info *stat, off_t *size)
+sparse_extract_file (int fd, struct tar_stat_info *st, off_t *size)
 {
   bool rc = true;
   struct tar_sparse_file file;
   size_t i;
-  
-  file.stat_info = stat;
-  file.fd = fd;
 
+  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;
@@ -374,25 +472,43 @@ sparse_extract_file (int fd, struct tar_stat_info *stat, off_t *size)
   return (tar_sparse_done (&file) && rc) ? dump_status_ok : dump_status_short;
 }
 
+enum dump_status
+sparse_skip_file (struct tar_stat_info *st)
+{
+  bool rc = true;
+  struct tar_sparse_file file;
+
+  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);
+  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)
 {
-  if (!lseek_or_error (file, beg, SEEK_SET))
+  if (!lseek_or_error (file, beg))
     return false;
-  
+
   while (beg < end)
     {
       size_t bytes_read;
       size_t rdsize = end - beg;
-      
+
       if (rdsize > BLOCKSIZE)
        rdsize = BLOCKSIZE;
       clear_block (diff_buffer);
       bytes_read = safe_read (file->fd, diff_buffer, rdsize);
-      if (bytes_read < 0)
+      if (bytes_read == SAFE_READ_ERROR)
        {
           read_diag_details (file->stat_info->orig_file_name,
                             beg,
@@ -412,19 +528,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 index)
+check_data_region (struct tar_sparse_file *file, size_t i)
 {
   size_t size_left;
-  
-  if (!lseek_or_error (file, file->stat_info->sparse_map[index].offset,
-                      SEEK_SET))
+
+  if (!lseek_or_error (file, file->stat_info->sparse_map[i].offset))
     return false;
-  size_left = file->stat_info->sparse_map[index].numbytes;
+  size_left = file->stat_info->sparse_map[i].numbytes;
   while (size_left > 0)
     {
       size_t bytes_read;
       size_t rdsize = (size_left > BLOCKSIZE) ? BLOCKSIZE : size_left;
-      
+
       union block *blk = find_next_block ();
       if (!blk)
        {
@@ -433,11 +548,11 @@ check_data_region (struct tar_sparse_file *file, size_t index)
        }
       set_next_block_after (blk);
       bytes_read = safe_read (file->fd, diff_buffer, rdsize);
-      if (bytes_read < 0)
+      if (bytes_read == SAFE_READ_ERROR)
        {
           read_diag_details (file->stat_info->orig_file_name,
-                            file->stat_info->sparse_map[index].offset
-                                + file->stat_info->sparse_map[index].numbytes
+                            file->stat_info->sparse_map[i].offset
+                                + file->stat_info->sparse_map[i].numbytes
                                 - size_left,
                             rdsize);
          return false;
@@ -454,14 +569,14 @@ check_data_region (struct tar_sparse_file *file, size_t index)
 }
 
 bool
-sparse_diff_file (int fd, struct tar_stat_info *stat)
+sparse_diff_file (int fd, struct tar_stat_info *st)
 {
   bool rc = true;
   struct tar_sparse_file file;
   size_t i;
   off_t offset = 0;
-  
-  file.stat_info = stat;
+
+  file.stat_info = st;
   file.fd = fd;
 
   if (!sparse_select_optab (&file)
@@ -478,21 +593,21 @@ sparse_diff_file (int fd, struct tar_stat_info *stat)
                + file.stat_info->sparse_map[i].numbytes;
     }
 
-  if (rc)
+  if (!rc)
     skip_file (file.stat_info->archive_file_size - file.dumped_size);
 
   tar_sparse_done (&file);
   return rc;
 }
 
-\f     
+\f
 /* 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 cumulative size of all non-empty blocks of the file. The
    actual file size is stored in `realsize' member of oldgnu_header.
-   
+
    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
@@ -511,8 +626,14 @@ enum oldgnu_add_status
     add_fail
   };
 
+static bool
+oldgnu_sparse_member_p (struct tar_sparse_file *file __attribute__ ((unused)))
+{
+  return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
 /* Add a sparse item to the sparse file and its obstack */
-static enum oldgnu_add_status 
+static enum oldgnu_add_status
 oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
 {
   struct sp_array sp;
@@ -530,8 +651,18 @@ oldgnu_add_sparse (struct tar_sparse_file *file, struct sparse *s)
   return add_ok;
 }
 
-/* Convert old GNU format sparse data to internal representation
-   FIXME: Clubbers current_header! */
+static bool
+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 */
+  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;
+}
+
+/* Convert old GNU format sparse data to internal representation */
 static bool
 oldgnu_get_sparse_info (struct tar_sparse_file *file)
 {
@@ -539,14 +670,8 @@ oldgnu_get_sparse_info (struct tar_sparse_file *file)
   union block *h = current_header;
   int ext_p;
   static enum oldgnu_add_status rc;
-  
-  /* FIXME: note this! st_size was initialized from the header
-     which actually contains archived size. The following fixes it */
-  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);
-  
-  file->stat_info->sparse_map_size = 0;
+
+  file->stat_info->sparse_map_avail = 0;
   for (i = 0; i < SPARSES_IN_OLDGNU_HEADER; i++)
     {
       rc = oldgnu_add_sparse (file, &h->oldgnu_header.sp[i]);
@@ -597,7 +722,7 @@ oldgnu_dump_header (struct tar_sparse_file *file)
   off_t block_ordinal = current_block_ordinal ();
   union block *blk;
   size_t i;
-    
+
   blk = start_header (file->stat_info);
   blk->header.typeflag = GNUTYPE_SPARSE;
   if (file->stat_info->sparse_map_avail > SPARSES_IN_OLDGNU_HEADER)
@@ -614,7 +739,7 @@ oldgnu_dump_header (struct tar_sparse_file *file)
                            SPARSES_IN_OLDGNU_HEADER);
   blk->oldgnu_header.isextended = i < file->stat_info->sparse_map_avail;
   finish_header (file->stat_info, blk, block_ordinal);
-  
+
   while (i < file->stat_info->sparse_map_avail)
     {
       blk = find_next_block ();
@@ -634,9 +759,146 @@ oldgnu_dump_header (struct tar_sparse_file *file)
 static struct tar_sparse_optab oldgnu_optab = {
   NULL,  /* No init function */
   NULL,  /* No done function */
+  oldgnu_sparse_member_p,
   oldgnu_dump_header,
+  oldgnu_fixup_header,
   oldgnu_get_sparse_info,
   NULL,  /* No scan_block function */
   sparse_dump_region,
   sparse_extract_region,
 };
+
+\f
+/* Star */
+
+static bool
+star_sparse_member_p (struct tar_sparse_file *file __attribute__ ((unused)))
+{
+  return current_header->header.typeflag == GNUTYPE_SPARSE;
+}
+
+static bool
+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 */
+  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;
+}
+
+/* Convert STAR format sparse data to internal representation */
+static bool
+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;
+
+  file->stat_info->sparse_map_avail = 0;
+
+  if (h->star_in_header.prefix[0] == '\0'
+      && h->star_in_header.sp[0].offset[10] != '\0')
+    {
+      /* Old star format */
+      for (i = 0; i < SPARSES_IN_STAR_HEADER; i++)
+       {
+         rc = oldgnu_add_sparse (file, &h->star_in_header.sp[i]);
+         if (rc != add_ok)
+           break;
+       }
+      ext_p = h->star_in_header.isextended;
+    }
+  else
+    ext_p = 1;
+
+  for (; rc == add_ok && ext_p; ext_p = h->star_ext_header.isextended)
+    {
+      h = find_next_block ();
+      if (!h)
+       {
+         ERROR ((0, 0, _("Unexpected EOF in archive")));
+         return false;
+       }
+      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]);
+    }
+
+  if (rc == add_fail)
+    {
+      ERROR ((0, 0, _("%s: invalid sparse archive member"),
+             file->stat_info->orig_file_name));
+      return false;
+    }
+  return true;
+}
+
+
+static struct tar_sparse_optab star_optab = {
+  NULL,  /* No init function */
+  NULL,  /* No done function */
+  star_sparse_member_p,
+  NULL,
+  star_fixup_header,
+  star_get_sparse_info,
+  NULL,  /* No scan_block function */
+  NULL, /* No dump region function */
+  sparse_extract_region,
+};
+
+\f
+/* GNU PAX sparse file format. 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
+   repeat numblocks time
+     GNU.sparse.offset    Offset of the next data block
+     GNU.sparse.numbytes  Size of the next data block
+   end repeat
+*/
+
+static bool
+pax_sparse_member_p (struct tar_sparse_file *file)
+{
+  return file->stat_info->archive_file_size != file->stat_info->stat.st_size;
+}
+
+static bool
+pax_dump_header (struct tar_sparse_file *file)
+{
+  off_t block_ordinal = current_block_ordinal ();
+  union block *blk;
+  size_t i;
+
+  /* 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++)
+    {
+      xheader_store ("GNU.sparse.offset", file->stat_info, &i);
+      xheader_store ("GNU.sparse.numbytes", file->stat_info, &i);
+    }
+
+  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);
+  return true;
+}
+
+static struct tar_sparse_optab 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,  /* No scan_block function */
+  sparse_dump_region,
+  sparse_extract_region,
+};
+
This page took 0.038709 seconds and 4 git commands to generate.