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