1 /* 2 * Copyright (C) 2010 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 //#define LOG_NDEBUG 0 18 #define LOG_TAG "szipinf" 19 #include <utils/Log.h> 20 21 #include <androidfw/StreamingZipInflater.h> 22 #include <utils/FileMap.h> 23 #include <string.h> 24 #include <stddef.h> 25 #include <assert.h> 26 27 static inline size_t min_of(size_t a, size_t b) { return (a < b) ? a : b; } 28 29 using namespace android; 30 31 /* 32 * Streaming access to compressed asset data in an open fd 33 */ 34 StreamingZipInflater::StreamingZipInflater(int fd, off64_t compDataStart, 35 size_t uncompSize, size_t compSize) { 36 mFd = fd; 37 mDataMap = NULL; 38 mInFileStart = compDataStart; 39 mOutTotalSize = uncompSize; 40 mInTotalSize = compSize; 41 42 mInBufSize = StreamingZipInflater::INPUT_CHUNK_SIZE; 43 mInBuf = new uint8_t[mInBufSize]; 44 45 mOutBufSize = StreamingZipInflater::OUTPUT_CHUNK_SIZE; 46 mOutBuf = new uint8_t[mOutBufSize]; 47 48 initInflateState(); 49 } 50 51 /* 52 * Streaming access to compressed data held in an mmapped region of memory 53 */ 54 StreamingZipInflater::StreamingZipInflater(FileMap* dataMap, size_t uncompSize) { 55 mFd = -1; 56 mDataMap = dataMap; 57 mOutTotalSize = uncompSize; 58 mInTotalSize = dataMap->getDataLength(); 59 60 mInBuf = (uint8_t*) dataMap->getDataPtr(); 61 mInBufSize = mInTotalSize; 62 63 mOutBufSize = StreamingZipInflater::OUTPUT_CHUNK_SIZE; 64 mOutBuf = new uint8_t[mOutBufSize]; 65 66 initInflateState(); 67 } 68 69 StreamingZipInflater::~StreamingZipInflater() { 70 // tear down the in-flight zip state just in case 71 ::inflateEnd(&mInflateState); 72 73 if (mDataMap == NULL) { 74 delete [] mInBuf; 75 } 76 delete [] mOutBuf; 77 } 78 79 void StreamingZipInflater::initInflateState() { 80 ALOGV("Initializing inflate state"); 81 82 memset(&mInflateState, 0, sizeof(mInflateState)); 83 mInflateState.zalloc = Z_NULL; 84 mInflateState.zfree = Z_NULL; 85 mInflateState.opaque = Z_NULL; 86 mInflateState.next_in = (Bytef*)mInBuf; 87 mInflateState.next_out = (Bytef*) mOutBuf; 88 mInflateState.avail_out = mOutBufSize; 89 mInflateState.data_type = Z_UNKNOWN; 90 91 mOutLastDecoded = mOutDeliverable = mOutCurPosition = 0; 92 mInNextChunkOffset = 0; 93 mStreamNeedsInit = true; 94 95 if (mDataMap == NULL) { 96 ::lseek(mFd, mInFileStart, SEEK_SET); 97 mInflateState.avail_in = 0; // set when a chunk is read in 98 } else { 99 mInflateState.avail_in = mInBufSize; 100 } 101 } 102 103 /* 104 * Basic approach: 105 * 106 * 1. If we have undelivered uncompressed data, send it. At this point 107 * either we've satisfied the request, or we've exhausted the available 108 * output data in mOutBuf. 109 * 110 * 2. While we haven't sent enough data to satisfy the request: 111 * 0. if the request is for more data than exists, bail. 112 * a. if there is no input data to decode, read some into the input buffer 113 * and readjust the z_stream input pointers 114 * b. point the output to the start of the output buffer and decode what we can 115 * c. deliver whatever output data we can 116 */ 117 ssize_t StreamingZipInflater::read(void* outBuf, size_t count) { 118 uint8_t* dest = (uint8_t*) outBuf; 119 size_t bytesRead = 0; 120 size_t toRead = min_of(count, size_t(mOutTotalSize - mOutCurPosition)); 121 while (toRead > 0) { 122 // First, write from whatever we already have decoded and ready to go 123 size_t deliverable = min_of(toRead, mOutLastDecoded - mOutDeliverable); 124 if (deliverable > 0) { 125 if (outBuf != NULL) memcpy(dest, mOutBuf + mOutDeliverable, deliverable); 126 mOutDeliverable += deliverable; 127 mOutCurPosition += deliverable; 128 dest += deliverable; 129 bytesRead += deliverable; 130 toRead -= deliverable; 131 } 132 133 // need more data? time to decode some. 134 if (toRead > 0) { 135 // if we don't have any data to decode, read some in. If we're working 136 // from mmapped data this won't happen, because the clipping to total size 137 // will prevent reading off the end of the mapped input chunk. 138 if (mInflateState.avail_in == 0) { 139 int err = readNextChunk(); 140 if (err < 0) { 141 ALOGE("Unable to access asset data: %d", err); 142 if (!mStreamNeedsInit) { 143 ::inflateEnd(&mInflateState); 144 initInflateState(); 145 } 146 return -1; 147 } 148 } 149 // we know we've drained whatever is in the out buffer now, so just 150 // start from scratch there, reading all the input we have at present. 151 mInflateState.next_out = (Bytef*) mOutBuf; 152 mInflateState.avail_out = mOutBufSize; 153 154 /* 155 ALOGV("Inflating to outbuf: avail_in=%u avail_out=%u next_in=%p next_out=%p", 156 mInflateState.avail_in, mInflateState.avail_out, 157 mInflateState.next_in, mInflateState.next_out); 158 */ 159 int result = Z_OK; 160 if (mStreamNeedsInit) { 161 ALOGV("Initializing zlib to inflate"); 162 result = inflateInit2(&mInflateState, -MAX_WBITS); 163 mStreamNeedsInit = false; 164 } 165 if (result == Z_OK) result = ::inflate(&mInflateState, Z_SYNC_FLUSH); 166 if (result < 0) { 167 // Whoops, inflation failed 168 ALOGE("Error inflating asset: %d", result); 169 ::inflateEnd(&mInflateState); 170 initInflateState(); 171 return -1; 172 } else { 173 if (result == Z_STREAM_END) { 174 // we know we have to have reached the target size here and will 175 // not try to read any further, so just wind things up. 176 ::inflateEnd(&mInflateState); 177 } 178 179 // Note how much data we got, and off we go 180 mOutDeliverable = 0; 181 mOutLastDecoded = mOutBufSize - mInflateState.avail_out; 182 } 183 } 184 } 185 return bytesRead; 186 } 187 188 int StreamingZipInflater::readNextChunk() { 189 assert(mDataMap == NULL); 190 191 if (mInNextChunkOffset < mInTotalSize) { 192 size_t toRead = min_of(mInBufSize, mInTotalSize - mInNextChunkOffset); 193 if (toRead > 0) { 194 ssize_t didRead = ::read(mFd, mInBuf, toRead); 195 //ALOGV("Reading input chunk, size %08x didread %08x", toRead, didRead); 196 if (didRead < 0) { 197 // TODO: error 198 ALOGE("Error reading asset data"); 199 return didRead; 200 } else { 201 mInNextChunkOffset += didRead; 202 mInflateState.next_in = (Bytef*) mInBuf; 203 mInflateState.avail_in = didRead; 204 } 205 } 206 } 207 return 0; 208 } 209 210 // seeking backwards requires uncompressing fom the beginning, so is very 211 // expensive. seeking forwards only requires uncompressing from the current 212 // position to the destination. 213 off64_t StreamingZipInflater::seekAbsolute(off64_t absoluteInputPosition) { 214 if (absoluteInputPosition < mOutCurPosition) { 215 // rewind and reprocess the data from the beginning 216 if (!mStreamNeedsInit) { 217 ::inflateEnd(&mInflateState); 218 } 219 initInflateState(); 220 read(NULL, absoluteInputPosition); 221 } else if (absoluteInputPosition > mOutCurPosition) { 222 read(NULL, absoluteInputPosition - mOutCurPosition); 223 } 224 // else if the target position *is* our current position, do nothing 225 return absoluteInputPosition; 226 } 227