/*
 * Copyright 2009-2014, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
 * Distributed under the terms of the MIT License.
 */
 
 
#include <package/hpkg/PackageWriterImpl.h>
 
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
 
#include <algorithm>
#include <new>
 
#include <ByteOrder.h>
#include <Directory.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <fs_attr.h>
#include <Path.h>
 
#include <package/hpkg/BlockBufferPoolNoLock.h>
#include <package/hpkg/PackageAttributeValue.h>
#include <package/hpkg/PackageContentHandler.h>
#include <package/hpkg/PackageData.h>
#include <package/hpkg/PackageDataReader.h>
 
#include <AutoDeleter.h>
#include <RangeArray.h>
 
#include <package/hpkg/HPKGDefsPrivate.h>
 
#include <package/hpkg/DataReader.h>
#include <package/hpkg/PackageFileHeapReader.h>
#include <package/hpkg/PackageFileHeapWriter.h>
#include <package/hpkg/PackageReaderImpl.h>
#include <package/hpkg/Stacker.h>
 
 
using BPrivate::FileDescriptorCloser;
 
 
static const char* const kPublicDomainLicenseName = "Public Domain";
 
 
#include <typeinfo>
 
namespace BPackageKit {
 
namespace BHPKG {
 
namespace BPrivate {
 
 
// #pragma mark - Attributes
 
 
struct PackageWriterImpl::Attribute
	: public DoublyLinkedListLinkImpl<Attribute> {
	BHPKGAttributeID			id;
	AttributeValue				value;
	DoublyLinkedList<Attribute>	children;
 
	Attribute(BHPKGAttributeID id_ = B_HPKG_ATTRIBUTE_ID_ENUM_COUNT)
		:
		id(id_)
	{
	}
 
	~Attribute()
	{
		DeleteChildren();
	}
 
	void AddChild(Attribute* child)
	{
		children.Add(child);
	}
 
	void RemoveChild(Attribute* child)
	{
		children.Remove(child);
	}
 
	void DeleteChildren()
	{
		while (Attribute* child = children.RemoveHead())
			delete child;
	}
 
	Attribute* FindEntryChild(const char* fileName) const
	{
		for (DoublyLinkedList<Attribute>::ConstIterator it
				= children.GetIterator(); Attribute* child = it.Next();) {
			if (child->id != B_HPKG_ATTRIBUTE_ID_DIRECTORY_ENTRY)
				continue;
			if (child->value.type != B_HPKG_ATTRIBUTE_TYPE_STRING)
				continue;
			const char* childName = child->value.string->string;
			if (strcmp(fileName, childName) == 0)
				return child;
		}
 
		return NULL;
	}
 
	Attribute* FindEntryChild(const char* fileName, size_t nameLength) const
	{
		BString name(fileName, nameLength);
		return (size_t)name.Length() == nameLength
			? FindEntryChild(name) : NULL;
	}
 
	Attribute* FindNodeAttributeChild(const char* attributeName) const
	{
		for (DoublyLinkedList<Attribute>::ConstIterator it
				= children.GetIterator(); Attribute* child = it.Next();) {
			if (child->id != B_HPKG_ATTRIBUTE_ID_FILE_ATTRIBUTE)
				continue;
			if (child->value.type != B_HPKG_ATTRIBUTE_TYPE_STRING)
				continue;
			const char* childName = child->value.string->string;
			if (strcmp(attributeName, childName) == 0)
				return child;
		}
 
		return NULL;
	}
 
	Attribute* ChildWithID(BHPKGAttributeID id) const
	{
		for (DoublyLinkedList<Attribute>::ConstIterator it
				= children.GetIterator(); Attribute* child = it.Next();) {
			if (child->id == id)
				return child;
		}
 
		return NULL;
	}
};
 
 
// #pragma mark - PackageContentHandler
 
 
struct PackageWriterImpl::PackageContentHandler
	: BLowLevelPackageContentHandler {
	PackageContentHandler(Attribute* rootAttribute, BErrorOutput* errorOutput,
		StringCache& stringCache)
		:
		fErrorOutput(errorOutput),
		fStringCache(stringCache),
		fRootAttribute(rootAttribute),
		fErrorOccurred(false)
	{
	}
 
	virtual status_t HandleSectionStart(BHPKGPackageSectionID sectionID,
		bool& _handleSection)
	{
		// we're only interested in the TOC
		_handleSection = sectionID == B_HPKG_SECTION_PACKAGE_TOC;
		return B_OK;
	}
 
	virtual status_t HandleSectionEnd(BHPKGPackageSectionID sectionID)
	{
		return B_OK;
	}
 
	virtual status_t HandleAttribute(BHPKGAttributeID attributeID,
		const BPackageAttributeValue& value, void* parentToken, void*& _token)
	{
		if (fErrorOccurred)
			return B_OK;
 
		Attribute* parentAttribute = parentToken != NULL
			? (Attribute*)parentToken : fRootAttribute;
 
		Attribute* attribute = new Attribute(attributeID);
		parentAttribute->AddChild(attribute);
 
		switch (value.type) {
			case B_HPKG_ATTRIBUTE_TYPE_INT:
				attribute->value.SetTo(value.signedInt);
				break;
 
			case B_HPKG_ATTRIBUTE_TYPE_UINT:
				attribute->value.SetTo(value.unsignedInt);
				break;
 
			case B_HPKG_ATTRIBUTE_TYPE_STRING:
			{
				CachedString* string = fStringCache.Get(value.string);
				if (string == NULL)
					throw std::bad_alloc();
				attribute->value.SetTo(string);
				break;
			}
 
			case B_HPKG_ATTRIBUTE_TYPE_RAW:
				if (value.encoding == B_HPKG_ATTRIBUTE_ENCODING_RAW_HEAP) {
					attribute->value.SetToData(value.data.size,
						value.data.offset);
				} else if (value.encoding
						== B_HPKG_ATTRIBUTE_ENCODING_RAW_INLINE) {
					attribute->value.SetToData(value.data.size, value.data.raw);
				} else {
					fErrorOutput->PrintError("Invalid attribute value encoding "
						"%d (attribute %d)\n", value.encoding, attributeID);
					return B_BAD_DATA;
				}
				break;
 
			case B_HPKG_ATTRIBUTE_TYPE_INVALID:
			default:
				fErrorOutput->PrintError("Invalid attribute value type %d "
					"(attribute %d)\n", value.type, attributeID);
				return B_BAD_DATA;
		}
 
		_token = attribute;
		return B_OK;
	}
 
	virtual status_t HandleAttributeDone(BHPKGAttributeID attributeID,
		const BPackageAttributeValue& value, void* parentToken, void* token)
	{
		return B_OK;
	}
 
	virtual void HandleErrorOccurred()
	{
		fErrorOccurred = true;
	}
 
private:
	BErrorOutput*	fErrorOutput;
	StringCache&	fStringCache;
	Attribute*		fRootAttribute;
	bool			fErrorOccurred;
};
 
 
// #pragma mark - Entry
 
 
struct PackageWriterImpl::Entry : DoublyLinkedListLinkImpl<Entry> {
	Entry(char* name, size_t nameLength, int fd, bool isImplicit)
		:
		fName(name),
		fNameLength(nameLength),
		fFD(fd),
		fIsImplicit(isImplicit)
	{
	}
 
