zip-writer: Fix writing a zip file to a pipe.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 25 Oct 2020 05:29:08 +0000 (22:29 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 25 Oct 2020 05:29:08 +0000 (22:29 -0700)
The code here had comment indicating that it could write a zip file to a
pipe, but it didn't actually work.  This fixes it and adds a test.

src/libpspp/zip-writer.c
tests/libpspp/zip.at

index 0959551abefd00720cd47f1e768910bcfa9bd340..b8240efe6bb8ba194308cb1aff64dc0229ce70e1 100644 (file)
@@ -23,6 +23,7 @@
 #include <errno.h>
 #include <stdlib.h>
 #include <time.h>
+#include <unistd.h>
 
 #include "gl/crc.h"
 #include "gl/fwriteerror.h"
@@ -38,6 +39,7 @@ struct zip_writer
   {
     char *file_name;            /* File name, for use in error mesages. */
     FILE *file;                 /* Output stream. */
+    uint32_t offset;            /* Offset in output stream. */
 
     uint16_t date, time;        /* Date and time in MS-DOS format. */
 
@@ -61,6 +63,7 @@ static void
 put_bytes (struct zip_writer *zw, const void *p, size_t n)
 {
   fwrite (p, 1, n, zw->file);
+  zw->offset += n;
 }
 
 static void
@@ -91,16 +94,30 @@ zip_writer_create (const char *file_name)
   time_t now;
   FILE *file;
 
-  file = fopen (file_name, "wb");
-  if (file == NULL)
+  if (strcmp (file_name, "-"))
     {
-      msg_error (errno, _("%s: error opening output file"), file_name);
-      return NULL;
+      file = fopen (file_name, "wb");
+      if (file == NULL)
+        {
+          msg_error (errno, _("%s: error opening output file"), file_name);
+          return NULL;
+        }
+    }
+  else
+    {
+      if (isatty (STDOUT_FILENO))
+        {
+          msg (ME, _("%s: not writing ZIP file to terminal"), file_name);
+          return NULL;
+        }
+
+      file = stdout;
     }
 
   zw = xmalloc (sizeof *zw);
   zw->file_name = xstrdup (file_name);
   zw->file = file;
+  zw->offset = 0;
 
   zw->ok = true;
 
@@ -139,13 +156,13 @@ void
 zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
 {
   struct zip_member *member;
-  uint32_t offset, size;
+  uint32_t size;
   size_t bytes_read;
   uint32_t crc;
   char buf[4096];
 
   /* Local file header. */
-  offset = ftello (zw->file);
+  uint32_t offset = zw->offset;
   put_local_header (zw, member_name, 0, 0, 1 << 3);
 
   /* File data. */
@@ -162,6 +179,7 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
      with the correct file size and CRC.  Otherwise, write data descriptor. */
   if (fseeko (zw->file, offset, SEEK_SET) == 0)
     {
+      uint32_t save_offset = zw->offset;
       put_local_header (zw, member_name, crc, size, 0);
       if (fseeko (zw->file, size, SEEK_CUR)
           && zw->ok)
@@ -169,6 +187,7 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
           msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
           zw->ok = false;
         }
+      zw->offset = save_offset;
     }
   else
     {
@@ -229,7 +248,7 @@ zip_writer_close (struct zip_writer *zw)
   if (zw == NULL)
     return true;
 
-  dir_start = ftello (zw->file);
+  dir_start = zw->offset;
   for (i = 0; i < zw->n_members; i++)
     {
       struct zip_member *m = &zw->members[i];
@@ -256,7 +275,7 @@ zip_writer_close (struct zip_writer *zw)
       free (m->name);
     }
   free (zw->members);
-  dir_end = ftello (zw->file);
+  dir_end = zw->offset;
 
   /* End of central directory record. */
   put_u32 (zw, MAGIC_EOCD);     /* end of central dir signature */
@@ -274,7 +293,7 @@ zip_writer_close (struct zip_writer *zw)
   put_u16 (zw, 0);              /* .ZIP file comment length */
 
   ok = zw->ok;
-  if (ok && fwriteerror (zw->file))
+  if (ok && zw->file != stdout && fwriteerror (zw->file))
     {
       msg_error (errno, _("%s: write failed"), zw->file_name);
       ok = false;
index 9598717f824e6f1d3b2804bf52583b6a7bd06076..77ca00e1c68cb38e16388b1ecd7f3f5c9df8d3d8 100644 (file)
@@ -16,10 +16,8 @@ dnl along with this program.  If not, see <http://www.gnu.org/licenses/>.
 dnl
 AT_BANNER([zip])
 
-AT_SETUP([Basic zip - unzip test])
+AT_SETUP([basic zip - unzip test])
 AT_KEYWORDS([compression])
-
-AT_CHECK([dnl
 here=`pwd`
 dir1=$here/original
 dir2=$here/recovered
@@ -37,23 +35,68 @@ while test $s -le 8192 ; do
         names="$names $bn";
 done
 
-(cd $dir1 && zip-test w foo.zip $names)
+AT_CHECK([cd $dir1 && zip-test w foo.zip $names])
 
+# If zipinfo is installed, make sure it can read the zipfile.
+if (zipinfo -v) >/dev/null 2>&1; then
+    AT_CHECK([zipinfo $dir1/foo.zip], [0], [ignore])
+fi
 
 mkdir -p $dir2
 cp $dir1/foo.zip $dir2
 cd $dir2
-zip-test r foo.zip $names
 
-# Compare the files to their originals
-for f in $names; do
- diff $dir1/$f $dir2/$f;
- if test $? -ne 0 ; then exit 1; fi;
-done
+AT_CHECK([zip-test r foo.zip $names])
 
-exit 0
+AT_CHECK([
+    # Compare the files to their originals
+    for f in $names; do
+     diff $dir1/$f $dir2/$f;
+     if test $? -ne 0 ; then exit 1; fi;
+    done
 ])
+AT_CLEANUP
 
 
-AT_CLEANUP
+AT_SETUP([zip to pipe])
+AT_KEYWORDS([compression])
+here=`pwd`
+dir1=$here/original
+dir2=$here/recovered
+
+mkdir -p $dir1
+
+# Generate files of differing sizes with random data in them
+names=""
+s=1;
+while test $s -le 8192 ; do
+       name=$dir1/$s
+       dd if=/dev/urandom of=$name count=1 bs=$s 2> /dev/null
+       s=$(($s * 2));
+       bn=`basename $name`;
+        names="$names $bn";
+done
+
+# The pipe through "cat" below is essential because it makes the
+# output file un-seekable.
+AT_CHECK([cd $dir1 && zip-test w - $names | cat > foo.zip])
+
+# If zipinfo is installed, make sure it can read the zipfile.
+if (zipinfo -v) >/dev/null 2>&1; then
+    AT_CHECK([zipinfo $dir1/foo.zip], [0], [ignore])
+fi
 
+mkdir -p $dir2
+cp $dir1/foo.zip $dir2
+cd $dir2
+
+AT_CHECK([zip-test r foo.zip $names])
+
+AT_CHECK([
+    # Compare the files to their originals
+    for f in $names; do
+     diff $dir1/$f $dir2/$f;
+     if test $? -ne 0 ; then exit 1; fi;
+    done
+])
+AT_CLEANUP