xref: /haiku/src/kits/package/hpkg/PackageDataReader.cpp (revision b46615c55ad2c8fe6de54412055a0713da3d610a)
1 /*
2  * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <package/hpkg/PackageDataReader.h>
8 
9 #include <string.h>
10 
11 #include <algorithm>
12 #include <new>
13 
14 #include <package/hpkg/HPKGDefsPrivate.h>
15 #include <package/hpkg/BufferCache.h>
16 #include <package/hpkg/CachedBuffer.h>
17 #include <package/hpkg/DataOutput.h>
18 #include <package/hpkg/PackageData.h>
19 #include <package/hpkg/ZlibDecompressor.h>
20 
21 
22 namespace BPackageKit {
23 
24 namespace BHPKG {
25 
26 
27 using namespace BPrivate;
28 
29 
30 // minimum/maximum zlib chunk size we consider sane
31 static const size_t kMinSaneZlibChunkSize = 1024;
32 static const size_t kMaxSaneZlibChunkSize = 10 * 1024 * 1024;
33 
34 // maximum number of entries in the zlib offset table buffer
35 static const uint32 kMaxZlibOffsetTableBufferSize = 512;
36 
37 static const size_t kUncompressedReaderBufferSize
38 	= B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB;
39 
40 
41 // #pragma mark - BPackageDataReader
42 
43 
44 BPackageDataReader::BPackageDataReader(BDataReader* dataReader)
45 	:
46 	fDataReader(dataReader)
47 {
48 }
49 
50 
51 BPackageDataReader::~BPackageDataReader()
52 {
53 }
54 
55 
56 status_t
57 BPackageDataReader::ReadData(off_t offset, void* buffer, size_t size)
58 {
59 	BBufferDataOutput output(buffer, size);
60 	return ReadDataToOutput(offset, size, &output);
61 }
62 
63 
64 // #pragma mark - UncompressedPackageDataReader
65 
66 
67 class UncompressedPackageDataReader : public BPackageDataReader {
68 public:
69 	UncompressedPackageDataReader(BDataReader* dataReader,
70 		BBufferCache* bufferCache)
71 		:
72 		BPackageDataReader(dataReader),
73 		fBufferCache(bufferCache)
74 	{
75 	}
76 
77 	status_t Init(const BPackageData& data)
78 	{
79 		fOffset = data.Offset();
80 		fSize = data.UncompressedSize();
81 		return B_OK;
82 	}
83 
84 	virtual uint64 Size() const
85 	{
86 		return fSize;
87 	}
88 
89 	virtual size_t BlockSize() const
90 	{
91 		// TODO: Some other value?
92 		return 64 * 1024;
93 	}
94 
95 	virtual status_t ReadData(off_t offset, void* buffer, size_t size)
96 	{
97 		if (size == 0)
98 			return B_OK;
99 
100 		if (offset < 0)
101 			return B_BAD_VALUE;
102 
103 		if ((uint64)offset > fSize || size > fSize - offset)
104 			return B_BAD_VALUE;
105 
106 		return fDataReader->ReadData(fOffset + offset, buffer, size);
107 	}
108 
109 	virtual status_t ReadDataToOutput(off_t offset, size_t size,
110 		BDataOutput* output)
111 	{
112 		if (size == 0)
113 			return B_OK;
114 
115 		if (offset < 0)
116 			return B_BAD_VALUE;
117 
118 		if ((uint64)offset > fSize || size > fSize - offset)
119 			return B_BAD_VALUE;
120 
121 		// get a temporary buffer
122 		CachedBuffer* buffer = fBufferCache->GetBuffer(
123 			kUncompressedReaderBufferSize);
124 		if (buffer == NULL)
125 			return B_NO_MEMORY;
126 		CachedBufferPutter bufferPutter(fBufferCache, &buffer);
127 
128 		while (size > 0) {
129 			// read into the buffer
130 			size_t toRead = std::min(size, buffer->Size());
131 			status_t error = fDataReader->ReadData(fOffset + offset,
132 				buffer->Buffer(), toRead);
133 			if (error != B_OK)
134 				return error;
135 
136 			// write to the output
137 			error = output->WriteData(buffer->Buffer(), toRead);
138 			if (error != B_OK)
139 				return error;
140 
141 			offset += toRead;
142 			size -= toRead;
143 		}
144 
145 		return B_OK;
146 	}
147 
148 private:
149 	BBufferCache*	fBufferCache;
150 	uint64			fOffset;
151 	uint64			fSize;
152 };
153 
154 
155 // #pragma mark - ZlibPackageDataReader
156 
157 
158 class ZlibPackageDataReader : public BPackageDataReader {
159 public:
160 	ZlibPackageDataReader(BDataReader* dataReader, BBufferCache* bufferCache)
161 		:
162 		BPackageDataReader(dataReader),
163 		fBufferCache(bufferCache),
164 		fUncompressBuffer(NULL),
165 		fOffsetTable(NULL)
166 	{
167 	}
168 
169 	~ZlibPackageDataReader()
170 	{
171 		delete[] fOffsetTable;
172 
173 		fBufferCache->PutBuffer(&fUncompressBuffer);
174 	}
175 
176 	status_t Init(const BPackageData& data)
177 	{
178 		fOffset = data.Offset();
179 		fCompressedSize = data.CompressedSize();
180 		fUncompressedSize = data.UncompressedSize();
181 		fChunkSize = data.ChunkSize();
182 
183 		// validate chunk size
184 		if (fChunkSize == 0)
185 			fChunkSize = B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB;
186 		if (fChunkSize < kMinSaneZlibChunkSize
187 			|| fChunkSize > kMaxSaneZlibChunkSize) {
188 			return B_BAD_DATA;
189 		}
190 
191 		fChunkCount = (fUncompressedSize + (fChunkSize - 1)) / fChunkSize;
192 		fOffsetTableSize = (fChunkCount - 1) * sizeof(uint64);
193 		if (fOffsetTableSize >= fCompressedSize)
194 			return B_BAD_DATA;
195 
196 		// allocate a buffer for the offset table
197 		if (fChunkCount > 1) {
198 			fOffsetTableBufferEntryCount = std::min(fChunkCount - 1,
199 				(uint64)kMaxZlibOffsetTableBufferSize);
200 			fOffsetTable = new(std::nothrow) uint64[
201 				fOffsetTableBufferEntryCount];
202 			if (fOffsetTable == NULL)
203 				return B_NO_MEMORY;
204 
205 			fOffsetTableIndex = -1;
206 				// mark the table content invalid
207 		} else
208 			fChunkSize = fUncompressedSize;
209 
210 		// mark uncompressed content invalid
211 		fUncompressedChunk = -1;
212 
213 		return B_OK;
214 	}
215 
216 	virtual uint64 Size() const
217 	{
218 		return fUncompressedSize;
219 	}
220 
221 	virtual size_t BlockSize() const
222 	{
223 		return fChunkSize;
224 	}
225 
226 	virtual status_t ReadDataToOutput(off_t offset, size_t size,
227 		BDataOutput* output)
228 	{
229 		// check offset and size
230 		if (size == 0)
231 			return B_OK;
232 
233 		if (offset < 0)
234 			return B_BAD_VALUE;
235 
236 		if ((uint64)offset > fUncompressedSize
237 			|| size > fUncompressedSize - offset) {
238 			return B_BAD_VALUE;
239 		}
240 
241 		// get our uncompressed chunk buffer back, if possible
242 		bool newBuffer;
243 		if (fBufferCache->GetBuffer(fChunkSize, &fUncompressBuffer, &newBuffer)
244 				== NULL) {
245 			return B_NO_MEMORY;
246 		}
247 		CachedBufferPutter uncompressBufferPutter(fBufferCache,
248 			&fUncompressBuffer);
249 
250 		if (newBuffer)
251 			fUncompressedChunk = -1;
252 
253 		// uncompress
254 		int64 chunkIndex = offset / fChunkSize;
255 		off_t chunkOffset = chunkIndex * fChunkSize;
256 		size_t inChunkOffset = offset - chunkOffset;
257 
258 		while (size > 0) {
259 			// read and uncompress the chunk
260 			status_t error = _ReadChunk(chunkIndex);
261 			if (error != B_OK)
262 				return error;
263 
264 			// write data to output
265 			size_t toCopy = std::min(size, (size_t)fChunkSize - inChunkOffset);
266 			error = output->WriteData(
267 				(uint8*)fUncompressBuffer->Buffer() + inChunkOffset, toCopy);
268 			if (error != B_OK)
269 				return error;
270 
271 			size -= toCopy;
272 
273 			chunkIndex++;
274 			chunkOffset += fChunkSize;
275 			inChunkOffset = 0;
276 		}
277 
278 		return B_OK;
279 	}
280 
281 private:
282 	status_t _ReadChunk(int64 chunkIndex)
283 	{
284 		if (chunkIndex == fUncompressedChunk)
285 			return B_OK;
286 
287 		// get the chunk offset and size
288 		uint64 offset;
289 		uint32 compressedSize;
290 		status_t error = _GetCompressedChunkOffsetAndSize(chunkIndex, offset,
291 			compressedSize);
292 		if (error != B_OK)
293 			return error;
294 
295 		uint32 uncompressedSize = (uint64)chunkIndex + 1 < fChunkCount
296 			? fChunkSize : fUncompressedSize - chunkIndex * fChunkSize;
297 
298 		// read the chunk
299 		if (compressedSize == uncompressedSize) {
300 			// the chunk is not compressed -- read it directly into the
301 			// uncompressed buffer
302 			error = fDataReader->ReadData(offset, fUncompressBuffer->Buffer(),
303 				compressedSize);
304 		} else {
305 			// read to a read buffer and uncompress
306 			CachedBuffer* readBuffer = fBufferCache->GetBuffer(fChunkSize);
307 			if (readBuffer == NULL)
308 				return B_NO_MEMORY;
309 			CachedBufferPutter readBufferPutter(fBufferCache, readBuffer);
310 
311 			error = fDataReader->ReadData(offset, readBuffer->Buffer(),
312 				compressedSize);
313 			if (error != B_OK)
314 				return error;
315 
316 			size_t actuallyUncompressedSize;
317 			error = ZlibDecompressor::DecompressSingleBuffer(
318 				readBuffer->Buffer(), compressedSize,
319 				fUncompressBuffer->Buffer(), uncompressedSize,
320 				actuallyUncompressedSize);
321 			if (error == B_OK && actuallyUncompressedSize != uncompressedSize)
322 				error = B_BAD_DATA;
323 		}
324 
325 		if (error != B_OK) {
326 			// error reading/decompressing data -- mark the cached data invalid
327 			fUncompressedChunk = -1;
328 			return error;
329 		}
330 
331 		fUncompressedChunk = chunkIndex;
332 		return B_OK;
333 	}
334 
335 	status_t _GetCompressedChunkOffsetAndSize(int64 chunkIndex, uint64& _offset,
336 		uint32& _size)
337 	{
338 		// get the offset
339 		uint64 offset;
340 		if (chunkIndex == 0) {
341 			// first chunk is at 0
342 			offset = 0;
343 		} else {
344 			status_t error = _GetCompressedChunkRelativeOffset(chunkIndex,
345 				offset);
346 			if (error != B_OK)
347 				return error;
348 		}
349 
350 		// get the end offset
351 		uint64 endOffset;
352 		if ((uint64)chunkIndex + 1 == fChunkCount) {
353 			// last chunk end with the end of the data
354 			endOffset = fCompressedSize - fOffsetTableSize;
355 		} else {
356 			status_t error = _GetCompressedChunkRelativeOffset(chunkIndex + 1,
357 				endOffset);
358 			if (error != B_OK)
359 				return error;
360 		}
361 
362 		// sanity check
363 		if (endOffset < offset)
364 			return B_BAD_DATA;
365 
366 		_offset = fOffset + fOffsetTableSize + offset;
367 		_size = endOffset - offset;
368 		return B_OK;
369 	}
370 
371 	status_t _GetCompressedChunkRelativeOffset(int64 chunkIndex,
372 		uint64& _offset)
373 	{
374 		if (fOffsetTableIndex < 0 || fOffsetTableIndex > chunkIndex
375 			|| fOffsetTableIndex + fOffsetTableBufferEntryCount <= chunkIndex) {
376 			// read the table at the given index, or, if we can, the whole table
377 			int64 readAtIndex = fChunkCount - 1 > fOffsetTableBufferEntryCount
378 				? chunkIndex : 1;
379 			uint32 entriesToRead = std::min(
380 				(uint64)fOffsetTableBufferEntryCount,
381 				fChunkCount - readAtIndex);
382 
383 			status_t error = fDataReader->ReadData(
384 				fOffset + (readAtIndex - 1) * sizeof(uint64),
385 				fOffsetTable, entriesToRead * sizeof(uint64));
386 			if (error != B_OK) {
387 				fOffsetTableIndex = -1;
388 				return error;
389 			}
390 
391 			fOffsetTableIndex = readAtIndex;
392 		}
393 
394 		// get and check the offset
395 		_offset = fOffsetTable[chunkIndex - fOffsetTableIndex];
396 		if (_offset > fCompressedSize - fOffsetTableSize)
397 			return B_BAD_DATA;
398 
399 		return B_OK;
400 	}
401 
402 private:
403 	BBufferCache*	fBufferCache;
404 	CachedBuffer*	fUncompressBuffer;
405 	int64			fUncompressedChunk;
406 
407 	uint64			fOffset;
408 	uint64			fUncompressedSize;
409 	uint64			fCompressedSize;
410 	uint64			fOffsetTableSize;
411 	uint64			fChunkCount;
412 	uint32			fChunkSize;
413 	uint32			fOffsetTableBufferEntryCount;
414 	uint64*			fOffsetTable;
415 	int32			fOffsetTableIndex;
416 };
417 
418 
419 // #pragma mark - BPackageDataReaderFactory
420 
421 
422 BPackageDataReaderFactory::BPackageDataReaderFactory(BBufferCache* bufferCache)
423 	:
424 	fBufferCache(bufferCache)
425 {
426 }
427 
428 
429 status_t
430 BPackageDataReaderFactory::CreatePackageDataReader(BDataReader* dataReader,
431 	const BPackageData& data, BPackageDataReader*& _reader)
432 {
433 	BPackageDataReader* reader;
434 
435 	switch (data.Compression()) {
436 		case B_HPKG_COMPRESSION_NONE:
437 			reader = new(std::nothrow) UncompressedPackageDataReader(
438 				dataReader, fBufferCache);
439 			break;
440 		case B_HPKG_COMPRESSION_ZLIB:
441 			reader = new(std::nothrow) ZlibPackageDataReader(dataReader,
442 				fBufferCache);
443 			break;
444 		default:
445 			return B_BAD_VALUE;
446 	}
447 
448 	if (reader == NULL)
449 		return B_NO_MEMORY;
450 
451 	status_t error = reader->Init(data);
452 	if (error != B_OK) {
453 		delete reader;
454 		return error;
455 	}
456 
457 	_reader = reader;
458 	return B_OK;
459 }
460 
461 
462 }	// namespace BHPKG
463 
464 }	// namespace BPackageKit
465