	~Entry()
	{
		DeleteChildren();
		free(fName);
	}
 
	static Entry* Create(const char* name, size_t nameLength, int fd,
		bool isImplicit)
	{
		char* clonedName = (char*)malloc(nameLength + 1);
		if (clonedName == NULL)
			throw std::bad_alloc();
		memcpy(clonedName, name, nameLength);
		clonedName[nameLength] = '\0';
 
		Entry* entry = new(std::nothrow) Entry(clonedName, nameLength, fd,
			isImplicit);
		if (entry == NULL) {
			free(clonedName);
			throw std::bad_alloc();
		}
 
		return entry;
	}
 
	const char* Name() const
	{
		return fName;
	}
 
	int FD() const
	{
		return fFD;
	}
 
	void SetFD(int fd)
	{
		fFD = fd;
	}
 
	bool IsImplicit() const
	{
		return fIsImplicit;
	}
 
	void SetImplicit(bool isImplicit)
	{
		fIsImplicit = isImplicit;
	}
 
	bool HasName(const char* name, size_t nameLength)
	{
		return nameLength == fNameLength
			&& strncmp(name, fName, nameLength) == 0;
	}
 
	void AddChild(Entry* child)
	{
		fChildren.Add(child);
	}
 
	void DeleteChildren()
	{
		while (Entry* child = fChildren.RemoveHead())
			delete child;
	}
 
	Entry* GetChild(const char* name, size_t nameLength) const
	{
		EntryList::ConstIterator it = fChildren.GetIterator();
		while (Entry* child = it.Next()) {
			if (child->HasName(name, nameLength))
				return child;
		}
 
		return NULL;
	}
 
	EntryList::ConstIterator ChildIterator() const
	{
		return fChildren.GetIterator();
	}
 
private:
	char*		fName;
	size_t		fNameLength;
	int			fFD;
	bool		fIsImplicit;
	EntryList	fChildren;
};
 
 
// #pragma mark - SubPathAdder
 
 
struct PackageWriterImpl::SubPathAdder {
	SubPathAdder(BErrorOutput* errorOutput, char* pathBuffer,
		const char* subPath)
		:
		fOriginalPathEnd(pathBuffer + strlen(pathBuffer))
	{
		if (fOriginalPathEnd != pathBuffer)
			strlcat(pathBuffer, "/", B_PATH_NAME_LENGTH);
 
		if (strlcat(pathBuffer, subPath, B_PATH_NAME_LENGTH)
				>= B_PATH_NAME_LENGTH) {
			*fOriginalPathEnd = '\0';
			errorOutput->PrintError("Path too long: \"%s/%s\"\n", pathBuffer,
				subPath);
			throw status_t(B_BUFFER_OVERFLOW);
		}
	}
 
	~SubPathAdder()
	{
		*fOriginalPathEnd = '\0';
	}
 
private:
	char* fOriginalPathEnd;
};
 
 
// #pragma mark - HeapAttributeOffsetter
 
 
struct PackageWriterImpl::HeapAttributeOffsetter {
	HeapAttributeOffsetter(const RangeArray<uint64>& ranges,
		const Array<uint64>& deltas)
		:
		fRanges(ranges),
		fDeltas(deltas)
	{
	}
 
