xref: /haiku/src/kits/package/hpkg/RepositoryWriterImpl.cpp (revision b46615c55ad2c8fe6de54412055a0713da3d610a)
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/HPKGDefsPrivate.h>
20 #include <package/hpkg/PackageDataReader.h>
21 #include <package/hpkg/PackageEntry.h>
22 #include <package/hpkg/PackageInfoAttributeValue.h>
23 #include <package/hpkg/PackageReader.h>
24 #include <package/BlockBufferCacheNoLock.h>
25 #include <package/ChecksumAccessors.h>
26 #include <package/HashableString.h>
27 #include <package/RepositoryInfo.h>
28 
29 
30 namespace BPackageKit {
31 
32 namespace BHPKG {
33 
34 namespace BPrivate {
35 
36 
37 using BPackageKit::BPrivate::GeneralFileChecksumAccessor;
38 using BPackageKit::BPrivate::HashableString;
39 
40 
41 namespace {
42 
43 
44 struct PackageEntryDataFetcher {
45 	PackageEntryDataFetcher(BErrorOutput* errorOutput,
46 		BPackageData& packageData)
47 		:
48 		fErrorOutput(errorOutput),
49 		fBufferCache(B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB, 2),
50 		fPackageData(packageData)
51 	{
52 	}
53 
54 	status_t ReadIntoString(BDataReader* dataReader, BString& _contents)
55 	{
56 		// create a BPackageDataReader
57 		BPackageDataReader* reader;
58 		status_t result = BPackageDataReaderFactory(&fBufferCache)
59 			.CreatePackageDataReader(dataReader, fPackageData, reader);
60 		if (result != B_OK)
61 			return result;
62 		ObjectDeleter<BPackageDataReader> readerDeleter(reader);
63 
64 		// copy data into the given string
65 		int32 bufferSize = fPackageData.UncompressedSize();
66 		char* buffer = _contents.LockBuffer(bufferSize);
67 		if (buffer == NULL)
68 			return B_NO_MEMORY;
69 
70 		result = reader->ReadData(0, buffer, bufferSize);
71 		if (result != B_OK) {
72 			fErrorOutput->PrintError("Error: Failed to read data: %s\n",
73 				strerror(result));
74 			_contents.UnlockBuffer(0);
75 		} else
76 			_contents.UnlockBuffer(bufferSize);
77 
78 		return result;
79 	}
80 
81 private:
82 	BErrorOutput*			fErrorOutput;
83 	BBlockBufferCacheNoLock fBufferCache;
84 	BPackageData&			fPackageData;
85 };
86 
87 
88 struct PackageContentHandler : public BPackageContentHandler {
89 	PackageContentHandler(BErrorOutput* errorOutput, BPackageInfo* packageInfo,
90 		int packageFileFD, BRepositoryInfo* repositoryInfo)
91 		:
92 		fErrorOutput(errorOutput),
93 		fPackageInfo(packageInfo),
94 		fPackageFileReader(packageFileFD),
95 		fRepositoryInfo(repositoryInfo)
96 	{
97 	}
98 
99 	virtual status_t HandleEntry(BPackageEntry* entry)
100 	{
101 		// if license must be approved, read any license files from package such
102 		// that those can be stored in the repository later
103 		if ((fPackageInfo->Flags() & B_PACKAGE_FLAG_APPROVE_LICENSE) == 0
104 			|| entry == NULL)
105 			return B_OK;
106 
107 		// return if not in ./data/licenses folder
108 		const BPackageEntry* parent = entry->Parent();
109 		BString licenseFolderName("licenses");
110 		if (parent == NULL || licenseFolderName != parent->Name())
111 			return B_OK;
112 
113 		parent = parent->Parent();
114 		BString dataFolderName("data");
115 		if (parent == NULL || dataFolderName != parent->Name())
116 			return B_OK;
117 
118 		if (parent->Parent() != NULL)
119 			return B_OK;
120 
121 		// check if license already is in repository
122 		const BObjectList<BString>& licenseNames
123 			= fRepositoryInfo->LicenseNames();
124 		for (int i = 0; i < licenseNames.CountItems(); ++i) {
125 			if (licenseNames.ItemAt(i)->ICompare(entry->Name()) == 0) {
126 				// license already exists
127 				return B_OK;
128 			}
129 		}
130 
131 		// fetch contents of license file
132 		BPackageData& packageData = entry->Data();
133 		PackageEntryDataFetcher dataFetcher(fErrorOutput, packageData);
134 
135 		BString licenseText;
136 		status_t result;
137 		if (packageData.IsEncodedInline()) {
138 			BBufferDataReader dataReader(packageData.InlineData(),
139 				packageData.CompressedSize());
140 			result = dataFetcher.ReadIntoString(&dataReader, licenseText);
141 		} else {
142 			result
143 				= dataFetcher.ReadIntoString(&fPackageFileReader, licenseText);
144 		}
145 
146 		if (result != B_OK)
147 			return result;
148 
149 		// add license to repository
150 		return fRepositoryInfo->AddLicense(entry->Name(), licenseText);
151 	}
152 
153 	virtual status_t HandleEntryAttribute(BPackageEntry* entry,
154 		BPackageEntryAttribute* attribute)
155 	{
156 		return B_OK;
157 	}
158 
159 	virtual status_t HandleEntryDone(BPackageEntry* entry)
160 	{
161 		return B_OK;
162 	}
163 
164 	virtual status_t HandlePackageAttribute(
165 		const BPackageInfoAttributeValue& value)
166 	{
167 		switch (value.attributeID) {
168 			case B_PACKAGE_INFO_NAME:
169 				fPackageInfo->SetName(value.string);
170 				break;
171 
172 			case B_PACKAGE_INFO_SUMMARY:
173 				fPackageInfo->SetSummary(value.string);
174 				break;
175 
176 			case B_PACKAGE_INFO_DESCRIPTION:
177 				fPackageInfo->SetDescription(value.string);
178 				break;
179 
180 			case B_PACKAGE_INFO_VENDOR:
181 				fPackageInfo->SetVendor(value.string);
182 				break;
183 
184 			case B_PACKAGE_INFO_PACKAGER:
185 				fPackageInfo->SetPackager(value.string);
186 				break;
187 
188 			case B_PACKAGE_INFO_FLAGS:
189 				fPackageInfo->SetFlags(value.unsignedInt);
190 				break;
191 
192 			case B_PACKAGE_INFO_ARCHITECTURE:
193 				fPackageInfo->SetArchitecture(
194 					(BPackageArchitecture)value.unsignedInt);
195 				break;
196 
197 			case B_PACKAGE_INFO_VERSION:
198 				fPackageInfo->SetVersion(value.version);
199 				break;
200 
201 			case B_PACKAGE_INFO_COPYRIGHTS:
202 				fPackageInfo->AddCopyright(value.string);
203 				break;
204 
205 			case B_PACKAGE_INFO_LICENSES:
206 				fPackageInfo->AddLicense(value.string);
207 				break;
208 
209 			case B_PACKAGE_INFO_PROVIDES:
210 				fPackageInfo->AddProvides(value.resolvable);
211 				break;
212 
213 			case B_PACKAGE_INFO_REQUIRES:
214 				fPackageInfo->AddRequires(value.resolvableExpression);
215 				break;
216 
217 			case B_PACKAGE_INFO_SUPPLEMENTS:
218 				fPackageInfo->AddSupplements(value.resolvableExpression);
219 				break;
220 
221 			case B_PACKAGE_INFO_CONFLICTS:
222 				fPackageInfo->AddConflicts(value.resolvableExpression);
223 				break;
224 
225 			case B_PACKAGE_INFO_FRESHENS:
226 				fPackageInfo->AddFreshens(value.resolvableExpression);
227 				break;
228 
229 			case B_PACKAGE_INFO_REPLACES:
230 				fPackageInfo->AddReplaces(value.string);
231 				break;
232 
233 			default:
234 				fErrorOutput->PrintError(
235 					"Invalid package attribute section: unexpected package "
236 					"attribute id %d encountered\n", value.attributeID);
237 				return B_BAD_DATA;
238 		}
239 
240 		return B_OK;
241 	}
242 
243 	virtual void HandleErrorOccurred()
244 	{
245 	}
246 
247 private:
248 	BErrorOutput* 		fErrorOutput;
249 	BPackageInfo* 		fPackageInfo;
250 	BPackageReader*		fPackageReader;
251 	BFDDataReader		fPackageFileReader;
252 	BRepositoryInfo*	fRepositoryInfo;
253 };
254 
255 
256 }	// anonymous namespace
257 
258 
259 struct RepositoryWriterImpl::PackageNameSet
260 	: public ::BPrivate::HashSet<HashableString> {
261 };
262 
263 
264 RepositoryWriterImpl::RepositoryWriterImpl(BRepositoryWriterListener* listener,
265 	BRepositoryInfo* repositoryInfo)
266 	:
267 	inherited(listener),
268 	fListener(listener),
269 	fRepositoryInfo(repositoryInfo),
270 	fPackageCount(0),
271 	fPackageNames(NULL)
272 {
273 }
274 
275 
276 RepositoryWriterImpl::~RepositoryWriterImpl()
277 {
278 	delete fPackageNames;
279 }
280 
281 
282 status_t
283 RepositoryWriterImpl::Init(const char* fileName)
284 {
285 	try {
286 		fPackageNames = new PackageNameSet();
287 		status_t result = fPackageNames->InitCheck();
288 		if (result != B_OK)
289 			return result;
290 		return _Init(fileName);
291 	} catch (status_t error) {
292 		return error;
293 	} catch (std::bad_alloc) {
294 		fListener->PrintError("Out of memory!\n");
295 		return B_NO_MEMORY;
296 	}
297 }
298 
299 
300 status_t
301 RepositoryWriterImpl::AddPackage(const BEntry& packageEntry)
302 {
303 	try {
304 		return _AddPackage(packageEntry);
305 	} catch (status_t error) {
306 		return error;
307 	} catch (std::bad_alloc) {
308 		fListener->PrintError("Out of memory!\n");
309 		return B_NO_MEMORY;
310 	}
311 }
312 
313 
314 status_t
315 RepositoryWriterImpl::Finish()
316 {
317 	try {
318 		return _Finish();
319 	} catch (status_t error) {
320 		return error;
321 	} catch (std::bad_alloc) {
322 		fListener->PrintError("Out of memory!\n");
323 		return B_NO_MEMORY;
324 	}
325 }
326 
327 
328 status_t
329 RepositoryWriterImpl::_Init(const char* fileName)
330 {
331 	return inherited::Init(fileName, "repository");
332 }
333 
334 
335 status_t
336 RepositoryWriterImpl::_Finish()
337 {
338 	hpkg_repo_header header;
339 
340 	// write repository header
341 	ssize_t infoLengthCompressed;
342 	status_t result = _WriteRepositoryInfo(header, infoLengthCompressed);
343 	if (result != B_OK)
344 		return result;
345 
346 	// write package attributes
347 	ssize_t packagesLengthCompressed;
348 	off_t totalSize = _WritePackageAttributes(header,
349 		sizeof(header) + infoLengthCompressed, packagesLengthCompressed);
350 
351 	fListener->OnRepositoryDone(sizeof(header), infoLengthCompressed,
352 		fRepositoryInfo->LicenseNames().CountItems(), fPackageCount,
353 		packagesLengthCompressed, totalSize);
354 
355 	// general
356 	header.magic = B_HOST_TO_BENDIAN_INT32(B_HPKG_REPO_MAGIC);
357 	header.header_size = B_HOST_TO_BENDIAN_INT16((uint16)sizeof(header));
358 	header.version = B_HOST_TO_BENDIAN_INT16(B_HPKG_REPO_VERSION);
359 	header.total_size = B_HOST_TO_BENDIAN_INT64(totalSize);
360 
361 	// write the header
362 	WriteBuffer(&header, sizeof(header), 0);
363 
364 	SetFinished(true);
365 	return B_OK;
366 }
367 
368 
369 status_t
370 RepositoryWriterImpl::_AddPackage(const BEntry& packageEntry)
371 {
372 	status_t result = packageEntry.InitCheck();
373 	if (result != B_OK) {
374 		fListener->PrintError("entry not initialized!\n");
375 		return result;
376 	}
377 
378 	BPath packagePath;
379 	if ((result = packageEntry.GetPath(&packagePath)) != B_OK) {
380 		fListener->PrintError("can't get path for entry!\n");
381 		return result;
382 	}
383 
384 	BPackageReader packageReader(fListener);
385 	if ((result = packageReader.Init(packagePath.Path())) != B_OK) {
386 		fListener->PrintError("can't create package reader!\n");
387 		return result;
388 	}
389 
390 	fPackageInfo.Clear();
391 
392 	// parse package
393 	PackageContentHandler contentHandler(fListener, &fPackageInfo,
394 		packageReader.PackageFileFD(), fRepositoryInfo);
395 	if ((result = packageReader.ParseContent(&contentHandler)) != B_OK)
396 		return result;
397 
398 	// determine package's checksum
399 	GeneralFileChecksumAccessor checksumAccessor(packageEntry);
400 	BString checksum;
401 	if ((result = checksumAccessor.GetChecksum(checksum)) != B_OK) {
402 		fListener->PrintError("can't compute checksum!\n");
403 		return result;
404 	}
405 	fPackageInfo.SetChecksum(checksum);
406 
407 	// register package's attributes
408 	if ((result = _RegisterCurrentPackageInfo()) != B_OK)
409 		return result;
410 
411 	return B_OK;
412 }
413 
414 
415 status_t
416 RepositoryWriterImpl::_RegisterCurrentPackageInfo()
417 {
418 	status_t result = fPackageInfo.InitCheck();
419 	if (result != B_OK) {
420 		fListener->PrintError("package %s has incomplete package-info!\n",
421 			fPackageInfo.Name().String());
422 		return result;
423 	}
424 
425 	// reject package with a name that we've seen already
426 	if (fPackageNames->Contains(fPackageInfo.Name())) {
427 		fListener->PrintError("package %s has already been added!\n",
428 			fPackageInfo.Name().String());
429 		return B_NAME_IN_USE;
430 	}
431 
432 	// all packages must have the same vendor as the repository
433 	const BString& expectedVendor = fRepositoryInfo->Vendor();
434 	if (fPackageInfo.Vendor().ICompare(expectedVendor) != 0) {
435 		fListener->PrintError("package '%s' has unexpected vendor '%s' "
436 			"(expected '%s')!\n", fPackageInfo.Name().String(),
437 			fPackageInfo.Vendor().String(), expectedVendor.String());
438 		return B_BAD_DATA;
439 	}
440 
441 	// all packages must have an architecture that's compatible with the one
442 	// used by the repository
443 	BPackageArchitecture expectedArchitecture = fRepositoryInfo->Architecture();
444 	if (fPackageInfo.Architecture() != expectedArchitecture
445 		&& fPackageInfo.Architecture() != B_PACKAGE_ARCHITECTURE_ANY) {
446 		fListener->PrintError(
447 			"package '%s' has non-matching architecture '%s' "
448 			"(expected '%s' or '%s')!\n", fPackageInfo.Name().String(),
449 			BPackageInfo::kArchitectureNames[fPackageInfo.Architecture()],
450 			BPackageInfo::kArchitectureNames[expectedArchitecture],
451 			BPackageInfo::kArchitectureNames[B_PACKAGE_ARCHITECTURE_ANY]
452 		);
453 		return B_BAD_DATA;
454 	}
455 
456 	if ((result = fPackageNames->Add(fPackageInfo.Name())) != B_OK)
457 		return result;
458 
459 	RegisterPackageInfo(PackageAttributes(), fPackageInfo);
460 	fPackageCount++;
461 	fListener->OnPackageAdded(fPackageInfo);
462 
463 	return B_OK;
464 }
465 
466 
467 status_t
468 RepositoryWriterImpl::_WriteRepositoryInfo(hpkg_repo_header& header,
469 	ssize_t& _infoLengthCompressed)
470 {
471 	BMessage archive;
472 	status_t result = fRepositoryInfo->Archive(&archive);
473 	if (result != B_OK) {
474 		fListener->PrintError("can't archive repository header!\n");
475 		return result;
476 	}
477 
478 	ssize_t	flattenedSize = archive.FlattenedSize();
479 	char buffer[flattenedSize];
480 	if ((result = archive.Flatten(buffer, flattenedSize)) != B_OK) {
481 		fListener->PrintError("can't flatten repository header!\n");
482 		return result;
483 	}
484 
485 	off_t startOffset = sizeof(hpkg_repo_header);
486 
487 	// write the package attributes (zlib writer on top of a file writer)
488 	FDDataWriter realWriter(FD(), startOffset, fListener);
489 	ZlibDataWriter zlibWriter(&realWriter);
490 	SetDataWriter(&zlibWriter);
491 	zlibWriter.Init();
492 
493 	DataWriter()->WriteDataThrows(buffer, flattenedSize);
494 
495 	zlibWriter.Finish();
496 	SetDataWriter(NULL);
497 
498 	fListener->OnRepositoryInfoSectionDone(zlibWriter.BytesWritten());
499 
500 	_infoLengthCompressed = realWriter.BytesWritten();
501 
502 	// update the header
503 	header.info_compression
504 		= B_HOST_TO_BENDIAN_INT32(B_HPKG_COMPRESSION_ZLIB);
505 	header.info_length_compressed
506 		= B_HOST_TO_BENDIAN_INT32(_infoLengthCompressed);
507 	header.info_length_uncompressed
508 		= B_HOST_TO_BENDIAN_INT32(flattenedSize);
509 
510 	return B_OK;
511 }
512 
513 
514 off_t
515 RepositoryWriterImpl::_WritePackageAttributes(hpkg_repo_header& header,
516 	off_t startOffset, ssize_t& _packagesLengthCompressed)
517 {
518 	// write the package attributes (zlib writer on top of a file writer)
519 	FDDataWriter realWriter(FD(), startOffset, fListener);
520 	ZlibDataWriter zlibWriter(&realWriter);
521 	SetDataWriter(&zlibWriter);
522 	zlibWriter.Init();
523 
524 	// write cached strings and package attributes tree
525 	uint32 stringsLengthUncompressed;
526 	uint32 stringsCount = WritePackageAttributes(PackageAttributes(),
527 		stringsLengthUncompressed);
528 
529 	zlibWriter.Finish();
530 	off_t endOffset = realWriter.Offset();
531 	SetDataWriter(NULL);
532 
533 	fListener->OnPackageAttributesSectionDone(stringsCount,
534 		zlibWriter.BytesWritten());
535 
536 	_packagesLengthCompressed = endOffset - startOffset;
537 
538 	// update the header
539 	header.packages_compression
540 		= B_HOST_TO_BENDIAN_INT32(B_HPKG_COMPRESSION_ZLIB);
541 	header.packages_length_compressed
542 		= B_HOST_TO_BENDIAN_INT64(_packagesLengthCompressed);
543 	header.packages_length_uncompressed
544 		= B_HOST_TO_BENDIAN_INT64(zlibWriter.BytesWritten());
545 	header.packages_strings_count = B_HOST_TO_BENDIAN_INT64(stringsCount);
546 	header.packages_strings_length
547 		= B_HOST_TO_BENDIAN_INT64(stringsLengthUncompressed);
548 
549 	return endOffset;
550 }
551 
552 
553 }	// namespace BPrivate
554 
555 }	// namespace BHPKG
556 
557 }	// namespace BPackageKit
558