xref: /haiku/src/kits/package/hpkg/PackageWriterImpl.cpp (revision 7b6bcdb146b8ac24ef3569fcb152627f724e156c)
1 /*
2  * Copyright 2009-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
4  * Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include <package/hpkg/PackageWriterImpl.h>
9 
10 #include <dirent.h>
11 #include <errno.h>
12 #include <fcntl.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
18 
19 #include <algorithm>
20 #include <new>
21 
22 #include <ByteOrder.h>
23 #include <Directory.h>
24 #include <Entry.h>
25 #include <FindDirectory.h>
26 #include <fs_attr.h>
27 #include <Path.h>
28 
29 #include <AutoDeleter.h>
30 
31 #include <package/hpkg/HPKGDefsPrivate.h>
32 
33 #include <package/hpkg/DataOutput.h>
34 #include <package/hpkg/DataReader.h>
35 #include <package/hpkg/Stacker.h>
36 
37 
38 using BPrivate::FileDescriptorCloser;
39 
40 
41 static const char* const kPublicDomainLicenseName = "Public Domain";
42 
43 
44 #include <typeinfo>
45 
46 namespace BPackageKit {
47 
48 namespace BHPKG {
49 
50 namespace BPrivate {
51 
52 
53 // minimum length of data we require before trying to zlib compress them
54 static const size_t kZlibCompressionSizeThreshold = 64;
55 
56 
57 // #pragma mark - Attributes
58 
59 
60 struct PackageWriterImpl::Attribute
61 	: public DoublyLinkedListLinkImpl<Attribute> {
62 	BHPKGAttributeID			id;
63 	AttributeValue				value;
64 	DoublyLinkedList<Attribute>	children;
65 
66 	Attribute(BHPKGAttributeID id_ = B_HPKG_ATTRIBUTE_ID_ENUM_COUNT)
67 		:
68 		id(id_)
69 	{
70 	}
71 
72 	~Attribute()
73 	{
74 		DeleteChildren();
75 	}
76 
77 	void AddChild(Attribute* child)
78 	{
79 		children.Add(child);
80 	}
81 
82 	void DeleteChildren()
83 	{
84 		while (Attribute* child = children.RemoveHead())
85 			delete child;
86 	}
87 };
88 
89 
90 // #pragma mark - Entry
91 
92 
93 struct PackageWriterImpl::Entry : DoublyLinkedListLinkImpl<Entry> {
94 	Entry(char* name, size_t nameLength, int fd, bool isImplicit)
95 		:
96 		fName(name),
97 		fNameLength(nameLength),
98 		fFD(fd),
99 		fIsImplicit(isImplicit)
100 	{
101 	}
102 
103 	~Entry()
104 	{
105 		DeleteChildren();
106 		free(fName);
107 	}
108 
109 	static Entry* Create(const char* name, size_t nameLength, int fd,
110 		bool isImplicit)
111 	{
112 		char* clonedName = (char*)malloc(nameLength + 1);
113 		if (clonedName == NULL)
114 			throw std::bad_alloc();
115 		memcpy(clonedName, name, nameLength);
116 		clonedName[nameLength] = '\0';
117 
118 		Entry* entry = new(std::nothrow) Entry(clonedName, nameLength, fd,
119 			isImplicit);
120 		if (entry == NULL) {
121 			free(clonedName);
122 			throw std::bad_alloc();
123 		}
124 
125 		return entry;
126 	}
127 
128 	const char* Name() const
129 	{
130 		return fName;
131 	}
132 
133 	int FD() const
134 	{
135 		return fFD;
136 	}
137 
138 	void SetFD(int fd)
139 	{
140 		fFD = fd;
141 	}
142 
143 	bool IsImplicit() const
144 	{
145 		return fIsImplicit;
146 	}
147 
148 	void SetImplicit(bool isImplicit)
149 	{
150 		fIsImplicit = isImplicit;
151 	}
152 
153 	bool HasName(const char* name, size_t nameLength)
154 	{
155 		return nameLength == fNameLength
156 			&& strncmp(name, fName, nameLength) == 0;
157 	}
158 
159 	void AddChild(Entry* child)
160 	{
161 		fChildren.Add(child);
162 	}
163 
164 	void DeleteChildren()
165 	{
166 		while (Entry* child = fChildren.RemoveHead())
167 			delete child;
168 	}
169 
170 	Entry* GetChild(const char* name, size_t nameLength) const
171 	{
172 		EntryList::ConstIterator it = fChildren.GetIterator();
173 		while (Entry* child = it.Next()) {
174 			if (child->HasName(name, nameLength))
175 				return child;
176 		}
177 
178 		return NULL;
179 	}
180 
181 	EntryList::ConstIterator ChildIterator() const
182 	{
183 		return fChildren.GetIterator();
184 	}
185 
186 private:
187 	char*		fName;
188 	size_t		fNameLength;
189 	int			fFD;
190 	bool		fIsImplicit;
191 	EntryList	fChildren;
192 };
193 
194 
195 // #pragma mark - SubPathAdder
196 
197 
198 struct PackageWriterImpl::SubPathAdder {
199 	SubPathAdder(BErrorOutput* errorOutput, char* pathBuffer,
200 		const char* subPath)
201 		:
202 		fOriginalPathEnd(pathBuffer + strlen(pathBuffer))
203 	{
204 		if (fOriginalPathEnd != pathBuffer)
205 			strlcat(pathBuffer, "/", B_PATH_NAME_LENGTH);
206 
207 		if (strlcat(pathBuffer, subPath, B_PATH_NAME_LENGTH)
208 				>= B_PATH_NAME_LENGTH) {
209 			*fOriginalPathEnd = '\0';
210 			errorOutput->PrintError("Path too long: \"%s/%s\"\n", pathBuffer,
211 				subPath);
212 			throw status_t(B_BUFFER_OVERFLOW);
213 		}
214 	}
215 
216 	~SubPathAdder()
217 	{
218 		*fOriginalPathEnd = '\0';
219 	}
220 
221 private:
222 	char* fOriginalPathEnd;
223 };
224 
225 
226 // #pragma mark - PackageWriterImpl (Inline Methods)
227 
228 
229 template<typename Type>
230 inline PackageWriterImpl::Attribute*
231 PackageWriterImpl::_AddAttribute(BHPKGAttributeID attributeID, Type value)
232 {
233 	AttributeValue attributeValue;
234 	attributeValue.SetTo(value);
235 	return _AddAttribute(attributeID, attributeValue);
236 }
237 
238 
239 // #pragma mark - PackageWriterImpl
240 
241 
242 PackageWriterImpl::PackageWriterImpl(BPackageWriterListener* listener)
243 	:
244 	inherited(listener),
245 	fListener(listener),
246 	fDataBuffer(NULL),
247 	fDataBufferSize(2 * B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB),
248 	fRootEntry(NULL),
249 	fRootAttribute(NULL),
250 	fTopAttribute(NULL)
251 {
252 }
253 
254 
255 PackageWriterImpl::~PackageWriterImpl()
256 {
257 	delete fRootAttribute;
258 
259 	delete fRootEntry;
260 
261 	free(fDataBuffer);
262 }
263 
264 
265 status_t
266 PackageWriterImpl::Init(const char* fileName)
267 {
268 	try {
269 		return _Init(fileName);
270 	} catch (status_t error) {
271 		return error;
272 	} catch (std::bad_alloc) {
273 		fListener->PrintError("Out of memory!\n");
274 		return B_NO_MEMORY;
275 	}
276 }
277 
278 
279 status_t
280 PackageWriterImpl::AddEntry(const char* fileName, int fd)
281 {
282 	try {
283 		// if it's ".PackageInfo", parse it
284 		if (strcmp(fileName, B_HPKG_PACKAGE_INFO_FILE_NAME) == 0) {
285 			struct ErrorListener : public BPackageInfo::ParseErrorListener {
286 				ErrorListener(BPackageWriterListener* _listener)
287 					: listener(_listener) {}
288 				virtual void OnError(const BString& msg, int line, int col) {
289 					listener->PrintError("Parse error in %s(%d:%d) -> %s\n",
290 						B_HPKG_PACKAGE_INFO_FILE_NAME, line, col, msg.String());
291 				}
292 				BPackageWriterListener* listener;
293 			} errorListener(fListener);
294 
295 			if (fd >= 0) {
296 				// a file descriptor is given -- read the config from there
297 				// stat the file to get the file size
298 				struct stat st;
299 				if (fstat(fd, &st) != 0)
300 					return errno;
301 
302 				BString packageInfoString;
303 				char* buffer = packageInfoString.LockBuffer(st.st_size);
304 				if (buffer == NULL)
305 					return B_NO_MEMORY;
306 
307 				ssize_t result = read_pos(fd, 0, buffer, st.st_size);
308 				if (result < 0) {
309 					packageInfoString.UnlockBuffer(0);
310 					return errno;
311 				}
312 
313 				buffer[st.st_size] = '\0';
314 				packageInfoString.UnlockBuffer(st.st_size);
315 
316 				result = fPackageInfo.ReadFromConfigString(packageInfoString,
317 					&errorListener);
318 				if (result != B_OK)
319 					return result;
320 			} else {
321 				// use the file name
322 				BEntry packageInfoEntry(fileName);
323 				status_t result = fPackageInfo.ReadFromConfigFile(
324 					packageInfoEntry, &errorListener);
325 				if (result != B_OK
326 					|| (result = fPackageInfo.InitCheck()) != B_OK) {
327 					return result;
328 				}
329 			}
330 		}
331 
332 		return _RegisterEntry(fileName, fd);
333 	} catch (status_t error) {
334 		return error;
335 	} catch (std::bad_alloc) {
336 		fListener->PrintError("Out of memory!\n");
337 		return B_NO_MEMORY;
338 	}
339 }
340 
341 
342 status_t
343 PackageWriterImpl::Finish()
344 {
345 	try {
346 		if (fPackageInfo.InitCheck() != B_OK) {
347 			fListener->PrintError("No package-info file found (%s)!\n",
348 				B_HPKG_PACKAGE_INFO_FILE_NAME);
349 			return B_BAD_DATA;
350 		}
351 
352 		RegisterPackageInfo(PackageAttributes(), fPackageInfo);
353 
354 		status_t result = _CheckLicenses();
355 		if (result != B_OK)
356 			return result;
357 
358 		return _Finish();
359 	} catch (status_t error) {
360 		return error;
361 	} catch (std::bad_alloc) {
362 		fListener->PrintError("Out of memory!\n");
363 		return B_NO_MEMORY;
364 	}
365 }
366 
367 
368 status_t
369 PackageWriterImpl::_Init(const char* fileName)
370 {
371 	status_t result = inherited::Init(fileName, "package");
372 	if (result != B_OK)
373 		return result;
374 
375 	// allocate data buffer
376 	fDataBuffer = malloc(fDataBufferSize);
377 	if (fDataBuffer == NULL)
378 		throw std::bad_alloc();
379 
380 	if (fStringCache.Init() != B_OK)
381 		throw std::bad_alloc();
382 
383 	// create entry list
384 	fRootEntry = new Entry(NULL, 0, -1, true);
385 
386 	fRootAttribute = new Attribute();
387 
388 	fHeapOffset = fHeapEnd = sizeof(hpkg_header);
389 	fTopAttribute = fRootAttribute;
390 
391 	return B_OK;
392 }
393 
394 
395 status_t
396 PackageWriterImpl::_CheckLicenses()
397 {
398 	BPath systemLicensePath;
399 	status_t result
400 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
401 		= find_directory(B_SYSTEM_DATA_DIRECTORY, &systemLicensePath);
402 #else
403 		= systemLicensePath.SetTo(HAIKU_BUILD_SYSTEM_DATA_DIRECTORY);
404 #endif
405 	if (result != B_OK) {
406 		fListener->PrintError("unable to find system data path!\n");
407 		return result;
408 	}
409 	if ((result = systemLicensePath.Append("licenses")) != B_OK) {
410 		fListener->PrintError("unable to append to system data path!\n");
411 		return result;
412 	}
413 
414 	BDirectory systemLicenseDir(systemLicensePath.Path());
415 	BDirectory packageLicenseDir("./data/licenses");
416 
417 	const BObjectList<BString>& licenseList = fPackageInfo.LicenseList();
418 	for (int i = 0; i < licenseList.CountItems(); ++i) {
419 		const BString& licenseName = *licenseList.ItemAt(i);
420 		if (licenseName == kPublicDomainLicenseName)
421 			continue;
422 
423 		BEntry license;
424 		if (systemLicenseDir.FindEntry(licenseName.String(), &license) == B_OK)
425 			continue;
426 
427 		// license is not a system license, so it must be contained in package
428 		if (packageLicenseDir.FindEntry(licenseName.String(),
429 				&license) != B_OK) {
430 			fListener->PrintError("License '%s' isn't contained in package!\n",
431 				licenseName.String());
432 			return B_BAD_DATA;
433 		}
434 	}
435 
436 	return B_OK;
437 }
438 
439 
440 status_t
441 PackageWriterImpl::_Finish()
442 {
443 	// write entries
444 	for (EntryList::ConstIterator it = fRootEntry->ChildIterator();
445 			Entry* entry = it.Next();) {
446 		char pathBuffer[B_PATH_NAME_LENGTH];
447 		pathBuffer[0] = '\0';
448 		_AddEntry(AT_FDCWD, entry, entry->Name(), pathBuffer);
449 	}
450 
451 	off_t heapSize = fHeapEnd - fHeapOffset;
452 
453 	hpkg_header header;
454 
455 	// write the TOC and package attributes
456 	_WriteTOC(header);
457 	_WritePackageAttributes(header);
458 
459 	off_t totalSize = fHeapEnd;
460 
461 	fListener->OnPackageSizeInfo(fHeapOffset, heapSize,
462 		B_BENDIAN_TO_HOST_INT64(header.toc_length_compressed),
463 		B_BENDIAN_TO_HOST_INT32(header.attributes_length_compressed),
464 		totalSize);
465 
466 	// prepare the header
467 
468 	// general
469 	header.magic = B_HOST_TO_BENDIAN_INT32(B_HPKG_MAGIC);
470 	header.header_size = B_HOST_TO_BENDIAN_INT16((uint16)fHeapOffset);
471 	header.version = B_HOST_TO_BENDIAN_INT16(B_HPKG_VERSION);
472 	header.total_size = B_HOST_TO_BENDIAN_INT64(totalSize);
473 
474 	// write the header
475 	WriteBuffer(&header, sizeof(hpkg_header), 0);
476 
477 	SetFinished(true);
478 	return B_OK;
479 }
480 
481 
482 status_t
483 PackageWriterImpl::_RegisterEntry(const char* fileName, int fd)
484 {
485 	if (*fileName == '\0') {
486 		fListener->PrintError("Invalid empty file name\n");
487 		return B_BAD_VALUE;
488 	}
489 
490 	// add all components of the path
491 	Entry* entry = fRootEntry;
492 	while (*fileName != 0) {
493 		const char* nextSlash = strchr(fileName, '/');
494 		// no slash, just add the file name
495 		if (nextSlash == NULL) {
496 			entry = _RegisterEntry(entry, fileName, strlen(fileName), fd,
497 				false);
498 			break;
499 		}
500 
501 		// find the start of the next component, skipping slashes
502 		const char* nextComponent = nextSlash + 1;
503 		while (*nextComponent == '/')
504 			nextComponent++;
505 
506 		bool lastComponent = *nextComponent != '\0';
507 
508 		if (nextSlash == fileName) {
509 			// the FS root
510 			entry = _RegisterEntry(entry, fileName, 1, lastComponent ? fd : -1,
511 				lastComponent);
512 		} else {
513 			entry = _RegisterEntry(entry, fileName, nextSlash - fileName,
514 				lastComponent ? fd : -1, lastComponent);
515 		}
516 
517 		fileName = nextComponent;
518 	}
519 
520 	return B_OK;
521 }
522 
523 
524 PackageWriterImpl::Entry*
525 PackageWriterImpl::_RegisterEntry(Entry* parent, const char* name,
526 	size_t nameLength, int fd, bool isImplicit)
527 {
528 	// check the component name -- don't allow "." or ".."
529 	if (name[0] == '.'
530 		&& (nameLength == 1 || (nameLength == 2 && name[1] == '.'))) {
531 		fListener->PrintError("Invalid file name: \".\" and \"..\" "
532 			"are not allowed as path components\n");
533 		throw status_t(B_BAD_VALUE);
534 	}
535 
536 	// the entry might already exist
537 	Entry* entry = parent->GetChild(name, nameLength);
538 	if (entry != NULL) {
539 		// If the entry was implicit and is no longer, we mark it non-implicit
540 		// and delete all of it's children.
541 		if (entry->IsImplicit() && !isImplicit) {
542 			entry->DeleteChildren();
543 			entry->SetImplicit(false);
544 			entry->SetFD(fd);
545 		}
546 	} else {
547 		// nope -- create it
548 		entry = Entry::Create(name, nameLength, fd, isImplicit);
549 		parent->AddChild(entry);
550 	}
551 
552 	return entry;
553 }
554 
555 
556 void
557 PackageWriterImpl::_WriteTOC(hpkg_header& header)
558 {
559 	// prepare the writer (zlib writer on top of a file writer)
560 	off_t startOffset = fHeapEnd;
561 	FDDataWriter realWriter(FD(), startOffset, fListener);
562 	ZlibDataWriter zlibWriter(&realWriter);
563 	SetDataWriter(&zlibWriter);
564 	zlibWriter.Init();
565 
566 	// write the sections
567 	uint64 uncompressedStringsSize;
568 	uint64 uncompressedMainSize;
569 	int32 cachedStringsWritten
570 		= _WriteTOCSections(uncompressedStringsSize, uncompressedMainSize);
571 
572 	// finish the writer
573 	zlibWriter.Finish();
574 	fHeapEnd = realWriter.Offset();
575 	SetDataWriter(NULL);
576 
577 	off_t endOffset = fHeapEnd;
578 
579 	fListener->OnTOCSizeInfo(uncompressedStringsSize, uncompressedMainSize,
580 		zlibWriter.BytesWritten());
581 
582 	// update the header
583 
584 	// TOC
585 	header.toc_compression = B_HOST_TO_BENDIAN_INT32(B_HPKG_COMPRESSION_ZLIB);
586 	header.toc_length_compressed = B_HOST_TO_BENDIAN_INT64(
587 		endOffset - startOffset);
588 	header.toc_length_uncompressed = B_HOST_TO_BENDIAN_INT64(
589 		zlibWriter.BytesWritten());
590 
591 	// TOC subsections
592 	header.toc_strings_length = B_HOST_TO_BENDIAN_INT64(
593 		uncompressedStringsSize);
594 	header.toc_strings_count = B_HOST_TO_BENDIAN_INT64(cachedStringsWritten);
595 }
596 
597 
598 int32
599 PackageWriterImpl::_WriteTOCSections(uint64& _stringsSize, uint64& _mainSize)
600 {
601 	// write the cached strings
602 	uint64 cachedStringsOffset = DataWriter()->BytesWritten();
603 	int32 cachedStringsWritten = WriteCachedStrings(fStringCache, 2);
604 
605 	// write the main TOC section
606 	uint64 mainOffset = DataWriter()->BytesWritten();
607 	_WriteAttributeChildren(fRootAttribute);
608 
609 	_stringsSize = mainOffset - cachedStringsOffset;
610 	_mainSize = DataWriter()->BytesWritten() - mainOffset;
611 
612 	return cachedStringsWritten;
613 }
614 
615 
616 void
617 PackageWriterImpl::_WriteAttributeChildren(Attribute* attribute)
618 {
619 	DoublyLinkedList<Attribute>::Iterator it
620 		= attribute->children.GetIterator();
621 	while (Attribute* child = it.Next()) {
622 		// write tag
623 		uint8 encoding = child->value.ApplicableEncoding();
624 		WriteUnsignedLEB128(HPKG_ATTRIBUTE_TAG_COMPOSE(child->id,
625 			child->value.type, encoding, !child->children.IsEmpty()));
626 
627 		// write value
628 		WriteAttributeValue(child->value, encoding);
629 
630 		if (!child->children.IsEmpty())
631 			_WriteAttributeChildren(child);
632 	}
633 
634 	WriteUnsignedLEB128(0);
635 }
636 
637 
638 void
639 PackageWriterImpl::_WritePackageAttributes(hpkg_header& header)
640 {
641 	// write the package attributes (zlib writer on top of a file writer)
642 	off_t startOffset = fHeapEnd;
643 
644 	uint32 compression = B_HPKG_COMPRESSION_ZLIB;
645 	uint32 stringsLengthUncompressed;
646 	uint32 attributesLengthUncompressed;
647 	uint32 stringsCount = _WritePackageAttributesCompressed(
648 		stringsLengthUncompressed, attributesLengthUncompressed);
649 
650 	off_t endOffset = fHeapEnd;
651 
652 	if ((off_t)attributesLengthUncompressed <= endOffset - startOffset) {
653 		// the compressed section isn't shorter -- write uncompressed
654 		fHeapEnd = startOffset;
655 		compression = B_HPKG_COMPRESSION_NONE;
656 		stringsCount = _WritePackageAttributesUncompressed(
657 			stringsLengthUncompressed, attributesLengthUncompressed);
658 
659 		endOffset = fHeapEnd;
660 	}
661 
662 	fListener->OnPackageAttributesSizeInfo(stringsCount,
663 		attributesLengthUncompressed);
664 
665 	// update the header
666 	header.attributes_compression = B_HOST_TO_BENDIAN_INT32(compression);
667 	header.attributes_length_compressed
668 		= B_HOST_TO_BENDIAN_INT32(endOffset - startOffset);
669 	header.attributes_length_uncompressed
670 		= B_HOST_TO_BENDIAN_INT32(attributesLengthUncompressed);
671 	header.attributes_strings_count = B_HOST_TO_BENDIAN_INT32(stringsCount);
672 	header.attributes_strings_length
673 		= B_HOST_TO_BENDIAN_INT32(stringsLengthUncompressed);
674 }
675 
676 
677 uint32
678 PackageWriterImpl::_WritePackageAttributesCompressed(
679 	uint32& _stringsLengthUncompressed, uint32& _attributesLengthUncompressed)
680 {
681 	off_t startOffset = fHeapEnd;
682 	FDDataWriter realWriter(FD(), startOffset, fListener);
683 	ZlibDataWriter zlibWriter(&realWriter);
684 	SetDataWriter(&zlibWriter);
685 	zlibWriter.Init();
686 
687 	// write cached strings and package attributes tree
688 	uint32 stringsCount = WritePackageAttributes(PackageAttributes(),
689 		_stringsLengthUncompressed);
690 
691 	zlibWriter.Finish();
692 	fHeapEnd = realWriter.Offset();
693 	SetDataWriter(NULL);
694 
695 	_attributesLengthUncompressed = zlibWriter.BytesWritten();
696 	return stringsCount;
697 }
698 
699 
700 uint32
701 PackageWriterImpl::_WritePackageAttributesUncompressed(
702 	uint32& _stringsLengthUncompressed, uint32& _attributesLengthUncompressed)
703 {
704 	off_t startOffset = fHeapEnd;
705 	FDDataWriter realWriter(FD(), startOffset, fListener);
706 
707 	SetDataWriter(&realWriter);
708 
709 	// write cached strings and package attributes tree
710 	uint32 stringsCount = WritePackageAttributes(PackageAttributes(),
711 		_stringsLengthUncompressed);
712 
713 	fHeapEnd = realWriter.Offset();
714 	SetDataWriter(NULL);
715 
716 	_attributesLengthUncompressed = realWriter.BytesWritten();
717 	return stringsCount;
718 }
719 
720 
721 void
722 PackageWriterImpl::_AddEntry(int dirFD, Entry* entry, const char* fileName,
723 	char* pathBuffer)
724 {
725 	bool isImplicitEntry = entry != NULL && entry->IsImplicit();
726 
727 	SubPathAdder pathAdder(fListener, pathBuffer, fileName);
728 	if (!isImplicitEntry)
729 		fListener->OnEntryAdded(pathBuffer);
730 
731 	// open the node
732 	int fd;
733 	FileDescriptorCloser fdCloser;
734 
735 	if (entry != NULL && entry->FD() >= 0) {
736 		// a file descriptor is already given -- use that
737 		fd = entry->FD();
738 	} else {
739 		fd = openat(dirFD, fileName,
740 			O_RDONLY | (isImplicitEntry ? 0 : O_NOTRAVERSE));
741 		if (fd < 0) {
742 			fListener->PrintError("Failed to open entry \"%s\": %s\n",
743 				pathBuffer, strerror(errno));
744 			throw status_t(errno);
745 		}
746 		fdCloser.SetTo(fd);
747 	}
748 
749 	// stat the node
750 	struct stat st;
751 	if (fstat(fd, &st) < 0) {
752 		fListener->PrintError("Failed to fstat() file \"%s\": %s\n", pathBuffer,
753 			strerror(errno));
754 		throw status_t(errno);
755 	}
756 
757 	// implicit entries must be directories
758 	if (isImplicitEntry && !S_ISDIR(st.st_mode)) {
759 		fListener->PrintError("Non-leaf path component \"%s\" is not a "
760 			"directory\n", pathBuffer);
761 		throw status_t(B_BAD_VALUE);
762 	}
763 
764 	// check/translate the node type
765 	uint8 fileType;
766 	uint32 defaultPermissions;
767 	if (S_ISREG(st.st_mode)) {
768 		fileType = B_HPKG_FILE_TYPE_FILE;
769 		defaultPermissions = B_HPKG_DEFAULT_FILE_PERMISSIONS;
770 	} else if (S_ISLNK(st.st_mode)) {
771 		fileType = B_HPKG_FILE_TYPE_SYMLINK;
772 		defaultPermissions = B_HPKG_DEFAULT_SYMLINK_PERMISSIONS;
773 	} else if (S_ISDIR(st.st_mode)) {
774 		fileType = B_HPKG_FILE_TYPE_DIRECTORY;
775 		defaultPermissions = B_HPKG_DEFAULT_DIRECTORY_PERMISSIONS;
776 	} else {
777 		// unsupported node type
778 		fListener->PrintError("Unsupported node type, entry: \"%s\"\n",
779 			pathBuffer);
780 		throw status_t(B_UNSUPPORTED);
781 	}
782 
783 	// add attribute entry
784 	Attribute* entryAttribute = _AddStringAttribute(
785 		B_HPKG_ATTRIBUTE_ID_DIRECTORY_ENTRY, fileName);
786 	Stacker<Attribute> entryAttributeStacker(fTopAttribute, entryAttribute);
787 
788 	// add stat data
789 	if (fileType != B_HPKG_DEFAULT_FILE_TYPE)
790 		_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_TYPE, fileType);
791 	if (defaultPermissions != uint32(st.st_mode & ALLPERMS)) {
792 		_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_PERMISSIONS,
793 			uint32(st.st_mode & ALLPERMS));
794 	}
795 	_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_ATIME, uint32(st.st_atime));
796 	_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_MTIME, uint32(st.st_mtime));
797 #ifdef __HAIKU__
798 	_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_CRTIME, uint32(st.st_crtime));
799 #else
800 	_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_CRTIME, uint32(st.st_mtime));
801 #endif
802 	// TODO: File user/group!
803 
804 	// add file data/symlink path
805 	if (S_ISREG(st.st_mode)) {
806 		// regular file -- add data
807 		if (st.st_size > 0) {
808 			BFDDataReader dataReader(fd);
809 			status_t error = _AddData(dataReader, st.st_size);
810 			if (error != B_OK)
811 				throw status_t(error);
812 		}
813 	} else if (S_ISLNK(st.st_mode)) {
814 		// symlink -- add link address
815 		char path[B_PATH_NAME_LENGTH + 1];
816 		ssize_t bytesRead = readlinkat(dirFD, fileName, path,
817 			B_PATH_NAME_LENGTH);
818 		if (bytesRead < 0) {
819 			fListener->PrintError("Failed to read symlink \"%s\": %s\n",
820 				pathBuffer, strerror(errno));
821 			throw status_t(errno);
822 		}
823 
824 		path[bytesRead] = '\0';
825 		_AddStringAttribute(B_HPKG_ATTRIBUTE_ID_SYMLINK_PATH, path);
826 	}
827 
828 	// add attributes
829 	if (DIR* attrDir = fs_fopen_attr_dir(fd)) {
830 		CObjectDeleter<DIR, int> attrDirCloser(attrDir, fs_close_attr_dir);
831 
832 		while (dirent* entry = fs_read_attr_dir(attrDir)) {
833 			attr_info attrInfo;
834 			if (fs_stat_attr(fd, entry->d_name, &attrInfo) < 0) {
835 				fListener->PrintError(
836 					"Failed to stat attribute \"%s\" of file \"%s\": %s\n",
837 					entry->d_name, pathBuffer, strerror(errno));
838 				throw status_t(errno);
839 			}
840 
841 			// create attribute entry
842 			Attribute* attributeAttribute = _AddStringAttribute(
843 				B_HPKG_ATTRIBUTE_ID_FILE_ATTRIBUTE, entry->d_name);
844 			Stacker<Attribute> attributeAttributeStacker(fTopAttribute,
845 				attributeAttribute);
846 
847 			// add type
848 			_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_ATTRIBUTE_TYPE,
849 				(uint32)attrInfo.type);
850 
851 			// add data
852 			BAttributeDataReader dataReader(fd, entry->d_name, attrInfo.type);
853 			status_t error = _AddData(dataReader, attrInfo.size);
854 			if (error != B_OK)
855 				throw status_t(error);
856 		}
857 	}
858 
859 	if (S_ISDIR(st.st_mode)) {
860 		// directory -- recursively add children
861 		if (isImplicitEntry) {
862 			// this is an implicit entry -- just add it's children
863 			for (EntryList::ConstIterator it = entry->ChildIterator();
864 					Entry* child = it.Next();) {
865 				_AddEntry(fd, child, child->Name(), pathBuffer);
866 			}
867 		} else {
868 			// we need to clone the directory FD for fdopendir()
869 			int clonedFD = dup(fd);
870 			if (clonedFD < 0) {
871 				fListener->PrintError(
872 					"Failed to dup() directory FD: %s\n", strerror(errno));
873 				throw status_t(errno);
874 			}
875 
876 			DIR* dir = fdopendir(clonedFD);
877 			if (dir == NULL) {
878 				fListener->PrintError(
879 					"Failed to open directory \"%s\": %s\n", pathBuffer,
880 					strerror(errno));
881 				close(clonedFD);
882 				throw status_t(errno);
883 			}
884 			CObjectDeleter<DIR, int> dirCloser(dir, closedir);
885 
886 			while (dirent* entry = readdir(dir)) {
887 				// skip "." and ".."
888 				if (strcmp(entry->d_name, ".") == 0
889 					|| strcmp(entry->d_name, "..") == 0) {
890 					continue;
891 				}
892 
893 				_AddEntry(fd, NULL, entry->d_name, pathBuffer);
894 			}
895 		}
896 	}
897 }
898 
899 
900 PackageWriterImpl::Attribute*
901 PackageWriterImpl::_AddAttribute(BHPKGAttributeID id,
902 	const AttributeValue& value)
903 {
904 	Attribute* attribute = new Attribute(id);
905 
906 	attribute->value = value;
907 	fTopAttribute->AddChild(attribute);
908 
909 	return attribute;
910 }
911 
912 
913 PackageWriterImpl::Attribute*
914 PackageWriterImpl::_AddStringAttribute(BHPKGAttributeID attributeID,
915 	const char* value)
916 {
917 	AttributeValue attributeValue;
918 	attributeValue.SetTo(fStringCache.Get(value));
919 	return _AddAttribute(attributeID, attributeValue);
920 }
921 
922 
923 PackageWriterImpl::Attribute*
924 PackageWriterImpl::_AddDataAttribute(BHPKGAttributeID attributeID,
925 	uint64 dataSize, uint64 dataOffset)
926 {
927 	AttributeValue attributeValue;
928 	attributeValue.SetToData(dataSize, dataOffset);
929 	return _AddAttribute(attributeID, attributeValue);
930 }
931 
932 
933 PackageWriterImpl::Attribute*
934 PackageWriterImpl::_AddDataAttribute(BHPKGAttributeID attributeID,
935 	uint64 dataSize, const uint8* data)
936 {
937 	AttributeValue attributeValue;
938 	attributeValue.SetToData(dataSize, data);
939 	return _AddAttribute(attributeID, attributeValue);
940 }
941 
942 
943 status_t
944 PackageWriterImpl::_AddData(BDataReader& dataReader, off_t size)
945 {
946 	// add short data inline
947 	if (size <= B_HPKG_MAX_INLINE_DATA_SIZE) {
948 		uint8 buffer[B_HPKG_MAX_INLINE_DATA_SIZE];
949 		status_t error = dataReader.ReadData(0, buffer, size);
950 		if (error != B_OK) {
951 			fListener->PrintError("Failed to read data: %s\n", strerror(error));
952 			return error;
953 		}
954 
955 		_AddDataAttribute(B_HPKG_ATTRIBUTE_ID_DATA, size, buffer);
956 		return B_OK;
957 	}
958 
959 	// longer data -- try to compress
960 	uint64 dataOffset = fHeapEnd;
961 
962 	uint64 compression = B_HPKG_COMPRESSION_NONE;
963 	uint64 compressedSize;
964 
965 	status_t error = _WriteZlibCompressedData(dataReader, size, dataOffset,
966 		compressedSize);
967 	if (error == B_OK) {
968 		compression = B_HPKG_COMPRESSION_ZLIB;
969 	} else {
970 		error = _WriteUncompressedData(dataReader, size, dataOffset);
971 		compressedSize = size;
972 	}
973 	if (error != B_OK)
974 		return error;
975 
976 	fHeapEnd = dataOffset + compressedSize;
977 
978 	// add data attribute
979 	Attribute* dataAttribute = _AddDataAttribute(B_HPKG_ATTRIBUTE_ID_DATA,
980 		compressedSize, dataOffset - fHeapOffset);
981 	Stacker<Attribute> attributeAttributeStacker(fTopAttribute, dataAttribute);
982 
983 	// if compressed, add compression attributes
984 	if (compression != B_HPKG_COMPRESSION_NONE) {
985 		_AddAttribute(B_HPKG_ATTRIBUTE_ID_DATA_COMPRESSION, compression);
986 		_AddAttribute(B_HPKG_ATTRIBUTE_ID_DATA_SIZE, (uint64)size);
987 			// uncompressed size
988 	}
989 
990 	return B_OK;
991 }
992 
993 
994 status_t
995 PackageWriterImpl::_WriteUncompressedData(BDataReader& dataReader, off_t size,
996 	uint64 writeOffset)
997 {
998 	// copy the data to the heap
999 	off_t readOffset = 0;
1000 	off_t remainingSize = size;
1001 	while (remainingSize > 0) {
1002 		// read data
1003 		size_t toCopy = std::min(remainingSize, (off_t)fDataBufferSize);
1004 		status_t error = dataReader.ReadData(readOffset, fDataBuffer, toCopy);
1005 		if (error != B_OK) {
1006 			fListener->PrintError("Failed to read data: %s\n", strerror(error));
1007 			return error;
1008 		}
1009 
1010 		// write to heap
1011 		ssize_t bytesWritten = pwrite(FD(), fDataBuffer, toCopy, writeOffset);
1012 		if (bytesWritten < 0) {
1013 			fListener->PrintError("Failed to write data: %s\n",
1014 				strerror(errno));
1015 			return errno;
1016 		}
1017 		if ((size_t)bytesWritten != toCopy) {
1018 			fListener->PrintError("Failed to write all data\n");
1019 			return B_ERROR;
1020 		}
1021 
1022 		remainingSize -= toCopy;
1023 		readOffset += toCopy;
1024 		writeOffset += toCopy;
1025 	}
1026 
1027 	return B_OK;
1028 }
1029 
1030 
1031 status_t
1032 PackageWriterImpl::_WriteZlibCompressedData(BDataReader& dataReader, off_t size,
1033 	uint64 writeOffset, uint64& _compressedSize)
1034 {
1035 	// Use zlib compression only for data large enough.
1036 	if (size < (off_t)kZlibCompressionSizeThreshold)
1037 		return B_BAD_VALUE;
1038 
1039 	// fDataBuffer is 2 * B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB, so split it into
1040 	// two halves we can use for reading and compressing
1041 	const size_t chunkSize = B_HPKG_DEFAULT_DATA_CHUNK_SIZE_ZLIB;
1042 	uint8* inputBuffer = (uint8*)fDataBuffer;
1043 	uint8* outputBuffer = (uint8*)fDataBuffer + chunkSize;
1044 
1045 	// account for the offset table
1046 	uint64 chunkCount = (size + (chunkSize - 1)) / chunkSize;
1047 	off_t offsetTableOffset = writeOffset;
1048 	uint64* offsetTable = NULL;
1049 	if (chunkCount > 1) {
1050 		offsetTable = new uint64[chunkCount - 1];
1051 		writeOffset = offsetTableOffset + (chunkCount - 1) * sizeof(uint64);
1052 	}
1053 	ArrayDeleter<uint64> offsetTableDeleter(offsetTable);
1054 
1055 	const uint64 dataOffset = writeOffset;
1056 	const uint64 dataEndLimit = offsetTableOffset + size;
1057 
1058 	// read the data, compress them and write them to the heap
1059 	off_t readOffset = 0;
1060 	off_t remainingSize = size;
1061 	uint64 chunkIndex = 0;
1062 	while (remainingSize > 0) {
1063 		// read data
1064 		size_t toCopy = std::min(remainingSize, (off_t)chunkSize);
1065 		status_t error = dataReader.ReadData(readOffset, inputBuffer, toCopy);
1066 		if (error != B_OK) {
1067 			fListener->PrintError("Failed to read data: %s\n", strerror(error));
1068 			return error;
1069 		}
1070 
1071 		// compress
1072 		size_t compressedSize;
1073 		error = ZlibCompressor::CompressSingleBuffer(inputBuffer, toCopy,
1074 			outputBuffer, toCopy, compressedSize);
1075 
1076 		const void* writeBuffer;
1077 		size_t bytesToWrite;
1078 		if (error == B_OK) {
1079 			writeBuffer = outputBuffer;
1080 			bytesToWrite = compressedSize;
1081 		} else {
1082 			if (error != B_BUFFER_OVERFLOW)
1083 				return error;
1084 			writeBuffer = inputBuffer;
1085 			bytesToWrite = toCopy;
1086 		}
1087 
1088 		// check the total compressed data size
1089 		if (writeOffset + bytesToWrite >= dataEndLimit)
1090 			return B_BUFFER_OVERFLOW;
1091 
1092 		if (chunkIndex > 0)
1093 			offsetTable[chunkIndex - 1] = writeOffset - dataOffset;
1094 
1095 		// write to heap
1096 		ssize_t bytesWritten = pwrite(FD(), writeBuffer, bytesToWrite,
1097 			writeOffset);
1098 		if (bytesWritten < 0) {
1099 			fListener->PrintError("Failed to write data: %s\n",
1100 				strerror(errno));
1101 			return errno;
1102 		}
1103 		if ((size_t)bytesWritten != bytesToWrite) {
1104 			fListener->PrintError("Failed to write all data\n");
1105 			return B_ERROR;
1106 		}
1107 
1108 		remainingSize -= toCopy;
1109 		readOffset += toCopy;
1110 		writeOffset += bytesToWrite;
1111 		chunkIndex++;
1112 	}
1113 
1114 	// write the offset table
1115 	if (chunkCount > 1) {
1116 		size_t bytesToWrite = (chunkCount - 1) * sizeof(uint64);
1117 		ssize_t bytesWritten = pwrite(FD(), offsetTable, bytesToWrite,
1118 			offsetTableOffset);
1119 		if (bytesWritten < 0) {
1120 			fListener->PrintError("Failed to write data: %s\n",
1121 				strerror(errno));
1122 			return errno;
1123 		}
1124 		if ((size_t)bytesWritten != bytesToWrite) {
1125 			fListener->PrintError("Failed to write all data\n");
1126 			return B_ERROR;
1127 		}
1128 	}
1129 
1130 	_compressedSize = writeOffset - offsetTableOffset;
1131 	return B_OK;
1132 }
1133 
1134 
1135 }	// namespace BPrivate
1136 
1137 }	// namespace BHPKG
1138 
1139 }	// namespace BPackageKit
1140