blob: a4c3500dcce962f01f9c7716dddec7351ca48716 [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
Kenny Root8f20e5e2010-08-04 16:30:40 -0700256 * archive. After that, we can release our EOCD hunt buffer.
Kenny Rootd4066a42010-04-22 18:28:29 -0700257 */
258 unsigned int numEntries = get2LE(eocdPtr + kEOCDNumEntries);
259 unsigned int dirSize = get4LE(eocdPtr + kEOCDSize);
260 unsigned int dirOffset = get4LE(eocdPtr + kEOCDFileOffset);
Kenny Root8f20e5e2010-08-04 16:30:40 -0700261 free(scanBuf);
Kenny Rootd4066a42010-04-22 18:28:29 -0700262
Kenny Root8f20e5e2010-08-04 16:30:40 -0700263 // Verify that they look reasonable.
Kenny Rootd4066a42010-04-22 18:28:29 -0700264 if ((long long) dirOffset + (long long) dirSize > (long long) eocdOffset) {
265 LOGW("bad offsets (dir %ld, size %u, eocd %ld)\n",
266 (long) dirOffset, dirSize, (long) eocdOffset);
Kenny Rootd4066a42010-04-22 18:28:29 -0700267 return false;
268 }
269 if (numEntries == 0) {
270 LOGW("empty archive?\n");
Kenny Rootd4066a42010-04-22 18:28:29 -0700271 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));
Kenny Rootd4066a42010-04-22 18:28:29 -0700280 return false;
281 }
282
283 if (!mDirectoryMap->create(mFileName, mFd, dirOffset, dirSize, true)) {
284 LOGW("Unable to map '%s' (%zd to %zd): %s\n", mFileName,
285 dirOffset, dirOffset + dirSize, strerror(errno));
Kenny Rootd4066a42010-04-22 18:28:29 -0700286 return false;
287 }
288
289 mNumEntries = numEntries;
290 mDirectoryOffset = dirOffset;
291
292 return true;
293}
294
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800295bool ZipFileRO::parseZipArchive(void)
296{
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800297 bool result = false;
Kenny Rootd4066a42010-04-22 18:28:29 -0700298 const unsigned char* cdPtr = (const unsigned char*) mDirectoryMap->getDataPtr();
299 size_t cdLength = mDirectoryMap->getDataLength();
300 int numEntries = mNumEntries;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800301
302 /*
303 * Create hash table. We have a minimum 75% load factor, possibly as
304 * low as 50% after we round off to a power of 2.
305 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700306 mHashTableSize = roundUpPower2(1 + (numEntries * 4) / 3);
307 mHashTable = (HashEntry*) calloc(mHashTableSize, sizeof(HashEntry));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800308
309 /*
310 * Walk through the central directory, adding entries to the hash
311 * table.
312 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700313 const unsigned char* ptr = cdPtr;
314 for (int i = 0; i < numEntries; i++) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800315 if (get4LE(ptr) != kCDESignature) {
316 LOGW("Missed a central dir sig (at %d)\n", i);
317 goto bail;
318 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700319 if (ptr + kCDELen > cdPtr + cdLength) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800320 LOGW("Ran off the end (at %d)\n", i);
321 goto bail;
322 }
323
Kenny Rootd4066a42010-04-22 18:28:29 -0700324 long localHdrOffset = (long) get4LE(ptr + kCDELocalOffset);
325 if (localHdrOffset >= mDirectoryOffset) {
326 LOGW("bad LFH offset %ld at entry %d\n", localHdrOffset, i);
327 goto bail;
328 }
329
330 unsigned int fileNameLen, extraLen, commentLen, hash;
331
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800332 fileNameLen = get2LE(ptr + kCDENameLen);
333 extraLen = get2LE(ptr + kCDEExtraLen);
334 commentLen = get2LE(ptr + kCDECommentLen);
335
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800336 /* add the CDE filename to the hash table */
337 hash = computeHash((const char*)ptr + kCDELen, fileNameLen);
338 addToHash((const char*)ptr + kCDELen, fileNameLen, hash);
339
Kenny Rootd4066a42010-04-22 18:28:29 -0700340 ptr += kCDELen + fileNameLen + extraLen + commentLen;
341 if ((size_t)(ptr - cdPtr) > cdLength) {
342 LOGW("bad CD advance (%d vs %zd) at entry %d\n",
343 (int) (ptr - cdPtr), cdLength, i);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800344 goto bail;
345 }
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800346 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700347 LOGV("+++ zip good scan %d entries\n", numEntries);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800348 result = true;
349
350bail:
351 return result;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800352}
353
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800354/*
355 * Simple string hash function for non-null-terminated strings.
356 */
357/*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len)
358{
359 unsigned int hash = 0;
360
361 while (len--)
362 hash = hash * 31 + *str++;
363
364 return hash;
365}
366
367/*
368 * Add a new entry to the hash table.
369 */
370void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash)
371{
372 int ent = hash & (mHashTableSize-1);
373
374 /*
375 * We over-allocate the table, so we're guaranteed to find an empty slot.
376 */
377 while (mHashTable[ent].name != NULL)
378 ent = (ent + 1) & (mHashTableSize-1);
379
380 mHashTable[ent].name = str;
381 mHashTable[ent].nameLen = strLen;
382}
383
384/*
385 * Find a matching entry.
386 *
387 * Returns 0 if not found.
388 */
389ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const
390{
391 int nameLen = strlen(fileName);
392 unsigned int hash = computeHash(fileName, nameLen);
393 int ent = hash & (mHashTableSize-1);
394
395 while (mHashTable[ent].name != NULL) {
396 if (mHashTable[ent].nameLen == nameLen &&
397 memcmp(mHashTable[ent].name, fileName, nameLen) == 0)
398 {
399 /* match */
Kenny Rootd4066a42010-04-22 18:28:29 -0700400 return (ZipEntryRO)(long)(ent + kZipEntryAdj);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800401 }
402
403 ent = (ent + 1) & (mHashTableSize-1);
404 }
405
406 return NULL;
407}
408
409/*
410 * Find the Nth entry.
411 *
412 * This currently involves walking through the sparse hash table, counting
413 * non-empty entries. If we need to speed this up we can either allocate
414 * a parallel lookup table or (perhaps better) provide an iterator interface.
415 */
416ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const
417{
418 if (idx < 0 || idx >= mNumEntries) {
419 LOGW("Invalid index %d\n", idx);
420 return NULL;
421 }
422
423 for (int ent = 0; ent < mHashTableSize; ent++) {
424 if (mHashTable[ent].name != NULL) {
425 if (idx-- == 0)
426 return (ZipEntryRO) (ent + kZipEntryAdj);
427 }
428 }
429
430 return NULL;
431}
432
433/*
434 * Get the useful fields from the zip entry.
435 *
436 * Returns "false" if the offsets to the fields or the contents of the fields
437 * appear to be bogus.
438 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700439bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
440 size_t* pCompLen, off_t* pOffset, long* pModWhen, long* pCrc32) const
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800441{
Kenny Rootd4066a42010-04-22 18:28:29 -0700442 bool ret = false;
443
444 const int ent = entryToIndex(entry);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800445 if (ent < 0)
446 return false;
447
Kenny Rootd4066a42010-04-22 18:28:29 -0700448 HashEntry hashEntry = mHashTable[ent];
449
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800450 /*
451 * Recover the start of the central directory entry from the filename
Kenny Rootd4066a42010-04-22 18:28:29 -0700452 * pointer. The filename is the first entry past the fixed-size data,
453 * so we can just subtract back from that.
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800454 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700455 const unsigned char* ptr = (const unsigned char*) hashEntry.name;
456 off_t cdOffset = mDirectoryOffset;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800457
458 ptr -= kCDELen;
459
460 int method = get2LE(ptr + kCDEMethod);
461 if (pMethod != NULL)
462 *pMethod = method;
463
464 if (pModWhen != NULL)
465 *pModWhen = get4LE(ptr + kCDEModWhen);
466 if (pCrc32 != NULL)
467 *pCrc32 = get4LE(ptr + kCDECRC);
468
Kenny Rootd4066a42010-04-22 18:28:29 -0700469 size_t compLen = get4LE(ptr + kCDECompLen);
470 if (pCompLen != NULL)
471 *pCompLen = compLen;
472 size_t uncompLen = get4LE(ptr + kCDEUncompLen);
473 if (pUncompLen != NULL)
474 *pUncompLen = uncompLen;
475
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800476 /*
Kenny Rootd4066a42010-04-22 18:28:29 -0700477 * If requested, determine the offset of the start of the data. All we
478 * have is the offset to the Local File Header, which is variable size,
479 * so we have to read the contents of the struct to figure out where
480 * the actual data starts.
481 *
482 * We also need to make sure that the lengths are not so large that
483 * somebody trying to map the compressed or uncompressed data runs
484 * off the end of the mapped region.
485 *
486 * Note we don't verify compLen/uncompLen if they don't request the
487 * dataOffset, because dataOffset is expensive to determine. However,
488 * if they don't have the file offset, they're not likely to be doing
489 * anything with the contents.
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800490 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800491 if (pOffset != NULL) {
Kenny Rootd4066a42010-04-22 18:28:29 -0700492 long localHdrOffset = get4LE(ptr + kCDELocalOffset);
493 if (localHdrOffset + kLFHLen >= cdOffset) {
494 LOGE("ERROR: bad local hdr offset in zip\n");
495 return false;
496 }
497
498 unsigned char lfhBuf[kLFHLen];
499 if (lseek(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
500 LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
501 return false;
502 }
503 ssize_t actual =
504 TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
505 if (actual != sizeof(lfhBuf)) {
506 LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
507 return false;
508 }
509
510 if (get4LE(lfhBuf) != kLFHSignature) {
511 LOGW("didn't find signature at start of lfh, offset=%ld\n",
512 localHdrOffset);
513 return false;
514 }
515
516 off_t dataOffset = localHdrOffset + kLFHLen
517 + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
518 if (dataOffset >= cdOffset) {
519 LOGW("bad data offset %ld in zip\n", (long) dataOffset);
520 return false;
521 }
522
523 /* check lengths */
524 if ((off_t)(dataOffset + compLen) > cdOffset) {
525 LOGW("bad compressed length in zip (%ld + %zd > %ld)\n",
526 (long) dataOffset, compLen, (long) cdOffset);
527 return false;
528 }
529
530 if (method == kCompressStored &&
531 (off_t)(dataOffset + uncompLen) > cdOffset)
532 {
533 LOGE("ERROR: bad uncompressed length in zip (%ld + %zd > %ld)\n",
534 (long) dataOffset, uncompLen, (long) cdOffset);
535 return false;
536 }
537
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800538 *pOffset = dataOffset;
539 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700540
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800541 return true;
542}
543
544/*
545 * Copy the entry's filename to the buffer.
546 */
547int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen)
548 const
549{
550 int ent = entryToIndex(entry);
551 if (ent < 0)
552 return -1;
553
554 int nameLen = mHashTable[ent].nameLen;
555 if (bufLen < nameLen+1)
556 return nameLen+1;
557
558 memcpy(buffer, mHashTable[ent].name, nameLen);
559 buffer[nameLen] = '\0';
560 return 0;
561}
562
563/*
564 * Create a new FileMap object that spans the data in "entry".
565 */
566FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const
567{
568 /*
569 * TODO: the efficient way to do this is to modify FileMap to allow
570 * sub-regions of a file to be mapped. A reference-counting scheme
571 * can manage the base memory mapping. For now, we just create a brand
572 * new mapping off of the Zip archive file descriptor.
573 */
574
575 FileMap* newMap;
Kenny Rootd4066a42010-04-22 18:28:29 -0700576 size_t compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800577 off_t offset;
578
579 if (!getEntryInfo(entry, NULL, NULL, &compLen, &offset, NULL, NULL))
580 return NULL;
581
582 newMap = new FileMap();
Kenny Rootd4066a42010-04-22 18:28:29 -0700583 if (!newMap->create(mFileName, mFd, offset, compLen, true)) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800584 newMap->release();
585 return NULL;
586 }
587
588 return newMap;
589}
590
591/*
592 * Uncompress an entry, in its entirety, into the provided output buffer.
593 *
594 * This doesn't verify the data's CRC, which might be useful for
595 * uncompressed data. The caller should be able to manage it.
596 */
597bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const
598{
Kenny Rootd4066a42010-04-22 18:28:29 -0700599 const size_t kSequentialMin = 32768;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800600 bool result = false;
601 int ent = entryToIndex(entry);
602 if (ent < 0)
603 return -1;
604
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800605 int method;
Kenny Rootd4066a42010-04-22 18:28:29 -0700606 size_t uncompLen, compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800607 off_t offset;
Kenny Rootd4066a42010-04-22 18:28:29 -0700608 const unsigned char* ptr;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800609
610 getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
611
Kenny Rootd4066a42010-04-22 18:28:29 -0700612 FileMap* file = createEntryFileMap(entry);
613 if (file == NULL) {
614 goto bail;
615 }
616
617 ptr = (const unsigned char*) file->getDataPtr();
618
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800619 /*
620 * Experiment with madvise hint. When we want to uncompress a file,
621 * we pull some stuff out of the central dir entry and then hit a
622 * bunch of compressed or uncompressed data sequentially. The CDE
623 * visit will cause a limited amount of read-ahead because it's at
624 * the end of the file. We could end up doing lots of extra disk
625 * access if the file we're prying open is small. Bottom line is we
626 * probably don't want to turn MADV_SEQUENTIAL on and leave it on.
627 *
628 * So, if the compressed size of the file is above a certain minimum
629 * size, temporarily boost the read-ahead in the hope that the extra
630 * pair of system calls are negated by a reduction in page faults.
631 */
632 if (compLen > kSequentialMin)
Kenny Rootd4066a42010-04-22 18:28:29 -0700633 file->advise(FileMap::SEQUENTIAL);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800634
635 if (method == kCompressStored) {
Kenny Rootd4066a42010-04-22 18:28:29 -0700636 memcpy(buffer, ptr, uncompLen);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800637 } else {
Kenny Rootd4066a42010-04-22 18:28:29 -0700638 if (!inflateBuffer(buffer, ptr, uncompLen, compLen))
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800639 goto bail;
640 }
641
642 if (compLen > kSequentialMin)
Kenny Rootd4066a42010-04-22 18:28:29 -0700643 file->advise(FileMap::NORMAL);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800644
645 result = true;
646
647bail:
648 return result;
649}
650
651/*
652 * Uncompress an entry, in its entirety, to an open file descriptor.
653 *
654 * This doesn't verify the data's CRC, but probably should.
655 */
656bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
657{
658 bool result = false;
659 int ent = entryToIndex(entry);
660 if (ent < 0)
661 return -1;
662
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800663 int method;
Kenny Rootd4066a42010-04-22 18:28:29 -0700664 size_t uncompLen, compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800665 off_t offset;
Kenny Rootd4066a42010-04-22 18:28:29 -0700666 const unsigned char* ptr;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800667
668 getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
669
Kenny Rootd4066a42010-04-22 18:28:29 -0700670 const FileMap* file = createEntryFileMap(entry);
671 if (file == NULL) {
672 goto bail;
673 }
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800674
Kenny Rootd4066a42010-04-22 18:28:29 -0700675 ptr = (const unsigned char*) file->getDataPtr();
676
677 if (method == kCompressStored) {
678 ssize_t actual = write(fd, ptr, uncompLen);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800679 if (actual < 0) {
680 LOGE("Write failed: %s\n", strerror(errno));
681 goto bail;
Kenny Rootd4066a42010-04-22 18:28:29 -0700682 } else if ((size_t) actual != uncompLen) {
683 LOGE("Partial write during uncompress (%zd of %zd)\n",
Kenny Root8f20e5e2010-08-04 16:30:40 -0700684 (size_t)actual, (size_t)uncompLen);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800685 goto bail;
686 } else {
687 LOGI("+++ successful write\n");
688 }
689 } else {
Kenny Rootd4066a42010-04-22 18:28:29 -0700690 if (!inflateBuffer(fd, ptr, uncompLen, compLen))
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800691 goto bail;
692 }
693
694 result = true;
695
696bail:
697 return result;
698}
699
700/*
701 * Uncompress "deflate" data from one buffer to another.
702 */
703/*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf,
Kenny Rootd4066a42010-04-22 18:28:29 -0700704 size_t uncompLen, size_t compLen)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800705{
706 bool result = false;
707 z_stream zstream;
708 int zerr;
709
710 /*
711 * Initialize the zlib stream struct.
712 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700713 memset(&zstream, 0, sizeof(zstream));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800714 zstream.zalloc = Z_NULL;
715 zstream.zfree = Z_NULL;
716 zstream.opaque = Z_NULL;
717 zstream.next_in = (Bytef*)inBuf;
718 zstream.avail_in = compLen;
719 zstream.next_out = (Bytef*) outBuf;
720 zstream.avail_out = uncompLen;
721 zstream.data_type = Z_UNKNOWN;
722
Kenny Rootd4066a42010-04-22 18:28:29 -0700723 /*
724 * Use the undocumented "negative window bits" feature to tell zlib
725 * that there's no zlib header waiting for it.
726 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800727 zerr = inflateInit2(&zstream, -MAX_WBITS);
728 if (zerr != Z_OK) {
729 if (zerr == Z_VERSION_ERROR) {
730 LOGE("Installed zlib is not compatible with linked version (%s)\n",
731 ZLIB_VERSION);
732 } else {
733 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
734 }
735 goto bail;
736 }
737
738 /*
739 * Expand data.
740 */
741 zerr = inflate(&zstream, Z_FINISH);
742 if (zerr != Z_STREAM_END) {
743 LOGW("Zip inflate failed, zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
744 zerr, zstream.next_in, zstream.avail_in,
745 zstream.next_out, zstream.avail_out);
746 goto z_bail;
747 }
748
749 /* paranoia */
Kenny Rootd4066a42010-04-22 18:28:29 -0700750 if (zstream.total_out != uncompLen) {
751 LOGW("Size mismatch on inflated file (%ld vs %zd)\n",
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800752 zstream.total_out, uncompLen);
753 goto z_bail;
754 }
755
756 result = true;
757
758z_bail:
759 inflateEnd(&zstream); /* free up any allocated structures */
760
761bail:
762 return result;
763}
764
765/*
766 * Uncompress "deflate" data from one buffer to an open file descriptor.
767 */
768/*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf,
Kenny Rootd4066a42010-04-22 18:28:29 -0700769 size_t uncompLen, size_t compLen)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800770{
771 bool result = false;
Kenny Rootd4066a42010-04-22 18:28:29 -0700772 const size_t kWriteBufSize = 32768;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800773 unsigned char writeBuf[kWriteBufSize];
774 z_stream zstream;
775 int zerr;
776
777 /*
778 * Initialize the zlib stream struct.
779 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700780 memset(&zstream, 0, sizeof(zstream));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800781 zstream.zalloc = Z_NULL;
782 zstream.zfree = Z_NULL;
783 zstream.opaque = Z_NULL;
784 zstream.next_in = (Bytef*)inBuf;
785 zstream.avail_in = compLen;
786 zstream.next_out = (Bytef*) writeBuf;
787 zstream.avail_out = sizeof(writeBuf);
788 zstream.data_type = Z_UNKNOWN;
789
Kenny Rootd4066a42010-04-22 18:28:29 -0700790 /*
791 * Use the undocumented "negative window bits" feature to tell zlib
792 * that there's no zlib header waiting for it.
793 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800794 zerr = inflateInit2(&zstream, -MAX_WBITS);
795 if (zerr != Z_OK) {
796 if (zerr == Z_VERSION_ERROR) {
797 LOGE("Installed zlib is not compatible with linked version (%s)\n",
798 ZLIB_VERSION);
799 } else {
800 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
801 }
802 goto bail;
803 }
804
805 /*
806 * Loop while we have more to do.
807 */
808 do {
809 /*
810 * Expand data.
811 */
812 zerr = inflate(&zstream, Z_NO_FLUSH);
813 if (zerr != Z_OK && zerr != Z_STREAM_END) {
814 LOGW("zlib inflate: zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
815 zerr, zstream.next_in, zstream.avail_in,
816 zstream.next_out, zstream.avail_out);
817 goto z_bail;
818 }
819
820 /* write when we're full or when we're done */
821 if (zstream.avail_out == 0 ||
822 (zerr == Z_STREAM_END && zstream.avail_out != sizeof(writeBuf)))
823 {
824 long writeSize = zstream.next_out - writeBuf;
825 int cc = write(fd, writeBuf, writeSize);
826 if (cc != (int) writeSize) {
827 LOGW("write failed in inflate (%d vs %ld)\n", cc, writeSize);
828 goto z_bail;
829 }
830
831 zstream.next_out = writeBuf;
832 zstream.avail_out = sizeof(writeBuf);
833 }
834 } while (zerr == Z_OK);
835
836 assert(zerr == Z_STREAM_END); /* other errors should've been caught */
837
838 /* paranoia */
Kenny Rootd4066a42010-04-22 18:28:29 -0700839 if (zstream.total_out != uncompLen) {
840 LOGW("Size mismatch on inflated file (%ld vs %zd)\n",
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800841 zstream.total_out, uncompLen);
842 goto z_bail;
843 }
844
845 result = true;
846
847z_bail:
848 inflateEnd(&zstream); /* free up any allocated structures */
849
850bail:
851 return result;
852}