blob: 28dc512bb65823c6fc01df34066c8c82af228735 [file] [log] [blame]
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//
18// Read-only access to Zip archives, with minimal heap allocation.
19//
20#define LOG_TAG "zipro"
21//#define LOG_NDEBUG 0
Mathias Agopianf446ba92009-06-04 13:53:57 -070022#include <utils/ZipFileRO.h>
23#include <utils/Log.h>
24#include <utils/misc.h>
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080025
26#include <zlib.h>
27
28#include <string.h>
29#include <fcntl.h>
30#include <errno.h>
31#include <assert.h>
Kenny Rootd4066a42010-04-22 18:28:29 -070032#include <unistd.h>
33
34/*
35 * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
36 * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
37 * not already defined, then define it here.
38 */
39#ifndef TEMP_FAILURE_RETRY
40/* Used to retry syscalls that can return EINTR. */
41#define TEMP_FAILURE_RETRY(exp) ({ \
42 typeof (exp) _rc; \
43 do { \
44 _rc = (exp); \
45 } while (_rc == -1 && errno == EINTR); \
46 _rc; })
47#endif
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080048
49using namespace android;
50
51/*
52 * Zip file constants.
53 */
54#define kEOCDSignature 0x06054b50
55#define kEOCDLen 22
56#define kEOCDNumEntries 8 // offset to #of entries in file
Kenny Rootd4066a42010-04-22 18:28:29 -070057#define kEOCDSize 12 // size of the central directory
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080058#define kEOCDFileOffset 16 // offset to central directory
59
60#define kMaxCommentLen 65535 // longest possible in ushort
61#define kMaxEOCDSearch (kMaxCommentLen + kEOCDLen)
62
63#define kLFHSignature 0x04034b50
64#define kLFHLen 30 // excluding variable-len fields
65#define kLFHNameLen 26 // offset to filename length
66#define kLFHExtraLen 28 // offset to extra length
67
68#define kCDESignature 0x02014b50
69#define kCDELen 46 // excluding variable-len fields
70#define kCDEMethod 10 // offset to compression method
71#define kCDEModWhen 12 // offset to modification timestamp
72#define kCDECRC 16 // offset to entry CRC
73#define kCDECompLen 20 // offset to compressed length
74#define kCDEUncompLen 24 // offset to uncompressed length
75#define kCDENameLen 28 // offset to filename length
76#define kCDEExtraLen 30 // offset to extra length
77#define kCDECommentLen 32 // offset to comment length
78#define kCDELocalOffset 42 // offset to local hdr
79
80/*
81 * The values we return for ZipEntryRO use 0 as an invalid value, so we
82 * want to adjust the hash table index by a fixed amount. Using a large
83 * value helps insure that people don't mix & match arguments, e.g. to
84 * findEntryByIndex().
85 */
86#define kZipEntryAdj 10000
87
88/*
89 * Convert a ZipEntryRO to a hash table index, verifying that it's in a
90 * valid range.
91 */
92int ZipFileRO::entryToIndex(const ZipEntryRO entry) const
93{
94 long ent = ((long) entry) - kZipEntryAdj;
95 if (ent < 0 || ent >= mHashTableSize || mHashTable[ent].name == NULL) {
96 LOGW("Invalid ZipEntryRO %p (%ld)\n", entry, ent);
97 return -1;
98 }
99 return ent;
100}
101
102
103/*
104 * Open the specified file read-only. We memory-map the entire thing and
105 * close the file before returning.
106 */
107status_t ZipFileRO::open(const char* zipFileName)
108{
109 int fd = -1;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800110
Kenny Rootd4066a42010-04-22 18:28:29 -0700111 assert(mDirectoryMap == NULL);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800112
113 /*
114 * Open and map the specified file.
115 */
116 fd = ::open(zipFileName, O_RDONLY);
117 if (fd < 0) {
118 LOGW("Unable to open zip '%s': %s\n", zipFileName, strerror(errno));
119 return NAME_NOT_FOUND;
120 }
121
Kenny Rootd4066a42010-04-22 18:28:29 -0700122 mFileLength = lseek(fd, 0, SEEK_END);
123 if (mFileLength < kEOCDLen) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800124 close(fd);
125 return UNKNOWN_ERROR;
126 }
127
Kenny Rootd4066a42010-04-22 18:28:29 -0700128 if (mFileName != NULL) {
129 free(mFileName);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800130 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700131 mFileName = strdup(zipFileName);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800132
133 mFd = fd;
134
135 /*
Kenny Rootd4066a42010-04-22 18:28:29 -0700136 * Find the Central Directory and store its size and number of entries.
137 */
138 if (!mapCentralDirectory()) {
139 goto bail;
140 }
141
142 /*
143 * Verify Central Directory and create data structures for fast access.
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800144 */
145 if (!parseZipArchive()) {
Kenny Rootd4066a42010-04-22 18:28:29 -0700146 goto bail;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800147 }
148
149 return OK;
Kenny Rootd4066a42010-04-22 18:28:29 -0700150
151bail:
152 free(mFileName);
153 mFileName = NULL;
154 close(fd);
155 return UNKNOWN_ERROR;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800156}
157
158/*
159 * Parse the Zip archive, verifying its contents and initializing internal
160 * data structures.
161 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700162bool ZipFileRO::mapCentralDirectory(void)
163{
164 size_t readAmount = kMaxEOCDSearch;
165 if (readAmount > (size_t) mFileLength)
166 readAmount = mFileLength;
167
168 unsigned char* scanBuf = (unsigned char*) malloc(readAmount);
169 if (scanBuf == NULL) {
170 LOGW("couldn't allocate scanBuf: %s", strerror(errno));
171 free(scanBuf);
172 return false;
173 }
174
175 /*
176 * Make sure this is a Zip archive.
177 */
178 if (lseek(mFd, 0, SEEK_SET) != 0) {
179 LOGW("seek to start failed: %s", strerror(errno));
180 free(scanBuf);
181 return false;
182 }
183
184 ssize_t actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, sizeof(int32_t)));
185 if (actual != (ssize_t) sizeof(int32_t)) {
186 LOGI("couldn't read first signature from zip archive: %s", strerror(errno));
187 free(scanBuf);
188 return false;
189 }
190
191 {
192 unsigned int header = get4LE(scanBuf);
193 if (header == kEOCDSignature) {
194 LOGI("Found Zip archive, but it looks empty\n");
195 free(scanBuf);
196 return false;
197 } else if (header != kLFHSignature) {
198 LOGV("Not a Zip archive (found 0x%08x)\n", val);
199 free(scanBuf);
200 return false;
201 }
202 }
203
204 /*
205 * Perform the traditional EOCD snipe hunt.
206 *
207 * We're searching for the End of Central Directory magic number,
208 * which appears at the start of the EOCD block. It's followed by
209 * 18 bytes of EOCD stuff and up to 64KB of archive comment. We
210 * need to read the last part of the file into a buffer, dig through
211 * it to find the magic number, parse some values out, and use those
212 * to determine the extent of the CD.
213 *
214 * We start by pulling in the last part of the file.
215 */
216 off_t searchStart = mFileLength - readAmount;
217
218 if (lseek(mFd, searchStart, SEEK_SET) != searchStart) {
219 LOGW("seek %ld failed: %s\n", (long) searchStart, strerror(errno));
220 free(scanBuf);
221 return false;
222 }
223 actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, readAmount));
224 if (actual != (ssize_t) readAmount) {
225 LOGW("Zip: read %zd failed: %s\n", readAmount, strerror(errno));
226 free(scanBuf);
227 return false;
228 }
229
230 /*
231 * Scan backward for the EOCD magic. In an archive without a trailing
232 * comment, we'll find it on the first try. (We may want to consider
233 * doing an initial minimal read; if we don't find it, retry with a
234 * second read as above.)
235 */
236 int i;
237 for (i = readAmount - kEOCDLen; i >= 0; i--) {
238 if (scanBuf[i] == 0x50 && get4LE(&scanBuf[i]) == kEOCDSignature) {
239 LOGV("+++ Found EOCD at buf+%d\n", i);
240 break;
241 }
242 }
243 if (i < 0) {
244 LOGD("Zip: EOCD not found, %s is not zip\n", mFileName);
245 free(scanBuf);
246 return false;
247 }
248
249 off_t eocdOffset = searchStart + i;
250 const unsigned char* eocdPtr = scanBuf + i;
251
252 assert(eocdOffset < mFileLength);
253
254 /*
255 * Grab the CD offset and size, and the number of entries in the
256 * archive. Verify that they look reasonable.
257 */
258 unsigned int numEntries = get2LE(eocdPtr + kEOCDNumEntries);
259 unsigned int dirSize = get4LE(eocdPtr + kEOCDSize);
260 unsigned int dirOffset = get4LE(eocdPtr + kEOCDFileOffset);
261
262 if ((long long) dirOffset + (long long) dirSize > (long long) eocdOffset) {
263 LOGW("bad offsets (dir %ld, size %u, eocd %ld)\n",
264 (long) dirOffset, dirSize, (long) eocdOffset);
265 free(scanBuf);
266 return false;
267 }
268 if (numEntries == 0) {
269 LOGW("empty archive?\n");
270 free(scanBuf);
271 return false;
272 }
273
274 LOGV("+++ numEntries=%d dirSize=%d dirOffset=%d\n",
275 numEntries, dirSize, dirOffset);
276
277 mDirectoryMap = new FileMap();
278 if (mDirectoryMap == NULL) {
279 LOGW("Unable to create directory map: %s", strerror(errno));
280 free(scanBuf);
281 return false;
282 }
283
284 if (!mDirectoryMap->create(mFileName, mFd, dirOffset, dirSize, true)) {
285 LOGW("Unable to map '%s' (%zd to %zd): %s\n", mFileName,
286 dirOffset, dirOffset + dirSize, strerror(errno));
287 free(scanBuf);
288 return false;
289 }
290
291 mNumEntries = numEntries;
292 mDirectoryOffset = dirOffset;
293
294 return true;
295}
296
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800297bool ZipFileRO::parseZipArchive(void)
298{
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800299 bool result = false;
Kenny Rootd4066a42010-04-22 18:28:29 -0700300 const unsigned char* cdPtr = (const unsigned char*) mDirectoryMap->getDataPtr();
301 size_t cdLength = mDirectoryMap->getDataLength();
302 int numEntries = mNumEntries;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800303
304 /*
305 * Create hash table. We have a minimum 75% load factor, possibly as
306 * low as 50% after we round off to a power of 2.
307 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700308 mHashTableSize = roundUpPower2(1 + (numEntries * 4) / 3);
309 mHashTable = (HashEntry*) calloc(mHashTableSize, sizeof(HashEntry));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800310
311 /*
312 * Walk through the central directory, adding entries to the hash
313 * table.
314 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700315 const unsigned char* ptr = cdPtr;
316 for (int i = 0; i < numEntries; i++) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800317 if (get4LE(ptr) != kCDESignature) {
318 LOGW("Missed a central dir sig (at %d)\n", i);
319 goto bail;
320 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700321 if (ptr + kCDELen > cdPtr + cdLength) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800322 LOGW("Ran off the end (at %d)\n", i);
323 goto bail;
324 }
325
Kenny Rootd4066a42010-04-22 18:28:29 -0700326 long localHdrOffset = (long) get4LE(ptr + kCDELocalOffset);
327 if (localHdrOffset >= mDirectoryOffset) {
328 LOGW("bad LFH offset %ld at entry %d\n", localHdrOffset, i);
329 goto bail;
330 }
331
332 unsigned int fileNameLen, extraLen, commentLen, hash;
333
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800334 fileNameLen = get2LE(ptr + kCDENameLen);
335 extraLen = get2LE(ptr + kCDEExtraLen);
336 commentLen = get2LE(ptr + kCDECommentLen);
337
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800338 /* add the CDE filename to the hash table */
339 hash = computeHash((const char*)ptr + kCDELen, fileNameLen);
340 addToHash((const char*)ptr + kCDELen, fileNameLen, hash);
341
Kenny Rootd4066a42010-04-22 18:28:29 -0700342 ptr += kCDELen + fileNameLen + extraLen + commentLen;
343 if ((size_t)(ptr - cdPtr) > cdLength) {
344 LOGW("bad CD advance (%d vs %zd) at entry %d\n",
345 (int) (ptr - cdPtr), cdLength, i);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800346 goto bail;
347 }
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800348 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700349 LOGV("+++ zip good scan %d entries\n", numEntries);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800350 result = true;
351
352bail:
353 return result;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800354}
355
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800356/*
357 * Simple string hash function for non-null-terminated strings.
358 */
359/*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len)
360{
361 unsigned int hash = 0;
362
363 while (len--)
364 hash = hash * 31 + *str++;
365
366 return hash;
367}
368
369/*
370 * Add a new entry to the hash table.
371 */
372void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash)
373{
374 int ent = hash & (mHashTableSize-1);
375
376 /*
377 * We over-allocate the table, so we're guaranteed to find an empty slot.
378 */
379 while (mHashTable[ent].name != NULL)
380 ent = (ent + 1) & (mHashTableSize-1);
381
382 mHashTable[ent].name = str;
383 mHashTable[ent].nameLen = strLen;
384}
385
386/*
387 * Find a matching entry.
388 *
389 * Returns 0 if not found.
390 */
391ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const
392{
393 int nameLen = strlen(fileName);
394 unsigned int hash = computeHash(fileName, nameLen);
395 int ent = hash & (mHashTableSize-1);
396
397 while (mHashTable[ent].name != NULL) {
398 if (mHashTable[ent].nameLen == nameLen &&
399 memcmp(mHashTable[ent].name, fileName, nameLen) == 0)
400 {
401 /* match */
Kenny Rootd4066a42010-04-22 18:28:29 -0700402 return (ZipEntryRO)(long)(ent + kZipEntryAdj);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800403 }
404
405 ent = (ent + 1) & (mHashTableSize-1);
406 }
407
408 return NULL;
409}
410
411/*
412 * Find the Nth entry.
413 *
414 * This currently involves walking through the sparse hash table, counting
415 * non-empty entries. If we need to speed this up we can either allocate
416 * a parallel lookup table or (perhaps better) provide an iterator interface.
417 */
418ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const
419{
420 if (idx < 0 || idx >= mNumEntries) {
421 LOGW("Invalid index %d\n", idx);
422 return NULL;
423 }
424
425 for (int ent = 0; ent < mHashTableSize; ent++) {
426 if (mHashTable[ent].name != NULL) {
427 if (idx-- == 0)
428 return (ZipEntryRO) (ent + kZipEntryAdj);
429 }
430 }
431
432 return NULL;
433}
434
435/*
436 * Get the useful fields from the zip entry.
437 *
438 * Returns "false" if the offsets to the fields or the contents of the fields
439 * appear to be bogus.
440 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700441bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
442 size_t* pCompLen, off_t* pOffset, long* pModWhen, long* pCrc32) const
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800443{
Kenny Rootd4066a42010-04-22 18:28:29 -0700444 bool ret = false;
445
446 const int ent = entryToIndex(entry);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800447 if (ent < 0)
448 return false;
449
Kenny Rootd4066a42010-04-22 18:28:29 -0700450 HashEntry hashEntry = mHashTable[ent];
451
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800452 /*
453 * Recover the start of the central directory entry from the filename
Kenny Rootd4066a42010-04-22 18:28:29 -0700454 * pointer. The filename is the first entry past the fixed-size data,
455 * so we can just subtract back from that.
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800456 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700457 const unsigned char* ptr = (const unsigned char*) hashEntry.name;
458 off_t cdOffset = mDirectoryOffset;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800459
460 ptr -= kCDELen;
461
462 int method = get2LE(ptr + kCDEMethod);
463 if (pMethod != NULL)
464 *pMethod = method;
465
466 if (pModWhen != NULL)
467 *pModWhen = get4LE(ptr + kCDEModWhen);
468 if (pCrc32 != NULL)
469 *pCrc32 = get4LE(ptr + kCDECRC);
470
Kenny Rootd4066a42010-04-22 18:28:29 -0700471 size_t compLen = get4LE(ptr + kCDECompLen);
472 if (pCompLen != NULL)
473 *pCompLen = compLen;
474 size_t uncompLen = get4LE(ptr + kCDEUncompLen);
475 if (pUncompLen != NULL)
476 *pUncompLen = uncompLen;
477
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800478 /*
Kenny Rootd4066a42010-04-22 18:28:29 -0700479 * If requested, determine the offset of the start of the data. All we
480 * have is the offset to the Local File Header, which is variable size,
481 * so we have to read the contents of the struct to figure out where
482 * the actual data starts.
483 *
484 * We also need to make sure that the lengths are not so large that
485 * somebody trying to map the compressed or uncompressed data runs
486 * off the end of the mapped region.
487 *
488 * Note we don't verify compLen/uncompLen if they don't request the
489 * dataOffset, because dataOffset is expensive to determine. However,
490 * if they don't have the file offset, they're not likely to be doing
491 * anything with the contents.
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800492 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800493 if (pOffset != NULL) {
Kenny Rootd4066a42010-04-22 18:28:29 -0700494 long localHdrOffset = get4LE(ptr + kCDELocalOffset);
495 if (localHdrOffset + kLFHLen >= cdOffset) {
496 LOGE("ERROR: bad local hdr offset in zip\n");
497 return false;
498 }
499
500 unsigned char lfhBuf[kLFHLen];
501 if (lseek(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
502 LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
503 return false;
504 }
505 ssize_t actual =
506 TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
507 if (actual != sizeof(lfhBuf)) {
508 LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
509 return false;
510 }
511
512 if (get4LE(lfhBuf) != kLFHSignature) {
513 LOGW("didn't find signature at start of lfh, offset=%ld\n",
514 localHdrOffset);
515 return false;
516 }
517
518 off_t dataOffset = localHdrOffset + kLFHLen
519 + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
520 if (dataOffset >= cdOffset) {
521 LOGW("bad data offset %ld in zip\n", (long) dataOffset);
522 return false;
523 }
524
525 /* check lengths */
526 if ((off_t)(dataOffset + compLen) > cdOffset) {
527 LOGW("bad compressed length in zip (%ld + %zd > %ld)\n",
528 (long) dataOffset, compLen, (long) cdOffset);
529 return false;
530 }
531
532 if (method == kCompressStored &&
533 (off_t)(dataOffset + uncompLen) > cdOffset)
534 {
535 LOGE("ERROR: bad uncompressed length in zip (%ld + %zd > %ld)\n",
536 (long) dataOffset, uncompLen, (long) cdOffset);
537 return false;
538 }
539
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800540 *pOffset = dataOffset;
541 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700542
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800543 return true;
544}
545
546/*
547 * Copy the entry's filename to the buffer.
548 */
549int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen)
550 const
551{
552 int ent = entryToIndex(entry);
553 if (ent < 0)
554 return -1;
555
556 int nameLen = mHashTable[ent].nameLen;
557 if (bufLen < nameLen+1)
558 return nameLen+1;
559
560 memcpy(buffer, mHashTable[ent].name, nameLen);
561 buffer[nameLen] = '\0';
562 return 0;
563}
564
565/*
566 * Create a new FileMap object that spans the data in "entry".
567 */
568FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const
569{
570 /*
571 * TODO: the efficient way to do this is to modify FileMap to allow
572 * sub-regions of a file to be mapped. A reference-counting scheme
573 * can manage the base memory mapping. For now, we just create a brand
574 * new mapping off of the Zip archive file descriptor.
575 */
576
577 FileMap* newMap;
Kenny Rootd4066a42010-04-22 18:28:29 -0700578 size_t compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800579 off_t offset;
580
581 if (!getEntryInfo(entry, NULL, NULL, &compLen, &offset, NULL, NULL))
582 return NULL;
583
584 newMap = new FileMap();
Kenny Rootd4066a42010-04-22 18:28:29 -0700585 if (!newMap->create(mFileName, mFd, offset, compLen, true)) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800586 newMap->release();
587 return NULL;
588 }
589
590 return newMap;
591}
592
593/*
594 * Uncompress an entry, in its entirety, into the provided output buffer.
595 *
596 * This doesn't verify the data's CRC, which might be useful for
597 * uncompressed data. The caller should be able to manage it.
598 */
599bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const
600{
Kenny Rootd4066a42010-04-22 18:28:29 -0700601 const size_t kSequentialMin = 32768;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800602 bool result = false;
603 int ent = entryToIndex(entry);
604 if (ent < 0)
605 return -1;
606
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800607 int method;
Kenny Rootd4066a42010-04-22 18:28:29 -0700608 size_t uncompLen, compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800609 off_t offset;
Kenny Rootd4066a42010-04-22 18:28:29 -0700610 const unsigned char* ptr;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800611
612 getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
613
Kenny Rootd4066a42010-04-22 18:28:29 -0700614 FileMap* file = createEntryFileMap(entry);
615 if (file == NULL) {
616 goto bail;
617 }
618
619 ptr = (const unsigned char*) file->getDataPtr();
620
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800621 /*
622 * Experiment with madvise hint. When we want to uncompress a file,
623 * we pull some stuff out of the central dir entry and then hit a
624 * bunch of compressed or uncompressed data sequentially. The CDE
625 * visit will cause a limited amount of read-ahead because it's at
626 * the end of the file. We could end up doing lots of extra disk
627 * access if the file we're prying open is small. Bottom line is we
628 * probably don't want to turn MADV_SEQUENTIAL on and leave it on.
629 *
630 * So, if the compressed size of the file is above a certain minimum
631 * size, temporarily boost the read-ahead in the hope that the extra
632 * pair of system calls are negated by a reduction in page faults.
633 */
634 if (compLen > kSequentialMin)
Kenny Rootd4066a42010-04-22 18:28:29 -0700635 file->advise(FileMap::SEQUENTIAL);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800636
637 if (method == kCompressStored) {
Kenny Rootd4066a42010-04-22 18:28:29 -0700638 memcpy(buffer, ptr, uncompLen);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800639 } else {
Kenny Rootd4066a42010-04-22 18:28:29 -0700640 if (!inflateBuffer(buffer, ptr, uncompLen, compLen))
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800641 goto bail;
642 }
643
644 if (compLen > kSequentialMin)
Kenny Rootd4066a42010-04-22 18:28:29 -0700645 file->advise(FileMap::NORMAL);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800646
647 result = true;
648
649bail:
650 return result;
651}
652
653/*
654 * Uncompress an entry, in its entirety, to an open file descriptor.
655 *
656 * This doesn't verify the data's CRC, but probably should.
657 */
658bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
659{
660 bool result = false;
661 int ent = entryToIndex(entry);
662 if (ent < 0)
663 return -1;
664
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800665 int method;
Kenny Rootd4066a42010-04-22 18:28:29 -0700666 size_t uncompLen, compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800667 off_t offset;
Kenny Rootd4066a42010-04-22 18:28:29 -0700668 const unsigned char* ptr;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800669
670 getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
671
Kenny Rootd4066a42010-04-22 18:28:29 -0700672 const FileMap* file = createEntryFileMap(entry);
673 if (file == NULL) {
674 goto bail;
675 }
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800676
Kenny Rootd4066a42010-04-22 18:28:29 -0700677 ptr = (const unsigned char*) file->getDataPtr();
678
679 if (method == kCompressStored) {
680 ssize_t actual = write(fd, ptr, uncompLen);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800681 if (actual < 0) {
682 LOGE("Write failed: %s\n", strerror(errno));
683 goto bail;
Kenny Rootd4066a42010-04-22 18:28:29 -0700684 } else if ((size_t) actual != uncompLen) {
685 LOGE("Partial write during uncompress (%zd of %zd)\n",
686 actual, uncompLen);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800687 goto bail;
688 } else {
689 LOGI("+++ successful write\n");
690 }
691 } else {
Kenny Rootd4066a42010-04-22 18:28:29 -0700692 if (!inflateBuffer(fd, ptr, uncompLen, compLen))
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800693 goto bail;
694 }
695
696 result = true;
697
698bail:
699 return result;
700}
701
702/*
703 * Uncompress "deflate" data from one buffer to another.
704 */
705/*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf,
Kenny Rootd4066a42010-04-22 18:28:29 -0700706 size_t uncompLen, size_t compLen)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800707{
708 bool result = false;
709 z_stream zstream;
710 int zerr;
711
712 /*
713 * Initialize the zlib stream struct.
714 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700715 memset(&zstream, 0, sizeof(zstream));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800716 zstream.zalloc = Z_NULL;
717 zstream.zfree = Z_NULL;
718 zstream.opaque = Z_NULL;
719 zstream.next_in = (Bytef*)inBuf;
720 zstream.avail_in = compLen;
721 zstream.next_out = (Bytef*) outBuf;
722 zstream.avail_out = uncompLen;
723 zstream.data_type = Z_UNKNOWN;
724
Kenny Rootd4066a42010-04-22 18:28:29 -0700725 /*
726 * Use the undocumented "negative window bits" feature to tell zlib
727 * that there's no zlib header waiting for it.
728 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800729 zerr = inflateInit2(&zstream, -MAX_WBITS);
730 if (zerr != Z_OK) {
731 if (zerr == Z_VERSION_ERROR) {
732 LOGE("Installed zlib is not compatible with linked version (%s)\n",
733 ZLIB_VERSION);
734 } else {
735 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
736 }
737 goto bail;
738 }
739
740 /*
741 * Expand data.
742 */
743 zerr = inflate(&zstream, Z_FINISH);
744 if (zerr != Z_STREAM_END) {
745 LOGW("Zip inflate failed, zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
746 zerr, zstream.next_in, zstream.avail_in,
747 zstream.next_out, zstream.avail_out);
748 goto z_bail;
749 }
750
751 /* paranoia */
Kenny Rootd4066a42010-04-22 18:28:29 -0700752 if (zstream.total_out != uncompLen) {
753 LOGW("Size mismatch on inflated file (%ld vs %zd)\n",
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800754 zstream.total_out, uncompLen);
755 goto z_bail;
756 }
757
758 result = true;
759
760z_bail:
761 inflateEnd(&zstream); /* free up any allocated structures */
762
763bail:
764 return result;
765}
766
767/*
768 * Uncompress "deflate" data from one buffer to an open file descriptor.
769 */
770/*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf,
Kenny Rootd4066a42010-04-22 18:28:29 -0700771 size_t uncompLen, size_t compLen)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800772{
773 bool result = false;
Kenny Rootd4066a42010-04-22 18:28:29 -0700774 const size_t kWriteBufSize = 32768;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800775 unsigned char writeBuf[kWriteBufSize];
776 z_stream zstream;
777 int zerr;
778
779 /*
780 * Initialize the zlib stream struct.
781 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700782 memset(&zstream, 0, sizeof(zstream));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800783 zstream.zalloc = Z_NULL;
784 zstream.zfree = Z_NULL;
785 zstream.opaque = Z_NULL;
786 zstream.next_in = (Bytef*)inBuf;
787 zstream.avail_in = compLen;
788 zstream.next_out = (Bytef*) writeBuf;
789 zstream.avail_out = sizeof(writeBuf);
790 zstream.data_type = Z_UNKNOWN;
791
Kenny Rootd4066a42010-04-22 18:28:29 -0700792 /*
793 * Use the undocumented "negative window bits" feature to tell zlib
794 * that there's no zlib header waiting for it.
795 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800796 zerr = inflateInit2(&zstream, -MAX_WBITS);
797 if (zerr != Z_OK) {
798 if (zerr == Z_VERSION_ERROR) {
799 LOGE("Installed zlib is not compatible with linked version (%s)\n",
800 ZLIB_VERSION);
801 } else {
802 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
803 }
804 goto bail;
805 }
806
807 /*
808 * Loop while we have more to do.
809 */
810 do {
811 /*
812 * Expand data.
813 */
814 zerr = inflate(&zstream, Z_NO_FLUSH);
815 if (zerr != Z_OK && zerr != Z_STREAM_END) {
816 LOGW("zlib inflate: zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
817 zerr, zstream.next_in, zstream.avail_in,
818 zstream.next_out, zstream.avail_out);
819 goto z_bail;
820 }
821
822 /* write when we're full or when we're done */
823 if (zstream.avail_out == 0 ||
824 (zerr == Z_STREAM_END && zstream.avail_out != sizeof(writeBuf)))
825 {
826 long writeSize = zstream.next_out - writeBuf;
827 int cc = write(fd, writeBuf, writeSize);
828 if (cc != (int) writeSize) {
829 LOGW("write failed in inflate (%d vs %ld)\n", cc, writeSize);
830 goto z_bail;
831 }
832
833 zstream.next_out = writeBuf;
834 zstream.avail_out = sizeof(writeBuf);
835 }
836 } while (zerr == Z_OK);
837
838 assert(zerr == Z_STREAM_END); /* other errors should've been caught */
839
840 /* paranoia */
Kenny Rootd4066a42010-04-22 18:28:29 -0700841 if (zstream.total_out != uncompLen) {
842 LOGW("Size mismatch on inflated file (%ld vs %zd)\n",
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800843 zstream.total_out, uncompLen);
844 goto z_bail;
845 }
846
847 result = true;
848
849z_bail:
850 inflateEnd(&zstream); /* free up any allocated structures */
851
852bail:
853 return result;
854}