* merged with tip (040f180a056b)
[cacao.git] / src / vm / zip.c
1 /* src/vm/zip.c - ZIP file handling for bootstrap classloader
2
3    Copyright (C) 1996-2005, 2006, 2007, 2008
4    CACAOVM - Verein zur Foerderung der freien virtuellen Maschine CACAO
5
6    This file is part of CACAO.
7
8    This program is free software; you can redistribute it and/or
9    modify it under the terms of the GNU General Public License as
10    published by the Free Software Foundation; either version 2, or (at
11    your option) any later version.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23 */
24
25
26 #include "config.h"
27
28 #include <assert.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <zlib.h>
33 #include <sys/mman.h>
34
35 #include "vm/types.h"
36
37 #include "toolbox/hashtable.h"
38
39 #include "mm/memory.h"
40
41 #include "vm/global.h"
42 #include "vm/suck.h"
43 #include "vm/utf8.h"
44 #include "vm/vm.hpp"
45 #include "vm/zip.h"
46
47
48 /* start size for classes hashtable *******************************************/
49
50 #define HASHTABLE_CLASSES_SIZE    (1 << 10)
51
52
53 /* info taken from:
54    http://www.pkware.com/business_and_developers/developer/popups/appnote.txt
55 */
56
57 /* all signatures in the ZIP file have a length of 4 bytes ********************/
58
59 #define SIGNATURE_LENGTH    4
60
61 /* Central directory structure *************************************************
62
63    [file header 1]
64    .
65    .
66    . 
67    [file header n]
68    [digital signature] 
69    
70    File header:
71    
72      central file header signature   4 bytes  (0x02014b50)
73      version made by                 2 bytes
74      version needed to extract       2 bytes
75      general purpose bit flag        2 bytes
76      compression method              2 bytes
77      last mod file time              2 bytes
78      last mod file date              2 bytes
79      crc-32                          4 bytes
80      compressed size                 4 bytes
81      uncompressed size               4 bytes
82      file name length                2 bytes
83      extra field length              2 bytes
84      file comment length             2 bytes
85      disk number start               2 bytes
86      internal file attributes        2 bytes
87      external file attributes        4 bytes
88      relative offset of local header 4 bytes
89    
90      file name (variable size)
91      extra field (variable size)
92      file comment (variable size)
93
94    Digital signature:
95    
96      header signature                4 bytes  (0x05054b50)
97      size of data                    2 bytes
98      signature data (variable size)
99
100 *******************************************************************************/
101
102 #define CDSFH_HEADER_SIZE            46
103
104 #define CDSFH_SIGNATURE              0x02014b50
105 #define CDSFH_COMPRESSION_METHOD     10
106 #define CDSFH_COMPRESSED_SIZE        20
107 #define CDSFH_UNCOMPRESSED_SIZE      24
108 #define CDSFH_FILE_NAME_LENGTH       28
109 #define CDSFH_EXTRA_FIELD_LENGTH     30
110 #define CDSFH_FILE_COMMENT_LENGTH    32
111 #define CDSFH_RELATIVE_OFFSET        42
112 #define CDSFH_FILENAME               46
113
114 typedef struct cdsfh cdsfh;
115
116 struct cdsfh {
117         u2 compressionmethod;
118         u4 compressedsize;
119         u4 uncompressedsize;
120         u2 filenamelength;
121         u2 extrafieldlength;
122         u2 filecommentlength;
123         u4 relativeoffset;
124 };
125
126
127 /* End of central directory record *********************************************
128
129    end of central dir signature    4 bytes  (0x06054b50)
130    number of this disk             2 bytes
131    number of the disk with the
132    start of the central directory  2 bytes
133    total number of entries in the
134    central directory on this disk  2 bytes
135    total number of entries in
136    the central directory           2 bytes
137    size of the central directory   4 bytes
138    offset of start of central
139    directory with respect to
140    the starting disk number        4 bytes
141    .ZIP file comment length        2 bytes
142    .ZIP file comment       (variable size)
143
144 *******************************************************************************/
145
146 #define EOCDR_SIGNATURE              0x06054b50
147 #define EOCDR_ENTRIES                10
148 #define EOCDR_OFFSET                 16
149
150 typedef struct eocdr eocdr;
151
152 struct eocdr {
153         u2 entries;
154         u4 offset;
155 };
156
157
158 /* zip_open ********************************************************************
159
160    XXX
161
162 *******************************************************************************/
163
164 hashtable *zip_open(char *path)
165 {
166         hashtable               *ht;
167         hashtable_zipfile_entry *htzfe;
168         int                      fd;
169         u1                       lfh_signature[SIGNATURE_LENGTH];
170         off_t                    len;
171         u1                      *filep;
172         s4                       i;
173         u1                      *p;
174         eocdr                    eocdr;
175         cdsfh                    cdsfh;
176         const char              *filename;
177         const char              *classext;
178         utf                     *u;
179         u4                       key;       /* hashkey computed from utf-text     */
180         u4                       slot;      /* slot in hashtable                  */
181
182         /* first of all, open the file */
183
184         if ((fd = open(path, O_RDONLY)) == -1)
185                 return NULL;
186
187         /* check for signature in first local file header */
188
189         if (read(fd, lfh_signature, SIGNATURE_LENGTH) != SIGNATURE_LENGTH)
190                 return NULL;
191
192         if (SUCK_LE_U4(lfh_signature) != LFH_SIGNATURE)
193                 return NULL;
194
195         /* get the file length */
196
197         if ((len = lseek(fd, 0, SEEK_END)) == -1)
198                 return NULL;
199
200         /* we better mmap the file */
201
202         filep = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0);
203
204         /* some older compilers, like DEC OSF cc, don't like comparisons
205        on void* types */
206
207         if ((ptrint) filep == (ptrint) MAP_FAILED)
208                 return NULL;
209
210         /* find end of central directory record */
211
212         for (p = filep + len; p >= filep; p--)
213                 if (SUCK_LE_U4(p) == EOCDR_SIGNATURE)
214                         break;
215
216         /* get number of entries in central directory */
217
218         eocdr.entries = SUCK_LE_U2(p + EOCDR_ENTRIES);
219         eocdr.offset  = SUCK_LE_U4(p + EOCDR_OFFSET);
220
221         /* create hashtable for filenames */
222
223         ht = NEW(hashtable);
224
225         hashtable_create(ht, HASHTABLE_CLASSES_SIZE);
226
227         /* add all file entries into the hashtable */
228
229         for (i = 0, p = filep + eocdr.offset; i < eocdr.entries; i++) {
230                 /* check file header signature */
231
232                 if (SUCK_LE_U4(p) != CDSFH_SIGNATURE)
233                         return NULL;
234
235                 /* we found an entry */
236
237                 cdsfh.compressionmethod = SUCK_LE_U2(p + CDSFH_COMPRESSION_METHOD);
238                 cdsfh.compressedsize    = SUCK_LE_U4(p + CDSFH_COMPRESSED_SIZE);
239                 cdsfh.uncompressedsize  = SUCK_LE_U4(p + CDSFH_UNCOMPRESSED_SIZE);
240                 cdsfh.filenamelength    = SUCK_LE_U2(p + CDSFH_FILE_NAME_LENGTH);
241                 cdsfh.extrafieldlength  = SUCK_LE_U2(p + CDSFH_EXTRA_FIELD_LENGTH);
242                 cdsfh.filecommentlength = SUCK_LE_U2(p + CDSFH_FILE_COMMENT_LENGTH);
243                 cdsfh.relativeoffset    = SUCK_LE_U4(p + CDSFH_RELATIVE_OFFSET);
244
245                 /* create utf8 string of filename, strip .class from classes */
246
247                 filename = (const char *) (p + CDSFH_FILENAME);
248                 classext = filename + cdsfh.filenamelength - strlen(".class");
249
250                 /* skip directory entries */
251
252                 if (filename[cdsfh.filenamelength - 1] != '/') {
253                         if (strncmp(classext, ".class", strlen(".class")) == 0)
254                                 u = utf_new(filename, cdsfh.filenamelength - strlen(".class"));
255                         else
256                                 u = utf_new(filename, cdsfh.filenamelength);
257
258                         /* insert class into hashtable */
259
260                         htzfe = NEW(hashtable_zipfile_entry);
261
262                         htzfe->filename          = u;
263                         htzfe->compressionmethod = cdsfh.compressionmethod;
264                         htzfe->compressedsize    = cdsfh.compressedsize;
265                         htzfe->uncompressedsize  = cdsfh.uncompressedsize;
266                         htzfe->data              = filep + cdsfh.relativeoffset;
267
268                         /* get hashtable slot */
269
270                         key  = utf_hashkey(u->text, u->blength);
271                         slot = key & (ht->size - 1);
272
273                         /* insert into external chain */
274
275                         htzfe->hashlink = ht->ptr[slot];
276
277                         /* insert hashtable zipfile entry */
278
279                         ht->ptr[slot] = htzfe;
280                         ht->entries++;
281                 }
282
283                 /* move to next central directory structure file header */
284
285                 p = p +
286                         CDSFH_HEADER_SIZE +
287                         cdsfh.filenamelength +
288                         cdsfh.extrafieldlength +
289                         cdsfh.filecommentlength;
290         }
291
292         /* return pointer to hashtable */
293
294         return ht;
295 }
296
297
298 /* zip_find ********************************************************************
299
300    Search for the given filename in the classpath entries of a zip file.
301
302    NOTE: The '.class' extension is stripped when reading a zip file, so if
303    you want to find a .class file, you must search for its name _without_
304    the '.class' extension. 
305    XXX I dont like that, it makes foo and foo.class ambiguous. -Edwin
306
307    IN:
308       lce..........the classpath entries for the zip file
309           u............the filename to look for
310
311    RETURN VALUE:
312       hashtable_zipfile_entry * of the entry if found, or
313           NULL if not found
314
315 *******************************************************************************/
316
317 hashtable_zipfile_entry *zip_find(list_classpath_entry *lce, utf *u)
318 {
319         hashtable               *ht;
320         u4                       key;       /* hashkey computed from utf-text     */
321         u4                       slot;      /* slot in hashtable                  */
322         hashtable_zipfile_entry *htzfe;     /* hashtable element                  */
323
324         /* get classes hashtable from the classpath entry */
325
326         ht = lce->htclasses;
327
328         /* get the hashtable slot of the name searched */
329
330         key   = utf_hashkey(u->text, u->blength);
331         slot  = key & (ht->size - 1);
332         htzfe = ht->ptr[slot];
333
334         /* search external hash chain for utf-symbol */
335
336         while (htzfe) {
337                 if (htzfe->filename == u)
338                         return htzfe;
339
340                 /* next element in external chain */
341
342                 htzfe = htzfe->hashlink;
343         }
344
345         /* file not found in this archive */
346
347         return NULL;
348 }
349
350
351 /* zip_get ********************************************************************
352
353    XXX
354
355 *******************************************************************************/
356
357 classbuffer *zip_get(list_classpath_entry *lce, classinfo *c)
358 {
359         hashtable_zipfile_entry *htzfe;
360         lfh                      lfh;
361         u1                      *indata;
362         u1                      *outdata;
363         z_stream                 zs;
364         int                      err;
365         classbuffer             *cb;
366
367         /* try to find the class in the current archive */
368
369         htzfe = zip_find(lce, c->name);
370
371         if (htzfe == NULL)
372                 return NULL;
373
374         /* read stuff from local file header */
375
376         lfh.filenamelength   = SUCK_LE_U2(htzfe->data + LFH_FILE_NAME_LENGTH);
377         lfh.extrafieldlength = SUCK_LE_U2(htzfe->data + LFH_EXTRA_FIELD_LENGTH);
378
379         indata = htzfe->data +
380                 LFH_HEADER_SIZE +
381                 lfh.filenamelength +
382                 lfh.extrafieldlength;
383
384         /* allocate buffer for uncompressed data */
385
386         outdata = MNEW(u1, htzfe->uncompressedsize);
387
388         /* how is the file stored? */
389
390         switch (htzfe->compressionmethod) {
391         case Z_DEFLATED:
392                 /* fill z_stream structure */
393
394                 zs.next_in   = indata;
395                 zs.avail_in  = htzfe->compressedsize;
396                 zs.next_out  = outdata;
397                 zs.avail_out = htzfe->uncompressedsize;
398
399                 zs.zalloc = Z_NULL;
400                 zs.zfree  = Z_NULL;
401                 zs.opaque = Z_NULL;
402
403                 /* initialize this inflate run */
404
405                 if (inflateInit2(&zs, -MAX_WBITS) != Z_OK)
406                         vm_abort("zip_get: inflateInit2 failed: %s", strerror(errno));
407
408                 /* decompress the file into buffer */
409
410                 err = inflate(&zs, Z_SYNC_FLUSH);
411
412                 if ((err != Z_STREAM_END) && (err != Z_OK))
413                         vm_abort("zip_get: inflate failed: %s", strerror(errno));
414
415                 /* finish this inflate run */
416
417                 if (inflateEnd(&zs) != Z_OK)
418                         vm_abort("zip_get: inflateEnd failed: %s", strerror(errno));
419                 break;
420
421         case 0:
422                 /* uncompressed file, just copy the data */
423                 MCOPY(outdata, indata, u1, htzfe->compressedsize);
424                 break;
425
426         default:
427                 vm_abort("zip_get: unknown compression method %d",
428                                  htzfe->compressionmethod);
429         }
430         
431         /* allocate classbuffer */
432
433         cb = NEW(classbuffer);
434
435         cb->clazz = c;
436         cb->size  = htzfe->uncompressedsize;
437         cb->data  = outdata;
438         cb->pos   = outdata;
439         cb->path  = lce->path;
440
441         /* return the filled classbuffer structure */
442
443         return cb;
444 }
445
446
447 /*
448  * These are local overrides for various environment variables in Emacs.
449  * Please do not remove this and leave it at the end of the file, where
450  * Emacs will automagically detect them.
451  * ---------------------------------------------------------------------
452  * Local variables:
453  * mode: c
454  * indent-tabs-mode: t
455  * c-basic-offset: 4
456  * tab-width: 4
457  * End:
458  * vim:noexpandtab:sw=4:ts=4:
459  */