1 /*
2 * Copyright (C) 2006 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 // Provide access to a read-only asset.
19 //
20
21 #define LOG_TAG "asset"
22 //#define NDEBUG 0
23
24 #include <androidfw/Asset.h>
25 #include <androidfw/StreamingZipInflater.h>
26 #include <androidfw/Util.h>
27 #include <androidfw/ZipFileRO.h>
28 #include <androidfw/ZipUtils.h>
29 #include <cutils/atomic.h>
30 #include <utils/FileMap.h>
31 #include <utils/Log.h>
32 #include <utils/threads.h>
33
34 #include <assert.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <memory.h>
38 #include <string.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <unistd.h>
42
43 using namespace android;
44
45 #ifndef O_BINARY
46 # define O_BINARY 0
47 #endif
48
49 static const bool kIsDebug = false;
50
51 static Mutex gAssetLock;
52 static int32_t gCount = 0;
53 static Asset* gHead = NULL;
54 static Asset* gTail = NULL;
55
registerAsset(Asset * asset)56 void Asset::registerAsset(Asset* asset)
57 {
58 AutoMutex _l(gAssetLock);
59 gCount++;
60 asset->mNext = asset->mPrev = NULL;
61 if (gTail == NULL) {
62 gHead = gTail = asset;
63 } else {
64 asset->mPrev = gTail;
65 gTail->mNext = asset;
66 gTail = asset;
67 }
68
69 if (kIsDebug) {
70 ALOGI("Creating Asset %p #%d\n", asset, gCount);
71 }
72 }
73
unregisterAsset(Asset * asset)74 void Asset::unregisterAsset(Asset* asset)
75 {
76 AutoMutex _l(gAssetLock);
77 gCount--;
78 if (gHead == asset) {
79 gHead = asset->mNext;
80 }
81 if (gTail == asset) {
82 gTail = asset->mPrev;
83 }
84 if (asset->mNext != NULL) {
85 asset->mNext->mPrev = asset->mPrev;
86 }
87 if (asset->mPrev != NULL) {
88 asset->mPrev->mNext = asset->mNext;
89 }
90 asset->mNext = asset->mPrev = NULL;
91
92 if (kIsDebug) {
93 ALOGI("Destroying Asset in %p #%d\n", asset, gCount);
94 }
95 }
96
getGlobalCount()97 int32_t Asset::getGlobalCount()
98 {
99 AutoMutex _l(gAssetLock);
100 return gCount;
101 }
102
getAssetAllocations()103 String8 Asset::getAssetAllocations()
104 {
105 AutoMutex _l(gAssetLock);
106 String8 res;
107 Asset* cur = gHead;
108 while (cur != NULL) {
109 if (cur->isAllocated()) {
110 res.append(" ");
111 res.append(cur->getAssetSource());
112 off64_t size = (cur->getLength()+512)/1024;
113 char buf[64];
114 snprintf(buf, sizeof(buf), ": %dK\n", (int)size);
115 res.append(buf);
116 }
117 cur = cur->mNext;
118 }
119
120 return res;
121 }
122
Asset(void)123 Asset::Asset(void)
124 : mAccessMode(ACCESS_UNKNOWN), mNext(NULL), mPrev(NULL)
125 {
126 }
127
128 /*
129 * Create a new Asset from a file on disk. There is a fair chance that
130 * the file doesn't actually exist.
131 *
132 * We can use "mode" to decide how we want to go about it.
133 */
createFromFile(const char * fileName,AccessMode mode)134 /*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
135 {
136 return createFromFd(open(fileName, O_RDONLY | O_BINARY), fileName, mode);
137 }
138
139 /*
140 * Create a new Asset from a file on disk. There is a fair chance that
141 * the file doesn't actually exist.
142 *
143 * We can use "mode" to decide how we want to go about it.
144 */
createFromFd(const int fd,const char * fileName,AccessMode mode)145 /*static*/ Asset* Asset::createFromFd(const int fd, const char* fileName, AccessMode mode)
146 {
147 if (fd < 0) {
148 return NULL;
149 }
150
151 _FileAsset* pAsset;
152 status_t result;
153 off64_t length;
154
155 /*
156 * Under Linux, the lseek fails if we actually opened a directory. To
157 * be correct we should test the file type explicitly, but since we
158 * always open things read-only it doesn't really matter, so there's
159 * no value in incurring the extra overhead of an fstat() call.
160 */
161 // TODO(kroot): replace this with fstat despite the plea above.
162 #if 1
163 length = lseek64(fd, 0, SEEK_END);
164 if (length < 0) {
165 ::close(fd);
166 return NULL;
167 }
168 (void) lseek64(fd, 0, SEEK_SET);
169 #else
170 struct stat st;
171 if (fstat(fd, &st) < 0) {
172 ::close(fd);
173 return NULL;
174 }
175
176 if (!S_ISREG(st.st_mode)) {
177 ::close(fd);
178 return NULL;
179 }
180 #endif
181
182 pAsset = new _FileAsset;
183 result = pAsset->openChunk(fileName, fd, 0, length);
184 if (result != NO_ERROR) {
185 delete pAsset;
186 return NULL;
187 }
188
189 pAsset->mAccessMode = mode;
190 return pAsset;
191 }
192
193
194 /*
195 * Create a new Asset from a compressed file on disk. There is a fair chance
196 * that the file doesn't actually exist.
197 *
198 * We currently support gzip files. We might want to handle .bz2 someday.
199 */
createFromCompressedFile(const char * fileName,AccessMode mode)200 /*static*/ Asset* Asset::createFromCompressedFile(const char* fileName,
201 AccessMode mode)
202 {
203 _CompressedAsset* pAsset;
204 status_t result;
205 off64_t fileLen;
206 bool scanResult;
207 long offset;
208 int method;
209 long uncompressedLen, compressedLen;
210 int fd;
211
212 fd = open(fileName, O_RDONLY | O_BINARY);
213 if (fd < 0)
214 return NULL;
215
216 fileLen = lseek(fd, 0, SEEK_END);
217 if (fileLen < 0) {
218 ::close(fd);
219 return NULL;
220 }
221 (void) lseek(fd, 0, SEEK_SET);
222
223 /* want buffered I/O for the file scan; must dup so fclose() is safe */
224 FILE* fp = fdopen(dup(fd), "rb");
225 if (fp == NULL) {
226 ::close(fd);
227 return NULL;
228 }
229
230 unsigned long crc32;
231 scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
232 &compressedLen, &crc32);
233 offset = ftell(fp);
234 fclose(fp);
235 if (!scanResult) {
236 ALOGD("File '%s' is not in gzip format\n", fileName);
237 ::close(fd);
238 return NULL;
239 }
240
241 pAsset = new _CompressedAsset;
242 result = pAsset->openChunk(fd, offset, method, uncompressedLen,
243 compressedLen);
244 if (result != NO_ERROR) {
245 delete pAsset;
246 return NULL;
247 }
248
249 pAsset->mAccessMode = mode;
250 return pAsset;
251 }
252
253
254 #if 0
255 /*
256 * Create a new Asset from part of an open file.
257 */
258 /*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
259 size_t length, AccessMode mode)
260 {
261 _FileAsset* pAsset;
262 status_t result;
263
264 pAsset = new _FileAsset;
265 result = pAsset->openChunk(NULL, fd, offset, length);
266 if (result != NO_ERROR) {
267 delete pAsset;
268 return NULL;
269 }
270
271 pAsset->mAccessMode = mode;
272 return pAsset;
273 }
274
275 /*
276 * Create a new Asset from compressed data in an open file.
277 */
278 /*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
279 int compressionMethod, size_t uncompressedLen, size_t compressedLen,
280 AccessMode mode)
281 {
282 _CompressedAsset* pAsset;
283 status_t result;
284
285 pAsset = new _CompressedAsset;
286 result = pAsset->openChunk(fd, offset, compressionMethod,
287 uncompressedLen, compressedLen);
288 if (result != NO_ERROR) {
289 delete pAsset;
290 return NULL;
291 }
292
293 pAsset->mAccessMode = mode;
294 return pAsset;
295 }
296 #endif
297
298 /*
299 * Create a new Asset from a memory mapping.
300 */
createFromUncompressedMap(FileMap * dataMap,AccessMode mode)301 /*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap, AccessMode mode)
302 {
303 _FileAsset* pAsset;
304 status_t result;
305
306 pAsset = new _FileAsset;
307 result = pAsset->openChunk(dataMap, base::unique_fd(-1));
308 if (result != NO_ERROR) {
309 delete pAsset;
310 return NULL;
311 }
312
313 pAsset->mAccessMode = mode;
314 return pAsset;
315 }
316
createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,base::unique_fd fd,AccessMode mode)317 /*static*/ std::unique_ptr<Asset> Asset::createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
318 base::unique_fd fd, AccessMode mode)
319 {
320 std::unique_ptr<_FileAsset> pAsset = util::make_unique<_FileAsset>();
321
322 status_t result = pAsset->openChunk(dataMap.get(), std::move(fd));
323 if (result != NO_ERROR) {
324 return NULL;
325 }
326
327 // We succeeded, so relinquish control of dataMap
328 (void) dataMap.release();
329 pAsset->mAccessMode = mode;
330 return std::move(pAsset);
331 }
332
333 /*
334 * Create a new Asset from compressed data in a memory mapping.
335 */
createFromCompressedMap(FileMap * dataMap,size_t uncompressedLen,AccessMode mode)336 /*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
337 size_t uncompressedLen, AccessMode mode)
338 {
339 _CompressedAsset* pAsset;
340 status_t result;
341
342 pAsset = new _CompressedAsset;
343 result = pAsset->openChunk(dataMap, uncompressedLen);
344 if (result != NO_ERROR) {
345 delete pAsset;
346 return NULL;
347 }
348
349 pAsset->mAccessMode = mode;
350 return pAsset;
351 }
352
createFromCompressedMap(std::unique_ptr<FileMap> dataMap,size_t uncompressedLen,AccessMode mode)353 /*static*/ std::unique_ptr<Asset> Asset::createFromCompressedMap(std::unique_ptr<FileMap> dataMap,
354 size_t uncompressedLen, AccessMode mode)
355 {
356 std::unique_ptr<_CompressedAsset> pAsset = util::make_unique<_CompressedAsset>();
357
358 status_t result = pAsset->openChunk(dataMap.get(), uncompressedLen);
359 if (result != NO_ERROR) {
360 return NULL;
361 }
362
363 // We succeeded, so relinquish control of dataMap
364 (void) dataMap.release();
365 pAsset->mAccessMode = mode;
366 return std::move(pAsset);
367 }
368
369 /*
370 * Do generic seek() housekeeping. Pass in the offset/whence values from
371 * the seek request, along with the current chunk offset and the chunk
372 * length.
373 *
374 * Returns the new chunk offset, or -1 if the seek is illegal.
375 */
handleSeek(off64_t offset,int whence,off64_t curPosn,off64_t maxPosn)376 off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn)
377 {
378 off64_t newOffset;
379
380 switch (whence) {
381 case SEEK_SET:
382 newOffset = offset;
383 break;
384 case SEEK_CUR:
385 newOffset = curPosn + offset;
386 break;
387 case SEEK_END:
388 newOffset = maxPosn + offset;
389 break;
390 default:
391 ALOGW("unexpected whence %d\n", whence);
392 // this was happening due to an off64_t size mismatch
393 assert(false);
394 return (off64_t) -1;
395 }
396
397 if (newOffset < 0 || newOffset > maxPosn) {
398 ALOGW("seek out of range: want %ld, end=%ld\n",
399 (long) newOffset, (long) maxPosn);
400 return (off64_t) -1;
401 }
402
403 return newOffset;
404 }
405
406
407 /*
408 * ===========================================================================
409 * _FileAsset
410 * ===========================================================================
411 */
412
413 /*
414 * Constructor.
415 */
_FileAsset(void)416 _FileAsset::_FileAsset(void)
417 : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mFd(-1), mMap(NULL), mBuf(NULL)
418 {
419 // Register the Asset with the global list here after it is fully constructed and its
420 // vtable pointer points to this concrete type. b/31113965
421 registerAsset(this);
422 }
423
424 /*
425 * Destructor. Release resources.
426 */
~_FileAsset(void)427 _FileAsset::~_FileAsset(void)
428 {
429 close();
430
431 // Unregister the Asset from the global list here before it is destructed and while its vtable
432 // pointer still points to this concrete type. b/31113965
433 unregisterAsset(this);
434 }
435
436 /*
437 * Operate on a chunk of an uncompressed file.
438 *
439 * Zero-length chunks are allowed.
440 */
openChunk(const char * fileName,int fd,off64_t offset,size_t length)441 status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
442 {
443 assert(mFp == NULL); // no reopen
444 assert(mMap == NULL);
445 assert(fd >= 0);
446 assert(offset >= 0);
447
448 /*
449 * Seek to end to get file length.
450 */
451 off64_t fileLength;
452 fileLength = lseek64(fd, 0, SEEK_END);
453 if (fileLength == (off64_t) -1) {
454 // probably a bad file descriptor
455 ALOGD("failed lseek (errno=%d)\n", errno);
456 return UNKNOWN_ERROR;
457 }
458
459 if ((off64_t) (offset + length) > fileLength) {
460 ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
461 (long) offset, (long) length, (long) fileLength);
462 return BAD_INDEX;
463 }
464
465 /* after fdopen, the fd will be closed on fclose() */
466 mFp = fdopen(fd, "rb");
467 if (mFp == NULL)
468 return UNKNOWN_ERROR;
469
470 mStart = offset;
471 mLength = length;
472 assert(mOffset == 0);
473
474 /* seek the FILE* to the start of chunk */
475 if (fseek(mFp, mStart, SEEK_SET) != 0) {
476 assert(false);
477 }
478
479 mFileName = fileName != NULL ? strdup(fileName) : NULL;
480
481 return NO_ERROR;
482 }
483
484 /*
485 * Create the chunk from the map.
486 */
openChunk(FileMap * dataMap,base::unique_fd fd)487 status_t _FileAsset::openChunk(FileMap* dataMap, base::unique_fd fd)
488 {
489 assert(mFp == NULL); // no reopen
490 assert(mMap == NULL);
491 assert(dataMap != NULL);
492
493 mMap = dataMap;
494 mStart = -1; // not used
495 mLength = dataMap->getDataLength();
496 mFd = std::move(fd);
497 assert(mOffset == 0);
498
499 return NO_ERROR;
500 }
501
502 /*
503 * Read a chunk of data.
504 */
read(void * buf,size_t count)505 ssize_t _FileAsset::read(void* buf, size_t count)
506 {
507 size_t maxLen;
508 size_t actual;
509
510 assert(mOffset >= 0 && mOffset <= mLength);
511
512 if (getAccessMode() == ACCESS_BUFFER) {
513 /*
514 * On first access, read or map the entire file. The caller has
515 * requested buffer access, either because they're going to be
516 * using the buffer or because what they're doing has appropriate
517 * performance needs and access patterns.
518 */
519 if (mBuf == NULL)
520 getBuffer(false);
521 }
522
523 /* adjust count if we're near EOF */
524 maxLen = mLength - mOffset;
525 if (count > maxLen)
526 count = maxLen;
527
528 if (!count)
529 return 0;
530
531 if (mMap != NULL) {
532 /* copy from mapped area */
533 //printf("map read\n");
534 memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
535 actual = count;
536 } else if (mBuf != NULL) {
537 /* copy from buffer */
538 //printf("buf read\n");
539 memcpy(buf, (char*)mBuf + mOffset, count);
540 actual = count;
541 } else {
542 /* read from the file */
543 //printf("file read\n");
544 if (ftell(mFp) != mStart + mOffset) {
545 ALOGE("Hosed: %ld != %ld+%ld\n",
546 ftell(mFp), (long) mStart, (long) mOffset);
547 assert(false);
548 }
549
550 /*
551 * This returns 0 on error or eof. We need to use ferror() or feof()
552 * to tell the difference, but we don't currently have those on the
553 * device. However, we know how much data is *supposed* to be in the
554 * file, so if we don't read the full amount we know something is
555 * hosed.
556 */
557 actual = fread(buf, 1, count, mFp);
558 if (actual == 0) // something failed -- I/O error?
559 return -1;
560
561 assert(actual == count);
562 }
563
564 mOffset += actual;
565 return actual;
566 }
567
568 /*
569 * Seek to a new position.
570 */
seek(off64_t offset,int whence)571 off64_t _FileAsset::seek(off64_t offset, int whence)
572 {
573 off64_t newPosn;
574 off64_t actualOffset;
575
576 // compute new position within chunk
577 newPosn = handleSeek(offset, whence, mOffset, mLength);
578 if (newPosn == (off64_t) -1)
579 return newPosn;
580
581 actualOffset = mStart + newPosn;
582
583 if (mFp != NULL) {
584 if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
585 return (off64_t) -1;
586 }
587
588 mOffset = actualOffset - mStart;
589 return mOffset;
590 }
591
592 /*
593 * Close the asset.
594 */
close(void)595 void _FileAsset::close(void)
596 {
597 if (mMap != NULL) {
598 delete mMap;
599 mMap = NULL;
600 }
601 if (mBuf != NULL) {
602 delete[] mBuf;
603 mBuf = NULL;
604 }
605
606 if (mFileName != NULL) {
607 free(mFileName);
608 mFileName = NULL;
609 }
610
611 if (mFp != NULL) {
612 // can only be NULL when called from destructor
613 // (otherwise we would never return this object)
614 fclose(mFp);
615 mFp = NULL;
616 }
617 }
618
619 /*
620 * Return a read-only pointer to a buffer.
621 *
622 * We can either read the whole thing in or map the relevant piece of
623 * the source file. Ideally a map would be established at a higher
624 * level and we'd be using a different object, but we didn't, so we
625 * deal with it here.
626 */
getBuffer(bool wordAligned)627 const void* _FileAsset::getBuffer(bool wordAligned)
628 {
629 /* subsequent requests just use what we did previously */
630 if (mBuf != NULL)
631 return mBuf;
632 if (mMap != NULL) {
633 if (!wordAligned) {
634 return mMap->getDataPtr();
635 }
636 return ensureAlignment(mMap);
637 }
638
639 assert(mFp != NULL);
640
641 if (mLength < kReadVsMapThreshold) {
642 unsigned char* buf;
643 long allocLen;
644
645 /* zero-length files are allowed; not sure about zero-len allocs */
646 /* (works fine with gcc + x86linux) */
647 allocLen = mLength;
648 if (mLength == 0)
649 allocLen = 1;
650
651 buf = new unsigned char[allocLen];
652 if (buf == NULL) {
653 ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
654 return NULL;
655 }
656
657 ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
658 if (mLength > 0) {
659 long oldPosn = ftell(mFp);
660 fseek(mFp, mStart, SEEK_SET);
661 if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
662 ALOGE("failed reading %ld bytes\n", (long) mLength);
663 delete[] buf;
664 return NULL;
665 }
666 fseek(mFp, oldPosn, SEEK_SET);
667 }
668
669 ALOGV(" getBuffer: loaded into buffer\n");
670
671 mBuf = buf;
672 return mBuf;
673 } else {
674 FileMap* map;
675
676 map = new FileMap;
677 if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
678 delete map;
679 return NULL;
680 }
681
682 ALOGV(" getBuffer: mapped\n");
683
684 mMap = map;
685 if (!wordAligned) {
686 return mMap->getDataPtr();
687 }
688 return ensureAlignment(mMap);
689 }
690 }
691
openFileDescriptor(off64_t * outStart,off64_t * outLength) const692 int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
693 {
694 if (mMap != NULL) {
695 if (mFd.ok()) {
696 *outStart = mMap->getDataOffset();
697 *outLength = mMap->getDataLength();
698 const int fd = dup(mFd);
699 if (fd < 0) {
700 ALOGE("Unable to dup fd (%d).", mFd.get());
701 return -1;
702 }
703 lseek64(fd, 0, SEEK_SET);
704 return fd;
705 }
706 const char* fname = mMap->getFileName();
707 if (fname == NULL) {
708 fname = mFileName;
709 }
710 if (fname == NULL) {
711 return -1;
712 }
713 *outStart = mMap->getDataOffset();
714 *outLength = mMap->getDataLength();
715 return open(fname, O_RDONLY | O_BINARY);
716 }
717 if (mFileName == NULL) {
718 return -1;
719 }
720 *outStart = mStart;
721 *outLength = mLength;
722 return open(mFileName, O_RDONLY | O_BINARY);
723 }
724
ensureAlignment(FileMap * map)725 const void* _FileAsset::ensureAlignment(FileMap* map)
726 {
727 void* data = map->getDataPtr();
728 if ((((size_t)data)&0x3) == 0) {
729 // We can return this directly if it is aligned on a word
730 // boundary.
731 ALOGV("Returning aligned FileAsset %p (%s).", this,
732 getAssetSource());
733 return data;
734 }
735 // If not aligned on a word boundary, then we need to copy it into
736 // our own buffer.
737 ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
738 getAssetSource(), (int)mLength);
739 unsigned char* buf = new unsigned char[mLength];
740 if (buf == NULL) {
741 ALOGE("alloc of %ld bytes failed\n", (long) mLength);
742 return NULL;
743 }
744 memcpy(buf, data, mLength);
745 mBuf = buf;
746 return buf;
747 }
748
749 /*
750 * ===========================================================================
751 * _CompressedAsset
752 * ===========================================================================
753 */
754
755 /*
756 * Constructor.
757 */
_CompressedAsset(void)758 _CompressedAsset::_CompressedAsset(void)
759 : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
760 mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL)
761 {
762 // Register the Asset with the global list here after it is fully constructed and its
763 // vtable pointer points to this concrete type. b/31113965
764 registerAsset(this);
765 }
766
767 /*
768 * Destructor. Release resources.
769 */
~_CompressedAsset(void)770 _CompressedAsset::~_CompressedAsset(void)
771 {
772 close();
773
774 // Unregister the Asset from the global list here before it is destructed and while its vtable
775 // pointer still points to this concrete type. b/31113965
776 unregisterAsset(this);
777 }
778
779 /*
780 * Open a chunk of compressed data inside a file.
781 *
782 * This currently just sets up some values and returns. On the first
783 * read, we expand the entire file into a buffer and return data from it.
784 */
openChunk(int fd,off64_t offset,int compressionMethod,size_t uncompressedLen,size_t compressedLen)785 status_t _CompressedAsset::openChunk(int fd, off64_t offset,
786 int compressionMethod, size_t uncompressedLen, size_t compressedLen)
787 {
788 assert(mFd < 0); // no re-open
789 assert(mMap == NULL);
790 assert(fd >= 0);
791 assert(offset >= 0);
792 assert(compressedLen > 0);
793
794 if (compressionMethod != ZipFileRO::kCompressDeflated) {
795 assert(false);
796 return UNKNOWN_ERROR;
797 }
798
799 mStart = offset;
800 mCompressedLen = compressedLen;
801 mUncompressedLen = uncompressedLen;
802 assert(mOffset == 0);
803 mFd = fd;
804 assert(mBuf == NULL);
805
806 if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
807 mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
808 }
809
810 return NO_ERROR;
811 }
812
813 /*
814 * Open a chunk of compressed data in a mapped region.
815 *
816 * Nothing is expanded until the first read call.
817 */
openChunk(FileMap * dataMap,size_t uncompressedLen)818 status_t _CompressedAsset::openChunk(FileMap* dataMap, size_t uncompressedLen)
819 {
820 assert(mFd < 0); // no re-open
821 assert(mMap == NULL);
822 assert(dataMap != NULL);
823
824 mMap = dataMap;
825 mStart = -1; // not used
826 mCompressedLen = dataMap->getDataLength();
827 mUncompressedLen = uncompressedLen;
828 assert(mOffset == 0);
829
830 if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
831 mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
832 }
833 return NO_ERROR;
834 }
835
836 /*
837 * Read data from a chunk of compressed data.
838 *
839 * [For now, that's just copying data out of a buffer.]
840 */
read(void * buf,size_t count)841 ssize_t _CompressedAsset::read(void* buf, size_t count)
842 {
843 size_t maxLen;
844 size_t actual;
845
846 assert(mOffset >= 0 && mOffset <= mUncompressedLen);
847
848 /* If we're relying on a streaming inflater, go through that */
849 if (mZipInflater) {
850 actual = mZipInflater->read(buf, count);
851 } else {
852 if (mBuf == NULL) {
853 if (getBuffer(false) == NULL)
854 return -1;
855 }
856 assert(mBuf != NULL);
857
858 /* adjust count if we're near EOF */
859 maxLen = mUncompressedLen - mOffset;
860 if (count > maxLen)
861 count = maxLen;
862
863 if (!count)
864 return 0;
865
866 /* copy from buffer */
867 //printf("comp buf read\n");
868 memcpy(buf, (char*)mBuf + mOffset, count);
869 actual = count;
870 }
871
872 mOffset += actual;
873 return actual;
874 }
875
876 /*
877 * Handle a seek request.
878 *
879 * If we're working in a streaming mode, this is going to be fairly
880 * expensive, because it requires plowing through a bunch of compressed
881 * data.
882 */
seek(off64_t offset,int whence)883 off64_t _CompressedAsset::seek(off64_t offset, int whence)
884 {
885 off64_t newPosn;
886
887 // compute new position within chunk
888 newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
889 if (newPosn == (off64_t) -1)
890 return newPosn;
891
892 if (mZipInflater) {
893 mZipInflater->seekAbsolute(newPosn);
894 }
895 mOffset = newPosn;
896 return mOffset;
897 }
898
899 /*
900 * Close the asset.
901 */
close(void)902 void _CompressedAsset::close(void)
903 {
904 if (mMap != NULL) {
905 delete mMap;
906 mMap = NULL;
907 }
908
909 delete[] mBuf;
910 mBuf = NULL;
911
912 delete mZipInflater;
913 mZipInflater = NULL;
914
915 if (mFd > 0) {
916 ::close(mFd);
917 mFd = -1;
918 }
919 }
920
921 /*
922 * Get a pointer to a read-only buffer of data.
923 *
924 * The first time this is called, we expand the compressed data into a
925 * buffer.
926 */
getBuffer(bool)927 const void* _CompressedAsset::getBuffer(bool)
928 {
929 unsigned char* buf = NULL;
930
931 if (mBuf != NULL)
932 return mBuf;
933
934 /*
935 * Allocate a buffer and read the file into it.
936 */
937 buf = new unsigned char[mUncompressedLen];
938 if (buf == NULL) {
939 ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
940 goto bail;
941 }
942
943 if (mMap != NULL) {
944 if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
945 mUncompressedLen, mCompressedLen))
946 goto bail;
947 } else {
948 assert(mFd >= 0);
949
950 /*
951 * Seek to the start of the compressed data.
952 */
953 if (lseek(mFd, mStart, SEEK_SET) != mStart)
954 goto bail;
955
956 /*
957 * Expand the data into it.
958 */
959 if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
960 mCompressedLen))
961 goto bail;
962 }
963
964 /*
965 * Success - now that we have the full asset in RAM we
966 * no longer need the streaming inflater
967 */
968 delete mZipInflater;
969 mZipInflater = NULL;
970
971 mBuf = buf;
972 buf = NULL;
973
974 bail:
975 delete[] buf;
976 return mBuf;
977 }
978
979