	void ProcessAttribute(Attribute* attribute)
	{
		// If the attribute refers to a heap value, adjust it
		AttributeValue& value = attribute->value;
 
		if (value.type == B_HPKG_ATTRIBUTE_TYPE_RAW
			&& value.encoding == B_HPKG_ATTRIBUTE_ENCODING_RAW_HEAP) {
			uint64 delta = fDeltas[fRanges.InsertionIndex(value.data.offset)];
			value.data.offset -= delta;
		}
 
		// recurse
		for (DoublyLinkedList<Attribute>::Iterator it
					= attribute->children.GetIterator();
				Attribute* child = it.Next();) {
			ProcessAttribute(child);
		}
	}
 
private:
	const RangeArray<uint64>&	fRanges;
	const Array<uint64>&		fDeltas;
};
 
 
// #pragma mark - PackageWriterImpl (Inline Methods)
 
 
template<typename Type>
inline PackageWriterImpl::Attribute*
PackageWriterImpl::_AddAttribute(BHPKGAttributeID attributeID, Type value)
{
	AttributeValue attributeValue;
	attributeValue.SetTo(value);
	return _AddAttribute(attributeID, attributeValue);
}
 
 
// #pragma mark - PackageWriterImpl
 
 
PackageWriterImpl::PackageWriterImpl(BPackageWriterListener* listener)
	:
	inherited("package", listener),
	fListener(listener),
	fHeapRangesToRemove(NULL),
	fRootEntry(NULL),
	fRootAttribute(NULL),
	fTopAttribute(NULL),
	fCheckLicenses(true)
{
}
 
 
PackageWriterImpl::~PackageWriterImpl()
{
	delete fHeapRangesToRemove;
	delete fRootAttribute;
	delete fRootEntry;
}
 
 
status_t
PackageWriterImpl::Init(const char* fileName,
	const BPackageWriterParameters& parameters)
{
	try {
		return _Init(NULL, false, fileName, parameters);
	} catch (status_t error) {
		return error;
	} catch (std::bad_alloc&) {
		fListener->PrintError("Out of memory!\n");
		return B_NO_MEMORY;
	}
}
 
 
status_t
PackageWriterImpl::Init(BPositionIO* file, bool keepFile,
	const BPackageWriterParameters& parameters)
{
	try {
		return _Init(file, keepFile, NULL, parameters);
	} catch (status_t error) {
		return error;
	} catch (std::bad_alloc&) {
		fListener->PrintError("Out of memory!\n");
		return B_NO_MEMORY;
	}
}
 
 
status_t
PackageWriterImpl::SetInstallPath(const char* installPath)
{
	fInstallPath = installPath;
	return installPath == NULL
		|| (size_t)fInstallPath.Length() == strlen(installPath)
		? B_OK : B_NO_MEMORY;
}
 
 
void
PackageWriterImpl::SetCheckLicenses(bool checkLicenses)
{
	fCheckLicenses = checkLicenses;
}
 
 
status_t
PackageWriterImpl::AddEntry(const char* fileName, int fd)
{
	try {
		// if it's ".PackageInfo", parse it
		if (strcmp(fileName, B_HPKG_PACKAGE_INFO_FILE_NAME) == 0) {
			struct ErrorListener : public BPackageInfo::ParseErrorListener {
				ErrorListener(BPackageWriterListener* _listener)
					:
					listener(_listener),
					errorSeen(false)
				{
				}
 
				virtual void OnError(const BString& msg, int line, int col) {
					listener->PrintError("Parse error in %s(%d:%d) -> %s\n",
						B_HPKG_PACKAGE_INFO_FILE_NAME, line, col, msg.String());
					errorSeen = true;
				}
 
				BPackageWriterListener* listener;
				bool errorSeen;
			} errorListener(fListener);
 
			if (fd >= 0) {
				// a file descriptor is given -- read the config from there
				// stat the file to get the file size
				struct stat st;
				if (fstat(fd, &st) != 0)
					return errno;
 
				BString packageInfoString;
				char* buffer = packageInfoString.LockBuffer(st.st_size);
				if (buffer == NULL)
					return B_NO_MEMORY;
 
				ssize_t result = read_pos(fd, 0, buffer, st.st_size);
				if (result < 0) {
					packageInfoString.UnlockBuffer(0);
					return errno;
				}
 
				buffer[st.st_size] = '\0';
				packageInfoString.UnlockBuffer(st.st_size);
 
				result = fPackageInfo.ReadFromConfigString(packageInfoString,
					&errorListener);
				if (result != B_OK)
					return result;
			} else {
				// use the file name
				BEntry packageInfoEntry(fileName);
				status_t result = fPackageInfo.ReadFromConfigFile(
					packageInfoEntry, &errorListener);
				if (result != B_OK
					|| (result = fPackageInfo.InitCheck()) != B_OK) {
					if (!errorListener.errorSeen) {
						fListener->PrintError("Failed to read %s: %s\n",
							fileName, strerror(result));
					}
					return result;
				}
			}
		}
 
		return _RegisterEntry(fileName, fd);
	} catch (status_t error) {
		return error;
	} catch (std::bad_alloc&) {
		fListener->PrintError("Out of memory!\n");
		return B_NO_MEMORY;
	}
}
 
 
status_t
PackageWriterImpl::Finish()
{
	try {
		if ((Flags() & B_HPKG_WRITER_UPDATE_PACKAGE) != 0) {
			_UpdateCheckEntryCollisions();
 
			if (fPackageInfo.InitCheck() != B_OK)
				_UpdateReadPackageInfo();
		}
 
		if (fPackageInfo.InitCheck() != B_OK) {
			fListener->PrintError("No package-info file found (%s)!\n",
				B_HPKG_PACKAGE_INFO_FILE_NAME);
			return B_BAD_DATA;
		}
 
		fPackageInfo.SetInstallPath(fInstallPath);
 
		RegisterPackageInfo(PackageAttributes(), fPackageInfo);
 
		if (fCheckLicenses) {
			status_t result = _CheckLicenses();
			if (result != B_OK)
				return result;
		}
 
		if ((Flags() & B_HPKG_WRITER_UPDATE_PACKAGE) != 0)
			_CompactHeap();
 
		return _Finish();
	} catch (status_t error) {
		return error;
	} catch (std::bad_alloc&) {
		fListener->PrintError("Out of memory!\n");
		return B_NO_MEMORY;
	}
}
 
 
status_t
PackageWriterImpl::Recompress(BPositionIO* inputFile)
{
	if (inputFile == NULL)
		return B_BAD_VALUE;
 
	try {
		return _Recompress(inputFile);
	} catch (status_t error) {
		return error;
	} catch (std::bad_alloc&) {
		fListener->PrintError("Out of memory!\n");
		return B_NO_MEMORY;
	}
}
 
 
status_t
PackageWriterImpl::_Init(BPositionIO* file, bool keepFile, const char* fileName,
	const BPackageWriterParameters& parameters)
{
	status_t result = inherited::Init(file, keepFile, fileName, parameters);
	if (result != B_OK)
		return result;
 
	if (fStringCache.Init() != B_OK)
		throw std::bad_alloc();
 
	// create entry list
	fRootEntry = new Entry(NULL, 0, -1, true);
 
	fRootAttribute = new Attribute();
 
	fHeapOffset = fHeaderSize = sizeof(hpkg_header);
	fTopAttribute = fRootAttribute;
 
	fHeapRangesToRemove = new RangeArray<uint64>;
 
	// in update mode, parse the TOC
	if ((Flags() & B_HPKG_WRITER_UPDATE_PACKAGE) != 0) {
		PackageReaderImpl packageReader(fListener);
		hpkg_header header;
		result = packageReader.Init(File(), false, 0, &header);
		if (result != B_OK)
			return result;
 
		fHeapOffset = packageReader.HeapOffset();
 
		PackageContentHandler handler(fRootAttribute, fListener, fStringCache);
 
		result = packageReader.ParseContent(&handler);
		if (result != B_OK)
			return result;
 
		// While the compression level can change, we have to reuse the
		// compression algorithm at least.
		SetCompression(B_BENDIAN_TO_HOST_INT16(header.heap_compression));
 
		result = InitHeapReader(fHeapOffset);
		if (result != B_OK)
			return result;
 
		fHeapWriter->Reinit(packageReader.RawHeapReader());
 
		// Remove the old packages attributes and TOC section from the heap.
		// We'll write new ones later.
		const PackageFileSection& attributesSection
			= packageReader.PackageAttributesSection();
		const PackageFileSection& tocSection = packageReader.TOCSection();
		if (!fHeapRangesToRemove->AddRange(attributesSection.offset,
				attributesSection.uncompressedLength)
		   || !fHeapRangesToRemove->AddRange(tocSection.offset,
				tocSection.uncompressedLength)) {
			throw std::bad_alloc();
		}
	} else {
		result = InitHeapReader(fHeapOffset);
		if (result != B_OK)
			return result;
	}
 
	return B_OK;
}
 
 
status_t
PackageWriterImpl::_Finish()
{
	// write entries
	for (EntryList::ConstIterator it = fRootEntry->ChildIterator();
			Entry* entry = it.Next();) {
		char pathBuffer[B_PATH_NAME_LENGTH];
		pathBuffer[0] = '\0';
		_AddEntry(AT_FDCWD, entry, entry->Name(), pathBuffer);
	}
 
	hpkg_header header;
 
	// write the TOC and package attributes
	uint64 tocLength;
	_WriteTOC(header, tocLength);
 
	uint64 attributesLength;
	_WritePackageAttributes(header, attributesLength);
 
	// flush the heap
	status_t error = fHeapWriter->Finish();
	if (error != B_OK)
		return error;
 
	uint64 compressedHeapSize = fHeapWriter->CompressedHeapSize();
 
	header.heap_compression = B_HOST_TO_BENDIAN_INT16(
		Parameters().Compression());
	header.heap_chunk_size = B_HOST_TO_BENDIAN_INT32(fHeapWriter->ChunkSize());
	header.heap_size_compressed = B_HOST_TO_BENDIAN_INT64(compressedHeapSize);
	header.heap_size_uncompressed = B_HOST_TO_BENDIAN_INT64(
		fHeapWriter->UncompressedHeapSize());
 
	// Truncate the file to the size it is supposed to have. In update mode, it
	// can be greater when one or more files are shrunk. In creation mode it
	// should already have the correct size.
	off_t totalSize = fHeapWriter->HeapOffset() + (off_t)compressedHeapSize;
	error = File()->SetSize(totalSize);
	if (error != B_OK) {
		fListener->PrintError("Failed to truncate package file to new "
			"size: %s\n", strerror(errno));
		return errno;
	}
 
	fListener->OnPackageSizeInfo(fHeaderSize, compressedHeapSize, tocLength,
		attributesLength, totalSize);
 
	// prepare the header
 
	// general
	header.magic = B_HOST_TO_BENDIAN_INT32(B_HPKG_MAGIC);
	header.header_size = B_HOST_TO_BENDIAN_INT16(fHeaderSize);
	header.version = B_HOST_TO_BENDIAN_INT16(B_HPKG_VERSION);
	header.total_size = B_HOST_TO_BENDIAN_INT64(totalSize);
	header.minor_version = B_HOST_TO_BENDIAN_INT16(B_HPKG_MINOR_VERSION);
 
	// write the header
	RawWriteBuffer(&header, sizeof(hpkg_header), 0);
 
	SetFinished(true);
	return B_OK;
}
 
 
status_t
PackageWriterImpl::_Recompress(BPositionIO* inputFile)
{
	if (inputFile == NULL)
		return B_BAD_VALUE;
 
	// create a package reader for the input file
	PackageReaderImpl reader(fListener);
	hpkg_header header;
	status_t error = reader.Init(inputFile, false, 0, &header);
	if (error != B_OK) {
		fListener->PrintError("Failed to open hpkg file: %s\n",
			strerror(error));
		return error;
	}
 
	// Update some header fields, assuming no compression. We'll rewrite the
	// header later, should compression have been used. Doing it this way allows
	// for streaming an uncompressed package.
	uint64 uncompressedHeapSize
		= reader.RawHeapReader()->UncompressedHeapSize();
	uint64 compressedHeapSize = uncompressedHeapSize;
 
	off_t totalSize = fHeapWriter->HeapOffset() + (off_t)compressedHeapSize;
 
	header.heap_compression = B_HOST_TO_BENDIAN_INT16(
		Parameters().Compression());
	header.heap_chunk_size = B_HOST_TO_BENDIAN_INT32(fHeapWriter->ChunkSize());
	header.heap_size_uncompressed
		= B_HOST_TO_BENDIAN_INT64(uncompressedHeapSize);
 
	if (Parameters().Compression() == B_HPKG_COMPRESSION_NONE) {
		header.heap_size_compressed
			= B_HOST_TO_BENDIAN_INT64(compressedHeapSize);
		header.total_size = B_HOST_TO_BENDIAN_INT64(totalSize);
 
		// write the header
		RawWriteBuffer(&header, sizeof(hpkg_header), 0);
	}
 
	// copy the heap data
	uint64 bytesCompressed;
	error = fHeapWriter->AddData(*reader.RawHeapReader(), uncompressedHeapSize,
		bytesCompressed);
	if (error != B_OK)
		return error;
 
	// flush the heap
	error = fHeapWriter->Finish();
	if (error != B_OK)
		return error;
 
	// If compression is enabled, update and write the header.
	if (Parameters().Compression() != B_HPKG_COMPRESSION_NONE) {
		compressedHeapSize = fHeapWriter->CompressedHeapSize();
		totalSize = fHeapWriter->HeapOffset() + (off_t)compressedHeapSize;
		header.heap_size_compressed = B_HOST_TO_BENDIAN_INT64(compressedHeapSize);
		header.total_size = B_HOST_TO_BENDIAN_INT64(totalSize);
 
		// write the header
		RawWriteBuffer(&header, sizeof(hpkg_header), 0);
	}
 
	SetFinished(true);
	return B_OK;
}
 
 
status_t
PackageWriterImpl::_CheckLicenses()
{
	BPath systemLicensePath;
	status_t result
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
		= find_directory(B_SYSTEM_DATA_DIRECTORY, &systemLicensePath);
#else
		= systemLicensePath.SetTo(HAIKU_BUILD_SYSTEM_DATA_DIRECTORY);
#endif
	if (result != B_OK) {
		fListener->PrintError("unable to find system data path: %s!\n",
			strerror(result));
		return result;
	}
	if ((result = systemLicensePath.Append("licenses")) != B_OK) {
		fListener->PrintError("unable to append to system data path!\n");
		return result;
	}
 
	BDirectory systemLicenseDir(systemLicensePath.Path());
 
	const BStringList& licenseList = fPackageInfo.LicenseList();
	for (int i = 0; i < licenseList.CountStrings(); ++i) {
		const BString& licenseName = licenseList.StringAt(i);
		if (licenseName == kPublicDomainLicenseName)
			continue;
 
		BEntry license;
		if (systemLicenseDir.FindEntry(licenseName.String(), &license) == B_OK)
			continue;
 
		// license is not a system license, so it must be contained in package
		BString licensePath("data/licenses/");
		licensePath << licenseName;
 
		if (!_IsEntryInPackage(licensePath)) {
			fListener->PrintError("License '%s' isn't contained in package!\n",
				licenseName.String());
			return B_BAD_DATA;
		}
	}
 
	return B_OK;
}
 
 
bool
PackageWriterImpl::_IsEntryInPackage(const char* fileName)
{
	const char* originalFileName = fileName;
 
	// find the closest ancestor of the entry that is in the added entries
	bool added = false;
	Entry* entry = fRootEntry;
	while (entry != NULL) {
		if (!entry->IsImplicit()) {
			added = true;
			break;
		}
 
		if (*fileName == '\0')
			break;
 
		const char* nextSlash = strchr(fileName, '/');
 
		if (nextSlash == NULL) {
			// no slash, just the file name
			size_t length = strlen(fileName);
			entry  = entry->GetChild(fileName, length);
			fileName += length;
			continue;
		}
 
		// find the start of the next component, skipping slashes
		const char* nextComponent = nextSlash + 1;
		while (*nextComponent == '/')
			nextComponent++;
 
		entry = entry->GetChild(fileName, nextSlash - fileName);
 
		fileName = nextComponent;
	}
 
	if (added) {
		// the entry itself or one of its ancestors has been added to the
		// package explicitly -- stat it, to see, if it exists
		struct stat st;
		if (entry->FD() >= 0) {
			if (fstatat(entry->FD(), *fileName != '\0' ? fileName : NULL, &st,
					AT_SYMLINK_NOFOLLOW) == 0) {
				return true;
			}
		} else {
			if (lstat(originalFileName, &st) == 0)
				return true;
		}
	}
 
	// In update mode the entry might already be in the package.
	Attribute* attribute = fRootAttribute;
	fileName = originalFileName;
 
	while (attribute != NULL) {
		if (*fileName == '\0')
			return true;
 
		const char* nextSlash = strchr(fileName, '/');
 
		if (nextSlash == NULL) {
			// no slash, just the file name
			return attribute->FindEntryChild(fileName) != NULL;
		}
 
		// find the start of the next component, skipping slashes
		const char* nextComponent = nextSlash + 1;
		while (*nextComponent == '/')
			nextComponent++;
 
		attribute = attribute->FindEntryChild(fileName, nextSlash - fileName);
 
		fileName = nextComponent;
	}
 
	return false;
}
 
 
void
PackageWriterImpl::_UpdateReadPackageInfo()
{
	// get the .PackageInfo entry attribute
	Attribute* attribute = fRootAttribute->FindEntryChild(
		B_HPKG_PACKAGE_INFO_FILE_NAME);
	if (attribute == NULL) {
		fListener->PrintError("No %s in package file.\n",
			B_HPKG_PACKAGE_INFO_FILE_NAME);
		throw status_t(B_BAD_DATA);
	}
 
	// get the data attribute
	Attribute* dataAttribute = attribute->ChildWithID(B_HPKG_ATTRIBUTE_ID_DATA);
	if (dataAttribute == NULL)  {
		fListener->PrintError("%s entry in package file doesn't have data.\n",
			B_HPKG_PACKAGE_INFO_FILE_NAME);
		throw status_t(B_BAD_DATA);
	}
 
	AttributeValue& value = dataAttribute->value;
	if (value.type != B_HPKG_ATTRIBUTE_TYPE_RAW) {
		fListener->PrintError("%s entry in package file has an invalid data "
			"attribute (not of type raw).\n", B_HPKG_PACKAGE_INFO_FILE_NAME);
		throw status_t(B_BAD_DATA);
	}
 
	BPackageData data;
	if (value.encoding == B_HPKG_ATTRIBUTE_ENCODING_RAW_INLINE)
		data.SetData(value.data.size, value.data.raw);
	else
		data.SetData(value.data.size, value.data.offset);
 
	// read the value into a string
	BString valueString;
	char* valueBuffer = valueString.LockBuffer(value.data.size);
	if (valueBuffer == NULL)
		throw std::bad_alloc();
 
	if (value.encoding == B_HPKG_ATTRIBUTE_ENCODING_RAW_INLINE) {
		// data encoded inline -- just copy to buffer
		memcpy(valueBuffer, value.data.raw, value.data.size);
	} else {
		// data on heap -- read from there
		status_t error = fHeapWriter->ReadData(data.Offset(), valueBuffer,
			data.Size());
		if (error != B_OK)
			throw error;
	}
 
	valueString.UnlockBuffer();
 
	// parse the package info
	status_t error = fPackageInfo.ReadFromConfigString(valueString);
	if (error != B_OK) {
		fListener->PrintError("Failed to parse package info data from package "
			"file: %s\n", strerror(error));
		throw status_t(error);
	}
}
 
 
void
PackageWriterImpl::_UpdateCheckEntryCollisions()
{
	for (EntryList::ConstIterator it = fRootEntry->ChildIterator();
			Entry* entry = it.Next();) {
		char pathBuffer[B_PATH_NAME_LENGTH];
		pathBuffer[0] = '\0';
		_UpdateCheckEntryCollisions(fRootAttribute, AT_FDCWD, entry,
			entry->Name(), pathBuffer);
	}
}
 
 
void
PackageWriterImpl::_UpdateCheckEntryCollisions(Attribute* parentAttribute,
	int dirFD, Entry* entry, const char* fileName, char* pathBuffer)
{
	bool isImplicitEntry = entry != NULL && entry->IsImplicit();
 
	SubPathAdder pathAdder(fListener, pathBuffer, fileName);
 
	// Check whether there's an entry attribute for this entry. If not, we can
	// ignore this entry.
	Attribute* entryAttribute = parentAttribute->FindEntryChild(fileName);
	if (entryAttribute == NULL)
		return;
 
	// open the node
	int fd;
	FileDescriptorCloser fdCloser;
 
	if (entry != NULL && entry->FD() >= 0) {
		// a file descriptor is already given -- use that
		fd = entry->FD();
	} else {
		fd = openat(dirFD, fileName,
			O_RDONLY | (isImplicitEntry ? 0 : O_NOTRAVERSE));
		if (fd < 0) {
			fListener->PrintError("Failed to open entry \"%s\": %s\n",
				pathBuffer, strerror(errno));
			throw status_t(errno);
		}
		fdCloser.SetTo(fd);
	}
 
	// stat the node
	struct stat st;
	if (fstat(fd, &st) < 0) {
		fListener->PrintError("Failed to fstat() file \"%s\": %s\n", pathBuffer,
			strerror(errno));
		throw status_t(errno);
	}
 
	// implicit entries must be directories
	if (isImplicitEntry && !S_ISDIR(st.st_mode)) {
		fListener->PrintError("Non-leaf path component \"%s\" is not a "
			"directory.\n", pathBuffer);
		throw status_t(B_BAD_VALUE);
	}
 
	// get the pre-existing node's file type
	uint32 preExistingFileType = B_HPKG_DEFAULT_FILE_TYPE;
	if (Attribute* fileTypeAttribute
			= entryAttribute->ChildWithID(B_HPKG_ATTRIBUTE_ID_FILE_TYPE)) {
		if (fileTypeAttribute->value.type == B_HPKG_ATTRIBUTE_TYPE_UINT)
			preExistingFileType = fileTypeAttribute->value.unsignedInt;
	}
 
	// Compare the node type with that of the pre-existing one.
	if (!S_ISDIR(st.st_mode)) {
		// the pre-existing must not a directory either -- we'll remove it
		if (preExistingFileType == B_HPKG_FILE_TYPE_DIRECTORY) {
			fListener->PrintError("Specified file \"%s\" clashes with an "
				"archived directory.\n", pathBuffer);
			throw status_t(B_BAD_VALUE);
		}
 
		if ((Flags() & B_HPKG_WRITER_FORCE_ADD) == 0) {
			fListener->PrintError("Specified file \"%s\" clashes with an "
				"archived file.\n", pathBuffer);
			throw status_t(B_FILE_EXISTS);
		}
 
		parentAttribute->RemoveChild(entryAttribute);
		_AttributeRemoved(entryAttribute);
 
		return;
	}
 
	// the pre-existing entry needs to be a directory too -- we will merge
	if (preExistingFileType != B_HPKG_FILE_TYPE_DIRECTORY) {
		fListener->PrintError("Specified directory \"%s\" clashes with an "
			"archived non-directory.\n", pathBuffer);
		throw status_t(B_BAD_VALUE);
	}
 
	// directory -- recursively add children
	if (isImplicitEntry) {
		// this is an implicit entry -- just check the child entries
		for (EntryList::ConstIterator it = entry->ChildIterator();
				Entry* child = it.Next();) {
			_UpdateCheckEntryCollisions(entryAttribute, fd, child,
				child->Name(), pathBuffer);
		}
	} else {
		// explicitly specified directory -- we need to read the directory
 
		// first we check for colliding node attributes, though
		if (DIR* attrDir = fs_fopen_attr_dir(fd)) {
			CObjectDeleter<DIR, int> attrDirCloser(attrDir, fs_close_attr_dir);
 
			while (dirent* entry = fs_read_attr_dir(attrDir)) {
				attr_info attrInfo;
				if (fs_stat_attr(fd, entry->d_name, &attrInfo) < 0) {
					fListener->PrintError(
						"Failed to stat attribute \"%s\" of directory \"%s\": "
						"%s\n", entry->d_name, pathBuffer, strerror(errno));
					throw status_t(errno);
				}
 
				// check whether the attribute exists
				Attribute* attributeAttribute
					= entryAttribute->FindNodeAttributeChild(entry->d_name);
				if (attributeAttribute == NULL)
					continue;
 
				if ((Flags() & B_HPKG_WRITER_FORCE_ADD) == 0) {
					fListener->PrintError("Attribute \"%s\" of specified "
						"directory \"%s\" clashes with an archived "
						"attribute.\n", pathBuffer);
					throw status_t(B_FILE_EXISTS);
				}
 
				// remove it
				entryAttribute->RemoveChild(attributeAttribute);
				_AttributeRemoved(attributeAttribute);
			}
		}
 
		// we need to clone the directory FD for fdopendir()
		int clonedFD = dup(fd);
		if (clonedFD < 0) {
			fListener->PrintError(
				"Failed to dup() directory FD: %s\n", strerror(errno));
			throw status_t(errno);
		}
 
		DIR* dir = fdopendir(clonedFD);
		if (dir == NULL) {
			fListener->PrintError(
				"Failed to open directory \"%s\": %s\n", pathBuffer,
				strerror(errno));
			close(clonedFD);
			throw status_t(errno);
		}
		CObjectDeleter<DIR, int> dirCloser(dir, closedir);
 
		while (dirent* entry = readdir(dir)) {
			// skip "." and ".."
			if (strcmp(entry->d_name, ".") == 0
				|| strcmp(entry->d_name, "..") == 0) {
				continue;
			}
 
			_UpdateCheckEntryCollisions(entryAttribute, fd, NULL, entry->d_name,
				pathBuffer);
		}
	}
}
 
 
void
PackageWriterImpl::_CompactHeap()
{
	int32 count = fHeapRangesToRemove->CountRanges();
	if (count == 0)
		return;
 
	// compute the move deltas for the ranges
	Array<uint64> deltas;
	uint64 delta = 0;
	for (int32 i = 0; i < count; i++) {
		if (!deltas.Add(delta))
			throw std::bad_alloc();
 
		delta += fHeapRangesToRemove->RangeAt(i).size;
	}
 
	if (!deltas.Add(delta))
		throw std::bad_alloc();
 
	// offset the attributes
	HeapAttributeOffsetter(*fHeapRangesToRemove, deltas).ProcessAttribute(
		fRootAttribute);
 
	// remove the ranges from the heap
	fHeapWriter->RemoveDataRanges(*fHeapRangesToRemove);
}
 
 
void
PackageWriterImpl::_AttributeRemoved(Attribute* attribute)
{
	AttributeValue& value = attribute->value;
	if (value.type == B_HPKG_ATTRIBUTE_TYPE_RAW
		&& value.encoding == B_HPKG_ATTRIBUTE_ENCODING_RAW_HEAP) {
		if (!fHeapRangesToRemove->AddRange(value.data.offset, value.data.size))
			throw std::bad_alloc();
	} else if (value.type == B_HPKG_ATTRIBUTE_TYPE_STRING)
		fStringCache.Put(value.string);
 
	for (DoublyLinkedList<Attribute>::Iterator it
				= attribute->children.GetIterator();
			Attribute* child = it.Next();) {
		_AttributeRemoved(child);
	}
}
 
 
status_t
PackageWriterImpl::_RegisterEntry(const char* fileName, int fd)
{
	if (*fileName == '\0') {
		fListener->PrintError("Invalid empty file name\n");
		return B_BAD_VALUE;
	}
 
	// add all components of the path
	Entry* entry = fRootEntry;
	while (*fileName != 0) {
		const char* nextSlash = strchr(fileName, '/');
		// no slash, just add the file name
		if (nextSlash == NULL) {
			entry = _RegisterEntry(entry, fileName, strlen(fileName), fd,
				false);
			break;
		}
 
		// find the start of the next component, skipping slashes
		const char* nextComponent = nextSlash + 1;
		while (*nextComponent == '/')
			nextComponent++;
 
		bool lastComponent = *nextComponent != '\0';
 
		if (nextSlash == fileName) {
			// the FS root
			entry = _RegisterEntry(entry, fileName, 1, lastComponent ? fd : -1,
				lastComponent);
		} else {
			entry = _RegisterEntry(entry, fileName, nextSlash - fileName,
				lastComponent ? fd : -1, lastComponent);
		}
 
		fileName = nextComponent;
	}
 
	return B_OK;
}
 
 
PackageWriterImpl::Entry*
PackageWriterImpl::_RegisterEntry(Entry* parent, const char* name,
	size_t nameLength, int fd, bool isImplicit)
{
	// check the component name -- don't allow "." or ".."
	if (name[0] == '.'
		&& (nameLength == 1 || (nameLength == 2 && name[1] == '.'))) {
		fListener->PrintError("Invalid file name: \".\" and \"..\" "
			"are not allowed as path components\n");
		throw status_t(B_BAD_VALUE);
	}
 
	// the entry might already exist
	Entry* entry = parent->GetChild(name, nameLength);
	if (entry != NULL) {
		// If the entry was implicit and is no longer, we mark it non-implicit
		// and delete all of it's children.
		if (entry->IsImplicit() && !isImplicit) {
			entry->DeleteChildren();
			entry->SetImplicit(false);
			entry->SetFD(fd);
		}
	} else {
		// nope -- create it
		entry = Entry::Create(name, nameLength, fd, isImplicit);
		parent->AddChild(entry);
	}
 
	return entry;
}
 
 
void
PackageWriterImpl::_WriteTOC(hpkg_header& header, uint64& _length)
{
	// write the subsections
	uint64 startOffset = fHeapWriter->UncompressedHeapSize();
 
	// cached strings
	uint64 cachedStringsOffset = fHeapWriter->UncompressedHeapSize();
	int32 cachedStringsWritten = WriteCachedStrings(fStringCache, 2);
 
	// main TOC section
	uint64 mainOffset = fHeapWriter->UncompressedHeapSize();
	_WriteAttributeChildren(fRootAttribute);
 
	// notify the listener
	uint64 endOffset = fHeapWriter->UncompressedHeapSize();
	uint64 stringsSize = mainOffset - cachedStringsOffset;
	uint64 mainSize = endOffset - mainOffset;
	uint64 tocSize = endOffset - startOffset;
	fListener->OnTOCSizeInfo(stringsSize, mainSize, tocSize);
 
	// update the header
	header.toc_length = B_HOST_TO_BENDIAN_INT64(tocSize);
	header.toc_strings_length = B_HOST_TO_BENDIAN_INT64(stringsSize);
	header.toc_strings_count = B_HOST_TO_BENDIAN_INT64(cachedStringsWritten);
 
	_length = tocSize;
}
 
 
void
PackageWriterImpl::_WriteAttributeChildren(Attribute* attribute)
{
	DoublyLinkedList<Attribute>::Iterator it
		= attribute->children.GetIterator();
	while (Attribute* child = it.Next()) {
		// write tag
		uint8 encoding = child->value.ApplicableEncoding();
		WriteUnsignedLEB128(compose_attribute_tag(child->id,
			child->value.type, encoding, !child->children.IsEmpty()));
 
		// write value
		WriteAttributeValue(child->value, encoding);
 
		if (!child->children.IsEmpty())
			_WriteAttributeChildren(child);
	}
 
	WriteUnsignedLEB128(0);
}
 
 
void
PackageWriterImpl::_WritePackageAttributes(hpkg_header& header, uint64& _length)
{
	// write cached strings and package attributes tree
	off_t startOffset = fHeapWriter->UncompressedHeapSize();
 
	uint32 stringsLength;
	uint32 stringsCount = WritePackageAttributes(PackageAttributes(),
		stringsLength);
 
	// notify listener
	uint32 attributesLength = fHeapWriter->UncompressedHeapSize() - startOffset;
	fListener->OnPackageAttributesSizeInfo(stringsCount, attributesLength);
 
	// update the header
	header.attributes_length = B_HOST_TO_BENDIAN_INT32(attributesLength);
	header.attributes_strings_count = B_HOST_TO_BENDIAN_INT32(stringsCount);
	header.attributes_strings_length = B_HOST_TO_BENDIAN_INT32(stringsLength);
 
	_length = attributesLength;
}
 
 
void
PackageWriterImpl::_AddEntry(int dirFD, Entry* entry, const char* fileName,
	char* pathBuffer)
{
	bool isImplicitEntry = entry != NULL && entry->IsImplicit();
 
	SubPathAdder pathAdder(fListener, pathBuffer, fileName);
	if (!isImplicitEntry)
		fListener->OnEntryAdded(pathBuffer);
 
	// open the node
	int fd;
	FileDescriptorCloser fdCloser;
 
	if (entry != NULL && entry->FD() >= 0) {
		// a file descriptor is already given -- use that
		fd = entry->FD();
	} else {
		fd = openat(dirFD, fileName,
			O_RDONLY | (isImplicitEntry ? 0 : O_NOTRAVERSE));
		if (fd < 0) {
			fListener->PrintError("Failed to open entry \"%s\": %s\n",
				pathBuffer, strerror(errno));
			throw status_t(errno);
		}
		fdCloser.SetTo(fd);
	}
 
	// stat the node
	struct stat st;
	if (fstat(fd, &st) < 0) {
		fListener->PrintError("Failed to fstat() file \"%s\": %s\n", pathBuffer,
			strerror(errno));
		throw status_t(errno);
	}
 
	// implicit entries must be directories
	if (isImplicitEntry && !S_ISDIR(st.st_mode)) {
		fListener->PrintError("Non-leaf path component \"%s\" is not a "
			"directory\n", pathBuffer);
		throw status_t(B_BAD_VALUE);
	}
 
	// In update mode we don't need to add an entry attribute for an implicit
	// directory, if there already is one.
	Attribute* entryAttribute = NULL;
	if (S_ISDIR(st.st_mode) && (Flags() & B_HPKG_WRITER_UPDATE_PACKAGE) != 0) {
		entryAttribute = fTopAttribute->FindEntryChild(fileName);
		if (entryAttribute != NULL && isImplicitEntry) {
			Stacker<Attribute> entryAttributeStacker(fTopAttribute,
				entryAttribute);
			_AddDirectoryChildren(entry, fd, pathBuffer);
			return;
		}
	}
 
	// check/translate the node type
	uint8 fileType;
	uint32 defaultPermissions;
	if (S_ISREG(st.st_mode)) {
		fileType = B_HPKG_FILE_TYPE_FILE;
		defaultPermissions = B_HPKG_DEFAULT_FILE_PERMISSIONS;
	} else if (S_ISLNK(st.st_mode)) {
		fileType = B_HPKG_FILE_TYPE_SYMLINK;
		defaultPermissions = B_HPKG_DEFAULT_SYMLINK_PERMISSIONS;
	} else if (S_ISDIR(st.st_mode)) {
		fileType = B_HPKG_FILE_TYPE_DIRECTORY;
		defaultPermissions = B_HPKG_DEFAULT_DIRECTORY_PERMISSIONS;
	} else {
		// unsupported node type
		fListener->PrintError("Unsupported node type, entry: \"%s\"\n",
			pathBuffer);
		throw status_t(B_UNSUPPORTED);
	}
 
	// add attribute entry, if it doesn't already exist (update mode, directory)
	bool isNewEntry = entryAttribute == NULL;
	if (entryAttribute == NULL) {
		entryAttribute = _AddStringAttribute(
			B_HPKG_ATTRIBUTE_ID_DIRECTORY_ENTRY, fileName);
	}
 
	Stacker<Attribute> entryAttributeStacker(fTopAttribute, entryAttribute);
 
	if (isNewEntry) {
		// add stat data
		if (fileType != B_HPKG_DEFAULT_FILE_TYPE)
			_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_TYPE, fileType);
		if (defaultPermissions != uint32(st.st_mode & ALLPERMS)) {
			_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_PERMISSIONS,
				uint32(st.st_mode & ALLPERMS));
		}
		_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_ATIME, uint32(st.st_atime));
		_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_MTIME, uint32(st.st_mtime));
#ifdef __HAIKU__
		_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_CRTIME, uint32(st.st_crtime));
