xref: /haiku/src/kits/package/hpkg/RepositoryWriterImpl.cpp (revision 17889a8c70dbb3d59c1412f6431968753c767bab)
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 	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 
276 status_t
277 RepositoryWriterImpl::_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 
323 status_t
324 RepositoryWriterImpl::_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 
372 status_t
373 RepositoryWriterImpl::_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 
386 status_t
387 RepositoryWriterImpl::_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 
441 status_t
442 RepositoryWriterImpl::_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 
473 void
474 RepositoryWriterImpl::_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