odt: Remove dependency on external "zip" program.
authorBen Pfaff <blp@gnu.org>
Wed, 24 Feb 2010 06:07:44 +0000 (22:07 -0800)
committerBen Pfaff <blp@gnu.org>
Wed, 24 Feb 2010 06:07:44 +0000 (22:07 -0800)
Smake
src/libpspp/automake.mk
src/libpspp/zip-writer.c [new file with mode: 0644]
src/libpspp/zip-writer.h [new file with mode: 0644]
src/output/odt.c

diff --git a/Smake b/Smake
index 10b5124373d0a87597f47ee170a5091753d23e1e..686fb756278834a9aafbce869972f97af42cedd0 100644 (file)
--- a/Smake
+++ b/Smake
@@ -11,6 +11,7 @@ GNULIB_MODULES = \
        c-strtod \
        close \
        count-one-bits \
+       crc \
        crypto/md4 \
        dirname \
        environ \
index 0172d80fd07a83570b077f53b4900428c69a5d7a..4f3a7fc1a9eb219b0c84449b793166115edd2646 100644 (file)
@@ -82,7 +82,9 @@ src_libpspp_libpspp_la_SOURCES = \
        src/libpspp/tmpfile.h \
        src/libpspp/tower.c \
        src/libpspp/tower.h \
-       src/libpspp/version.h 
+       src/libpspp/version.h \
+       src/libpspp/zip-writer.c \
+       src/libpspp/zip-writer.h
 
 DISTCLEANFILES+=src/libpspp/version.c
 
