xref: /haiku/src/kits/package/hpkg/RepositoryWriterImpl.cpp (revision f5821a1aee77d3b9a979b42c68a79e50b5ebaefe)
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::Finish()
238 {
239 	try {
240 		return _Finish();
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::_Init(const char* fileName)
252 {
253 	return inherited::Init(fileName, sizeof(hpkg_repo_header),
254 		BPackageWriterParameters());
255 }
256 
257 
258 status_t
259 RepositoryWriterImpl::_Finish()
260 {
261 	hpkg_repo_header header;
262 
263 	// write repository info
264 	uint64 infoLength;
265 	status_t result = _WriteRepositoryInfo(header, infoLength);
266 	if (result != B_OK)
267 		return result;
268 
269 	// write package attributes
270 	uint64 packagesLength;
271 	_WritePackageAttributes(header, packagesLength);
272 
273 	// flush the heap writer
274 	result = fHeapWriter->Finish();
275 	if (result != B_OK)
276 		return result;
277 	uint64 compressedHeapSize = fHeapWriter->CompressedHeapSize();
278 	uint64 totalSize = fHeapWriter->HeapOffset() + compressedHeapSize;
279 
280 	header.heap_compression = B_HOST_TO_BENDIAN_INT16(B_HPKG_COMPRESSION_ZLIB);
281 	header.heap_chunk_size = B_HOST_TO_BENDIAN_INT32(fHeapWriter->ChunkSize());
282 	header.heap_size_compressed = B_HOST_TO_BENDIAN_INT64(compressedHeapSize);
283 	header.heap_size_uncompressed = B_HOST_TO_BENDIAN_INT64(
284 		fHeapWriter->UncompressedHeapSize());
285 
286 	fListener->OnRepositoryDone(sizeof(header), infoLength,
287 		fRepositoryInfo->LicenseNames().CountStrings(), fPackageCount,
288 		packagesLength, totalSize);
289 
290 	// update the general header info and write the header
291 	header.magic = B_HOST_TO_BENDIAN_INT32(B_HPKG_REPO_MAGIC);
292 	header.header_size = B_HOST_TO_BENDIAN_INT16((uint16)sizeof(header));
293 	header.version = B_HOST_TO_BENDIAN_INT16(B_HPKG_REPO_VERSION);
294 	header.total_size = B_HOST_TO_BENDIAN_INT64(totalSize);
295 	header.minor_version = B_HOST_TO_BENDIAN_INT16(B_HPKG_REPO_MINOR_VERSION);
296 
297 	RawWriteBuffer(&header, sizeof(header), 0);
298 
299 	SetFinished(true);
300 	return B_OK;
301 }
302 
303 
304 status_t
305 RepositoryWriterImpl::_AddPackage(const BEntry& packageEntry)
306 {
307 	status_t result = packageEntry.InitCheck();
308 	if (result != B_OK) {
309 		fListener->PrintError("entry not initialized!\n");
310 		return result;
311 	}
312 
313 	BPath packagePath;
314 	if ((result = packageEntry.GetPath(&packagePath)) != B_OK) {
315 		fListener->PrintError("can't get path for entry '%s'!\n",
316 			packageEntry.Name());
317 		return result;
318 	}
319 
320 	BPackageReader packageReader(fListener);
321 	if ((result = packageReader.Init(packagePath.Path())) != B_OK) {
322 		fListener->PrintError("can't create package reader for '%s'!\n",
323 			packagePath.Path());
324 		return result;
325 	}
326 
327 	fPackageInfo.Clear();
328 
329 	// parse package
330 	PackageContentHandler contentHandler(fListener, &fPackageInfo,
331 		packageReader.HeapReader(), fRepositoryInfo);
332 	if ((result = packageReader.ParseContent(&contentHandler)) != B_OK)
333 		return result;
334 
335 	// determine package's checksum
336 	GeneralFileChecksumAccessor checksumAccessor(packageEntry);
337 	BString checksum;
338 	if ((result = checksumAccessor.GetChecksum(checksum)) != B_OK) {
339 		fListener->PrintError("can't compute checksum of file '%s'!\n",
340 			packagePath.Path());
341 		return result;
342 	}
343 	fPackageInfo.SetChecksum(checksum);
344 
345 	// register package's attributes
346 	if ((result = _RegisterCurrentPackageInfo()) != B_OK)
347 		return result;
348 
349 	return B_OK;
350 }
351 
352 
353 status_t
354 RepositoryWriterImpl::_RegisterCurrentPackageInfo()
355 {
356 	status_t result = fPackageInfo.InitCheck();
357 	if (result != B_OK) {
358 		fListener->PrintError("package %s has incomplete package-info!\n",
359 			fPackageInfo.Name().String());
360 		return result;
361 	}
362 
363 	// reject package with a name that we've seen already
364 	if (fPackageNames->Contains(fPackageInfo.Name())) {
365 		fListener->PrintError("package %s has already been added!\n",
366 			fPackageInfo.Name().String());
367 		return B_NAME_IN_USE;
368 	}
369 
370 	// all packages must have the same vendor as the repository
371 	const BString& expectedVendor = fRepositoryInfo->Vendor();
372 	if (fPackageInfo.Vendor().ICompare(expectedVendor) != 0) {
373 		fListener->PrintError("package '%s' has unexpected vendor '%s' "
374 			"(expected '%s')!\n", fPackageInfo.Name().String(),
375 			fPackageInfo.Vendor().String(), expectedVendor.String());
376 		return B_BAD_DATA;
377 	}
378 
379 	// all packages must have an architecture that's compatible with the one
380 	// used by the repository
381 	BPackageArchitecture expectedArchitecture = fRepositoryInfo->Architecture();
382 	if (fPackageInfo.Architecture() != expectedArchitecture
383 		&& fPackageInfo.Architecture() != B_PACKAGE_ARCHITECTURE_ANY
384 		&& fPackageInfo.Architecture() != B_PACKAGE_ARCHITECTURE_SOURCE) {
385 		fListener->PrintError(
386 			"package '%s' has non-matching architecture '%s' "
387 			"(expected '%s', '%s', or '%s')!\n", fPackageInfo.Name().String(),
388 			BPackageInfo::kArchitectureNames[fPackageInfo.Architecture()],
389 			BPackageInfo::kArchitectureNames[expectedArchitecture],
390 			BPackageInfo::kArchitectureNames[B_PACKAGE_ARCHITECTURE_ANY],
391 			BPackageInfo::kArchitectureNames[B_PACKAGE_ARCHITECTURE_SOURCE]);
392 		return B_BAD_DATA;
393 	}
394 
395 	if ((result = fPackageNames->Add(fPackageInfo.Name())) != B_OK)
396 		return result;
397 
398 	PackageAttribute* packageAttribute = AddStringAttribute(
399 		B_HPKG_ATTRIBUTE_ID_PACKAGE, fPackageInfo.Name(), PackageAttributes());
400 	RegisterPackageInfo(packageAttribute->children, fPackageInfo);
401 	fPackageCount++;
402 	fListener->OnPackageAdded(fPackageInfo);
403 
404 	return B_OK;
405 }
406 
407 
408 status_t
409 RepositoryWriterImpl::_WriteRepositoryInfo(hpkg_repo_header& header,
410 	uint64& _length)
411 {
412 	// archive and flatten the repository info and write it
413 	BMessage archive;
414 	status_t result = fRepositoryInfo->Archive(&archive);
415 	if (result != B_OK) {
416 		fListener->PrintError("can't archive repository header!\n");
417 		return result;
418 	}
419 
420 	ssize_t	flattenedSize = archive.FlattenedSize();
421 	char buffer[flattenedSize];
422 	if ((result = archive.Flatten(buffer, flattenedSize)) != B_OK) {
423 		fListener->PrintError("can't flatten repository header!\n");
424 		return result;
425 	}
426 
427 	WriteBuffer(buffer, flattenedSize);
428 
429 	// notify listener
430 	fListener->OnRepositoryInfoSectionDone(flattenedSize);
431 
432 	// update the header
433 	header.info_length = B_HOST_TO_BENDIAN_INT32(flattenedSize);
434 
435 	_length = flattenedSize;
436 	return B_OK;
437 }
438 
439 
440 void
441 RepositoryWriterImpl::_WritePackageAttributes(hpkg_repo_header& header,
442 	uint64& _length)
443 {
444 	// write the package attributes (zlib writer on top of a file writer)
445 	uint64 startOffset = fHeapWriter->UncompressedHeapSize();
446 
447 	uint32 stringsLength;
448 	uint32 stringsCount = WritePackageAttributes(PackageAttributes(),
449 		stringsLength);
450 
451 	uint64 sectionSize = fHeapWriter->UncompressedHeapSize() - startOffset;
452 
453 	fListener->OnPackageAttributesSectionDone(stringsCount, sectionSize);
454 
455 	// update the header
456 	header.packages_length = B_HOST_TO_BENDIAN_INT64(sectionSize);
457 	header.packages_strings_count = B_HOST_TO_BENDIAN_INT64(stringsCount);
458 	header.packages_strings_length = B_HOST_TO_BENDIAN_INT64(stringsLength);
459 
460 	_length = sectionSize;
461 }
462 
463 
464 }	// namespace BPrivate
465 
466 }	// namespace BHPKG
467 
468 }	// namespace BPackageKit
469