Asset.cpp revision 78c405178c57bb45e40f1e2839d6a18d91f7f02c
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 <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. 585 LOGV("Returning aligned FileAsset %p (%s).", this, 586 getAssetSource()); 587 return data; 588 } 589 // If not aligned on a word boundary, then we need to copy it into 590 // our own buffer. 591 LOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this, 592 getAssetSource(), (int)mLength); 593 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 817