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