blob: 59809c2f94d68e70e40fba64ab1c60b1c131e3b1 [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>
Kenny Rootfa989202010-09-24 07:57:37 -070025#include <utils/threads.h>
The Android Open Source Projectedbf3b62009-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 Rootd4066a42010-04-22 18:28:29 -070033#include <unistd.h>
34
35/*
36 * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
37 * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
38 * not already defined, then define it here.
39 */
40#ifndef TEMP_FAILURE_RETRY
41/* Used to retry syscalls that can return EINTR. */
42#define TEMP_FAILURE_RETRY(exp) ({ \
43 typeof (exp) _rc; \
44 do { \
45 _rc = (exp); \
46 } while (_rc == -1 && errno == EINTR); \
47 _rc; })
48#endif
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080049
50using namespace android;
51
52/*
53 * Zip file constants.
54 */
55#define kEOCDSignature 0x06054b50
56#define kEOCDLen 22
57#define kEOCDNumEntries 8 // offset to #of entries in file
Kenny Rootd4066a42010-04-22 18:28:29 -070058#define kEOCDSize 12 // size of the central directory
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -080059#define kEOCDFileOffset 16 // offset to central directory
60
61#define kMaxCommentLen 65535 // longest possible in ushort
62#define kMaxEOCDSearch (kMaxCommentLen + kEOCDLen)
63
64#define kLFHSignature 0x04034b50
65#define kLFHLen 30 // excluding variable-len fields
66#define kLFHNameLen 26 // offset to filename length
67#define kLFHExtraLen 28 // offset to extra length
68
69#define kCDESignature 0x02014b50
70#define kCDELen 46 // excluding variable-len fields
71#define kCDEMethod 10 // offset to compression method
72#define kCDEModWhen 12 // offset to modification timestamp
73#define kCDECRC 16 // offset to entry CRC
74#define kCDECompLen 20 // offset to compressed length
75#define kCDEUncompLen 24 // offset to uncompressed length
76#define kCDENameLen 28 // offset to filename length
77#define kCDEExtraLen 30 // offset to extra length
78#define kCDECommentLen 32 // offset to comment length
79#define kCDELocalOffset 42 // offset to local hdr
80
81/*
82 * The values we return for ZipEntryRO use 0 as an invalid value, so we
83 * want to adjust the hash table index by a fixed amount. Using a large
84 * value helps insure that people don't mix & match arguments, e.g. to
85 * findEntryByIndex().
86 */
87#define kZipEntryAdj 10000
88
89/*
90 * Convert a ZipEntryRO to a hash table index, verifying that it's in a
91 * valid range.
92 */
93int ZipFileRO::entryToIndex(const ZipEntryRO entry) const
94{
95 long ent = ((long) entry) - kZipEntryAdj;
96 if (ent < 0 || ent >= mHashTableSize || mHashTable[ent].name == NULL) {
97 LOGW("Invalid ZipEntryRO %p (%ld)\n", entry, ent);
98 return -1;
99 }
100 return ent;
101}
102
103
104/*
105 * Open the specified file read-only. We memory-map the entire thing and
106 * close the file before returning.
107 */
108status_t ZipFileRO::open(const char* zipFileName)
109{
110 int fd = -1;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800111
Kenny Rootd4066a42010-04-22 18:28:29 -0700112 assert(mDirectoryMap == NULL);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800113
114 /*
115 * Open and map the specified file.
116 */
117 fd = ::open(zipFileName, O_RDONLY);
118 if (fd < 0) {
119 LOGW("Unable to open zip '%s': %s\n", zipFileName, strerror(errno));
120 return NAME_NOT_FOUND;
121 }
122
Kenny Rootd4066a42010-04-22 18:28:29 -0700123 mFileLength = lseek(fd, 0, SEEK_END);
124 if (mFileLength < kEOCDLen) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800125 close(fd);
126 return UNKNOWN_ERROR;
127 }
128
Kenny Rootd4066a42010-04-22 18:28:29 -0700129 if (mFileName != NULL) {
130 free(mFileName);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800131 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700132 mFileName = strdup(zipFileName);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800133
134 mFd = fd;
135
136 /*
Kenny Rootd4066a42010-04-22 18:28:29 -0700137 * Find the Central Directory and store its size and number of entries.
138 */
139 if (!mapCentralDirectory()) {
140 goto bail;
141 }
142
143 /*
144 * Verify Central Directory and create data structures for fast access.
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800145 */
146 if (!parseZipArchive()) {
Kenny Rootd4066a42010-04-22 18:28:29 -0700147 goto bail;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800148 }
149
150 return OK;
Kenny Rootd4066a42010-04-22 18:28:29 -0700151
152bail:
153 free(mFileName);
154 mFileName = NULL;
155 close(fd);
156 return UNKNOWN_ERROR;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800157}
158
159/*
160 * Parse the Zip archive, verifying its contents and initializing internal
161 * data structures.
162 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700163bool ZipFileRO::mapCentralDirectory(void)
164{
165 size_t readAmount = kMaxEOCDSearch;
166 if (readAmount > (size_t) mFileLength)
167 readAmount = mFileLength;
168
169 unsigned char* scanBuf = (unsigned char*) malloc(readAmount);
170 if (scanBuf == NULL) {
171 LOGW("couldn't allocate scanBuf: %s", strerror(errno));
172 free(scanBuf);
173 return false;
174 }
175
176 /*
177 * Make sure this is a Zip archive.
178 */
179 if (lseek(mFd, 0, SEEK_SET) != 0) {
180 LOGW("seek to start failed: %s", strerror(errno));
181 free(scanBuf);
182 return false;
183 }
184
185 ssize_t actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, sizeof(int32_t)));
186 if (actual != (ssize_t) sizeof(int32_t)) {
187 LOGI("couldn't read first signature from zip archive: %s", strerror(errno));
188 free(scanBuf);
189 return false;
190 }
191
192 {
193 unsigned int header = get4LE(scanBuf);
194 if (header == kEOCDSignature) {
195 LOGI("Found Zip archive, but it looks empty\n");
196 free(scanBuf);
197 return false;
198 } else if (header != kLFHSignature) {
Kenny Rootfa989202010-09-24 07:57:37 -0700199 LOGV("Not a Zip archive (found 0x%08x)\n", header);
Kenny Rootd4066a42010-04-22 18:28:29 -0700200 free(scanBuf);
201 return false;
202 }
203 }
204
205 /*
206 * Perform the traditional EOCD snipe hunt.
207 *
208 * We're searching for the End of Central Directory magic number,
209 * which appears at the start of the EOCD block. It's followed by
210 * 18 bytes of EOCD stuff and up to 64KB of archive comment. We
211 * need to read the last part of the file into a buffer, dig through
212 * it to find the magic number, parse some values out, and use those
213 * to determine the extent of the CD.
214 *
215 * We start by pulling in the last part of the file.
216 */
217 off_t searchStart = mFileLength - readAmount;
218
219 if (lseek(mFd, searchStart, SEEK_SET) != searchStart) {
220 LOGW("seek %ld failed: %s\n", (long) searchStart, strerror(errno));
221 free(scanBuf);
222 return false;
223 }
224 actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, readAmount));
225 if (actual != (ssize_t) readAmount) {
226 LOGW("Zip: read %zd failed: %s\n", readAmount, strerror(errno));
227 free(scanBuf);
228 return false;
229 }
230
231 /*
232 * Scan backward for the EOCD magic. In an archive without a trailing
233 * comment, we'll find it on the first try. (We may want to consider
234 * doing an initial minimal read; if we don't find it, retry with a
235 * second read as above.)
236 */
237 int i;
238 for (i = readAmount - kEOCDLen; i >= 0; i--) {
239 if (scanBuf[i] == 0x50 && get4LE(&scanBuf[i]) == kEOCDSignature) {
240 LOGV("+++ Found EOCD at buf+%d\n", i);
241 break;
242 }
243 }
244 if (i < 0) {
245 LOGD("Zip: EOCD not found, %s is not zip\n", mFileName);
246 free(scanBuf);
247 return false;
248 }
249
250 off_t eocdOffset = searchStart + i;
251 const unsigned char* eocdPtr = scanBuf + i;
252
253 assert(eocdOffset < mFileLength);
254
255 /*
256 * Grab the CD offset and size, and the number of entries in the
Kenny Root8f20e5e2010-08-04 16:30:40 -0700257 * archive. After that, we can release our EOCD hunt buffer.
Kenny Rootd4066a42010-04-22 18:28:29 -0700258 */
259 unsigned int numEntries = get2LE(eocdPtr + kEOCDNumEntries);
260 unsigned int dirSize = get4LE(eocdPtr + kEOCDSize);
261 unsigned int dirOffset = get4LE(eocdPtr + kEOCDFileOffset);
Kenny Root8f20e5e2010-08-04 16:30:40 -0700262 free(scanBuf);
Kenny Rootd4066a42010-04-22 18:28:29 -0700263
Kenny Root8f20e5e2010-08-04 16:30:40 -0700264 // Verify that they look reasonable.
Kenny Rootd4066a42010-04-22 18:28:29 -0700265 if ((long long) dirOffset + (long long) dirSize > (long long) eocdOffset) {
266 LOGW("bad offsets (dir %ld, size %u, eocd %ld)\n",
267 (long) dirOffset, dirSize, (long) eocdOffset);
Kenny Rootd4066a42010-04-22 18:28:29 -0700268 return false;
269 }
270 if (numEntries == 0) {
271 LOGW("empty archive?\n");
Kenny Rootd4066a42010-04-22 18:28:29 -0700272 return false;
273 }
274
275 LOGV("+++ numEntries=%d dirSize=%d dirOffset=%d\n",
276 numEntries, dirSize, dirOffset);
277
278 mDirectoryMap = new FileMap();
279 if (mDirectoryMap == NULL) {
280 LOGW("Unable to create directory map: %s", strerror(errno));
Kenny Rootd4066a42010-04-22 18:28:29 -0700281 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));
Kenny Rootd4066a42010-04-22 18:28:29 -0700287 return false;
288 }
289
290 mNumEntries = numEntries;
291 mDirectoryOffset = dirOffset;
292
293 return true;
294}
295
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800296bool ZipFileRO::parseZipArchive(void)
297{
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800298 bool result = false;
Kenny Rootd4066a42010-04-22 18:28:29 -0700299 const unsigned char* cdPtr = (const unsigned char*) mDirectoryMap->getDataPtr();
300 size_t cdLength = mDirectoryMap->getDataLength();
301 int numEntries = mNumEntries;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800302
303 /*
304 * Create hash table. We have a minimum 75% load factor, possibly as
305 * low as 50% after we round off to a power of 2.
306 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700307 mHashTableSize = roundUpPower2(1 + (numEntries * 4) / 3);
308 mHashTable = (HashEntry*) calloc(mHashTableSize, sizeof(HashEntry));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800309
310 /*
311 * Walk through the central directory, adding entries to the hash
312 * table.
313 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700314 const unsigned char* ptr = cdPtr;
315 for (int i = 0; i < numEntries; i++) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800316 if (get4LE(ptr) != kCDESignature) {
317 LOGW("Missed a central dir sig (at %d)\n", i);
318 goto bail;
319 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700320 if (ptr + kCDELen > cdPtr + cdLength) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800321 LOGW("Ran off the end (at %d)\n", i);
322 goto bail;
323 }
324
Kenny Rootd4066a42010-04-22 18:28:29 -0700325 long localHdrOffset = (long) get4LE(ptr + kCDELocalOffset);
326 if (localHdrOffset >= mDirectoryOffset) {
327 LOGW("bad LFH offset %ld at entry %d\n", localHdrOffset, i);
328 goto bail;
329 }
330
331 unsigned int fileNameLen, extraLen, commentLen, hash;
332
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800333 fileNameLen = get2LE(ptr + kCDENameLen);
334 extraLen = get2LE(ptr + kCDEExtraLen);
335 commentLen = get2LE(ptr + kCDECommentLen);
336
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800337 /* add the CDE filename to the hash table */
338 hash = computeHash((const char*)ptr + kCDELen, fileNameLen);
339 addToHash((const char*)ptr + kCDELen, fileNameLen, hash);
340
Kenny Rootd4066a42010-04-22 18:28:29 -0700341 ptr += kCDELen + fileNameLen + extraLen + commentLen;
342 if ((size_t)(ptr - cdPtr) > cdLength) {
343 LOGW("bad CD advance (%d vs %zd) at entry %d\n",
344 (int) (ptr - cdPtr), cdLength, i);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800345 goto bail;
346 }
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800347 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700348 LOGV("+++ zip good scan %d entries\n", numEntries);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800349 result = true;
350
351bail:
352 return result;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800353}
354
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800355/*
356 * Simple string hash function for non-null-terminated strings.
357 */
358/*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len)
359{
360 unsigned int hash = 0;
361
362 while (len--)
363 hash = hash * 31 + *str++;
364
365 return hash;
366}
367
368/*
369 * Add a new entry to the hash table.
370 */
371void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash)
372{
373 int ent = hash & (mHashTableSize-1);
374
375 /*
376 * We over-allocate the table, so we're guaranteed to find an empty slot.
377 */
378 while (mHashTable[ent].name != NULL)
379 ent = (ent + 1) & (mHashTableSize-1);
380
381 mHashTable[ent].name = str;
382 mHashTable[ent].nameLen = strLen;
383}
384
385/*
386 * Find a matching entry.
387 *
388 * Returns 0 if not found.
389 */
390ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const
391{
392 int nameLen = strlen(fileName);
393 unsigned int hash = computeHash(fileName, nameLen);
394 int ent = hash & (mHashTableSize-1);
395
396 while (mHashTable[ent].name != NULL) {
397 if (mHashTable[ent].nameLen == nameLen &&
398 memcmp(mHashTable[ent].name, fileName, nameLen) == 0)
399 {
400 /* match */
Kenny Rootd4066a42010-04-22 18:28:29 -0700401 return (ZipEntryRO)(long)(ent + kZipEntryAdj);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800402 }
403
404 ent = (ent + 1) & (mHashTableSize-1);
405 }
406
407 return NULL;
408}
409
410/*
411 * Find the Nth entry.
412 *
413 * This currently involves walking through the sparse hash table, counting
414 * non-empty entries. If we need to speed this up we can either allocate
415 * a parallel lookup table or (perhaps better) provide an iterator interface.
416 */
417ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const
418{
419 if (idx < 0 || idx >= mNumEntries) {
420 LOGW("Invalid index %d\n", idx);
421 return NULL;
422 }
423
424 for (int ent = 0; ent < mHashTableSize; ent++) {
425 if (mHashTable[ent].name != NULL) {
426 if (idx-- == 0)
427 return (ZipEntryRO) (ent + kZipEntryAdj);
428 }
429 }
430
431 return NULL;
432}
433
434/*
435 * Get the useful fields from the zip entry.
436 *
437 * Returns "false" if the offsets to the fields or the contents of the fields
438 * appear to be bogus.
439 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700440bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
441 size_t* pCompLen, off_t* pOffset, long* pModWhen, long* pCrc32) const
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800442{
Kenny Rootd4066a42010-04-22 18:28:29 -0700443 bool ret = false;
444
445 const int ent = entryToIndex(entry);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800446 if (ent < 0)
447 return false;
448
Kenny Rootd4066a42010-04-22 18:28:29 -0700449 HashEntry hashEntry = mHashTable[ent];
450
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800451 /*
452 * Recover the start of the central directory entry from the filename
Kenny Rootd4066a42010-04-22 18:28:29 -0700453 * pointer. The filename is the first entry past the fixed-size data,
454 * so we can just subtract back from that.
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800455 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700456 const unsigned char* ptr = (const unsigned char*) hashEntry.name;
457 off_t cdOffset = mDirectoryOffset;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800458
459 ptr -= kCDELen;
460
461 int method = get2LE(ptr + kCDEMethod);
462 if (pMethod != NULL)
463 *pMethod = method;
464
465 if (pModWhen != NULL)
466 *pModWhen = get4LE(ptr + kCDEModWhen);
467 if (pCrc32 != NULL)
468 *pCrc32 = get4LE(ptr + kCDECRC);
469
Kenny Rootd4066a42010-04-22 18:28:29 -0700470 size_t compLen = get4LE(ptr + kCDECompLen);
471 if (pCompLen != NULL)
472 *pCompLen = compLen;
473 size_t uncompLen = get4LE(ptr + kCDEUncompLen);
474 if (pUncompLen != NULL)
475 *pUncompLen = uncompLen;
476
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800477 /*
Kenny Rootd4066a42010-04-22 18:28:29 -0700478 * If requested, determine the offset of the start of the data. All we
479 * have is the offset to the Local File Header, which is variable size,
480 * so we have to read the contents of the struct to figure out where
481 * the actual data starts.
482 *
483 * We also need to make sure that the lengths are not so large that
484 * somebody trying to map the compressed or uncompressed data runs
485 * off the end of the mapped region.
486 *
487 * Note we don't verify compLen/uncompLen if they don't request the
488 * dataOffset, because dataOffset is expensive to determine. However,
489 * if they don't have the file offset, they're not likely to be doing
490 * anything with the contents.
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800491 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800492 if (pOffset != NULL) {
Kenny Rootd4066a42010-04-22 18:28:29 -0700493 long localHdrOffset = get4LE(ptr + kCDELocalOffset);
494 if (localHdrOffset + kLFHLen >= cdOffset) {
495 LOGE("ERROR: bad local hdr offset in zip\n");
496 return false;
497 }
498
499 unsigned char lfhBuf[kLFHLen];
Kenny Rootfa989202010-09-24 07:57:37 -0700500
501 {
502 AutoMutex _l(mFdLock);
503
504 if (lseek(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
505 LOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
506 return false;
507 }
508
509 ssize_t actual =
510 TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
511 if (actual != sizeof(lfhBuf)) {
512 LOGW("failed reading lfh from offset %ld\n", localHdrOffset);
513 return false;
514 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700515 }
516
517 if (get4LE(lfhBuf) != kLFHSignature) {
Kenny Roote0573a42010-08-27 12:47:32 -0700518 LOGW("didn't find signature at start of lfh, offset=%ld (got 0x%08lx, expected 0x%08x)\n",
519 localHdrOffset, get4LE(lfhBuf), kLFHSignature);
Kenny Rootd4066a42010-04-22 18:28:29 -0700520 return false;
521 }
522
523 off_t dataOffset = localHdrOffset + kLFHLen
524 + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
525 if (dataOffset >= cdOffset) {
526 LOGW("bad data offset %ld in zip\n", (long) dataOffset);
527 return false;
528 }
529
530 /* check lengths */
531 if ((off_t)(dataOffset + compLen) > cdOffset) {
532 LOGW("bad compressed length in zip (%ld + %zd > %ld)\n",
533 (long) dataOffset, compLen, (long) cdOffset);
534 return false;
535 }
536
537 if (method == kCompressStored &&
538 (off_t)(dataOffset + uncompLen) > cdOffset)
539 {
540 LOGE("ERROR: bad uncompressed length in zip (%ld + %zd > %ld)\n",
541 (long) dataOffset, uncompLen, (long) cdOffset);
542 return false;
543 }
544
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800545 *pOffset = dataOffset;
546 }
Kenny Rootd4066a42010-04-22 18:28:29 -0700547
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800548 return true;
549}
550
551/*
552 * Copy the entry's filename to the buffer.
553 */
554int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen)
555 const
556{
557 int ent = entryToIndex(entry);
558 if (ent < 0)
559 return -1;
560
561 int nameLen = mHashTable[ent].nameLen;
562 if (bufLen < nameLen+1)
563 return nameLen+1;
564
565 memcpy(buffer, mHashTable[ent].name, nameLen);
566 buffer[nameLen] = '\0';
567 return 0;
568}
569
570/*
571 * Create a new FileMap object that spans the data in "entry".
572 */
573FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const
574{
575 /*
576 * TODO: the efficient way to do this is to modify FileMap to allow
577 * sub-regions of a file to be mapped. A reference-counting scheme
578 * can manage the base memory mapping. For now, we just create a brand
579 * new mapping off of the Zip archive file descriptor.
580 */
581
582 FileMap* newMap;
Kenny Rootd4066a42010-04-22 18:28:29 -0700583 size_t compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800584 off_t offset;
585
586 if (!getEntryInfo(entry, NULL, NULL, &compLen, &offset, NULL, NULL))
587 return NULL;
588
589 newMap = new FileMap();
Kenny Rootd4066a42010-04-22 18:28:29 -0700590 if (!newMap->create(mFileName, mFd, offset, compLen, true)) {
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800591 newMap->release();
592 return NULL;
593 }
594
595 return newMap;
596}
597
598/*
599 * Uncompress an entry, in its entirety, into the provided output buffer.
600 *
601 * This doesn't verify the data's CRC, which might be useful for
602 * uncompressed data. The caller should be able to manage it.
603 */
604bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const
605{
Kenny Rootd4066a42010-04-22 18:28:29 -0700606 const size_t kSequentialMin = 32768;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800607 bool result = false;
608 int ent = entryToIndex(entry);
609 if (ent < 0)
610 return -1;
611
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800612 int method;
Kenny Rootd4066a42010-04-22 18:28:29 -0700613 size_t uncompLen, compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800614 off_t offset;
Kenny Rootd4066a42010-04-22 18:28:29 -0700615 const unsigned char* ptr;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800616
617 getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
618
Kenny Rootd4066a42010-04-22 18:28:29 -0700619 FileMap* file = createEntryFileMap(entry);
620 if (file == NULL) {
621 goto bail;
622 }
623
624 ptr = (const unsigned char*) file->getDataPtr();
625
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800626 /*
627 * Experiment with madvise hint. When we want to uncompress a file,
628 * we pull some stuff out of the central dir entry and then hit a
629 * bunch of compressed or uncompressed data sequentially. The CDE
630 * visit will cause a limited amount of read-ahead because it's at
631 * the end of the file. We could end up doing lots of extra disk
632 * access if the file we're prying open is small. Bottom line is we
633 * probably don't want to turn MADV_SEQUENTIAL on and leave it on.
634 *
635 * So, if the compressed size of the file is above a certain minimum
636 * size, temporarily boost the read-ahead in the hope that the extra
637 * pair of system calls are negated by a reduction in page faults.
638 */
639 if (compLen > kSequentialMin)
Kenny Rootd4066a42010-04-22 18:28:29 -0700640 file->advise(FileMap::SEQUENTIAL);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800641
642 if (method == kCompressStored) {
Kenny Rootd4066a42010-04-22 18:28:29 -0700643 memcpy(buffer, ptr, uncompLen);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800644 } else {
Kenny Rootd4066a42010-04-22 18:28:29 -0700645 if (!inflateBuffer(buffer, ptr, uncompLen, compLen))
Kenny Rootdc2d8402010-09-07 19:30:22 -0700646 goto bail;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800647 }
648
649 if (compLen > kSequentialMin)
Kenny Rootd4066a42010-04-22 18:28:29 -0700650 file->advise(FileMap::NORMAL);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800651
652 result = true;
653
654bail:
655 return result;
656}
657
658/*
659 * Uncompress an entry, in its entirety, to an open file descriptor.
660 *
661 * This doesn't verify the data's CRC, but probably should.
662 */
663bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
664{
665 bool result = false;
666 int ent = entryToIndex(entry);
667 if (ent < 0)
668 return -1;
669
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800670 int method;
Kenny Rootd4066a42010-04-22 18:28:29 -0700671 size_t uncompLen, compLen;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800672 off_t offset;
Kenny Rootd4066a42010-04-22 18:28:29 -0700673 const unsigned char* ptr;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800674
675 getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
676
Kenny Rootdc2d8402010-09-07 19:30:22 -0700677 const FileMap* file = createEntryFileMap(entry);
Kenny Rootd4066a42010-04-22 18:28:29 -0700678 if (file == NULL) {
679 goto bail;
680 }
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800681
Kenny Rootd4066a42010-04-22 18:28:29 -0700682 ptr = (const unsigned char*) file->getDataPtr();
683
684 if (method == kCompressStored) {
685 ssize_t actual = write(fd, ptr, uncompLen);
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800686 if (actual < 0) {
687 LOGE("Write failed: %s\n", strerror(errno));
Kenny Rootdc2d8402010-09-07 19:30:22 -0700688 goto bail;
Kenny Rootd4066a42010-04-22 18:28:29 -0700689 } else if ((size_t) actual != uncompLen) {
690 LOGE("Partial write during uncompress (%zd of %zd)\n",
Kenny Root8f20e5e2010-08-04 16:30:40 -0700691 (size_t)actual, (size_t)uncompLen);
Kenny Rootdc2d8402010-09-07 19:30:22 -0700692 goto bail;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800693 } else {
694 LOGI("+++ successful write\n");
695 }
696 } else {
Kenny Rootd4066a42010-04-22 18:28:29 -0700697 if (!inflateBuffer(fd, ptr, uncompLen, compLen))
Kenny Rootdc2d8402010-09-07 19:30:22 -0700698 goto bail;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800699 }
700
701 result = true;
702
703bail:
704 return result;
705}
706
707/*
708 * Uncompress "deflate" data from one buffer to another.
709 */
710/*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf,
Kenny Rootd4066a42010-04-22 18:28:29 -0700711 size_t uncompLen, size_t compLen)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800712{
713 bool result = false;
714 z_stream zstream;
715 int zerr;
716
717 /*
718 * Initialize the zlib stream struct.
719 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700720 memset(&zstream, 0, sizeof(zstream));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800721 zstream.zalloc = Z_NULL;
722 zstream.zfree = Z_NULL;
723 zstream.opaque = Z_NULL;
724 zstream.next_in = (Bytef*)inBuf;
725 zstream.avail_in = compLen;
726 zstream.next_out = (Bytef*) outBuf;
727 zstream.avail_out = uncompLen;
728 zstream.data_type = Z_UNKNOWN;
729
Kenny Rootd4066a42010-04-22 18:28:29 -0700730 /*
731 * Use the undocumented "negative window bits" feature to tell zlib
732 * that there's no zlib header waiting for it.
733 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800734 zerr = inflateInit2(&zstream, -MAX_WBITS);
735 if (zerr != Z_OK) {
736 if (zerr == Z_VERSION_ERROR) {
737 LOGE("Installed zlib is not compatible with linked version (%s)\n",
738 ZLIB_VERSION);
739 } else {
740 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
741 }
742 goto bail;
743 }
744
745 /*
746 * Expand data.
747 */
748 zerr = inflate(&zstream, Z_FINISH);
749 if (zerr != Z_STREAM_END) {
750 LOGW("Zip inflate failed, zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
751 zerr, zstream.next_in, zstream.avail_in,
752 zstream.next_out, zstream.avail_out);
753 goto z_bail;
754 }
755
756 /* paranoia */
Kenny Rootd4066a42010-04-22 18:28:29 -0700757 if (zstream.total_out != uncompLen) {
758 LOGW("Size mismatch on inflated file (%ld vs %zd)\n",
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800759 zstream.total_out, uncompLen);
760 goto z_bail;
761 }
762
763 result = true;
764
765z_bail:
766 inflateEnd(&zstream); /* free up any allocated structures */
767
768bail:
769 return result;
770}
771
772/*
773 * Uncompress "deflate" data from one buffer to an open file descriptor.
774 */
775/*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf,
Kenny Rootd4066a42010-04-22 18:28:29 -0700776 size_t uncompLen, size_t compLen)
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800777{
778 bool result = false;
Kenny Rootd4066a42010-04-22 18:28:29 -0700779 const size_t kWriteBufSize = 32768;
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800780 unsigned char writeBuf[kWriteBufSize];
781 z_stream zstream;
782 int zerr;
783
784 /*
785 * Initialize the zlib stream struct.
786 */
Kenny Rootd4066a42010-04-22 18:28:29 -0700787 memset(&zstream, 0, sizeof(zstream));
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800788 zstream.zalloc = Z_NULL;
789 zstream.zfree = Z_NULL;
790 zstream.opaque = Z_NULL;
791 zstream.next_in = (Bytef*)inBuf;
792 zstream.avail_in = compLen;
793 zstream.next_out = (Bytef*) writeBuf;
794 zstream.avail_out = sizeof(writeBuf);
795 zstream.data_type = Z_UNKNOWN;
796
Kenny Rootd4066a42010-04-22 18:28:29 -0700797 /*
798 * Use the undocumented "negative window bits" feature to tell zlib
799 * that there's no zlib header waiting for it.
800 */
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800801 zerr = inflateInit2(&zstream, -MAX_WBITS);
802 if (zerr != Z_OK) {
803 if (zerr == Z_VERSION_ERROR) {
804 LOGE("Installed zlib is not compatible with linked version (%s)\n",
805 ZLIB_VERSION);
806 } else {
807 LOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
808 }
809 goto bail;
810 }
811
812 /*
813 * Loop while we have more to do.
814 */
815 do {
816 /*
817 * Expand data.
818 */
819 zerr = inflate(&zstream, Z_NO_FLUSH);
820 if (zerr != Z_OK && zerr != Z_STREAM_END) {
821 LOGW("zlib inflate: zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
822 zerr, zstream.next_in, zstream.avail_in,
823 zstream.next_out, zstream.avail_out);
824 goto z_bail;
825 }
826
827 /* write when we're full or when we're done */
828 if (zstream.avail_out == 0 ||
829 (zerr == Z_STREAM_END && zstream.avail_out != sizeof(writeBuf)))
830 {
831 long writeSize = zstream.next_out - writeBuf;
832 int cc = write(fd, writeBuf, writeSize);
833 if (cc != (int) writeSize) {
834 LOGW("write failed in inflate (%d vs %ld)\n", cc, writeSize);
835 goto z_bail;
836 }
837
838 zstream.next_out = writeBuf;
839 zstream.avail_out = sizeof(writeBuf);
840 }
841 } while (zerr == Z_OK);
842
843 assert(zerr == Z_STREAM_END); /* other errors should've been caught */
844
845 /* paranoia */
Kenny Rootd4066a42010-04-22 18:28:29 -0700846 if (zstream.total_out != uncompLen) {
847 LOGW("Size mismatch on inflated file (%ld vs %zd)\n",
The Android Open Source Projectedbf3b62009-03-03 19:31:44 -0800848 zstream.total_out, uncompLen);
849 goto z_bail;
850 }
851
852 result = true;
853
854z_bail:
855 inflateEnd(&zstream); /* free up any allocated structures */
856
857bail:
858 return result;
859}