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