xref: /aosp_15_r20/frameworks/base/libs/androidfw/StreamingZipInflater.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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