Add support for reading and writing SPV files.
[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
27 #include "gl/crc.h"
28 #include "gl/fwriteerror.h"
29 #include "gl/xalloc.h"
30
31 #include "libpspp/message.h"
32 #include "libpspp/temp-file.h"
33
34 #include "gettext.h"
35 #define _(msgid) gettext (msgid)
36
37 struct zip_writer
38   {
39     char *file_name;            /* File name, for use in error mesages. */
40     FILE *file;                 /* Output stream. */
41
42     uint16_t date, time;        /* Date and time in MS-DOS format. */
43
44     bool ok;
45
46     /* Members already added to the file, so that we can summarize them to the
47        central directory at the end of the ZIP file. */
48     struct zip_member *members;
49     size_t n_members, allocated_members;
50   };
51
52 struct zip_member
53   {
54     uint32_t offset;            /* Starting offset in file. */
55     uint32_t size;              /* Length of member file data, in bytes. */
56     uint32_t crc;               /* CRC-32 of member file data.. */
57     char *name;                 /* Name of member file. */
58   };
59
60 static void
61 put_bytes (struct zip_writer *zw, const void *p, size_t n)
62 {
63   fwrite (p, 1, n, zw->file);
64 }
65
66 static void
67 put_u16 (struct zip_writer *zw, uint16_t x)
68 {
69 #ifdef WORDS_BIGENDIAN
70   x = bswap_16 (x);
71 #endif
72   put_bytes (zw, &x, sizeof x);
73 }
74
75 static void
76 put_u32 (struct zip_writer *zw, uint32_t x)
77 {
78 #ifdef WORDS_BIGENDIAN
79   x = bswap_32 (x);
80 #endif
81   put_bytes (zw, &x, sizeof x);
82 }
83
84 /* Starts writing a new ZIP file named FILE_NAME.  Returns a new zip_writer if
85    successful, otherwise a null pointer. */
86 struct zip_writer *
87 zip_writer_create (const char *file_name)
88 {
89   struct zip_writer *zw;
90   struct tm *tm;
91   time_t now;
92   FILE *file;
93
94   file = fopen (file_name, "wb");
95   if (file == NULL)
96     {
97       msg_error (errno, _("%s: error opening output file"), file_name);
98       return NULL;
99     }
100
101   zw = xmalloc (sizeof *zw);
102   zw->file_name = xstrdup (file_name);
103   zw->file = file;
104
105   zw->ok = true;
106
107   now = time (NULL);
108   tm = localtime (&now);
109   zw->date = tm->tm_mday + ((tm->tm_mon + 1) << 5) + ((tm->tm_year - 80) << 9);
110   zw->time = tm->tm_sec / 2 + (tm->tm_min << 5) + (tm->tm_hour << 11);
111
112   zw->members = NULL;
113   zw->n_members = 0;
114   zw->allocated_members = 0;
115
116   return zw;
117 }
118
119 static void
120 put_local_header (struct zip_writer *zw, const char *member_name, uint32_t crc,
121                   uint32_t size, int flag)
122 {
123   put_u32 (zw, MAGIC_LHDR);     /* local file header signature */
124   put_u16 (zw, 10);             /* version needed to extract */
125   put_u16 (zw, flag);           /* general purpose bit flag */
126   put_u16 (zw, 0);              /* compression method */
127   put_u16 (zw, zw->time);       /* last mod file time */
128   put_u16 (zw, zw->date);       /* last mod file date */
129   put_u32 (zw, crc);            /* crc-32 */
130   put_u32 (zw, size);           /* compressed size */
131   put_u32 (zw, size);           /* uncompressed size */
132   put_u16 (zw, strlen (member_name)); /* file name length */
133   put_u16 (zw, 0);                    /* extra field length */
134   put_bytes (zw, member_name, strlen (member_name));
135 }
136
137 /* Adds the contents of FILE, with name MEMBER_NAME, to ZW. */
138 void
139 zip_writer_add (struct zip_writer *zw, FILE *file, const char *member_name)
140 {
141   struct zip_member *member;
142   uint32_t offset, size;
143   size_t bytes_read;
144   uint32_t crc;
145   char buf[4096];
146
147   /* Local file header. */
148   offset = ftello (zw->file);
149   put_local_header (zw, member_name, 0, 0, 1 << 3);
150
151   /* File data. */
152   size = crc = 0;
153   fseeko (file, 0, SEEK_SET);
154   while ((bytes_read = fread (buf, 1, sizeof buf, file)) > 0)
155     {
156       put_bytes (zw, buf, bytes_read);
157       size += bytes_read;
158       crc = crc32_update (crc, buf, bytes_read);
159     }
160
161   /* Try to seek back to the local file header.  If successful, overwrite it
162      with the correct file size and CRC.  Otherwise, write data descriptor. */
163   if (fseeko (zw->file, offset, SEEK_SET) == 0)
164     {
165       put_local_header (zw, member_name, crc, size, 0);
166       if (fseeko (zw->file, size, SEEK_CUR)
167           && zw->ok)
168         {
169           msg_error (errno, _("%s: error seeking in output file"), zw->file_name);
170           zw->ok = false;
171         }
172     }
173   else
174     {
175       put_u32 (zw, MAGIC_DDHD);
176       put_u32 (zw, crc);
177       put_u32 (zw, size);
178       put_u32 (zw, size);
179     }
180
181   /* Add to set of members. */
182   if (zw->n_members >= zw->allocated_members)
183     zw->members = x2nrealloc (zw->members, &zw->allocated_members,
184                               sizeof *zw->members);
185   member = &zw->members[zw->n_members++];
186   member->offset = offset;
187   member->size = size;
188   member->crc = crc;
189   member->name = xstrdup (member_name);
190 }
191
192 /* Adds a member named MEMBER_NAME whose contents is the null-terminated string
193    CONTENT. */
194 void
195 zip_writer_add_string (struct zip_writer *zw, const char *member_name,
196                        const char *content)
197 {
198   zip_writer_add_memory (zw, member_name, content, strlen (content));
199 }
200
201 /* Adds a member named MEMBER_NAME whose contents is the SIZE bytes of
202    CONTENT. */
203 void
204 zip_writer_add_memory (struct zip_writer *zw, const char *member_name,
205                        const void *content, size_t size)
206 {
207   FILE *fp = create_temp_file ();
208   if (fp == NULL)
209     {
210       msg_error (errno, _("error creating temporary file"));
211       zw->ok = false;
212       return;
213     }
214   fwrite (content, size, 1, fp);
215   zip_writer_add (zw, fp, member_name);
216   close_temp_file (fp);
217 }
218
219 /* Finalizes the contents of ZW and closes it.  Returns true if successful,
220    false if a write error occurred while finalizing the file or at any earlier
221    time. */
222 bool
223 zip_writer_close (struct zip_writer *zw)
224 {
225   uint32_t dir_start, dir_end;
226   size_t i;
227   bool ok;
228
229   if (zw == NULL)
230     return true;
231
232   dir_start = ftello (zw->file);
233   for (i = 0; i < zw->n_members; i++)
234     {
235       struct zip_member *m = &zw->members[i];
236
237       /* Central directory file header. */
238       put_u32 (zw, MAGIC_SOCD);       /* central file header signature */
239       put_u16 (zw, 63);               /* version made by */
240       put_u16 (zw, 10);               /* version needed to extract */
241       put_u16 (zw, 1 << 3);           /* general purpose bit flag */
242       put_u16 (zw, 0);                /* compression method */
243       put_u16 (zw, zw->time);         /* last mod file time */
244       put_u16 (zw, zw->date);         /* last mod file date */
245       put_u32 (zw, m->crc);           /* crc-32 */
246       put_u32 (zw, m->size);          /* compressed size */
247       put_u32 (zw, m->size);          /* uncompressed size */
248       put_u16 (zw, strlen (m->name)); /* file name length */
249       put_u16 (zw, 0);                /* extra field length */
250       put_u16 (zw, 0);                /* file comment length */
251       put_u16 (zw, 0);                /* disk number start */
252       put_u16 (zw, 0);                /* internal file attributes */
253       put_u32 (zw, 0);                /* external file attributes */
254       put_u32 (zw, m->offset);        /* relative offset of local header */
255       put_bytes (zw, m->name, strlen (m->name));
256       free (m->name);
257     }
258   free (zw->members);
259   dir_end = ftello (zw->file);
260
261   /* End of central directory record. */
262   put_u32 (zw, MAGIC_EOCD);     /* end of central dir signature */
263   put_u16 (zw, 0);              /* number of this disk */
264   put_u16 (zw, 0);              /* number of the disk with the
265                                    start of the central directory */
266   put_u16 (zw, zw->n_members);  /* total number of entries in the
267                                    central directory on this disk */
268   put_u16 (zw, zw->n_members);  /* total number of entries in
269                                    the central directory */
270   put_u32 (zw, dir_end - dir_start); /* size of the central directory */
271   put_u32 (zw, dir_start);      /* offset of start of central
272                                    directory with respect to
273                                    the starting disk number */
274   put_u16 (zw, 0);              /* .ZIP file comment length */
275
276   ok = zw->ok;
277   if (ok && fwriteerror (zw->file))
278     {
279       msg_error (errno, _("%s: write failed"), zw->file_name);
280       ok = false;
281     }
282
283   free (zw->file_name);
284   free (zw);
285
286   return ok;
287 }