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