Add support for reading and writing SPV files.
[pspp] / src / libpspp / zip-writer.c
index 01c11d77c0469643e66bc6fe972ae1bbbc1ff14e..0959551abefd00720cd47f1e768910bcfa9bd340 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2010 Free Software Foundation, Inc.
+   Copyright (C) 2010, 2012, 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
 #include "libpspp/zip-writer.h"
 #include "libpspp/zip-private.h"
 
+#include <byteswap.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <time.h>
 
 #include "gl/crc.h"
-#include "gl/error.h"
 #include "gl/fwriteerror.h"
 #include "gl/xalloc.h"
 
+#include "libpspp/message.h"
+#include "libpspp/temp-file.h"
+
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
@@ -38,6 +41,8 @@ struct zip_writer
 
     uint16_t date, time;        /* Date and time in MS-DOS format. */
 
+    bool ok;
+
     /* Members already added to the file, so that we can summarize them to the
        central directory at the end of the ZIP file. */
     struct zip_member *members;
@@ -89,7 +94,7 @@ zip_writer_create (const char *file_name)
   file = fopen (file_name, "wb");
   if (file == NULL)
     {
-      error (0, errno, _("%s: error opening output file"), file_name);
+      msg_error (errno, _("%s: error opening output file"), file_name);
       return NULL;
     }
 
@@ -97,6 +102,8 @@ zip_writer_create (const char *file_name)
   zw->file_name = xstrdup (file_name);
   zw->file = file;
 
+  zw->ok = true;
+
   now = time (NULL);
   tm = localtime (&now);
   zw->date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9);
@@ -109,6 +116,24 @@ zip_writer_create (const char *file_name)
   return zw;
 }
 
+static void
+put_local_header (struct zip_writer *zw, const char *member_name, uint32_t crc,
+                  uint32_t size, int flag)
+{
+  put_u32 (zw, MAGIC_LHDR);     /* local file header signature */
+  put_u16 (zw, 10);             /* version needed to extract */
+  put_u16 (zw, flag);           /* general purpose bit flag */
+  put_u16 (zw, 0);              /* compression method */
+  put_u16 (zw, zw->time);       /* last mod file time */
+  put_u16 (zw, zw->date);       /* last mod file date */
+  put_u32 (zw, crc);            /* crc-32 */
+  put_u32 (zw, size);           /* compressed size */
+  put_u32 (zw, size);           /* uncompressed size */
+  put_u16 (zw, strlen (member_name)); /* file name length */
+  put_u16 (zw, 0);                    /* extra field length */
+  put_bytes (zw, member_name, strlen (member_name));
+}
+
 /* Adds the contents of FILE, with name MEMBER_NAME, to ZW. */
 void
 zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
@@ -121,18 +146,7 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
 
   /* Local file header. */
   offset = ftello (zw->file);
-  put_u32 (zw, MAGIC_LHDR);     /* local file header signature */
-  put_u16 (zw, 10);             /* version needed to extract */
-  put_u16 (zw, 1 << 3);         /* general purpose bit flag */
-  put_u16 (zw, 0);              /* compression method */
-  put_u16 (zw, zw->time);       /* last mod file time */
-  put_u16 (zw, zw->date);       /* last mod file date */
-  put_u32 (zw, 0);              /* crc-32 */
-  put_u32 (zw, 0);              /* compressed size */
-  put_u32 (zw, 0);              /* uncompressed size */
-  put_u16 (zw, strlen (member_name)); /* file name length */
-  put_u16 (zw, 0);                    /* extra field length */
-  put_bytes (zw, member_name, strlen (member_name));
+  put_local_header (zw, member_name, 0, 0, 1 << 3);
 
   /* File data. */
   size = crc = 0;
@@ -144,11 +158,25 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
       crc = crc32_update (crc, buf, bytes_read);
     }
 
-  /* Data descriptor. */
-  put_u32 (zw, MAGIC_DDHD);
-  put_u32 (zw, crc);
-  put_u32 (zw, size);
-  put_u32 (zw, size);
+  /* Try to seek back to the local file header.  If successful, overwrite it
+     with the correct file size and CRC.  Otherwise, write data descriptor. */
+  if (fseeko (zw->file, offset, SEEK_SET) == 0)
+    {
+      put_local_header (zw, member_name, crc, size, 0);
+      if (fseeko (zw->file, size, SEEK_CUR)
+          && zw->ok)
+        {
+          msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
+          zw->ok = false;
+        }
+    }
+  else
+    {
+      put_u32 (zw, MAGIC_DDHD);
+      put_u32 (zw, crc);
+      put_u32 (zw, size);
+      put_u32 (zw, size);
+    }
 
   /* Add to set of members. */
   if (zw->n_members >= zw->allocated_members)
@@ -161,6 +189,33 @@ zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
   member->name = xstrdup (member_name);
 }
 
+/* Adds a member named MEMBER_NAME whose contents is the null-terminated string
+   CONTENT. */
+void
+zip_writer_add_string (struct zip_writer *zw, const char *member_name,
+                       const char *content)
+{
+  zip_writer_add_memory (zw, member_name, content, strlen (content));
+}
+
+/* Adds a member named MEMBER_NAME whose contents is the SIZE bytes of
+   CONTENT. */
+void
+zip_writer_add_memory (struct zip_writer *zw, const char *member_name,
+                       const void *content, size_t size)
+{
+  FILE *fp = create_temp_file ();
+  if (fp == NULL)
+    {
+      msg_error (errno, _("error creating temporary file"));
+      zw->ok = false;
+      return;
+    }
+  fwrite (content, size, 1, fp);
+  zip_writer_add (zw, fp, member_name);
+  close_temp_file (fp);
+}
+
 /* Finalizes the contents of ZW and closes it.  Returns true if successful,
    false if a write error occurred while finalizing the file or at any earlier
    time. */
@@ -218,11 +273,10 @@ zip_writer_close (struct zip_writer *zw)
                                    the starting disk number */
   put_u16 (zw, 0);              /* .ZIP file comment length */
 
-  if (!fwriteerror (zw->file))
-    ok = true;
-  else
+  ok = zw->ok;
+  if (ok && fwriteerror (zw->file))
     {
-      error (0, errno, _("%s: write failed"), zw->file_name);
+      msg_error (errno, _("%s: write failed"), zw->file_name);
       ok = false;
     }