root/src/kits/package/hpkg/PackageWriterImpl.cpp
/*
 * 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 <AutoDeleterPosix.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
                AttrDirCloser attrDir(fs_fopen_attr_dir(fd));
                if (attrDir.IsSet()) {
                        while (dirent* entry = fs_read_attr_dir(attrDir.Get())) {
                                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);
                }

                DirCloser dir(fdopendir(clonedFD));
                if (!dir.IsSet()) {
                        fListener->PrintError(
                                "Failed to open directory \"%s\": %s\n", pathBuffer,
                                strerror(errno));
                        close(clonedFD);
                        throw status_t(errno);
                }

                while (dirent* entry = readdir(dir.Get())) {
                        // 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
        AttrDirCloser attrDir(fs_fopen_attr_dir(fd));
        if (attrDir.IsSet()) {
                while (dirent* entry = fs_read_attr_dir(attrDir.Get())) {
                        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);
                }

                DirCloser dir(fdopendir(clonedFD));
                if (!dir.IsSet()) {
                        fListener->PrintError(
                                "Failed to open directory \"%s\": %s\n", pathBuffer,
                                strerror(errno));
                        close(clonedFD);
                        throw status_t(errno);
                }

                while (dirent* entry = readdir(dir.Get())) {
                        // 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