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