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