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