#else
		_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_CRTIME, uint32(st.st_mtime));
#endif
		// TODO: File user/group!
 
		// add file data/symlink path
		if (S_ISREG(st.st_mode)) {
			// regular file -- add data
			if (st.st_size > 0) {
				BFDDataReader dataReader(fd);
				status_t error = _AddData(dataReader, st.st_size);
				if (error != B_OK)
					throw status_t(error);
			}
		} else if (S_ISLNK(st.st_mode)) {
			// symlink -- add link address
			char path[B_PATH_NAME_LENGTH + 1];
			ssize_t bytesRead = readlinkat(dirFD, fileName, path,
				B_PATH_NAME_LENGTH);
			if (bytesRead < 0) {
				fListener->PrintError("Failed to read symlink \"%s\": %s\n",
					pathBuffer, strerror(errno));
				throw status_t(errno);
			}
 
			path[bytesRead] = '\0';
			_AddStringAttribute(B_HPKG_ATTRIBUTE_ID_SYMLINK_PATH, path);
		}
	}
 
	// add attributes
	if (DIR* attrDir = fs_fopen_attr_dir(fd)) {
		CObjectDeleter<DIR, int> attrDirCloser(attrDir, fs_close_attr_dir);
 
		while (dirent* entry = fs_read_attr_dir(attrDir)) {
			attr_info attrInfo;
			if (fs_stat_attr(fd, entry->d_name, &attrInfo) < 0) {
				fListener->PrintError(
					"Failed to stat attribute \"%s\" of file \"%s\": %s\n",
					entry->d_name, pathBuffer, strerror(errno));
				throw status_t(errno);
			}
 
			// create attribute entry
			Attribute* attributeAttribute = _AddStringAttribute(
				B_HPKG_ATTRIBUTE_ID_FILE_ATTRIBUTE, entry->d_name);
			Stacker<Attribute> attributeAttributeStacker(fTopAttribute,
				attributeAttribute);
 
			// add type
			_AddAttribute(B_HPKG_ATTRIBUTE_ID_FILE_ATTRIBUTE_TYPE,
				(uint32)attrInfo.type);
 
			// add data
			BAttributeDataReader dataReader(fd, entry->d_name, attrInfo.type);
			status_t error = _AddData(dataReader, attrInfo.size);
			if (error != B_OK)
				throw status_t(error);
		}
	}
 
	if (S_ISDIR(st.st_mode))
		_AddDirectoryChildren(entry, fd, pathBuffer);
}
 
 
void
PackageWriterImpl::_AddDirectoryChildren(Entry* entry, int fd, char* pathBuffer)
{
	// directory -- recursively add children
	if (entry != NULL && entry->IsImplicit()) {
		// this is an implicit entry -- just add it's children
		for (EntryList::ConstIterator it = entry->ChildIterator();
				Entry* child = it.Next();) {
			_AddEntry(fd, child, child->Name(), pathBuffer);
		}
	} else {
		// we need to clone the directory FD for fdopendir()
		int clonedFD = dup(fd);
		if (clonedFD < 0) {
			fListener->PrintError(
				"Failed to dup() directory FD: %s\n", strerror(errno));
			throw status_t(errno);
		}
 
		DIR* dir = fdopendir(clonedFD);
		if (dir == NULL) {
			fListener->PrintError(
				"Failed to open directory \"%s\": %s\n", pathBuffer,
				strerror(errno));
			close(clonedFD);
			throw status_t(errno);
		}
		CObjectDeleter<DIR, int> dirCloser(dir, closedir);
 
		while (dirent* entry = readdir(dir)) {
			// skip "." and ".."
			if (strcmp(entry->d_name, ".") == 0
				|| strcmp(entry->d_name, "..") == 0) {
				continue;
			}
 
			_AddEntry(fd, NULL, entry->d_name, pathBuffer);
		}
	}
}
 
 
PackageWriterImpl::Attribute*
PackageWriterImpl::_AddAttribute(BHPKGAttributeID id,
	const AttributeValue& value)
{
	Attribute* attribute = new Attribute(id);
 
	attribute->value = value;
	fTopAttribute->AddChild(attribute);
 
	return attribute;
}
 
 
PackageWriterImpl::Attribute*
PackageWriterImpl::_AddStringAttribute(BHPKGAttributeID attributeID,
	const char* value)
{
	AttributeValue attributeValue;
	attributeValue.SetTo(fStringCache.Get(value));
	return _AddAttribute(attributeID, attributeValue);
}
 
 
PackageWriterImpl::Attribute*
PackageWriterImpl::_AddDataAttribute(BHPKGAttributeID attributeID,
	uint64 dataSize, uint64 dataOffset)
{
	AttributeValue attributeValue;
	attributeValue.SetToData(dataSize, dataOffset);
	return _AddAttribute(attributeID, attributeValue);
}
 
 
PackageWriterImpl::Attribute*
PackageWriterImpl::_AddDataAttribute(BHPKGAttributeID attributeID,
	uint64 dataSize, const uint8* data)
{
	AttributeValue attributeValue;
	attributeValue.SetToData(dataSize, data);
	return _AddAttribute(attributeID, attributeValue);
}
 
 
status_t
PackageWriterImpl::_AddData(BDataReader& dataReader, off_t size)
{
	// add short data inline
	if (size <= B_HPKG_MAX_INLINE_DATA_SIZE) {
		uint8 buffer[B_HPKG_MAX_INLINE_DATA_SIZE];
		status_t error = dataReader.ReadData(0, buffer, size);
		if (error != B_OK) {
			fListener->PrintError("Failed to read data: %s\n", strerror(error));
			return error;
		}
 
		_AddDataAttribute(B_HPKG_ATTRIBUTE_ID_DATA, size, buffer);
		return B_OK;
	}
 
	// add data to heap
	uint64 dataOffset;
	status_t error = fHeapWriter->AddData(dataReader, size, dataOffset);
	if (error != B_OK)
		return error;
 
	_AddDataAttribute(B_HPKG_ATTRIBUTE_ID_DATA, size, dataOffset);
	return B_OK;
}
 
 
}	// namespace BPrivate
 
}	// namespace BHPKG
 
}	// namespace BPackageKit

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fHeapOffset, fHeaderSize.