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(incfs::IncFsFileMap && dataMap,AccessMode mode,base::unique_fd fd)301 /*static*/ std::unique_ptr<Asset> Asset::createFromUncompressedMap(incfs::IncFsFileMap&& dataMap,
302 AccessMode mode,
303 base::unique_fd fd)
304 {
305 auto pAsset = util::make_unique<_FileAsset>();
306
307 status_t result = pAsset->openChunk(std::move(dataMap), std::move(fd));
308 if (result != NO_ERROR) {
309 return NULL;
310 }
311
312 // We succeeded, so relinquish control of dataMap
313 pAsset->mAccessMode = mode;
314 return std::move(pAsset);
315 }
316
317 /*
318 * Create a new Asset from compressed data in a memory mapping.
319 */
createFromCompressedMap(incfs::IncFsFileMap && dataMap,size_t uncompressedLen,AccessMode mode)320 /*static*/ std::unique_ptr<Asset> Asset::createFromCompressedMap(incfs::IncFsFileMap&& dataMap,
321 size_t uncompressedLen,
322 AccessMode mode)
323 {
324 auto pAsset = util::make_unique<_CompressedAsset>();
325
326 status_t result = pAsset->openChunk(std::move(dataMap), uncompressedLen);
327 if (result != NO_ERROR) {
328 return NULL;
329 }
330
331 // We succeeded, so relinquish control of dataMap
332 pAsset->mAccessMode = mode;
333 return std::move(pAsset);
334 }
335
336 /*
337 * Do generic seek() housekeeping. Pass in the offset/whence values from
338 * the seek request, along with the current chunk offset and the chunk
339 * length.
340 *
341 * Returns the new chunk offset, or -1 if the seek is illegal.
342 */
handleSeek(off64_t offset,int whence,off64_t curPosn,off64_t maxPosn)343 off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn)
344 {
345 off64_t newOffset;
346
347 switch (whence) {
348 case SEEK_SET:
349 newOffset = offset;
350 break;
351 case SEEK_CUR:
352 newOffset = curPosn + offset;
353 break;
354 case SEEK_END:
355 newOffset = maxPosn + offset;
356 break;
357 default:
358 ALOGW("unexpected whence %d\n", whence);
359 // this was happening due to an off64_t size mismatch
360 assert(false);
361 return (off64_t) -1;
362 }
363
364 if (newOffset < 0 || newOffset > maxPosn) {
365 ALOGW("seek out of range: want %ld, end=%ld\n",
366 (long) newOffset, (long) maxPosn);
367 return (off64_t) -1;
368 }
369
370 return newOffset;
371 }
372
373
374 /*
375 * ===========================================================================
376 * _FileAsset
377 * ===========================================================================
378 */
379
380 /*
381 * Constructor.
382 */
_FileAsset(void)383 _FileAsset::_FileAsset(void)
384 : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mFd(-1), mBuf(NULL)
385 {
386 // Register the Asset with the global list here after it is fully constructed and its
387 // vtable pointer points to this concrete type. b/31113965
388 registerAsset(this);
389 }
390
391 /*
392 * Destructor. Release resources.
393 */
~_FileAsset(void)394 _FileAsset::~_FileAsset(void)
395 {
396 close();
397
398 // Unregister the Asset from the global list here before it is destructed and while its vtable
399 // pointer still points to this concrete type. b/31113965
400 unregisterAsset(this);
401 }
402
403 /*
404 * Operate on a chunk of an uncompressed file.
405 *
406 * Zero-length chunks are allowed.
407 */
openChunk(const char * fileName,int fd,off64_t offset,size_t length)408 status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
409 {
410 assert(mFp == NULL); // no reopen
411 assert(!mMap.has_value());
412 assert(fd >= 0);
413 assert(offset >= 0);
414
415 /*
416 * Seek to end to get file length.
417 */
418 off64_t fileLength;
419 fileLength = lseek64(fd, 0, SEEK_END);
420 if (fileLength == (off64_t) -1) {
421 // probably a bad file descriptor
422 ALOGD("failed lseek (errno=%d)\n", errno);
423 return UNKNOWN_ERROR;
424 }
425
426 if ((off64_t) (offset + length) > fileLength) {
427 ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
428 (long) offset, (long) length, (long) fileLength);
429 return BAD_INDEX;
430 }
431
432 /* after fdopen, the fd will be closed on fclose() */
433 mFp = fdopen(fd, "rb");
434 if (mFp == NULL)
435 return UNKNOWN_ERROR;
436
437 mStart = offset;
438 mLength = length;
439 assert(mOffset == 0);
440
441 /* seek the FILE* to the start of chunk */
442 if (fseek(mFp, mStart, SEEK_SET) != 0) {
443 assert(false);
444 }
445
446 mFileName = fileName != NULL ? strdup(fileName) : NULL;
447
448 return NO_ERROR;
449 }
450
451 /*
452 * Create the chunk from the map.
453 */
openChunk(incfs::IncFsFileMap && dataMap,base::unique_fd fd)454 status_t _FileAsset::openChunk(incfs::IncFsFileMap&& dataMap, base::unique_fd fd)
455 {
456 assert(mFp == NULL); // no reopen
457 assert(!mMap.has_value());
458 assert(dataMap != NULL);
459
460 mMap = std::move(dataMap);
461 mStart = -1; // not used
462 mLength = mMap->length();
463 mFd = std::move(fd);
464 assert(mOffset == 0);
465
466 return NO_ERROR;
467 }
468
469 /*
470 * Read a chunk of data.
471 */
read(void * buf,size_t count)472 ssize_t _FileAsset::read(void* buf, size_t count)
473 {
474 size_t maxLen;
475 size_t actual;
476
477 assert(mOffset >= 0 && mOffset <= mLength);
478
479 if (getAccessMode() == ACCESS_BUFFER) {
480 /*
481 * On first access, read or map the entire file. The caller has
482 * requested buffer access, either because they're going to be
483 * using the buffer or because what they're doing has appropriate
484 * performance needs and access patterns.
485 */
486 if (mBuf == NULL)
487 getBuffer(false);
488 }
489
490 /* adjust count if we're near EOF */
491 maxLen = mLength - mOffset;
492 if (count > maxLen)
493 count = maxLen;
494
495 if (!count)
496 return 0;
497
498 if (mMap.has_value()) {
499 /* copy from mapped area */
500 //printf("map read\n");
501 const auto readPos = mMap->data().offset(mOffset).convert<char>();
502 if (!readPos.verify(count)) {
503 return -1;
504 }
505
506 memcpy(buf, readPos.unsafe_ptr(), count);
507 actual = count;
508 } else if (mBuf != NULL) {
509 /* copy from buffer */
510 //printf("buf read\n");
511 memcpy(buf, (char*)mBuf + mOffset, count);
512 actual = count;
513 } else {
514 /* read from the file */
515 //printf("file read\n");
516 if (ftell(mFp) != mStart + mOffset) {
517 ALOGE("Hosed: %ld != %ld+%ld\n",
518 ftell(mFp), (long) mStart, (long) mOffset);
519 assert(false);
520 }
521
522 /*
523 * This returns 0 on error or eof. We need to use ferror() or feof()
524 * to tell the difference, but we don't currently have those on the
525 * device. However, we know how much data is *supposed* to be in the
526 * file, so if we don't read the full amount we know something is
527 * hosed.
528 */
529 actual = fread(buf, 1, count, mFp);
530 if (actual == 0) // something failed -- I/O error?
531 return -1;
532
533 assert(actual == count);
534 }
535
536 mOffset += actual;
537 return actual;
538 }
539
540 /*
541 * Seek to a new position.
542 */
seek(off64_t offset,int whence)543 off64_t _FileAsset::seek(off64_t offset, int whence)
544 {
545 off64_t newPosn;
546 off64_t actualOffset;
547
548 // compute new position within chunk
549 newPosn = handleSeek(offset, whence, mOffset, mLength);
550 if (newPosn == (off64_t) -1)
551 return newPosn;
552
553 actualOffset = mStart + newPosn;
554
555 if (mFp != NULL) {
556 if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
557 return (off64_t) -1;
558 }
559
560 mOffset = actualOffset - mStart;
561 return mOffset;
562 }
563
564 /*
565 * Close the asset.
566 */
close(void)567 void _FileAsset::close(void)
568 {
569 if (mBuf != NULL) {
570 delete[] mBuf;
571 mBuf = NULL;
572 }
573
574 if (mFileName != NULL) {
575 free(mFileName);
576 mFileName = NULL;
577 }
578
579 if (mFp != NULL) {
580 // can only be NULL when called from destructor
581 // (otherwise we would never return this object)
582 fclose(mFp);
583 mFp = NULL;
584 }
585 }
586
587 /*
588 * Return a read-only pointer to a buffer.
589 *
590 * We can either read the whole thing in or map the relevant piece of
591 * the source file. Ideally a map would be established at a higher
592 * level and we'd be using a different object, but we didn't, so we
593 * deal with it here.
594 */
getBuffer(bool aligned)595 const void* _FileAsset::getBuffer(bool aligned)
596 {
597 auto buffer = getIncFsBuffer(aligned);
598 if (mBuf != NULL)
599 return mBuf;
600 if (!buffer.convert<uint8_t>().verify(mLength))
601 return NULL;
602 return buffer.unsafe_ptr();
603 }
604
getIncFsBuffer(bool aligned)605 incfs::map_ptr<void> _FileAsset::getIncFsBuffer(bool aligned)
606 {
607 /* subsequent requests just use what we did previously */
608 if (mBuf != NULL)
609 return mBuf;
610 if (mMap.has_value()) {
611 if (!aligned) {
612 return mMap->data();
613 }
614 return ensureAlignment(*mMap);
615 }
616
617 assert(mFp != NULL);
618
619 if (mLength < kReadVsMapThreshold) {
620 unsigned char* buf;
621 long allocLen;
622
623 /* zero-length files are allowed; not sure about zero-len allocs */
624 /* (works fine with gcc + x86linux) */
625 allocLen = mLength;
626 if (mLength == 0)
627 allocLen = 1;
628
629 buf = new unsigned char[allocLen];
630 if (buf == NULL) {
631 ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
632 return NULL;
633 }
634
635 ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
636 if (mLength > 0) {
637 long oldPosn = ftell(mFp);
638 fseek(mFp, mStart, SEEK_SET);
639 if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
640 ALOGE("failed reading %ld bytes\n", (long) mLength);
641 delete[] buf;
642 return NULL;
643 }
644 fseek(mFp, oldPosn, SEEK_SET);
645 }
646
647 ALOGV(" getBuffer: loaded into buffer\n");
648
649 mBuf = buf;
650 return mBuf;
651 } else {
652 incfs::IncFsFileMap map;
653 if (!map.Create(fileno(mFp), mStart, mLength, NULL /* file_name */ )) {
654 return NULL;
655 }
656
657 ALOGV(" getBuffer: mapped\n");
658
659 mMap = std::move(map);
660 if (!aligned) {
661 return mMap->data();
662 }
663 return ensureAlignment(*mMap);
664 }
665 }
666
openFileDescriptor(off64_t * outStart,off64_t * outLength) const667 int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
668 {
669 if (mMap.has_value()) {
670 if (mFd.ok()) {
671 *outStart = mMap->offset();
672 *outLength = mMap->length();
673 const int fd = dup(mFd);
674 if (fd < 0) {
675 ALOGE("Unable to dup fd (%d).", mFd.get());
676 return -1;
677 }
678 lseek64(fd, 0, SEEK_SET);
679 return fd;
680 }
681 const char* fname = mMap->file_name();
682 if (fname == NULL) {
683 fname = mFileName;
684 }
685 if (fname == NULL) {
686 return -1;
687 }
688 *outStart = mMap->offset();
689 *outLength = mMap->length();
690 return open(fname, O_RDONLY | O_BINARY);
691 }
692 if (mFileName == NULL) {
693 return -1;
694 }
695 *outStart = mStart;
696 *outLength = mLength;
697 return open(mFileName, O_RDONLY | O_BINARY);
698 }
699
ensureAlignment(const incfs::IncFsFileMap & map)700 incfs::map_ptr<void> _FileAsset::ensureAlignment(const incfs::IncFsFileMap& map)
701 {
702 const auto data = map.data();
703 if (util::IsFourByteAligned(data)) {
704 // We can return this directly if it is aligned on a word
705 // boundary.
706 ALOGV("Returning aligned FileAsset %p (%s).", this,
707 getAssetSource());
708 return data;
709 }
710
711 if (!data.convert<uint8_t>().verify(mLength)) {
712 return NULL;
713 }
714
715 // If not aligned on a word boundary, then we need to copy it into
716 // our own buffer.
717 ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
718 getAssetSource(), (int)mLength);
719 unsigned char* buf = new unsigned char[mLength];
720 if (buf == NULL) {
721 ALOGE("alloc of %ld bytes failed\n", (long) mLength);
722 return NULL;
723 }
724
725 memcpy(buf, data.unsafe_ptr(), mLength);
726 mBuf = buf;
727 return buf;
728 }
729
730 /*
731 * ===========================================================================
732 * _CompressedAsset
733 * ===========================================================================
734 */
735
736 /*
737 * Constructor.
738 */
_CompressedAsset(void)739 _CompressedAsset::_CompressedAsset(void)
740 : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
741 mFd(-1), mZipInflater(NULL), mBuf(NULL)
742 {
743 // Register the Asset with the global list here after it is fully constructed and its
744 // vtable pointer points to this concrete type. b/31113965
745 registerAsset(this);
746 }
747
748 /*
749 * Destructor. Release resources.
750 */
~_CompressedAsset(void)751 _CompressedAsset::~_CompressedAsset(void)
752 {
753 close();
754
755 // Unregister the Asset from the global list here before it is destructed and while its vtable
756 // pointer still points to this concrete type. b/31113965
757 unregisterAsset(this);
758 }
759
760 /*
761 * Open a chunk of compressed data inside a file.
762 *
763 * This currently just sets up some values and returns. On the first
764 * read, we expand the entire file into a buffer and return data from it.
765 */
openChunk(int fd,off64_t offset,int compressionMethod,size_t uncompressedLen,size_t compressedLen)766 status_t _CompressedAsset::openChunk(int fd, off64_t offset,
767 int compressionMethod, size_t uncompressedLen, size_t compressedLen)
768 {
769 assert(mFd < 0); // no re-open
770 assert(!mMap.has_value());
771 assert(fd >= 0);
772 assert(offset >= 0);
773 assert(compressedLen > 0);
774
775 if (compressionMethod != ZipFileRO::kCompressDeflated) {
776 assert(false);
777 return UNKNOWN_ERROR;
778 }
779
780 mStart = offset;
781 mCompressedLen = compressedLen;
782 mUncompressedLen = uncompressedLen;
783 assert(mOffset == 0);
784 mFd = fd;
785 assert(mBuf == NULL);
786
787 if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
788 mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
789 }
790
791 return NO_ERROR;
792 }
793
794 /*
795 * Open a chunk of compressed data in a mapped region.
796 *
797 * Nothing is expanded until the first read call.
798 */
openChunk(incfs::IncFsFileMap && dataMap,size_t uncompressedLen)799 status_t _CompressedAsset::openChunk(incfs::IncFsFileMap&& dataMap, size_t uncompressedLen)
800 {
801 assert(mFd < 0); // no re-open
802 assert(!mMap.has_value());
803 assert(dataMap != NULL);
804
805 mMap = std::move(dataMap);
806 mStart = -1; // not used
807 mCompressedLen = mMap->length();
808 mUncompressedLen = uncompressedLen;
809 assert(mOffset == 0);
810
811 if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
812 mZipInflater = new StreamingZipInflater(&(*mMap), uncompressedLen);
813 }
814 return NO_ERROR;
815 }
816
817 /*
818 * Read data from a chunk of compressed data.
819 *
820 * [For now, that's just copying data out of a buffer.]
821 */
read(void * buf,size_t count)822 ssize_t _CompressedAsset::read(void* buf, size_t count)
823 {
824 size_t maxLen;
825 size_t actual;
826
827 assert(mOffset >= 0 && mOffset <= mUncompressedLen);
828
829 /* If we're relying on a streaming inflater, go through that */
830 if (mZipInflater) {
831 actual = mZipInflater->read(buf, count);
832 } else {
833 if (mBuf == NULL) {
834 if (getBuffer(false) == NULL)
835 return -1;
836 }
837 assert(mBuf != NULL);
838
839 /* adjust count if we're near EOF */
840 maxLen = mUncompressedLen - mOffset;
841 if (count > maxLen)
842 count = maxLen;
843
844 if (!count)
845 return 0;
846
847 /* copy from buffer */
848 //printf("comp buf read\n");
849 memcpy(buf, (char*)mBuf + mOffset, count);
850 actual = count;
851 }
852
853 mOffset += actual;
854 return actual;
855 }
856
857 /*
858 * Handle a seek request.
859 *
860 * If we're working in a streaming mode, this is going to be fairly
861 * expensive, because it requires plowing through a bunch of compressed
862 * data.
863 */
seek(off64_t offset,int whence)864 off64_t _CompressedAsset::seek(off64_t offset, int whence)
865 {
866 off64_t newPosn;
867
868 // compute new position within chunk
869 newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
870 if (newPosn == (off64_t) -1)
871 return newPosn;
872
873 if (mZipInflater) {
874 mZipInflater->seekAbsolute(newPosn);
875 }
876 mOffset = newPosn;
877 return mOffset;
878 }
879
880 /*
881 * Close the asset.
882 */
close(void)883 void _CompressedAsset::close(void)
884 {
885 delete[] mBuf;
886 mBuf = NULL;
887
888 delete mZipInflater;
889 mZipInflater = NULL;
890
891 if (mFd > 0) {
892 ::close(mFd);
893 mFd = -1;
894 }
895 }
896
897 /*
898 * Get a pointer to a read-only buffer of data.
899 *
900 * The first time this is called, we expand the compressed data into a
901 * buffer.
902 */
getBuffer(bool)903 const void* _CompressedAsset::getBuffer(bool)
904 {
905 unsigned char* buf = NULL;
906
907 if (mBuf != NULL)
908 return mBuf;
909
910 /*
911 * Allocate a buffer and read the file into it.
912 */
913 buf = new unsigned char[mUncompressedLen];
914 if (buf == NULL) {
915 ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
916 goto bail;
917 }
918
919 if (mMap.has_value()) {
920 if (!ZipUtils::inflateToBuffer(mMap->data(), buf,
921 mUncompressedLen, mCompressedLen))
922 goto bail;
923 } else {
924 assert(mFd >= 0);
925
926 /*
927 * Seek to the start of the compressed data.
928 */
929 if (lseek(mFd, mStart, SEEK_SET) != mStart)
930 goto bail;
931
932 /*
933 * Expand the data into it.
934 */
935 if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
936 mCompressedLen))
937 goto bail;
938 }
939
940 /*
941 * Success - now that we have the full asset in RAM we
942 * no longer need the streaming inflater
943 */
944 delete mZipInflater;
945 mZipInflater = NULL;
946
947 mBuf = buf;
948 buf = NULL;
949
950 bail:
951 delete[] buf;
952 return mBuf;
953 }
954
getIncFsBuffer(bool aligned)955 incfs::map_ptr<void> _CompressedAsset::getIncFsBuffer(bool aligned) {
956 return incfs::map_ptr<void>(getBuffer(aligned));
957 }
958