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