1/*
2 * Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <package/hpkg/RepositoryWriterImpl.h>
8
9#include <algorithm>
10#include <new>
11
12#include <ByteOrder.h>
13#include <Message.h>
14#include <Path.h>
15
16#include <AutoDeleter.h>
17#include <HashSet.h>
18
19#include <package/hpkg/BlockBufferPoolNoLock.h>
20#include <package/hpkg/HPKGDefsPrivate.h>
21#include <package/hpkg/PackageDataReader.h>
22#include <package/hpkg/PackageEntry.h>
23#include <package/hpkg/PackageFileHeapWriter.h>
24#include <package/hpkg/PackageInfoAttributeValue.h>
25#include <package/hpkg/PackageReader.h>
26#include <package/ChecksumAccessors.h>
27#include <package/HashableString.h>
28#include <package/PackageInfoContentHandler.h>
29#include <package/RepositoryInfo.h>
30
31
32namespace BPackageKit {
33
34namespace BHPKG {
35
36namespace BPrivate {
37
38
39using BPackageKit::BPrivate::GeneralFileChecksumAccessor;
40using BPackageKit::BPrivate::HashableString;
41
42
43namespace {
44
45
46// #pragma mark - PackageEntryDataFetcher
47
48
49struct PackageEntryDataFetcher {
50	PackageEntryDataFetcher(BErrorOutput* errorOutput,
51		BPackageData& packageData)
52		:
53		fErrorOutput(errorOutput),
54		fPackageData(packageData)
55	{
56	}
57
58	status_t ReadIntoString(BAbstractBufferedDataReader* heapReader,
59		BString& _contents)
60	{
61		// create a PackageDataReader
62		BAbstractBufferedDataReader* reader;
63		status_t result = BPackageDataReaderFactory()
64			.CreatePackageDataReader(heapReader, fPackageData, reader);
65		if (result != B_OK)
66			return result;
67		ObjectDeleter<BAbstractBufferedDataReader> readerDeleter(reader);
68
69		// copy data into the given string
70		int32 bufferSize = fPackageData.Size();
71		char* buffer = _contents.LockBuffer(bufferSize);
72		if (buffer == NULL)
73			return B_NO_MEMORY;
74
75		result = reader->ReadData(0, buffer, bufferSize);
76		if (result != B_OK) {
77			fErrorOutput->PrintError("Error: Failed to read data: %s\n",
78				strerror(result));
79			_contents.UnlockBuffer(0);
80		} else
81			_contents.UnlockBuffer(bufferSize);
82
83		return result;
84	}
85
86private:
87	BErrorOutput*			fErrorOutput;
88	BPackageData&			fPackageData;
89};
90
91
92// #pragma mark - PackageContentHandler
93
94
95struct PackageContentHandler : public BPackageInfoContentHandler {
96	PackageContentHandler(BErrorOutput* errorOutput, BPackageInfo* packageInfo,
97		BAbstractBufferedDataReader* heapReader,
98		BRepositoryInfo* repositoryInfo)
99		:
100		BPackageInfoContentHandler(*packageInfo, errorOutput),
101		fHeapReader(heapReader),
102		fRepositoryInfo(repositoryInfo)
103	{
104	}
105
106	virtual status_t HandleEntry(BPackageEntry* entry)
107	{
108		// if license must be approved, read any license files from package such
109		// that those can be stored in the repository later
110		if ((fPackageInfo.Flags() & B_PACKAGE_FLAG_APPROVE_LICENSE) == 0
111			|| entry == NULL)
112			return B_OK;
113
114		// return if not in ./data/licenses folder
115		const BPackageEntry* parent = entry->Parent();
116		BString licenseFolderName("licenses");
117		if (parent == NULL || licenseFolderName != parent->Name())
118			return B_OK;
119
120		parent = parent->Parent();
121		BString dataFolderName("data");
122		if (parent == NULL || dataFolderName != parent->Name())
123			return B_OK;
124
125		if (parent->Parent() != NULL)
126			return B_OK;
127
128		// check if license already is in repository
129		const BStringList& licenseNames = fRepositoryInfo->LicenseNames();
130		for (int i = 0; i < licenseNames.CountStrings(); ++i) {
131			if (licenseNames.StringAt(i).ICompare(entry->Name()) == 0) {
132				// license already exists
133				return B_OK;
134			}
135		}
136
137		// fetch contents of license file
138		BPackageData& packageData = entry->Data();
139		PackageEntryDataFetcher dataFetcher(fErrorOutput, packageData);
140
141		BString licenseText;
142		status_t result = dataFetcher.ReadIntoString(fHeapReader, licenseText);
143		if (result != B_OK)
144			return result;
145
146		// add license to repository
147		return fRepositoryInfo->AddLicense(entry->Name(), licenseText);
148	}
149
150	virtual status_t HandleEntryAttribute(BPackageEntry* entry,
151		BPackageEntryAttribute* attribute)
152	{
153		return B_OK;
154	}
155
156	virtual status_t HandleEntryDone(BPackageEntry* entry)
157	{
158		return B_OK;
159	}
160
161	virtual void HandleErrorOccurred()
162	{
163	}
164
165private:
166	BPackageReader*					fPackageReader;
167	BAbstractBufferedDataReader*	fHeapReader;
168	BRepositoryInfo*				fRepositoryInfo;
169};
170
171
172}	// anonymous namespace
173
174
175// #pragma mark - PackageNameSet
176
177
178struct RepositoryWriterImpl::PackageNameSet
179	: public ::BPrivate::HashSet<HashableString> {
180};
181
182
183// #pragma mark - RepositoryWriterImpl
184
185
186RepositoryWriterImpl::RepositoryWriterImpl(BRepositoryWriterListener* listener,
187	BRepositoryInfo* repositoryInfo)
188	:
189	inherited("repository", listener),
190	fListener(listener),
191	fRepositoryInfo(repositoryInfo),
192	fPackageCount(0),
193	fPackageNames(NULL)
194{
195}
196
197
198RepositoryWriterImpl::~RepositoryWriterImpl()
199{
200	delete fPackageNames;
201}
202
203
204status_t
205RepositoryWriterImpl::Init(const char* fileName)
206{
207	try {
208		fPackageNames = new PackageNameSet();
209		status_t result = fPackageNames->InitCheck();
210		if (result != B_OK)
211			return result;
212		return _Init(fileName);
213	} catch (status_t error) {
214		return error;
215	} catch (std::bad_alloc&) {
216		fListener->PrintError("Out of memory!\n");
217		return B_NO_MEMORY;
218	}
219}
220
221
222status_t
223RepositoryWriterImpl::AddPackage(const BEntry& packageEntry)
224{
225	try {
226		return _AddPackage(packageEntry);
227	} catch (status_t error) {
228		return error;
229	} catch (std::bad_alloc&) {
230		fListener->PrintError("Out of memory!\n");
231		return B_NO_MEMORY;
232	}
233}
234
235
236status_t
237RepositoryWriterImpl::AddPackageInfo(const BPackageInfo& packageInfo)
238{
239	try {
240		return _AddPackageInfo(packageInfo);
241	} catch (status_t error) {
242		return error;
243	} catch (std::bad_alloc&) {
244		fListener->PrintError("Out of memory!\n");
245		return B_NO_MEMORY;
246	}
247}
248
249
250status_t
251RepositoryWriterImpl::Finish()
252{
253	try {
254		return _Finish();
255	} catch (status_t error) {
256		return error;
257	} catch (std::bad_alloc&) {
258		fListener->PrintError("Out of memory!\n");
259		return B_NO_MEMORY;
260	}
261}
262
263
264status_t
265RepositoryWriterImpl::_Init(const char* fileName)
266{
267	status_t error = inherited::Init(NULL, false, fileName,
268		BPackageWriterParameters());
269	if (error != B_OK)
270		return error;
271
272	return InitHeapReader(sizeof(hpkg_repo_header));
273}
274
275
276status_t
277RepositoryWriterImpl::_Finish()
278{
279	hpkg_repo_header header;
280
281	// write repository info
282	uint64 infoLength;
283	status_t result = _WriteRepositoryInfo(header, infoLength);
284	if (result != B_OK)
285		return result;
286
287	// write package attributes
288	uint64 packagesLength;
289	_WritePackageAttributes(header, packagesLength);
290
291	// flush the heap writer
292	result = fHeapWriter->Finish();
293	if (result != B_OK)
294		return result;
295	uint64 compressedHeapSize = fHeapWriter->CompressedHeapSize();
296	uint64 totalSize = fHeapWriter->HeapOffset() + compressedHeapSize;
297
298	header.heap_compression = B_HOST_TO_BENDIAN_INT16(
299		Parameters().Compression());
300	header.heap_chunk_size = B_HOST_TO_BENDIAN_INT32(fHeapWriter->ChunkSize());
301	header.heap_size_compressed = B_HOST_TO_BENDIAN_INT64(compressedHeapSize);
302	header.heap_size_uncompressed = B_HOST_TO_BENDIAN_INT64(
303		fHeapWriter->UncompressedHeapSize());
304
305	fListener->OnRepositoryDone(sizeof(header), infoLength,
306		fRepositoryInfo->LicenseNames().CountStrings(), fPackageCount,
307		packagesLength, totalSize);
308
309	// update the general header info and write the header
310	header.magic = B_HOST_TO_BENDIAN_INT32(B_HPKG_REPO_MAGIC);
311	header.header_size = B_HOST_TO_BENDIAN_INT16((uint16)sizeof(header));
312	header.version = B_HOST_TO_BENDIAN_INT16(B_HPKG_REPO_VERSION);
313	header.total_size = B_HOST_TO_BENDIAN_INT64(totalSize);
314	header.minor_version = B_HOST_TO_BENDIAN_INT16(B_HPKG_REPO_MINOR_VERSION);
315
316	RawWriteBuffer(&header, sizeof(header), 0);
317
318	SetFinished(true);
319	return B_OK;
320}
321
322
323status_t
324RepositoryWriterImpl::_AddPackage(const BEntry& packageEntry)
325{
326	status_t result = packageEntry.InitCheck();
327	if (result != B_OK) {
328		fListener->PrintError("entry not initialized!\n");
329		return result;
330	}
331
332	BPath packagePath;
333	if ((result = packageEntry.GetPath(&packagePath)) != B_OK) {
334		fListener->PrintError("can't get path for entry '%s'!\n",
335			packageEntry.Name());
336		return result;
337	}
338
339	BPackageReader packageReader(fListener);
340	if ((result = packageReader.Init(packagePath.Path())) != B_OK) {
341		fListener->PrintError("can't create package reader for '%s'!\n",
342			packagePath.Path());
343		return result;
344	}
345
346	fPackageInfo.Clear();
347
348	// parse package
349	PackageContentHandler contentHandler(fListener, &fPackageInfo,
350		packageReader.HeapReader(), fRepositoryInfo);
351	if ((result = packageReader.ParseContent(&contentHandler)) != B_OK)
352		return result;
353
354	// determine package's checksum
355	GeneralFileChecksumAccessor checksumAccessor(packageEntry);
356	BString checksum;
357	if ((result = checksumAccessor.GetChecksum(checksum)) != B_OK) {
358		fListener->PrintError("can't compute checksum of file '%s'!\n",
359			packagePath.Path());
360		return result;
361	}
362	fPackageInfo.SetChecksum(checksum);
363
364	// register package's attributes
365	if ((result = _RegisterCurrentPackageInfo()) != B_OK)
366		return result;
367
368	return B_OK;
369}
370
371
372status_t
373RepositoryWriterImpl::_AddPackageInfo(const BPackageInfo& packageInfo)
374{
375	fPackageInfo = packageInfo;
376
377	// register package's attributes
378	status_t result = _RegisterCurrentPackageInfo();
379	if (result != B_OK)
380		return result;
381
382	return B_OK;
383}
384
385
386status_t
387RepositoryWriterImpl::_RegisterCurrentPackageInfo()
388{
389	status_t result = fPackageInfo.InitCheck();
390	if (result != B_OK) {
391		fListener->PrintError("package %s has incomplete package-info!\n",
392			fPackageInfo.Name().String());
393		return result;
394	}
395
396	// reject package with a name that we've seen already
397	if (fPackageNames->Contains(fPackageInfo.Name())) {
398		fListener->PrintError("package %s has already been added!\n",
399			fPackageInfo.Name().String());
400		return B_NAME_IN_USE;
401	}
402
403	// all packages must have the same vendor as the repository
404	const BString& expectedVendor = fRepositoryInfo->Vendor();
405	if (fPackageInfo.Vendor().ICompare(expectedVendor) != 0) {
406		fListener->PrintError("package '%s' has unexpected vendor '%s' "
407			"(expected '%s')!\n", fPackageInfo.Name().String(),
408			fPackageInfo.Vendor().String(), expectedVendor.String());
409		return B_BAD_DATA;
410	}
411
412	// all packages must have an architecture that's compatible with the one
413	// used by the repository
414	BPackageArchitecture expectedArchitecture = fRepositoryInfo->Architecture();
415	if (fPackageInfo.Architecture() != expectedArchitecture
416		&& fPackageInfo.Architecture() != B_PACKAGE_ARCHITECTURE_ANY
417		&& fPackageInfo.Architecture() != B_PACKAGE_ARCHITECTURE_SOURCE) {
418		fListener->PrintError(
419			"package '%s' has non-matching architecture '%s' "
420			"(expected '%s', '%s', or '%s')!\n", fPackageInfo.Name().String(),
421			BPackageInfo::kArchitectureNames[fPackageInfo.Architecture()],
422			BPackageInfo::kArchitectureNames[expectedArchitecture],
423			BPackageInfo::kArchitectureNames[B_PACKAGE_ARCHITECTURE_ANY],
424			BPackageInfo::kArchitectureNames[B_PACKAGE_ARCHITECTURE_SOURCE]);
425		return B_BAD_DATA;
426	}
427
428	if ((result = fPackageNames->Add(fPackageInfo.Name())) != B_OK)
429		return result;
430
431	PackageAttribute* packageAttribute = AddStringAttribute(
432		B_HPKG_ATTRIBUTE_ID_PACKAGE, fPackageInfo.Name(), PackageAttributes());
433	RegisterPackageInfo(packageAttribute->children, fPackageInfo);
434	fPackageCount++;
435	fListener->OnPackageAdded(fPackageInfo);
436
437	return B_OK;
438}
439
440
441status_t
442RepositoryWriterImpl::_WriteRepositoryInfo(hpkg_repo_header& header,
443	uint64& _length)
444{
445	// archive and flatten the repository info and write it
446	BMessage archive;
447	status_t result = fRepositoryInfo->Archive(&archive);
448	if (result != B_OK) {
449		fListener->PrintError("can't archive repository header!\n");
450		return result;
451	}
452
453	ssize_t	flattenedSize = archive.FlattenedSize();
454	char buffer[flattenedSize];
455	if ((result = archive.Flatten(buffer, flattenedSize)) != B_OK) {
456		fListener->PrintError("can't flatten repository header!\n");
457		return result;
458	}
459
460	WriteBuffer(buffer, flattenedSize);
461
462	// notify listener
463	fListener->OnRepositoryInfoSectionDone(flattenedSize);
464
465	// update the header
466	header.info_length = B_HOST_TO_BENDIAN_INT32(flattenedSize);
467
468	_length = flattenedSize;
469	return B_OK;
470}
471
472
473void
474RepositoryWriterImpl::_WritePackageAttributes(hpkg_repo_header& header,
475	uint64& _length)
476{
477	// write the package attributes (zlib writer on top of a file writer)
478	uint64 startOffset = fHeapWriter->UncompressedHeapSize();
479
480	uint32 stringsLength;
481	uint32 stringsCount = WritePackageAttributes(PackageAttributes(),
482		stringsLength);
483
484	uint64 sectionSize = fHeapWriter->UncompressedHeapSize() - startOffset;
485
486	fListener->OnPackageAttributesSectionDone(stringsCount, sectionSize);
487
488	// update the header
489	header.packages_length = B_HOST_TO_BENDIAN_INT64(sectionSize);
490	header.packages_strings_count = B_HOST_TO_BENDIAN_INT64(stringsCount);
491	header.packages_strings_length = B_HOST_TO_BENDIAN_INT64(stringsLength);
492
493	_length = sectionSize;
494}
495
496
497}	// namespace BPrivate
498
499}	// namespace BHPKG
500
501}	// namespace BPackageKit
502