/* PSPP - a program for statistical analysis.
- Copyright (C) 2010, 2012 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 <assert.h>
#include <byteswap.h>
#include <errno.h>
#include <stdlib.h>
#include <time.h>
+#include <unistd.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)
{
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. */
+ bool ok;
+
+ /* Member being added to the file. */
+ char *m_name;
+ uint32_t m_start;
+ uint32_t m_size;
+ uint32_t m_crc;
+
/* 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;
put_bytes (struct zip_writer *zw, const void *p, size_t n)
{
fwrite (p, 1, n, zw->file);
+ zw->offset += n;
}
static void
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)
+ if (strcmp (file_name, "-"))
{
- error (0, 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;
+ }
- 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);
+ file = stdout;
+ }
- zw->members = NULL;
- zw->n_members = 0;
- zw->allocated_members = 0;
+ time_t now = time (NULL);
+ struct tm *tm = localtime (&now);
+ struct zip_writer *zw = xmalloc (sizeof *zw);
+ *zw = (struct zip_writer) {
+ .file_name = xstrdup (file_name),
+ .file = file,
+ .ok = true,
+ .date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9),
+ .time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11),
+ };
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)
+static void
+put_local_header (struct zip_writer *zw, const char *member_name, uint32_t crc,
+ uint32_t size, int flag)
{
- struct zip_member *member;
- uint32_t offset, size;
- size_t bytes_read;
- uint32_t crc;
- char buf[4096];
-
- /* 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, 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, 0); /* crc-32 */
- put_u32 (zw, 0); /* compressed size */
- put_u32 (zw, 0); /* uncompressed size */
+ 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));
+}
- /* File data. */
- size = crc = 0;
- fseeko (file, 0, SEEK_SET);
- while ((bytes_read = fread (buf, 1, sizeof buf, file)) > 0)
+/* Start adding a new member, named MEMBER_NAME, to ZW. Add content to the
+ member by calling zip_writer_add_write() possibly multiple times, then
+ finish it off with zip_writer_add_finish().
+
+ Only one member may be open at a time. */
+void
+zip_writer_add_start (struct zip_writer *zw, const char *member_name)
+{
+ assert (!zw->m_name);
+ zw->m_name = xstrdup (member_name);
+ zw->m_start = zw->offset;
+ zw->m_size = 0;
+ zw->m_crc = 0;
+ put_local_header (zw, member_name, 0, 0, 1 << 3);
+}
+
+/* Adds the N bytes in BUF to the currently open member in ZW. */
+void
+zip_writer_add_write (struct zip_writer *zw, const void *buf, size_t n)
+{
+ assert (zw->m_name);
+ put_bytes (zw, buf, n);
+ zw->m_size += n;
+ zw->m_crc = crc32_update (zw->m_crc, buf, n);
+}
+
+/* Finishes writing the currently open member in ZW. */
+void
+zip_writer_add_finish (struct zip_writer *zw)
+{
+ assert (zw->m_name);
+
+ /* 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, zw->m_start, SEEK_SET) == 0)
{
- put_bytes (zw, buf, bytes_read);
- size += bytes_read;
- crc = crc32_update (crc, buf, bytes_read);
+ uint32_t save_offset = zw->offset;
+ put_local_header (zw, zw->m_name, zw->m_crc, zw->m_size, 0);
+ if (fseeko (zw->file, zw->m_size, SEEK_CUR)
+ && zw->ok)
+ {
+ msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
+ zw->ok = false;
+ }
+ zw->offset = save_offset;
+ }
+ else
+ {
+ put_u32 (zw, MAGIC_DDHD);
+ put_u32 (zw, zw->m_crc);
+ put_u32 (zw, zw->m_size);
+ put_u32 (zw, zw->m_size);
}
-
- /* Data descriptor. */
- 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)
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);
+ struct zip_member *member = &zw->members[zw->n_members++];
+ member->offset = zw->m_start;
+ member->size = zw->m_size;
+ member->crc = zw->m_crc;
+ member->name = zw->m_name;
+
+ zw->m_name = NULL;
+ zw->m_start = zw->m_size = zw->m_crc = 0;
+}
+
+/* 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)
+{
+ zip_writer_add_start (zw, member_name);
+
+ fseeko (file, 0, SEEK_SET);
+ for (;;)
+ {
+ char buf[4096];
+ size_t n = fread (buf, 1, sizeof buf, file);
+ if (!n)
+ break;
+ zip_writer_add_write (zw, buf, n);
+ }
+ zip_writer_add_finish (zw);
+}
+
+/* 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)
+{
+ zip_writer_add_start (zw, member_name);
+ zip_writer_add_write (zw, content, size);
+ zip_writer_add_finish (zw);
}
/* Finalizes the contents of ZW and closes it. Returns true if successful,
if (zw == NULL)
return true;
- dir_start = ftello (zw->file);
+ assert (!zw->m_name);
+
+ dir_start = zw->offset;
for (i = 0; i < zw->n_members; i++)
{
struct zip_member *m = &zw->members[i];
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 */
the starting disk number */
put_u16 (zw, 0); /* .ZIP file comment length */
- if (!fwriteerror (zw->file))
- ok = true;
- else
+ ok = zw->ok;
+ if (ok && zw->file != stdout && fwriteerror (zw->file))
{
- error (0, errno, _("%s: write failed"), zw->file_name);
+ msg_error (errno, _("%s: write failed"), zw->file_name);
ok = false;
}