StreamingZipInflater.cpp revision 83c64e6b624a876436d2ef5d2f173b10407e27b4
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
27static inline size_t min_of(size_t a, size_t b) { return (a < b) ? a : b; }
28
29using namespace android;
30
31/*
32 * Streaming access to compressed asset data in an open fd
33 */
34StreamingZipInflater::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 */
54StreamingZipInflater::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
69StreamingZipInflater::~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
79void 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 */
117ssize_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
188int 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.
213off64_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