1/*
2 * Copyright 2013-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <package/hpkg/PackageFileHeapAccessorBase.h>
8
9#include <stdlib.h>
10#include <string.h>
11
12#include <algorithm>
13#include <new>
14#ifdef _KERNEL_MODE
15#include <slab/Slab.h>
16#endif
17
18#include <ByteOrder.h>
19#include <DataIO.h>
20#include <package/hpkg/ErrorOutput.h>
21
22#include <AutoDeleter.h>
23#include <CompressionAlgorithm.h>
24
25
26namespace BPackageKit {
27
28namespace BHPKG {
29
30namespace BPrivate {
31
32
33#if defined(_KERNEL_MODE)
34void* PackageFileHeapAccessorBase::sChunkCache = NULL;
35#endif
36
37
38// #pragma mark - OffsetArray
39
40
41PackageFileHeapAccessorBase::OffsetArray::OffsetArray()
42	:
43	fOffsets(NULL)
44{
45}
46
47
48PackageFileHeapAccessorBase::OffsetArray::~OffsetArray()
49{
50	delete[] fOffsets;
51}
52
53
54bool
55PackageFileHeapAccessorBase::OffsetArray::InitUncompressedChunksOffsets(
56	size_t totalChunkCount)
57{
58	if (totalChunkCount <= 1)
59		return true;
60
61	const size_t max32BitChunks = (uint64(1) << 32) / kChunkSize;
62	size_t actual32BitChunks = totalChunkCount;
63	if (totalChunkCount - 1 > max32BitChunks) {
64		actual32BitChunks = max32BitChunks;
65		fOffsets = _AllocateOffsetArray(totalChunkCount, max32BitChunks);
66	} else
67		fOffsets = _AllocateOffsetArray(totalChunkCount, 0);
68
69	if (fOffsets == NULL)
70		return false;
71
72	{
73		uint32 offset = kChunkSize;
74		for (size_t i = 1; i < actual32BitChunks; i++, offset += kChunkSize)
75			fOffsets[i] = offset;
76
77	}
78
79	if (actual32BitChunks < totalChunkCount) {
80		uint64 offset = actual32BitChunks * kChunkSize;
81		uint32* offsets = fOffsets + actual32BitChunks;
82		for (size_t i = actual32BitChunks; i < totalChunkCount;
83				i++, offset += kChunkSize) {
84			*offsets++ = (uint32)offset;
85			*offsets++ = uint32(offset >> 32);
86		}
87	}
88
89	return true;
90}
91
92
93bool
94PackageFileHeapAccessorBase::OffsetArray::InitChunksOffsets(
95	size_t totalChunkCount, size_t baseIndex, const uint16* chunkSizes,
96	size_t chunkCount)
97{
98	if (totalChunkCount <= 1)
99		return true;
100
101	if (fOffsets == NULL) {
102		fOffsets = _AllocateOffsetArray(totalChunkCount, totalChunkCount);
103		if (fOffsets == NULL)
104			return false;
105	}
106
107	uint64 offset = (*this)[baseIndex];
108	for (size_t i = 0; i < chunkCount; i++) {
109		offset += (uint64)B_BENDIAN_TO_HOST_INT16(chunkSizes[i]) + 1;
110			// the stored value is chunkSize - 1
111		size_t index = baseIndex + i + 1;
112			// (baseIndex + i) is the index of the chunk whose size is stored in
113			// chunkSizes[i]. We compute the offset of the following element
114			// which we store at index (baseIndex + i + 1).
115
116		if (offset <= ~(uint32)0) {
117			fOffsets[index] = (uint32)offset;
118		} else {
119			if (fOffsets[0] == 0) {
120				// Not scaled to allow for 64 bit offsets yet. Do that.
121				uint32* newOffsets = _AllocateOffsetArray(totalChunkCount,
122					index);
123				if (newOffsets == NULL)
124					return false;
125
126				fOffsets[0] = index;
127				memcpy(newOffsets, fOffsets, sizeof(newOffsets[0]) * index);
128
129				delete[] fOffsets;
130				fOffsets = newOffsets;
131			}
132
133			index += index - fOffsets[0];
134			fOffsets[index] = (uint32)offset;
135			fOffsets[index + 1] = uint32(offset >> 32);
136		}
137	}
138
139	return true;
140}
141
142
143bool
144PackageFileHeapAccessorBase::OffsetArray::Init(size_t totalChunkCount,
145	const OffsetArray& other)
146{
147	if (other.fOffsets == NULL)
148		return true;
149
150	size_t elementCount = other.fOffsets[0] == 0
151		? totalChunkCount
152		: 2 * totalChunkCount - other.fOffsets[0];
153
154	fOffsets = new(std::nothrow) uint32[elementCount];
155	if (fOffsets == NULL)
156		return false;
157
158	memcpy(fOffsets, other.fOffsets, elementCount * sizeof(fOffsets[0]));
159	return true;
160}
161
162
163/*static*/ uint32*
164PackageFileHeapAccessorBase::OffsetArray::_AllocateOffsetArray(
165	size_t totalChunkCount, size_t offset32BitChunkCount)
166{
167	uint32* offsets = new(std::nothrow) uint32[
168		2 * totalChunkCount - offset32BitChunkCount];
169
170	if (offsets != NULL) {
171		offsets[0] = offset32BitChunkCount == totalChunkCount
172			? 0 : offset32BitChunkCount;
173			// 0 means that all offsets are 32 bit. Otherwise it's the index of
174			// the first 64 bit offset.
175	}
176
177	return offsets;
178}
179
180
181// #pragma mark - PackageFileHeapAccessorBase
182
183
184PackageFileHeapAccessorBase::PackageFileHeapAccessorBase(
185	BErrorOutput* errorOutput, BPositionIO* file, off_t heapOffset,
186	DecompressionAlgorithmOwner* decompressionAlgorithm)
187	:
188	fErrorOutput(errorOutput),
189	fFile(file),
190	fHeapOffset(heapOffset),
191	fCompressedHeapSize(0),
192	fUncompressedHeapSize(0),
193	fDecompressionAlgorithm(decompressionAlgorithm)
194{
195	if (fDecompressionAlgorithm != NULL)
196		fDecompressionAlgorithm->AcquireReference();
197}
198
199
200PackageFileHeapAccessorBase::~PackageFileHeapAccessorBase()
201{
202	if (fDecompressionAlgorithm != NULL)
203		fDecompressionAlgorithm->ReleaseReference();
204}
205
206
207status_t
208PackageFileHeapAccessorBase::ReadDataToOutput(off_t offset, size_t size,
209	BDataIO* output)
210{
211	if (size == 0)
212		return B_OK;
213
214	if (offset < 0 || (uint64)offset > fUncompressedHeapSize
215		|| size > fUncompressedHeapSize - offset) {
216		return B_BAD_VALUE;
217	}
218
219	// allocate buffers for compressed and uncompressed data
220	uint16* compressedDataBuffer, *uncompressedDataBuffer;
221	MemoryDeleter compressedMemoryDeleter, uncompressedMemoryDeleter;
222
223#if defined(_KERNEL_MODE) && !defined(_BOOT_MODE)
224	struct ObjectCacheDeleter {
225		object_cache* cache;
226		void* object;
227
228		ObjectCacheDeleter(object_cache* c)
229			: cache(c)
230			, object(NULL)
231		{
232		}
233
234		~ObjectCacheDeleter()
235		{
236			if (cache != NULL && object != NULL)
237				object_cache_free(cache, object, 0);
238		}
239	};
240
241	ObjectCacheDeleter compressedCacheDeleter((object_cache*)sChunkCache),
242		uncompressedCacheDeleter((object_cache*)sChunkCache);
243	if (sChunkCache != NULL) {
244		compressedDataBuffer = (uint16*)object_cache_alloc((object_cache*)sChunkCache, 0);
245		uncompressedDataBuffer = (uint16*)object_cache_alloc((object_cache*)sChunkCache, 0);
246		compressedCacheDeleter.object = compressedDataBuffer;
247		uncompressedCacheDeleter.object = uncompressedDataBuffer;
248	} else
249#endif
250	{
251		compressedDataBuffer = (uint16*)malloc(kChunkSize);
252		uncompressedDataBuffer = (uint16*)malloc(kChunkSize);
253		compressedMemoryDeleter.SetTo(compressedDataBuffer);
254		uncompressedMemoryDeleter.SetTo(uncompressedDataBuffer);
255	}
256
257	if (compressedDataBuffer == NULL || uncompressedDataBuffer == NULL)
258		return B_NO_MEMORY;
259
260	// read the data
261	size_t chunkIndex = size_t(offset / kChunkSize);
262	size_t inChunkOffset = (uint64)offset - (uint64)chunkIndex * kChunkSize;
263	size_t remainingBytes = size;
264
265	while (remainingBytes > 0) {
266		status_t error = ReadAndDecompressChunk(chunkIndex,
267			compressedDataBuffer, uncompressedDataBuffer);
268		if (error != B_OK)
269			return error;
270
271		size_t toWrite = std::min((size_t)kChunkSize - inChunkOffset,
272			remainingBytes);
273			// The last chunk may be shorter than kChunkSize, but since
274			// size (and thus remainingSize) had been clamped, that doesn't
275			// harm.
276		error = output->WriteExactly(
277			(char*)uncompressedDataBuffer + inChunkOffset, toWrite);
278		if (error != B_OK)
279			return error;
280
281		remainingBytes -= toWrite;
282		chunkIndex++;
283		inChunkOffset = 0;
284	}
285
286	return B_OK;
287}
288
289
290status_t
291PackageFileHeapAccessorBase::ReadAndDecompressChunkData(uint64 offset,
292	size_t compressedSize, size_t uncompressedSize, void* compressedDataBuffer,
293	void* uncompressedDataBuffer)
294{
295	// if uncompressed, read directly into the uncompressed data buffer
296	if (compressedSize == uncompressedSize)
297		return ReadFileData(offset, uncompressedDataBuffer, compressedSize);
298
299	// otherwise read into the other buffer and decompress
300	status_t error = ReadFileData(offset, compressedDataBuffer, compressedSize);
301	if (error != B_OK)
302		return error;
303
304	return DecompressChunkData(compressedDataBuffer, compressedSize,
305		uncompressedDataBuffer, uncompressedSize);
306}
307
308
309status_t
310PackageFileHeapAccessorBase::DecompressChunkData(void* compressedDataBuffer,
311	size_t compressedSize, void* uncompressedDataBuffer,
312	size_t uncompressedSize)
313{
314	size_t actualSize;
315	status_t error = fDecompressionAlgorithm->algorithm->DecompressBuffer(
316		compressedDataBuffer, compressedSize, uncompressedDataBuffer,
317		uncompressedSize, actualSize, fDecompressionAlgorithm->parameters);
318	if (error != B_OK) {
319		fErrorOutput->PrintError("Failed to decompress chunk data: %s\n",
320			strerror(error));
321		return error;
322	}
323
324	if (actualSize != uncompressedSize) {
325		fErrorOutput->PrintError("Failed to decompress chunk data: size "
326			"mismatch\n");
327		return B_ERROR;
328	}
329
330	return B_OK;
331}
332
333
334status_t
335PackageFileHeapAccessorBase::ReadFileData(uint64 offset, void* buffer,
336	size_t size)
337{
338	status_t error = fFile->ReadAtExactly(fHeapOffset + (off_t)offset, buffer,
339		size);
340	if (error != B_OK) {
341		fErrorOutput->PrintError("ReadFileData(%" B_PRIu64 ", %p, %zu) failed "
342			"to read data: %s\n", offset, buffer, size, strerror(error));
343		return error;
344	}
345
346	return B_OK;
347}
348
349
350}	// namespace BPrivate
351
352}	// namespace BHPKG
353
354}	// namespace BPackageKit
355