• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 1
18 #define LOG_TAG "szipinf"
19 #include <utils/Log.h>
20 
21 #include <utils/FileMap.h>
22 #include <utils/StreamingZipInflater.h>
23 #include <string.h>
24 #include <stddef.h>
25 #include <assert.h>
26 
min_of(size_t a,size_t b)27 static inline size_t min_of(size_t a, size_t b) { return (a < b) ? a : b; }
28 
29 using namespace android;
30 
31 /*
32  * Streaming access to compressed asset data in an open fd
33  */
StreamingZipInflater(int fd,off_t compDataStart,size_t uncompSize,size_t compSize)34 StreamingZipInflater::StreamingZipInflater(int fd, off_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  */
StreamingZipInflater(FileMap * dataMap,size_t uncompSize)54 StreamingZipInflater::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 
~StreamingZipInflater()69 StreamingZipInflater::~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 
initInflateState()79 void StreamingZipInflater::initInflateState() {
80     LOGD("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  */
read(void * outBuf,size_t count)117 ssize_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                     LOGE("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             LOGD("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                 LOGD("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                 LOGE("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 
readNextChunk()188 int 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             //LOGD("Reading input chunk, size %08x didread %08x", toRead, didRead);
196             if (didRead < 0) {
197                 // TODO: error
198                 LOGE("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.
seekAbsolute(off_t absoluteInputPosition)213 off_t StreamingZipInflater::seekAbsolute(off_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