xref: /haiku/src/kits/package/hpkg/RepositoryWriterImpl.cpp (revision 04a0e9c7b68cbe3a43d38e2bca8e860fd80936fb)
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 
32 namespace BPackageKit {
33 
34 namespace BHPKG {
35 
36 namespace BPrivate {
37 
38 
39 using BPackageKit::BPrivate::GeneralFileChecksumAccessor;
40 using BPackageKit::BPrivate::HashableString;
41 
42 
43 namespace {
44 
45 
46 // #pragma mark - PackageEntryDataFetcher
47 
48 
49 struct 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 
86 private:
87 	BErrorOutput*			fErrorOutput;
88 	BPackageData&			fPackageData;
89 };
90 
91 
92 // #pragma mark - PackageContentHandler
93 
94 
95 struct 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 
165 private:
166 	BPackageReader*					fPackageReader;
167 	BAbstractBufferedDataReader*	fHeapReader;
168 	BRepositoryInfo*				fRepositoryInfo;
169 };
170 
171 
172 }	// anonymous namespace
173 
174 
175 // #pragma mark - PackageNameSet
176 
177 
178 struct RepositoryWriterImpl::PackageNameSet
179 	: public ::BPrivate::HashSet<HashableString> {
180 };
181 
182 
183 // #pragma mark - RepositoryWriterImpl
184 
185 
186 RepositoryWriterImpl::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 
198 RepositoryWriterImpl::~RepositoryWriterImpl()
199 {
200 	delete fPackageNames;
201 }
202 
203 
204 status_t
205 RepositoryWriterImpl::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 
222 status_t
223 RepositoryWriterImpl::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 
236 status_t
237 RepositoryWriterImpl::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 
250 status_t
251 RepositoryWriterImpl::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 
264 status_t
265 RepositoryWriterImpl::_Init(const char* fileName)
266 {
267 	return inherited::Init(fileName, sizeof(hpkg_repo_header),
268 		BPackageWriterParameters());
269 }
270 
271 
272 status_t
273 RepositoryWriterImpl::_Finish()
274 {
275 	hpkg_repo_header header;
276 
277 	// write repository info
278 	uint64 infoLength;
279 	status_t result = _WriteRepositoryInfo(header, infoLength);
280 	if (result != B_OK)
281 		return result;
282 
283 	// write package attributes
284 	uint64 packagesLength;
285 	_WritePackageAttributes(header, packagesLength);
286 
287 	// flush the heap writer
288 	result = fHeapWriter->Finish();
289 	if (result != B_OK)
290 		return result;
291 	uint64 compressedHeapSize = fHeapWriter->CompressedHeapSize();
292 	uint64 totalSize = fHeapWriter->HeapOffset() + compressedHeapSize;
293 
294 	header.heap_compression = B_HOST_TO_BENDIAN_INT16(B_HPKG_COMPRESSION_ZLIB);
295 	header.heap_chunk_size = B_HOST_TO_BENDIAN_INT32(fHeapWriter->ChunkSize());
296 	header.heap_size_compressed = B_HOST_TO_BENDIAN_INT64(compressedHeapSize);
297 	header.heap_size_uncompressed = B_HOST_TO_BENDIAN_INT64(
298 		fHeapWriter->UncompressedHeapSize());
299 
300 	fListener->OnRepositoryDone(sizeof(header), infoLength,
301 		fRepositoryInfo->LicenseNames().CountStrings(), fPackageCount,
302 		packagesLength, totalSize);
303 
304 	// update the general header info and write the header
305 	header.magic = B_HOST_TO_BENDIAN_INT32(B_HPKG_REPO_MAGIC);
306 	header.header_size = B_HOST_TO_BENDIAN_INT16((uint16)sizeof(header));
307 	header.version = B_HOST_TO_BENDIAN_INT16(B_HPKG_REPO_VERSION);
308 	header.total_size = B_HOST_TO_BENDIAN_INT64(totalSize);
309 	header.minor_version = B_HOST_TO_BENDIAN_INT16(B_HPKG_REPO_MINOR_VERSION);
310 
311 	RawWriteBuffer(&header, sizeof(header), 0);
312 
313 	SetFinished(true);
314 	return B_OK;
315 }
316 
317 
318 status_t
319 RepositoryWriterImpl::_AddPackage(const BEntry& packageEntry)
320 {
321 	status_t result = packageEntry.InitCheck();
322 	if (result != B_OK) {
323 		fListener->PrintError("entry not initialized!\n");
324 		return result;
325 	}
326 
327 	BPath packagePath;
328 	if ((result = packageEntry.GetPath(&packagePath)) != B_OK) {
329 		fListener->PrintError("can't get path for entry '%s'!\n",
330 			packageEntry.Name());
331 		return result;
332 	}
333 
334 	BPackageReader packageReader(fListener);
335 	if ((result = packageReader.Init(packagePath.Path())) != B_OK) {
336 		fListener->PrintError("can't create package reader for '%s'!\n",
337 			packagePath.Path());
338 		return result;
339 	}
340 
341 	fPackageInfo.Clear();
342 
343 	// parse package
344 	PackageContentHandler contentHandler(fListener, &fPackageInfo,
345 		packageReader.HeapReader(), fRepositoryInfo);
346 	if ((result = packageReader.ParseContent(&contentHandler)) != B_OK)
347 		return result;
348 
349 	// determine package's checksum
350 	GeneralFileChecksumAccessor checksumAccessor(packageEntry);
351 	BString checksum;
352 	if ((result = checksumAccessor.GetChecksum(checksum)) != B_OK) {
353 		fListener->PrintError("can't compute checksum of file '%s'!\n",
354 			packagePath.Path());
355 		return result;
356 	}
357 	fPackageInfo.SetChecksum(checksum);
358 
359 	// register package's attributes
360 	if ((result = _RegisterCurrentPackageInfo()) != B_OK)
361 		return result;
362 
363 	return B_OK;
364 }
365 
366 
367 status_t
368 RepositoryWriterImpl::_AddPackageInfo(const BPackageInfo& packageInfo)
369 {
370 	fPackageInfo = packageInfo;
371 
372 	// register package's attributes
373 	status_t result = _RegisterCurrentPackageInfo();
374 	if (result != B_OK)
375 		return result;
376 
377 	return B_OK;
378 }
379 
380 
381 status_t
382 RepositoryWriterImpl::_RegisterCurrentPackageInfo()
383 {
384 	status_t result = fPackageInfo.InitCheck();
385 	if (result != B_OK) {
386 		fListener->PrintError("package %s has incomplete package-info!\n",
387 			fPackageInfo.Name().String());
388 		return result;
389 	}
390 
391 	// reject package with a name that we've seen already
392 	if (fPackageNames->Contains(fPackageInfo.Name())) {
393 		fListener->PrintError("package %s has already been added!\n",
394 			fPackageInfo.Name().String());
395 		return B_NAME_IN_USE;
396 	}
397 
398 	// all packages must have the same vendor as the repository
399 	const BString& expectedVendor = fRepositoryInfo->Vendor();
400 	if (fPackageInfo.Vendor().ICompare(expectedVendor) != 0) {
401 		fListener->PrintError("package '%s' has unexpected vendor '%s' "
402 			"(expected '%s')!\n", fPackageInfo.Name().String(),
403 			fPackageInfo.Vendor().String(), expectedVendor.String());
404 		return B_BAD_DATA;
405 	}
406 
407 	// all packages must have an architecture that's compatible with the one
408 	// used by the repository
409 	BPackageArchitecture expectedArchitecture = fRepositoryInfo->Architecture();
410 	if (fPackageInfo.Architecture() != expectedArchitecture
411 		&& fPackageInfo.Architecture() != B_PACKAGE_ARCHITECTURE_ANY
412 		&& fPackageInfo.Architecture() != B_PACKAGE_ARCHITECTURE_SOURCE) {
413 		fListener->PrintError(
414 			"package '%s' has non-matching architecture '%s' "
415 			"(expected '%s', '%s', or '%s')!\n", fPackageInfo.Name().String(),
416 			BPackageInfo::kArchitectureNames[fPackageInfo.Architecture()],
417 			BPackageInfo::kArchitectureNames[expectedArchitecture],
418 			BPackageInfo::kArchitectureNames[B_PACKAGE_ARCHITECTURE_ANY],
419 			BPackageInfo::kArchitectureNames[B_PACKAGE_ARCHITECTURE_SOURCE]);
420 		return B_BAD_DATA;
421 	}
422 
423 	if ((result = fPackageNames->Add(fPackageInfo.Name())) != B_OK)
424 		return result;
425 
426 	PackageAttribute* packageAttribute = AddStringAttribute(
427 		B_HPKG_ATTRIBUTE_ID_PACKAGE, fPackageInfo.Name(), PackageAttributes());
428 	RegisterPackageInfo(packageAttribute->children, fPackageInfo);
429 	fPackageCount++;
430 	fListener->OnPackageAdded(fPackageInfo);
431 
432 	return B_OK;
433 }
434 
435 
436 status_t
437 RepositoryWriterImpl::_WriteRepositoryInfo(hpkg_repo_header& header,
438 	uint64& _length)
439 {
440 	// archive and flatten the repository info and write it
441 	BMessage archive;
442 	status_t result = fRepositoryInfo->Archive(&archive);
443 	if (result != B_OK) {
444 		fListener->PrintError("can't archive repository header!\n");
445 		return result;
446 	}
447 
448 	ssize_t	flattenedSize = archive.FlattenedSize();
449 	char buffer[flattenedSize];
450 	if ((result = archive.Flatten(buffer, flattenedSize)) != B_OK) {
451 		fListener->PrintError("can't flatten repository header!\n");
452 		return result;
453 	}
454 
455 	WriteBuffer(buffer, flattenedSize);
456 
457 	// notify listener
458 	fListener->OnRepositoryInfoSectionDone(flattenedSize);
459 
460 	// update the header
461 	header.info_length = B_HOST_TO_BENDIAN_INT32(flattenedSize);
462 
463 	_length = flattenedSize;
464 	return B_OK;
465 }
466 
467 
468 void
469 RepositoryWriterImpl::_WritePackageAttributes(hpkg_repo_header& header,
470 	uint64& _length)
471 {
472 	// write the package attributes (zlib writer on top of a file writer)
473 	uint64 startOffset = fHeapWriter->UncompressedHeapSize();
474 
475 	uint32 stringsLength;
476 	uint32 stringsCount = WritePackageAttributes(PackageAttributes(),
477 		stringsLength);
478 
479 	uint64 sectionSize = fHeapWriter->UncompressedHeapSize() - startOffset;
480 
481 	fListener->OnPackageAttributesSectionDone(stringsCount, sectionSize);
482 
483 	// update the header
484 	header.packages_length = B_HOST_TO_BENDIAN_INT64(sectionSize);
485 	header.packages_strings_count = B_HOST_TO_BENDIAN_INT64(stringsCount);
486 	header.packages_strings_length = B_HOST_TO_BENDIAN_INT64(stringsLength);
487 
488 	_length = sectionSize;
489 }
490 
491 
492 }	// namespace BPrivate
493 
494 }	// namespace BHPKG
495 
496 }	// namespace BPackageKit
497