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