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