blob: b18c383aeeaf1327818adf0fdad4cbbd0bdb2bfe [file] [log] [blame]
The Android Open Source Projectcbb10112009-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 Agopian7c889142009-06-04 13:53:57 -070022#include <utils/ZipFileRO.h>
23#include <utils/Log.h>
24#include <utils/misc.h>
Kenny Rootf7c1be02010-09-24 07:57:37 -070025#include <utils/threads.h>
The Android Open Source Projectcbb10112009-03-03 19:31:44 -080026
27#include <zlib.h>
28
29#include <string.h>
30#include <fcntl.h>
31#include <errno.h>
32#include <assert.h>
Kenny Rootc2b77d22010-04-22 18:28:29 -070033#include <unistd.h>
34
Raphael Moll929e4ef2010-10-13 19:13:48 -070035#if HAVE_PRINTF_ZD
36# define ZD "%zd"
37# define ZD_TYPE ssize_t
38#else
39# define ZD "%ld"
40# define ZD_TYPE long
41#endif
42
43/*
44 * We must open binary files using open(path, ... | O_BINARY) under Windows.
45 * Otherwise strange read errors will happen.
46 */
47#ifndef O_BINARY
48# define O_BINARY 0
49#endif
50
Kenny Rootc2b77d22010-04-22 18:28:29 -070051/*
52 * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
53 * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
54 * not already defined, then define it here.
55 */
56#ifndef TEMP_FAILURE_RETRY
57/* Used to retry syscalls that can return EINTR. */
58#define TEMP_FAILURE_RETRY(exp) ({ \
59 typeof (exp) _rc; \
60 do { \
61 _rc = (exp); \
62 } while (_rc == -1 && errno == EINTR); \
63 _rc; })
64#endif
The Android Open Source Projectcbb10112009-03-03 19:31:44 -080065
66using namespace android;
67
68/*
69 * Zip file constants.
70 */
71#define kEOCDSignature 0x06054b50
72#define kEOCDLen 22
73#define kEOCDNumEntries 8 // offset to #of entries in file
Kenny Rootc2b77d22010-04-22 18:28:29 -070074#define kEOCDSize 12 // size of the central directory
The Android Open Source Projectcbb10112009-03-03 19:31:44 -080075#define kEOCDFileOffset 16 // offset to central directory
76
77#define kMaxCommentLen 65535 // longest possible in ushort
78#define kMaxEOCDSearch (kMaxCommentLen + kEOCDLen)
79
80#define kLFHSignature 0x04034b50
81#define kLFHLen 30 // excluding variable-len fields
82#define kLFHNameLen 26 // offset to filename length
83#define kLFHExtraLen 28 // offset to extra length
84
85#define kCDESignature 0x02014b50
86#define kCDELen 46 // excluding variable-len fields
87#define kCDEMethod 10 // offset to compression method
88#define kCDEModWhen 12 // offset to modification timestamp
89#define kCDECRC 16 // offset to entry CRC
90#define kCDECompLen 20 // offset to compressed length
91#define kCDEUncompLen 24 // offset to uncompressed length
92#define kCDENameLen 28 // offset to filename length
93#define kCDEExtraLen 30 // offset to extra length
94#define kCDECommentLen 32 // offset to comment length
95#define kCDELocalOffset 42 // offset to local hdr
96
97/*
98 * The values we return for ZipEntryRO use 0 as an invalid value, so we
99 * want to adjust the hash table index by a fixed amount. Using a large
100 * value helps insure that people don't mix & match arguments, e.g. to
101 * findEntryByIndex().
102 */
103#define kZipEntryAdj 10000
104
Kenny Roota1ef2e02010-10-01 18:28:28 -0700105ZipFileRO::~ZipFileRO() {
106 free(mHashTable);
107 if (mDirectoryMap)
108 mDirectoryMap->release();
109 if (mFd >= 0)
110 TEMP_FAILURE_RETRY(close(mFd));
111 if (mFileName)
112 free(mFileName);
113}
114
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800115/*
116 * Convert a ZipEntryRO to a hash table index, verifying that it's in a
117 * valid range.
118 */
119int ZipFileRO::entryToIndex(const ZipEntryRO entry) const
120{
121 long ent = ((long) entry) - kZipEntryAdj;
122 if (ent < 0 || ent >= mHashTableSize || mHashTable[ent].name == NULL) {
123 LOGW("Invalid ZipEntryRO %p (%ld)\n", entry, ent);
124 return -1;
125 }
126 return ent;
127}
128
129
130/*
131 * Open the specified file read-only. We memory-map the entire thing and
132 * close the file before returning.
133 */
134status_t ZipFileRO::open(const char* zipFileName)
135{
136 int fd = -1;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800137
Kenny Rootc2b77d22010-04-22 18:28:29 -0700138 assert(mDirectoryMap == NULL);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800139
140 /*
141 * Open and map the specified file.
142 */
Raphael Moll929e4ef2010-10-13 19:13:48 -0700143 fd = ::open(zipFileName, O_RDONLY | O_BINARY);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800144 if (fd < 0) {
145 LOGW("Unable to open zip '%s': %s\n", zipFileName, strerror(errno));
146 return NAME_NOT_FOUND;
147 }
148
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800149 mFileLength = lseek64(fd, 0, SEEK_END);
Kenny Rootc2b77d22010-04-22 18:28:29 -0700150 if (mFileLength < kEOCDLen) {
Kenny Roota1ef2e02010-10-01 18:28:28 -0700151 TEMP_FAILURE_RETRY(close(fd));
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800152 return UNKNOWN_ERROR;
153 }
154
Kenny Rootc2b77d22010-04-22 18:28:29 -0700155 if (mFileName != NULL) {
156 free(mFileName);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800157 }
Kenny Rootc2b77d22010-04-22 18:28:29 -0700158 mFileName = strdup(zipFileName);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800159
160 mFd = fd;
161
162 /*
Kenny Rootc2b77d22010-04-22 18:28:29 -0700163 * Find the Central Directory and store its size and number of entries.
164 */
165 if (!mapCentralDirectory()) {
166 goto bail;
167 }
168
169 /*
170 * Verify Central Directory and create data structures for fast access.
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800171 */
172 if (!parseZipArchive()) {
Kenny Rootc2b77d22010-04-22 18:28:29 -0700173 goto bail;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800174 }
175
176 return OK;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700177
178bail:
179 free(mFileName);
180 mFileName = NULL;
Kenny Roota1ef2e02010-10-01 18:28:28 -0700181 TEMP_FAILURE_RETRY(close(fd));
Kenny Rootc2b77d22010-04-22 18:28:29 -0700182 return UNKNOWN_ERROR;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800183}
184
185/*
186 * Parse the Zip archive, verifying its contents and initializing internal
187 * data structures.
188 */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700189bool ZipFileRO::mapCentralDirectory(void)
190{
Raphael Moll929e4ef2010-10-13 19:13:48 -0700191 ssize_t readAmount = kMaxEOCDSearch;
192 if (readAmount > (ssize_t) mFileLength)
Kenny Rootc2b77d22010-04-22 18:28:29 -0700193 readAmount = mFileLength;
194
195 unsigned char* scanBuf = (unsigned char*) malloc(readAmount);
196 if (scanBuf == NULL) {
197 LOGW("couldn't allocate scanBuf: %s", strerror(errno));
198 free(scanBuf);
199 return false;
200 }
201
202 /*
203 * Make sure this is a Zip archive.
204 */
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800205 if (lseek64(mFd, 0, SEEK_SET) != 0) {
Kenny Rootc2b77d22010-04-22 18:28:29 -0700206 LOGW("seek to start failed: %s", strerror(errno));
207 free(scanBuf);
208 return false;
209 }
210
211 ssize_t actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, sizeof(int32_t)));
212 if (actual != (ssize_t) sizeof(int32_t)) {
213 LOGI("couldn't read first signature from zip archive: %s", strerror(errno));
214 free(scanBuf);
215 return false;
216 }
217
218 {
219 unsigned int header = get4LE(scanBuf);
220 if (header == kEOCDSignature) {
221 LOGI("Found Zip archive, but it looks empty\n");
222 free(scanBuf);
223 return false;
224 } else if (header != kLFHSignature) {
Kenny Rootf7c1be02010-09-24 07:57:37 -0700225 LOGV("Not a Zip archive (found 0x%08x)\n", header);
Kenny Rootc2b77d22010-04-22 18:28:29 -0700226 free(scanBuf);
227 return false;
228 }
229 }
230
231 /*
232 * Perform the traditional EOCD snipe hunt.
233 *
234 * We're searching for the End of Central Directory magic number,
235 * which appears at the start of the EOCD block. It's followed by
236 * 18 bytes of EOCD stuff and up to 64KB of archive comment. We
237 * need to read the last part of the file into a buffer, dig through
238 * it to find the magic number, parse some values out, and use those
239 * to determine the extent of the CD.
240 *
241 * We start by pulling in the last part of the file.
242 */
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800243 off64_t searchStart = mFileLength - readAmount;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700244
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800245 if (lseek64(mFd, searchStart, SEEK_SET) != searchStart) {
Kenny Rootc2b77d22010-04-22 18:28:29 -0700246 LOGW("seek %ld failed: %s\n", (long) searchStart, strerror(errno));
247 free(scanBuf);
248 return false;
249 }
250 actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, readAmount));
251 if (actual != (ssize_t) readAmount) {
Raphael Moll929e4ef2010-10-13 19:13:48 -0700252 LOGW("Zip: read " ZD ", expected " ZD ". Failed: %s\n",
253 (ZD_TYPE) actual, (ZD_TYPE) readAmount, strerror(errno));
Kenny Rootc2b77d22010-04-22 18:28:29 -0700254 free(scanBuf);
255 return false;
256 }
257
258 /*
259 * Scan backward for the EOCD magic. In an archive without a trailing
260 * comment, we'll find it on the first try. (We may want to consider
261 * doing an initial minimal read; if we don't find it, retry with a
262 * second read as above.)
263 */
264 int i;
265 for (i = readAmount - kEOCDLen; i >= 0; i--) {
266 if (scanBuf[i] == 0x50 && get4LE(&scanBuf[i]) == kEOCDSignature) {
267 LOGV("+++ Found EOCD at buf+%d\n", i);
268 break;
269 }
270 }
271 if (i < 0) {
272 LOGD("Zip: EOCD not found, %s is not zip\n", mFileName);
273 free(scanBuf);
274 return false;
275 }
276
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800277 off64_t eocdOffset = searchStart + i;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700278 const unsigned char* eocdPtr = scanBuf + i;
279
280 assert(eocdOffset < mFileLength);
281
282 /*
283 * Grab the CD offset and size, and the number of entries in the
Kenny Root3da482e2010-08-04 16:30:40 -0700284 * archive. After that, we can release our EOCD hunt buffer.
Kenny Rootc2b77d22010-04-22 18:28:29 -0700285 */
286 unsigned int numEntries = get2LE(eocdPtr + kEOCDNumEntries);
287 unsigned int dirSize = get4LE(eocdPtr + kEOCDSize);
288 unsigned int dirOffset = get4LE(eocdPtr + kEOCDFileOffset);
Kenny Root3da482e2010-08-04 16:30:40 -0700289 free(scanBuf);
Kenny Rootc2b77d22010-04-22 18:28:29 -0700290
Kenny Root3da482e2010-08-04 16:30:40 -0700291 // Verify that they look reasonable.
Kenny Rootc2b77d22010-04-22 18:28:29 -0700292 if ((long long) dirOffset + (long long) dirSize > (long long) eocdOffset) {
293 LOGW("bad offsets (dir %ld, size %u, eocd %ld)\n",
294 (long) dirOffset, dirSize, (long) eocdOffset);
Kenny Rootc2b77d22010-04-22 18:28:29 -0700295 return false;
296 }
297 if (numEntries == 0) {
298 LOGW("empty archive?\n");
Kenny Rootc2b77d22010-04-22 18:28:29 -0700299 return false;
300 }
301
302 LOGV("+++ numEntries=%d dirSize=%d dirOffset=%d\n",
303 numEntries, dirSize, dirOffset);
304
305 mDirectoryMap = new FileMap();
306 if (mDirectoryMap == NULL) {
307 LOGW("Unable to create directory map: %s", strerror(errno));
Kenny Rootc2b77d22010-04-22 18:28:29 -0700308 return false;
309 }
310
311 if (!mDirectoryMap->create(mFileName, mFd, dirOffset, dirSize, true)) {
Raphael Moll929e4ef2010-10-13 19:13:48 -0700312 LOGW("Unable to map '%s' (" ZD " to " ZD "): %s\n", mFileName,
313 (ZD_TYPE) dirOffset, (ZD_TYPE) (dirOffset + dirSize), strerror(errno));
Kenny Rootc2b77d22010-04-22 18:28:29 -0700314 return false;
315 }
316
317 mNumEntries = numEntries;
318 mDirectoryOffset = dirOffset;
319
320 return true;
321}
322
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800323bool ZipFileRO::parseZipArchive(void)
324{
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800325 bool result = false;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700326 const unsigned char* cdPtr = (const unsigned char*) mDirectoryMap->getDataPtr();
327 size_t cdLength = mDirectoryMap->getDataLength();
328 int numEntries = mNumEntries;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800329
330 /*
331 * Create hash table. We have a minimum 75% load factor, possibly as
332 * low as 50% after we round off to a power of 2.
333 */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700334 mHashTableSize = roundUpPower2(1 + (numEntries * 4) / 3);
335 mHashTable = (HashEntry*) calloc(mHashTableSize, sizeof(HashEntry));
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800336
337 /*
338 * Walk through the central directory, adding entries to the hash
339 * table.
340 */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700341 const unsigned char* ptr = cdPtr;
342 for (int i = 0; i < numEntries; i++) {
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800343 if (get4LE(ptr) != kCDESignature) {
344 LOGW("Missed a central dir sig (at %d)\n", i);
345 goto bail;
346 }
Kenny Rootc2b77d22010-04-22 18:28:29 -0700347 if (ptr + kCDELen > cdPtr + cdLength) {
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800348 LOGW("Ran off the end (at %d)\n", i);
349 goto bail;
350 }
351
Kenny Rootc2b77d22010-04-22 18:28:29 -0700352 long localHdrOffset = (long) get4LE(ptr + kCDELocalOffset);
353 if (localHdrOffset >= mDirectoryOffset) {
354 LOGW("bad LFH offset %ld at entry %d\n", localHdrOffset, i);
355 goto bail;
356 }
357
358 unsigned int fileNameLen, extraLen, commentLen, hash;
359
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800360 fileNameLen = get2LE(ptr + kCDENameLen);
361 extraLen = get2LE(ptr + kCDEExtraLen);
362 commentLen = get2LE(ptr + kCDECommentLen);
363
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800364 /* add the CDE filename to the hash table */
365 hash = computeHash((const char*)ptr + kCDELen, fileNameLen);
366 addToHash((const char*)ptr + kCDELen, fileNameLen, hash);
367
Kenny Rootc2b77d22010-04-22 18:28:29 -0700368 ptr += kCDELen + fileNameLen + extraLen + commentLen;
369 if ((size_t)(ptr - cdPtr) > cdLength) {
Raphael Moll929e4ef2010-10-13 19:13:48 -0700370 LOGW("bad CD advance (%d vs " ZD ") at entry %d\n",
371 (int) (ptr - cdPtr), (ZD_TYPE) cdLength, i);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800372 goto bail;
373 }
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800374 }
Kenny Rootc2b77d22010-04-22 18:28:29 -0700375 LOGV("+++ zip good scan %d entries\n", numEntries);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800376 result = true;
377
378bail:
379 return result;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800380}
381
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800382/*
383 * Simple string hash function for non-null-terminated strings.
384 */
385/*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len)
386{
387 unsigned int hash = 0;
388
389 while (len--)
390 hash = hash * 31 + *str++;
391
392 return hash;
393}
394
395/*
396 * Add a new entry to the hash table.
397 */
398void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash)
399{
400 int ent = hash & (mHashTableSize-1);
401
402 /*
403 * We over-allocate the table, so we're guaranteed to find an empty slot.
404 */
405 while (mHashTable[ent].name != NULL)
406 ent = (ent + 1) & (mHashTableSize-1);
407
408 mHashTable[ent].name = str;
409 mHashTable[ent].nameLen = strLen;
410}
411
412/*
413 * Find a matching entry.
414 *
Kenny Roote76184c2010-10-21 15:18:28 -0700415 * Returns NULL if not found.
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800416 */
417ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const
418{
Kenny Roote76184c2010-10-21 15:18:28 -0700419 /*
420 * If the ZipFileRO instance is not initialized, the entry number will
421 * end up being garbage since mHashTableSize is -1.
422 */
423 if (mHashTableSize <= 0) {
424 return NULL;
425 }
426
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800427 int nameLen = strlen(fileName);
428 unsigned int hash = computeHash(fileName, nameLen);
429 int ent = hash & (mHashTableSize-1);
430
431 while (mHashTable[ent].name != NULL) {
432 if (mHashTable[ent].nameLen == nameLen &&
433 memcmp(mHashTable[ent].name, fileName, nameLen) == 0)
434 {
435 /* match */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700436 return (ZipEntryRO)(long)(ent + kZipEntryAdj);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800437 }
438
439 ent = (ent + 1) & (mHashTableSize-1);
440 }
441
442 return NULL;
443}
444
445/*
446 * Find the Nth entry.
447 *
448 * This currently involves walking through the sparse hash table, counting
449 * non-empty entries. If we need to speed this up we can either allocate
450 * a parallel lookup table or (perhaps better) provide an iterator interface.
451 */
452ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const
453{
454 if (idx < 0 || idx >= mNumEntries) {
455 LOGW("Invalid index %d\n", idx);
456 return NULL;
457 }
458
459 for (int ent = 0; ent < mHashTableSize; ent++) {
460 if (mHashTable[ent].name != NULL) {
461 if (idx-- == 0)
462 return (ZipEntryRO) (ent + kZipEntryAdj);
463 }
464 }
465
466 return NULL;
467}
468
469/*
470 * Get the useful fields from the zip entry.
471 *
472 * Returns "false" if the offsets to the fields or the contents of the fields
473 * appear to be bogus.
474 */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700475bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800476 size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) const
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800477{
Kenny Rootc2b77d22010-04-22 18:28:29 -0700478 bool ret = false;
479
480 const int ent = entryToIndex(entry);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800481 if (ent < 0)
482 return false;
483
Kenny Rootc2b77d22010-04-22 18:28:29 -0700484 HashEntry hashEntry = mHashTable[ent];
485
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800486 /*
487 * Recover the start of the central directory entry from the filename
Kenny Rootc2b77d22010-04-22 18:28:29 -0700488 * pointer. The filename is the first entry past the fixed-size data,
489 * so we can just subtract back from that.
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800490 */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700491 const unsigned char* ptr = (const unsigned char*) hashEntry.name;
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800492 off64_t cdOffset = mDirectoryOffset;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800493
494 ptr -= kCDELen;
495
496 int method = get2LE(ptr + kCDEMethod);
497 if (pMethod != NULL)
498 *pMethod = method;
499
500 if (pModWhen != NULL)
501 *pModWhen = get4LE(ptr + kCDEModWhen);
502 if (pCrc32 != NULL)
503 *pCrc32 = get4LE(ptr + kCDECRC);
504
Kenny Rootc2b77d22010-04-22 18:28:29 -0700505 size_t compLen = get4LE(ptr + kCDECompLen);
506 if (pCompLen != NULL)
507 *pCompLen = compLen;
508 size_t uncompLen = get4LE(ptr + kCDEUncompLen);
509 if (pUncompLen != NULL)
510 *pUncompLen = uncompLen;
511
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800512 /*
Kenny Rootc2b77d22010-04-22 18:28:29 -0700513 * If requested, determine the offset of the start of the data. All we
514 * have is the offset to the Local File Header, which is variable size,
515 * so we have to read the contents of the struct to figure out where
516 * the actual data starts.
517 *
518 * We also need to make sure that the lengths are not so large that
519 * somebody trying to map the compressed or uncompressed data runs
520 * off the end of the mapped region.
521 *
522 * Note we don't verify compLen/uncompLen if they don't request the
523 * dataOffset, because dataOffset is expensive to determine. However,
524 * if they don't have the file offset, they're not likely to be doing
525 * anything with the contents.
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800526 */
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800527 if (pOffset != NULL) {
Kenny Rootc2b77d22010-04-22 18:28:29 -0700528 long localHdrOffset = get4LE(ptr + kCDELocalOffset);
529 if (localHdrOffset + kLFHLen >= cdOffset) {
530 LOGE("ERROR: bad local hdr offset in zip\n");
531 return false;
532 }
533
534 unsigned char lfhBuf[kLFHLen];
Kenny Rootf7c1be02010-09-24 07:57:37 -0700535
Kenny Rootbf2ad6d2010-10-04 14:20:14 -0700536#ifdef HAVE_PREAD
537 /*
538 * This file descriptor might be from zygote's preloaded assets,
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800539 * so we need to do an pread64() instead of a lseek64() + read() to
Kenny Rootbf2ad6d2010-10-04 14:20:14 -0700540 * guarantee atomicity across the processes with the shared file
541 * descriptors.
542 */
543 ssize_t actual =
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800544 TEMP_FAILURE_RETRY(pread64(mFd, lfhBuf, sizeof(lfhBuf), localHdrOffset));
Kenny Rootbf2ad6d2010-10-04 14:20:14 -0700545
546 if (actual != sizeof(lfhBuf)) {
547 LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
548 return false;
549 }
550
551 if (get4LE(lfhBuf) != kLFHSignature) {
552 LOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
553 "got: data=0x%08lx\n",
554 localHdrOffset, kLFHSignature, get4LE(lfhBuf));
555 return false;
556 }
557#else /* HAVE_PREAD */
558 /*
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800559 * For hosts don't have pread64() we cannot guarantee atomic reads from
Kenny Rootbf2ad6d2010-10-04 14:20:14 -0700560 * an offset in a file. Android should never run on those platforms.
561 * File descriptors inherited from a fork() share file offsets and
562 * there would be nothing to protect from two different processes
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800563 * calling lseek64() concurrently.
Kenny Rootbf2ad6d2010-10-04 14:20:14 -0700564 */
565
Kenny Rootf7c1be02010-09-24 07:57:37 -0700566 {
567 AutoMutex _l(mFdLock);
568
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800569 if (lseek64(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
Kenny Rootf7c1be02010-09-24 07:57:37 -0700570 LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
571 return false;
572 }
573
574 ssize_t actual =
Kenny Rootbf2ad6d2010-10-04 14:20:14 -0700575 TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
Kenny Rootf7c1be02010-09-24 07:57:37 -0700576 if (actual != sizeof(lfhBuf)) {
577 LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
578 return false;
579 }
Kenny Rootc2b77d22010-04-22 18:28:29 -0700580
Kenny Roota1ef2e02010-10-01 18:28:28 -0700581 if (get4LE(lfhBuf) != kLFHSignature) {
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800582 off64_t actualOffset = lseek64(mFd, 0, SEEK_CUR);
Kenny Roota1ef2e02010-10-01 18:28:28 -0700583 LOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
Raphael Moll929e4ef2010-10-13 19:13:48 -0700584 "got: offset=" ZD " data=0x%08lx\n",
585 localHdrOffset, kLFHSignature, (ZD_TYPE) actualOffset, get4LE(lfhBuf));
Kenny Roota1ef2e02010-10-01 18:28:28 -0700586 return false;
587 }
Kenny Rootc2b77d22010-04-22 18:28:29 -0700588 }
Kenny Rootbf2ad6d2010-10-04 14:20:14 -0700589#endif /* HAVE_PREAD */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700590
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800591 off64_t dataOffset = localHdrOffset + kLFHLen
Kenny Rootc2b77d22010-04-22 18:28:29 -0700592 + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
593 if (dataOffset >= cdOffset) {
594 LOGW("bad data offset %ld in zip\n", (long) dataOffset);
595 return false;
596 }
597
598 /* check lengths */
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800599 if ((off64_t)(dataOffset + compLen) > cdOffset) {
Raphael Moll929e4ef2010-10-13 19:13:48 -0700600 LOGW("bad compressed length in zip (%ld + " ZD " > %ld)\n",
601 (long) dataOffset, (ZD_TYPE) compLen, (long) cdOffset);
Kenny Rootc2b77d22010-04-22 18:28:29 -0700602 return false;
603 }
604
605 if (method == kCompressStored &&
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800606 (off64_t)(dataOffset + uncompLen) > cdOffset)
Kenny Rootc2b77d22010-04-22 18:28:29 -0700607 {
Raphael Moll929e4ef2010-10-13 19:13:48 -0700608 LOGE("ERROR: bad uncompressed length in zip (%ld + " ZD " > %ld)\n",
609 (long) dataOffset, (ZD_TYPE) uncompLen, (long) cdOffset);
Kenny Rootc2b77d22010-04-22 18:28:29 -0700610 return false;
611 }
612
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800613 *pOffset = dataOffset;
614 }
Kenny Rootc2b77d22010-04-22 18:28:29 -0700615
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800616 return true;
617}
618
619/*
620 * Copy the entry's filename to the buffer.
621 */
622int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen)
623 const
624{
625 int ent = entryToIndex(entry);
626 if (ent < 0)
627 return -1;
628
629 int nameLen = mHashTable[ent].nameLen;
630 if (bufLen < nameLen+1)
631 return nameLen+1;
632
633 memcpy(buffer, mHashTable[ent].name, nameLen);
634 buffer[nameLen] = '\0';
635 return 0;
636}
637
638/*
639 * Create a new FileMap object that spans the data in "entry".
640 */
641FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const
642{
643 /*
644 * TODO: the efficient way to do this is to modify FileMap to allow
645 * sub-regions of a file to be mapped. A reference-counting scheme
646 * can manage the base memory mapping. For now, we just create a brand
647 * new mapping off of the Zip archive file descriptor.
648 */
649
650 FileMap* newMap;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700651 size_t compLen;
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800652 off64_t offset;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800653
654 if (!getEntryInfo(entry, NULL, NULL, &compLen, &offset, NULL, NULL))
655 return NULL;
656
657 newMap = new FileMap();
Kenny Rootc2b77d22010-04-22 18:28:29 -0700658 if (!newMap->create(mFileName, mFd, offset, compLen, true)) {
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800659 newMap->release();
660 return NULL;
661 }
662
663 return newMap;
664}
665
666/*
667 * Uncompress an entry, in its entirety, into the provided output buffer.
668 *
669 * This doesn't verify the data's CRC, which might be useful for
670 * uncompressed data. The caller should be able to manage it.
671 */
672bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const
673{
Kenny Rootc2b77d22010-04-22 18:28:29 -0700674 const size_t kSequentialMin = 32768;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800675 bool result = false;
676 int ent = entryToIndex(entry);
677 if (ent < 0)
678 return -1;
679
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800680 int method;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700681 size_t uncompLen, compLen;
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800682 off64_t offset;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700683 const unsigned char* ptr;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800684
685 getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
686
Kenny Rootc2b77d22010-04-22 18:28:29 -0700687 FileMap* file = createEntryFileMap(entry);
688 if (file == NULL) {
689 goto bail;
690 }
691
692 ptr = (const unsigned char*) file->getDataPtr();
693
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800694 /*
695 * Experiment with madvise hint. When we want to uncompress a file,
696 * we pull some stuff out of the central dir entry and then hit a
697 * bunch of compressed or uncompressed data sequentially. The CDE
698 * visit will cause a limited amount of read-ahead because it's at
699 * the end of the file. We could end up doing lots of extra disk
700 * access if the file we're prying open is small. Bottom line is we
701 * probably don't want to turn MADV_SEQUENTIAL on and leave it on.
702 *
703 * So, if the compressed size of the file is above a certain minimum
704 * size, temporarily boost the read-ahead in the hope that the extra
705 * pair of system calls are negated by a reduction in page faults.
706 */
707 if (compLen > kSequentialMin)
Kenny Rootc2b77d22010-04-22 18:28:29 -0700708 file->advise(FileMap::SEQUENTIAL);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800709
710 if (method == kCompressStored) {
Kenny Rootc2b77d22010-04-22 18:28:29 -0700711 memcpy(buffer, ptr, uncompLen);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800712 } else {
Kenny Rootc2b77d22010-04-22 18:28:29 -0700713 if (!inflateBuffer(buffer, ptr, uncompLen, compLen))
Kenny Rootcea27782010-09-24 09:11:28 -0700714 goto unmap;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800715 }
716
717 if (compLen > kSequentialMin)
Kenny Rootc2b77d22010-04-22 18:28:29 -0700718 file->advise(FileMap::NORMAL);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800719
720 result = true;
721
Kenny Rootcea27782010-09-24 09:11:28 -0700722unmap:
723 file->release();
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800724bail:
725 return result;
726}
727
728/*
729 * Uncompress an entry, in its entirety, to an open file descriptor.
730 *
731 * This doesn't verify the data's CRC, but probably should.
732 */
733bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
734{
735 bool result = false;
736 int ent = entryToIndex(entry);
737 if (ent < 0)
738 return -1;
739
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800740 int method;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700741 size_t uncompLen, compLen;
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800742 off64_t offset;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700743 const unsigned char* ptr;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800744
745 getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
746
Kenny Rootcea27782010-09-24 09:11:28 -0700747 FileMap* file = createEntryFileMap(entry);
Kenny Rootc2b77d22010-04-22 18:28:29 -0700748 if (file == NULL) {
749 goto bail;
750 }
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800751
Kenny Rootc2b77d22010-04-22 18:28:29 -0700752 ptr = (const unsigned char*) file->getDataPtr();
753
754 if (method == kCompressStored) {
755 ssize_t actual = write(fd, ptr, uncompLen);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800756 if (actual < 0) {
757 LOGE("Write failed: %s\n", strerror(errno));
Kenny Rootcea27782010-09-24 09:11:28 -0700758 goto unmap;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700759 } else if ((size_t) actual != uncompLen) {
Raphael Moll929e4ef2010-10-13 19:13:48 -0700760 LOGE("Partial write during uncompress (" ZD " of " ZD ")\n",
761 (ZD_TYPE) actual, (ZD_TYPE) uncompLen);
Kenny Rootcea27782010-09-24 09:11:28 -0700762 goto unmap;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800763 } else {
764 LOGI("+++ successful write\n");
765 }
766 } else {
Kenny Rootc2b77d22010-04-22 18:28:29 -0700767 if (!inflateBuffer(fd, ptr, uncompLen, compLen))
Kenny Rootcea27782010-09-24 09:11:28 -0700768 goto unmap;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800769 }
770
771 result = true;
772
Kenny Rootcea27782010-09-24 09:11:28 -0700773unmap:
774 file->release();
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800775bail:
776 return result;
777}
778
779/*
780 * Uncompress "deflate" data from one buffer to another.
781 */
782/*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf,
Kenny Rootc2b77d22010-04-22 18:28:29 -0700783 size_t uncompLen, size_t compLen)
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800784{
785 bool result = false;
786 z_stream zstream;
787 int zerr;
788
789 /*
790 * Initialize the zlib stream struct.
791 */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700792 memset(&zstream, 0, sizeof(zstream));
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800793 zstream.zalloc = Z_NULL;
794 zstream.zfree = Z_NULL;
795 zstream.opaque = Z_NULL;
796 zstream.next_in = (Bytef*)inBuf;
797 zstream.avail_in = compLen;
798 zstream.next_out = (Bytef*) outBuf;
799 zstream.avail_out = uncompLen;
800 zstream.data_type = Z_UNKNOWN;
801
Kenny Rootc2b77d22010-04-22 18:28:29 -0700802 /*
803 * Use the undocumented "negative window bits" feature to tell zlib
804 * that there's no zlib header waiting for it.
805 */
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800806 zerr = inflateInit2(&zstream, -MAX_WBITS);
807 if (zerr != Z_OK) {
808 if (zerr == Z_VERSION_ERROR) {
809 LOGE("Installed zlib is not compatible with linked version (%s)\n",
810 ZLIB_VERSION);
811 } else {
812 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
813 }
814 goto bail;
815 }
816
817 /*
818 * Expand data.
819 */
820 zerr = inflate(&zstream, Z_FINISH);
821 if (zerr != Z_STREAM_END) {
822 LOGW("Zip inflate failed, zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
823 zerr, zstream.next_in, zstream.avail_in,
824 zstream.next_out, zstream.avail_out);
825 goto z_bail;
826 }
827
828 /* paranoia */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700829 if (zstream.total_out != uncompLen) {
Raphael Moll929e4ef2010-10-13 19:13:48 -0700830 LOGW("Size mismatch on inflated file (%ld vs " ZD ")\n",
831 zstream.total_out, (ZD_TYPE) uncompLen);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800832 goto z_bail;
833 }
834
835 result = true;
836
837z_bail:
838 inflateEnd(&zstream); /* free up any allocated structures */
839
840bail:
841 return result;
842}
843
844/*
845 * Uncompress "deflate" data from one buffer to an open file descriptor.
846 */
847/*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf,
Kenny Rootc2b77d22010-04-22 18:28:29 -0700848 size_t uncompLen, size_t compLen)
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800849{
850 bool result = false;
Kenny Rootc2b77d22010-04-22 18:28:29 -0700851 const size_t kWriteBufSize = 32768;
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800852 unsigned char writeBuf[kWriteBufSize];
853 z_stream zstream;
854 int zerr;
855
856 /*
857 * Initialize the zlib stream struct.
858 */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700859 memset(&zstream, 0, sizeof(zstream));
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800860 zstream.zalloc = Z_NULL;
861 zstream.zfree = Z_NULL;
862 zstream.opaque = Z_NULL;
863 zstream.next_in = (Bytef*)inBuf;
864 zstream.avail_in = compLen;
865 zstream.next_out = (Bytef*) writeBuf;
866 zstream.avail_out = sizeof(writeBuf);
867 zstream.data_type = Z_UNKNOWN;
868
Kenny Rootc2b77d22010-04-22 18:28:29 -0700869 /*
870 * Use the undocumented "negative window bits" feature to tell zlib
871 * that there's no zlib header waiting for it.
872 */
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800873 zerr = inflateInit2(&zstream, -MAX_WBITS);
874 if (zerr != Z_OK) {
875 if (zerr == Z_VERSION_ERROR) {
876 LOGE("Installed zlib is not compatible with linked version (%s)\n",
877 ZLIB_VERSION);
878 } else {
879 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
880 }
881 goto bail;
882 }
883
884 /*
885 * Loop while we have more to do.
886 */
887 do {
888 /*
889 * Expand data.
890 */
891 zerr = inflate(&zstream, Z_NO_FLUSH);
892 if (zerr != Z_OK && zerr != Z_STREAM_END) {
893 LOGW("zlib inflate: zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
894 zerr, zstream.next_in, zstream.avail_in,
895 zstream.next_out, zstream.avail_out);
896 goto z_bail;
897 }
898
899 /* write when we're full or when we're done */
900 if (zstream.avail_out == 0 ||
901 (zerr == Z_STREAM_END && zstream.avail_out != sizeof(writeBuf)))
902 {
903 long writeSize = zstream.next_out - writeBuf;
904 int cc = write(fd, writeBuf, writeSize);
905 if (cc != (int) writeSize) {
906 LOGW("write failed in inflate (%d vs %ld)\n", cc, writeSize);
907 goto z_bail;
908 }
909
910 zstream.next_out = writeBuf;
911 zstream.avail_out = sizeof(writeBuf);
912 }
913 } while (zerr == Z_OK);
914
915 assert(zerr == Z_STREAM_END); /* other errors should've been caught */
916
917 /* paranoia */
Kenny Rootc2b77d22010-04-22 18:28:29 -0700918 if (zstream.total_out != uncompLen) {
Raphael Moll929e4ef2010-10-13 19:13:48 -0700919 LOGW("Size mismatch on inflated file (%ld vs " ZD ")\n",
920 zstream.total_out, (ZD_TYPE) uncompLen);
The Android Open Source Projectcbb10112009-03-03 19:31:44 -0800921 goto z_bail;
922 }
923
924 result = true;
925
926z_bail:
927 inflateEnd(&zstream); /* free up any allocated structures */
928
929bail:
930 return result;
931}