diff --git a/src/libpspp/zip-writer.c b/src/libpspp/zip-writer.c
new file mode 100644 (file)
index 0000000..534e7eb
--- /dev/null
@@ -0,0 +1,232 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2010 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 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "libpspp/zip-writer.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "libpspp/integer-format.h"
+
+#include "gl/crc.h"
+#include "gl/error.h"
+#include "gl/fwriteerror.h"
+#include "gl/xalloc.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+struct zip_writer
+  {
+    char *file_name;            /* File name, for use in error mesages. */
+    FILE *file;                 /* Output stream. */
+
+    uint16_t date, time;        /* Date and time in MS-DOS format. */
+
+    /* 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;
+    size_t n_members, allocated_members;
+  };
+
+struct zip_member
+  {
+    uint32_t offset;            /* Starting offset in file. */
+    uint32_t size;              /* Length of member file data, in bytes. */
+    uint32_t crc;               /* CRC-32 of member file data.. */
+    char *name;                 /* Name of member file. */
+  };
+
+static void
+put_bytes (struct zip_writer *zw, const void *p, size_t n)
+{
+  fwrite (p, 1, n, zw->file);
+}
+
+static void
+put_u16 (struct zip_writer *zw, uint16_t x)
+{
+  if (INTEGER_NATIVE != INTEGER_LSB_FIRST)
+    integer_convert (INTEGER_NATIVE, &x, INTEGER_MSB_FIRST, &x, sizeof x);
+  put_bytes (zw, &x, sizeof x);
+}
+
+static void
+put_u32 (struct zip_writer *zw, uint32_t x)
+{
+  if (INTEGER_NATIVE != INTEGER_LSB_FIRST)
+    integer_convert (INTEGER_NATIVE, &x, INTEGER_MSB_FIRST, &x, sizeof x);
+  put_bytes (zw, &x, sizeof x);
+}
+
+/* Starts writing a new ZIP file named FILE_NAME.  Returns a new zip_writer if
+   successful, otherwise a null pointer. */
+struct zip_writer *
+zip_writer_create (const char *file_name)
+{
+  struct zip_writer *zw;
+  struct tm *tm;
+  time_t now;
+  FILE *file;
+
+  file = fopen (file_name, "wb");
+  if (file == NULL)
+    {
+      error (0, errno, _("%s: error opening output file"), file_name);
+      return NULL;
+    }
+
+  zw = xmalloc (sizeof *zw);
+  zw->file_name = xstrdup (file_name);
+  zw->file = file;
+
+  now = time (NULL);
+  tm = localtime (&now);
+  zw->date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9);
+  zw->time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11);
+
+  zw->members = NULL;
+  zw->n_members = 0;
+  zw->allocated_members = 0;
+
+  return zw;
+}
+
+/* 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)
+{
+  struct zip_member *member;
+  uint32_t offset, size;
+  size_t bytes_read;
+  uint32_t crc;
+  char buf[4096];
+
+  /* Local file header. */
+  offset = ftell (zw->file);
+  put_u32 (zw, 0x04034b50);     /* 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));
+
+  /* File data. */
+  size = crc = 0;
+  fseek (file, 0, SEEK_SET);
+  while ((bytes_read = fread (buf, 1, sizeof buf, file)) > 0)
+    {
+      put_bytes (zw, buf, bytes_read);
+      size += bytes_read;
+      crc = crc32_update (crc, buf, bytes_read);
+    }
+
+  /* Data descriptor. */
+  put_u32 (zw, 0x08074b50);
+  put_u32 (zw, crc);
+  put_u32 (zw, size);
+  put_u32 (zw, size);
+
+  /* Add to set of members. */
+  if (zw->n_members >= zw->allocated_members)
+    zw->members = x2nrealloc (zw->members, &zw->allocated_members,
+                              sizeof *zw->members);
+  member = &zw->members[zw->n_members++];
+  member->offset = offset;
+  member->size = size;
+  member->crc = crc;
+  member->name = xstrdup (member_name);
+}
+
+/* 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. */
+bool
+zip_writer_close (struct zip_writer *zw)
+{
+  uint32_t dir_start, dir_end;
+  size_t i;
+  bool ok;
+
+  if (zw == NULL)
+    return true;
+
+  dir_start = ftell (zw->file);
+  for (i = 0; i < zw->n_members; i++)
+    {
+      struct zip_member *m = &zw->members[i];
+
+      /* Central directory file header. */
+      put_u32 (zw, 0x02014b50);       /* central file header signature */
+      put_u16 (zw, 63);               /* version made by */
+      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, m->crc);           /* crc-32 */
+      put_u32 (zw, m->size);          /* compressed size */
+      put_u32 (zw, m->size);          /* uncompressed size */
+      put_u16 (zw, strlen (m->name)); /* file name length */
+      put_u16 (zw, 0);                /* extra field length */
+      put_u16 (zw, 0);                /* file comment length */
+      put_u16 (zw, 0);                /* disk number start */
+      put_u16 (zw, 0);                /* internal file attributes */
+      put_u32 (zw, 0);                /* external file attributes */
+      put_u32 (zw, m->offset);        /* relative offset of local header */
+      put_bytes (zw, m->name, strlen (m->name));
+      free (m->name);
+    }
+  free (zw->members);
+  dir_end = ftell (zw->file);
+
+  /* End of central directory record. */
+  put_u32 (zw, 0x06054b50);     /* end of central dir signature */
+  put_u16 (zw, 0);              /* number of this disk */
+  put_u16 (zw, 0);              /* number of the disk with the
+                                   start of the central directory */
+  put_u16 (zw, zw->n_members);  /* total number of entries in the
+                                   central directory on this disk */
+  put_u16 (zw, zw->n_members);  /* total number of entries in
+                                   the central directory */
+  put_u32 (zw, dir_end - dir_start); /* size of the central directory */
+  put_u32 (zw, dir_start);      /* offset of start of central
+                                   directory with respect to
+                                   the starting disk number */
+  put_u16 (zw, 0);              /* .ZIP file comment length */
+
+  if (!fwriteerror (zw->file))
+    ok = true;
+  else
+    {
+      error (0, errno, _("%s: write failed"), zw->file_name);
+      ok = false;
+    }
+
+  free (zw->file_name);
+  free (zw);
+
+  return ok;
+}
diff --git a/src/libpspp/zip-writer.h b/src/libpspp/zip-writer.h
new file mode 100644 (file)
index 0000000..aff3f99
--- /dev/null
@@ -0,0 +1,27 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2010 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 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef LIBPSPP_ZIP_WRITER_H
+#define LIBPSPP_ZIP_WRITER_H 1
+
+#include <stdbool.h>
+#include <stdio.h>
+
+struct zip_writer *zip_writer_create (const char *file_name);
+void zip_writer_add (struct zip_writer *, FILE *, const char *member_name);
+bool zip_writer_close (struct zip_writer *);
+
+#endif /* libpspp/zip-writer.h */
index 99f5e60d6ec7cb8fdb45265f424b16a37f18d133..49a55b4ccef3cbf9d3827607f673397d2f3cb670 100644 (file)
@@ -33,6 +33,7 @@
 #include "libpspp/cast.h"
 #include "libpspp/str.h"
 #include "libpspp/version.h"
