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