zip-writer: Fix writing a zip file to a pipe.
[pspp] / src / libpspp / zip-writer.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2010, 2012, 2014 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "libpspp/zip-writer.h"
20 #include "libpspp/zip-private.h"
21
22 #include <byteswap.h>
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <time.h>
26 #include <unistd.h>
27
28 #include "gl/crc.h"
29 #include "gl/fwriteerror.h"
30 #include "gl/xalloc.h"
31
32 #include "libpspp/message.h"
33 #include "libpspp/temp-file.h"
34
35 #include "gettext.h"
36 #define _(msgid) gettext (msgid)
37
38 struct zip_writer
39   {
40     char *file_name;            /* File name, for use in error mesages. */
41     FILE *file;                 /* Output stream. */
42     uint32_t offset;            /* Offset in output stream. */
43
44     uint16_t date, time;        /* Date and time in MS-DOS format. */
45
46     bool ok;
47
48     /* Members already added to the file, so that we can summarize them to the
49        central directory at the end of the ZIP file. */
50     struct zip_member *members;
51     size_t n_members, allocated_members;
52   };
53
54 struct zip_member
55   {
56     uint32_t offset;            /* Starting offset in file. */
57     uint32_t size;              /* Length of member file data, in bytes. */
58     uint32_t crc;               /* CRC-32 of member file data.. */
59     char *name;                 /* Name of member file. */
60   };
61
62 static void
63 put_bytes (struct zip_writer *zw, const void *p, size_t n)
64 {
65   fwrite (p, 1, n, zw->file);
66   zw->offset += n;
67 }
68
69 static void
70 put_u16 (struct zip_writer *zw, uint16_t x)
71 {
72 #ifdef WORDS_BIGENDIAN
73   x = bswap_16 (x);
74 #endif
75   put_bytes (zw, &x, sizeof x);
76 }
77
78 static void
79 put_u32 (struct zip_writer *zw, uint32_t x)
80 {
81 #ifdef WORDS_BIGENDIAN
82   x = bswap_32 (x);
83 #endif
84   put_bytes (zw, &x, sizeof x);
85 }
86
87 /* Starts writing a new ZIP file named FILE_NAME.  Returns a new zip_writer if
88    successful, otherwise a null pointer. */
89 struct zip_writer *
90 zip_writer_create (const char *file_name)
91 {
92   struct zip_writer *zw;
93   struct tm *tm;
94   time_t now;
95   FILE *file;
96
97   if (strcmp (file_name, "-"))
98     {
99       file = fopen (file_name, "wb");
100       if (file == NULL)
101         {
102           msg_error (errno, _("%s: error opening output file"), file_name);
103           return NULL;
104         }
105     }
106   else
107     {
108       if (isatty (STDOUT_FILENO))
109         {
110           msg (ME, _("%s: not writing ZIP file to terminal"), file_name);
111           return NULL;
112         }
113
114       file = stdout;
115     }
116
117   zw = xmalloc (sizeof *zw);
118   zw->file_name = xstrdup (file_name);
119   zw->file = file;
120   zw->offset = 0;
121
122   zw->ok = true;
123
124   now = time (NULL);
125   tm = localtime (&now);
126   zw->date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9);
127   zw->time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11);
128
129   zw->members = NULL;
130   zw->n_members = 0;
131   zw->allocated_members = 0;
132
133   return zw;
134 }
135
136 static void
137 put_local_header (struct zip_writer *zw, const char *member_name, uint32_t crc,
138                   uint32_t size, int flag)
139 {
140   put_u32 (zw, MAGIC_LHDR);     /* local file header signature */
141   put_u16 (zw, 10);             /* version needed to extract */
142   put_u16 (zw, flag);           /* general purpose bit flag */
143   put_u16 (zw, 0);              /* compression method */
144   put_u16 (zw, zw->time);       /* last mod file time */
145   put_u16 (zw, zw->date);       /* last mod file date */
146   put_u32 (zw, crc);            /* crc-32 */
147   put_u32 (zw, size);           /* compressed size */
148   put_u32 (zw, size);           /* uncompressed size */
149   put_u16 (zw, strlen (member_name)); /* file name length */
150   put_u16 (zw, 0);                    /* extra field length */
151   put_bytes (zw, member_name, strlen (member_name));
152 }
153
154 /* Adds the contents of FILE, with name MEMBER_NAME, to ZW. */
155 void
156 zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
157 {
158   struct zip_member *member;
159   uint32_t size;
160   size_t bytes_read;
161   uint32_t crc;
162   char buf[4096];
163
164   /* Local file header. */
165   uint32_t offset = zw->offset;
166   put_local_header (zw, member_name, 0, 0, 1 << 3);
167
168   /* File data. */
169   size = crc = 0;
170   fseeko (file, 0, SEEK_SET);
171   while ((bytes_read = fread (buf, 1, sizeof buf, file)) > 0)
172     {
173       put_bytes (zw, buf, bytes_read);
174       size += bytes_read;
175       crc = crc32_update (crc, buf, bytes_read);
176     }
177
178   /* Try to seek back to the local file header.  If successful, overwrite it
179      with the correct file size and CRC.  Otherwise, write data descriptor. */
180   if (fseeko (zw->file, offset, SEEK_SET) == 0)
181     {
182       uint32_t save_offset = zw->offset;
183       put_local_header (zw, member_name, crc, size, 0);
184       if (fseeko (zw->file, size, SEEK_CUR)
185           && zw->ok)
186         {
187           msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
188           zw->ok = false;
189         }
190       zw->offset = save_offset;
191     }
192   else
193     {
194       put_u32 (zw, MAGIC_DDHD);
195       put_u32 (zw, crc);
196       put_u32 (zw, size);
197       put_u32 (zw, size);
198     }
199
200   /* Add to set of members. */
201   if (zw->n_members >= zw->allocated_members)
202     zw->members = x2nrealloc (zw->members, &zw->allocated_members,
203                               sizeof *zw->members);
204   member = &zw->members[zw->n_members++];
205   member->offset = offset;
206   member->size = size;
207   member->crc = crc;
208   member->name = xstrdup (member_name);
209 }
210
211 /* Adds a member named MEMBER_NAME whose contents is the null-terminated string
212    CONTENT. */
213 void
214 zip_writer_add_string (struct zip_writer *zw, const char *member_name,
215                        const char *content)
216 {
217   zip_writer_add_memory (zw, member_name, content, strlen (content));
218 }
219
220 /* Adds a member named MEMBER_NAME whose contents is the SIZE bytes of
221    CONTENT. */
222 void
223 zip_writer_add_memory (struct zip_writer *zw, const char *member_name,
224                        const void *content, size_t size)
225 {
226   FILE *fp = create_temp_file ();
227   if (fp == NULL)
228     {
229       msg_error (errno, _("error creating temporary file"));
230       zw->ok = false;
231       return;
232     }
233   fwrite (content, size, 1, fp);
234   zip_writer_add (zw, fp, member_name);
235   close_temp_file (fp);
236 }
237
238 /* Finalizes the contents of ZW and closes it.  Returns true if successful,
239    false if a write error occurred while finalizing the file or at any earlier
240    time. */
241 bool
242 zip_writer_close (struct zip_writer *zw)
243 {
244   uint32_t dir_start, dir_end;
245   size_t i;
246   bool ok;
247
248   if (zw == NULL)
249     return true;
250
251   dir_start = zw->offset;
252   for (i = 0; i < zw->n_members; i++)
253     {
254       struct zip_member *m = &zw->members[i];
255
256       /* Central directory file header. */
257       put_u32 (zw, MAGIC_SOCD);       /* central file header signature */
258       put_u16 (zw, 63);               /* version made by */
259       put_u16 (zw, 10);               /* version needed to extract */
260       put_u16 (zw, 1 << 3);           /* general purpose bit flag */
261       put_u16 (zw, 0);                /* compression method */
262       put_u16 (zw, zw->time);         /* last mod file time */
263       put_u16 (zw, zw->date);         /* last mod file date */
264       put_u32 (zw, m->crc);           /* crc-32 */
265       put_u32 (zw, m->size);          /* compressed size */
266       put_u32 (zw, m->size);          /* uncompressed size */
267       put_u16 (zw, strlen (m->name)); /* file name length */
268       put_u16 (zw, 0);                /* extra field length */
269       put_u16 (zw, 0);                /* file comment length */
270       put_u16 (zw, 0);                /* disk number start */
271       put_u16 (zw, 0);                /* internal file attributes */
272       put_u32 (zw, 0);                /* external file attributes */
273       put_u32 (zw, m->offset);        /* relative offset of local header */
274       put_bytes (zw, m->name, strlen (m->name));
275       free (m->name);
276     }
277   free (zw->members);
278   dir_end = zw->offset;
279
280   /* End of central directory record. */
281   put_u32 (zw, MAGIC_EOCD);     /* end of central dir signature */
282   put_u16 (zw, 0);              /* number of this disk */
283   put_u16 (zw, 0);              /* number of the disk with the
284                                    start of the central directory */
285   put_u16 (zw, zw->n_members);  /* total number of entries in the
286                                    central directory on this disk */
287   put_u16 (zw, zw->n_members);  /* total number of entries in
288                                    the central directory */
289   put_u32 (zw, dir_end - dir_start); /* size of the central directory */
290   put_u32 (zw, dir_start);      /* offset of start of central
291                                    directory with respect to
292                                    the starting disk number */
293   put_u16 (zw, 0);              /* .ZIP file comment length */
294
295   ok = zw->ok;
296   if (ok && zw->file != stdout && fwriteerror (zw->file))
297     {
298       msg_error (errno, _("%s: write failed"), zw->file_name);
299       ok = false;
300     }
301
302   free (zw->file_name);
303   free (zw);
304
305   return ok;
306 }