+#include "libpspp/zip-writer.h"
 #include "output/driver-provider.h"
 #include "output/message-item.h"
 #include "output/options.h"
@@ -53,17 +54,16 @@ struct odt_driver
 {
   struct output_driver driver;
 
+  struct zip_writer *zip;     /* ZIP file writer. */
   char *file_name;            /* Output file name. */
-  bool debug;
 
-  /* The name of the temporary directory used to construct the ODF */
-  char *dirname;
+  /* content.xml */
+  xmlTextWriterPtr content_wtr; /* XML writer. */
+  FILE *content_file;           /* Temporary file. */
 
-  /* Writer for the content.xml file */
-  xmlTextWriterPtr content_wtr;
-
-  /* Writer fot the manifest.xml file */
-  xmlTextWriterPtr manifest_wtr;
+  /* manifest.xml */
+  xmlTextWriterPtr manifest_wtr; /* XML writer. */
+  FILE *manifest_file;           /* Temporary file. */
 
   /* Number of tables so far. */
   int table_num;
@@ -83,52 +83,34 @@ odt_driver_cast (struct output_driver *driver)
 
 /* Create the "mimetype" file needed by ODF */
 static bool
-create_mimetype (const char *dirname)
+create_mimetype (struct zip_writer *zip)
 {
   FILE *fp;
-  struct string filename;
-  ds_init_cstr (&filename, dirname);
-  ds_put_cstr (&filename, "/mimetype");
-  fp = fopen (ds_cstr (&filename), "w");
 
+  fp = tmpfile ();
   if (fp == NULL)
     {
-      error (0, errno, _("error opening output file \"%s\""),
-             ds_cstr (&filename));
-      ds_destroy (&filename);
+      error (0, errno, _("error creating temporary file"));
       return false;
     }
-  ds_destroy (&filename);
 
   fprintf (fp, "application/vnd.oasis.opendocument.text");
+  zip_writer_add (zip, fp, "mimetype");
   fclose (fp);
 
   return true;
 }
 
-/* Create a new XML file called FILENAME in the temp directory, and return a writer for it */
-static xmlTextWriterPtr
-create_writer (const struct odt_driver *driver, const char *filename)
+/* Creates a new temporary file and stores it in *FILE, then creates an XML
+   writer for it and stores it in *W. */
+static void
+create_writer (FILE **file, xmlTextWriterPtr *w)
 {
-  char *copy = NULL;
-  xmlTextWriterPtr w;
-  struct string str;
-  ds_init_cstr (&str, driver->dirname);
-  ds_put_cstr (&str, "/");
-  ds_put_cstr (&str, filename);
-
-  /* dirname modifies its argument, so we must copy it */
-  copy = xstrdup (ds_cstr (&str));
-  mkdir (dirname (copy), 0700);
-  free (copy);
-
-  w = xmlNewTextWriterFilename (ds_cstr (&str), 0);
-
-  ds_destroy (&str);
-
-  xmlTextWriterStartDocument (w, NULL, "UTF-8", NULL);
+  /* XXX this can fail */
+  *file = tmpfile ();
+  *w = xmlNewTextWriter (xmlOutputBufferCreateFile (*file, NULL));
 
-  return w;
+  xmlTextWriterStartDocument (*w, NULL, "UTF-8", NULL);
 }
 
 
@@ -145,7 +127,10 @@ register_file (struct odt_driver *odt, const char *filename)
 static void
 write_style_data (struct odt_driver *odt)
 {
-  xmlTextWriterPtr w = create_writer (odt, "styles.xml");
+  xmlTextWriterPtr w;
+  FILE *file;
+
+  create_writer (&file, &w);
   register_file (odt, "styles.xml");
 
   xmlTextWriterStartElement (w, _xml ("office:document-styles"));
@@ -232,12 +217,17 @@ write_style_data (struct odt_driver *odt)
 
   xmlTextWriterEndDocument (w);
   xmlFreeTextWriter (w);
+  zip_writer_add (odt->zip, file, "styles.xml");
+  fclose (file);
 }
 
 static void
 write_meta_data (struct odt_driver *odt)
 {
-  xmlTextWriterPtr w = create_writer (odt, "meta.xml");
+  xmlTextWriterPtr w;
+  FILE *file;
+
+  create_writer (&file, &w);
   register_file (odt, "meta.xml");
 
   xmlTextWriterStartElement (w, _xml ("office:document-meta"));
@@ -291,46 +281,37 @@ write_meta_data (struct odt_driver *odt)
   xmlTextWriterEndElement (w);
   xmlTextWriterEndDocument (w);
   xmlFreeTextWriter (w);
-}
-
-enum
-{
-  output_file_arg,
-  boolean_arg,
-};
-
-static struct driver_option *
-opt (struct output_driver *d, struct string_map *options, const char *key,
-     const char *default_value)
-{
-  return driver_option_get (d, options, key, default_value);
+  zip_writer_add (odt->zip, file, "meta.xml");
+  fclose (file);
 }
 
 static struct output_driver *
 odt_create (const char *file_name, enum settings_output_devices device_type,
-            struct string_map *o)
+            struct string_map *o UNUSED)
 {
   struct output_driver *d;
   struct odt_driver *odt;
+  struct zip_writer *zip;
+
+  zip = zip_writer_create (file_name);
+  if (zip == NULL)
+    return NULL;
 
   odt = xzalloc (sizeof *odt);
   d = &odt->driver;
   output_driver_init (d, &odt_driver_class, file_name, device_type);
 
+  odt->zip = zip;
   odt->file_name = xstrdup (file_name);
-  odt->debug = parse_boolean (opt (d, o, "debug", "false"));
-
-  odt->dirname = xstrdup ("odt-XXXXXX");
-  mkdtemp (odt->dirname);
 
-  if (!create_mimetype (odt->dirname))
+  if (!create_mimetype (zip))
     {
       output_driver_destroy (d);
       return NULL;
     }
 
   /* Create the manifest */
-  odt->manifest_wtr = create_writer (odt, "META-INF/manifest.xml");
+  create_writer (&odt->manifest_file, &odt->manifest_wtr);
 
   xmlTextWriterStartElement (odt->manifest_wtr, _xml("manifest:manifest"));
   xmlTextWriterWriteAttribute (odt->manifest_wtr, _xml("xmlns:manifest"),
@@ -347,7 +328,7 @@ odt_create (const char *file_name, enum settings_output_devices device_type,
   write_meta_data (odt);
   write_style_data (odt);
 
-  odt->content_wtr = create_writer (odt, "content.xml");
+  create_writer (&odt->content_file, &odt->content_wtr);
   register_file (odt, "content.xml");
 
 
@@ -373,6 +354,8 @@ odt_create (const char *file_name, enum settings_output_devices device_type,
   xmlTextWriterEndElement (odt->manifest_wtr);
   xmlTextWriterEndDocument (odt->manifest_wtr);
   xmlFreeTextWriter (odt->manifest_wtr);
+  zip_writer_add (odt->zip, odt->manifest_file, "META-INF/manifest.xml");
+  fclose (odt->manifest_file);
 
   return d;
 }
@@ -384,39 +367,19 @@ odt_destroy (struct output_driver *driver)
 
   if (odt->content_wtr != NULL)
     {
-      struct string zip_cmd;
-
       xmlTextWriterEndElement (odt->content_wtr); /* office:text */
       xmlTextWriterEndElement (odt->content_wtr); /* office:body */
       xmlTextWriterEndElement (odt->content_wtr); /* office:document-content */
 
       xmlTextWriterEndDocument (odt->content_wtr);
       xmlFreeTextWriter (odt->content_wtr);
+      zip_writer_add (odt->zip, odt->content_file, "content.xml");
+      fclose (odt->content_file);
 
-      /* Zip up the directory */
-      ds_init_empty (&zip_cmd);
-      ds_put_format (&zip_cmd,
-                     "cd %s ; rm -f ../%s; zip -q -X ../%s mimetype; zip -q -X -u -r ../%s .",
-                     odt->dirname, odt->file_name, odt->file_name, odt->file_name);
-      system (ds_cstr (&zip_cmd));
-      ds_destroy (&zip_cmd);
+      zip_writer_close (odt->zip);
     }
-
-  if ( !odt->debug )
-    {
-      /* Remove the temp dir */
-      struct string rm_cmd;
-
-      ds_init_empty (&rm_cmd);
-      ds_put_format (&rm_cmd, "rm -r %s", odt->dirname);
-      system (ds_cstr (&rm_cmd));
-      ds_destroy (&rm_cmd);
-    }
-  else
-    fprintf (stderr, "Not removing directory %s\n", odt->dirname);
-
+  
   free (odt->command_name);
-  free (odt->dirname);
   free (odt);
 }