blob: 00498bd1fcb8ed9b0be8e294ca65910093290463 [file] [log] [blame]
Christopher Tatec28f2142010-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
Dianne Hackbornf90c1e22011-06-07 14:09:47 -070017//#define LOG_NDEBUG 0
Christopher Tatec28f2142010-07-26 11:24:18 -070018#define LOG_TAG "szipinf"
19#include <utils/Log.h>
20
21#include <utils/FileMap.h>
22#include <utils/StreamingZipInflater.h>
23#include <string.h>
Kenny Rootf2729492010-07-28 16:46:12 -070024#include <stddef.h>
Christopher Tatec28f2142010-07-26 11:24:18 -070025#include <assert.h>
26
Christopher Tate0374b422010-07-29 13:16:38 -070027static inline size_t min_of(size_t a, size_t b) { return (a < b) ? a : b; }
Christopher Tatec28f2142010-07-26 11:24:18 -070028
29using namespace android;
30
31/*
32 * Streaming access to compressed asset data in an open fd
33 */
Kenny Roote2fa7dc2010-11-24 12:56:06 -080034StreamingZipInflater::StreamingZipInflater(int fd, off64_t compDataStart,
Christopher Tatec28f2142010-07-26 11:24:18 -070035 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 */
54StreamingZipInflater::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
69StreamingZipInflater::~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
79void StreamingZipInflater::initInflateState() {
Dianne Hackbornf90c1e22011-06-07 14:09:47 -070080 LOGV("Initializing inflate state");
Christopher Tatec28f2142010-07-26 11:24:18 -070081
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 */
117ssize_t StreamingZipInflater::read(void* outBuf, size_t count) {
118 uint8_t* dest = (uint8_t*) outBuf;
119 size_t bytesRead = 0;
Christopher Tate0374b422010-07-29 13:16:38 -0700120 size_t toRead = min_of(count, size_t(mOutTotalSize - mOutCurPosition));
Christopher Tatec28f2142010-07-26 11:24:18 -0700121 while (toRead > 0) {
122 // First, write from whatever we already have decoded and ready to go
Christopher Tate0374b422010-07-29 13:16:38 -0700123 size_t deliverable = min_of(toRead, mOutLastDecoded - mOutDeliverable);
Christopher Tatec28f2142010-07-26 11:24:18 -0700124 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 /*
Dianne Hackbornf90c1e22011-06-07 14:09:47 -0700155 LOGV("Inflating to outbuf: avail_in=%u avail_out=%u next_in=%p next_out=%p",
Christopher Tatec28f2142010-07-26 11:24:18 -0700156 mInflateState.avail_in, mInflateState.avail_out,
157 mInflateState.next_in, mInflateState.next_out);
158 */
159 int result = Z_OK;
160 if (mStreamNeedsInit) {
Dianne Hackbornf90c1e22011-06-07 14:09:47 -0700161 LOGV("Initializing zlib to inflate");
Christopher Tatec28f2142010-07-26 11:24:18 -0700162 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
188int StreamingZipInflater::readNextChunk() {
189 assert(mDataMap == NULL);
190
191 if (mInNextChunkOffset < mInTotalSize) {
Christopher Tate0374b422010-07-29 13:16:38 -0700192 size_t toRead = min_of(mInBufSize, mInTotalSize - mInNextChunkOffset);
Christopher Tatec28f2142010-07-26 11:24:18 -0700193 if (toRead > 0) {
194 ssize_t didRead = ::read(mFd, mInBuf, toRead);
Dianne Hackbornf90c1e22011-06-07 14:09:47 -0700195 //LOGV("Reading input chunk, size %08x didread %08x", toRead, didRead);
Christopher Tatec28f2142010-07-26 11:24:18 -0700196 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.
Kenny Roote2fa7dc2010-11-24 12:56:06 -0800213off64_t StreamingZipInflater::seekAbsolute(off64_t absoluteInputPosition) {
Christopher Tatec28f2142010-07-26 11:24:18 -0700214 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}