/* * Copyright 2009-2014, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2011, Oliver Tappe * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using BPrivate::FileDescriptorCloser; static const char* const kPublicDomainLicenseName = "Public Domain"; #include namespace BPackageKit { namespace BHPKG { namespace BPrivate { // #pragma mark - Attributes struct PackageWriterImpl::Attribute : public DoublyLinkedListLinkImpl { BHPKGAttributeID id; AttributeValue value; DoublyLinkedList 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::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::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::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(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& ranges, const Array& 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::Iterator it = attribute->children.GetIterator(); Attribute* child = it.Next();) { ProcessAttribute(child); } } private: const RangeArray& fRanges; const Array& fDeltas; }; // #pragma mark - PackageWriterImpl (Inline Methods) template 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; // 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 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::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::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 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 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 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