/* * 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 namespace BPackageKit { namespace BHPKG { namespace BPrivate { // #pragma mark - AttributeValue WriterImplBase::AttributeValue::AttributeValue() : type(B_HPKG_ATTRIBUTE_TYPE_INVALID), encoding(-1) { } WriterImplBase::AttributeValue::~AttributeValue() { } void WriterImplBase::AttributeValue::SetTo(int8 value) { signedInt = value; type = B_HPKG_ATTRIBUTE_TYPE_INT; } void WriterImplBase::AttributeValue::SetTo(uint8 value) { unsignedInt = value; type = B_HPKG_ATTRIBUTE_TYPE_UINT; } void WriterImplBase::AttributeValue::SetTo(int16 value) { signedInt = value; type = B_HPKG_ATTRIBUTE_TYPE_INT; } void WriterImplBase::AttributeValue::SetTo(uint16 value) { unsignedInt = value; type = B_HPKG_ATTRIBUTE_TYPE_UINT; } void WriterImplBase::AttributeValue::SetTo(int32 value) { signedInt = value; type = B_HPKG_ATTRIBUTE_TYPE_INT; } void WriterImplBase::AttributeValue::SetTo(uint32 value) { unsignedInt = value; type = B_HPKG_ATTRIBUTE_TYPE_UINT; } void WriterImplBase::AttributeValue::SetTo(int64 value) { signedInt = value; type = B_HPKG_ATTRIBUTE_TYPE_INT; } void WriterImplBase::AttributeValue::SetTo(uint64 value) { unsignedInt = value; type = B_HPKG_ATTRIBUTE_TYPE_UINT; } void WriterImplBase::AttributeValue::SetTo(CachedString* value) { string = value; type = B_HPKG_ATTRIBUTE_TYPE_STRING; } void WriterImplBase::AttributeValue::SetToData(uint64 size, uint64 offset) { data.size = size; data.offset = offset; type = B_HPKG_ATTRIBUTE_TYPE_RAW; encoding = B_HPKG_ATTRIBUTE_ENCODING_RAW_HEAP; } void WriterImplBase::AttributeValue::SetToData(uint64 size, const void* rawData) { data.size = size; if (size > 0) memcpy(data.raw, rawData, size); type = B_HPKG_ATTRIBUTE_TYPE_RAW; encoding = B_HPKG_ATTRIBUTE_ENCODING_RAW_INLINE; } uint8 WriterImplBase::AttributeValue::ApplicableEncoding() const { switch (type) { case B_HPKG_ATTRIBUTE_TYPE_INT: return _ApplicableIntEncoding(signedInt >= 0 ? (uint64)signedInt << 1 : (uint64)(-(signedInt + 1) << 1)); case B_HPKG_ATTRIBUTE_TYPE_UINT: return _ApplicableIntEncoding(unsignedInt); case B_HPKG_ATTRIBUTE_TYPE_STRING: return string->index >= 0 ? B_HPKG_ATTRIBUTE_ENCODING_STRING_TABLE : B_HPKG_ATTRIBUTE_ENCODING_STRING_INLINE; case B_HPKG_ATTRIBUTE_TYPE_RAW: return encoding; default: return 0; } } /*static*/ uint8 WriterImplBase::AttributeValue::_ApplicableIntEncoding(uint64 value) { if (value <= 0xff) return B_HPKG_ATTRIBUTE_ENCODING_INT_8_BIT; if (value <= 0xffff) return B_HPKG_ATTRIBUTE_ENCODING_INT_16_BIT; if (value <= 0xffffffff) return B_HPKG_ATTRIBUTE_ENCODING_INT_32_BIT; return B_HPKG_ATTRIBUTE_ENCODING_INT_64_BIT; } // #pragma mark - PackageAttribute WriterImplBase::PackageAttribute::PackageAttribute(BHPKGAttributeID id_, uint8 type_, uint8 encoding_) : id(id_) { type = type_; encoding = encoding_; } WriterImplBase::PackageAttribute::~PackageAttribute() { _DeleteChildren(); } void WriterImplBase::PackageAttribute::AddChild(PackageAttribute* child) { children.Add(child); } void WriterImplBase::PackageAttribute::_DeleteChildren() { while (PackageAttribute* child = children.RemoveHead()) delete child; } // #pragma mark - WriterImplBase WriterImplBase::WriterImplBase(const char* fileType, BErrorOutput* errorOutput) : fHeapWriter(NULL), fCompressionAlgorithm(NULL), fCompressionParameters(NULL), fDecompressionAlgorithm(NULL), fDecompressionParameters(NULL), fFileType(fileType), fErrorOutput(errorOutput), fFileName(NULL), fParameters(), fFile(NULL), fOwnsFile(false), fFinished(false) { } WriterImplBase::~WriterImplBase() { delete fHeapWriter; delete fCompressionAlgorithm; delete fCompressionParameters; delete fDecompressionAlgorithm; delete fDecompressionParameters; if (fOwnsFile) delete fFile; if (!fFinished && fFileName != NULL && (Flags() & B_HPKG_WRITER_UPDATE_PACKAGE) == 0) { unlink(fFileName); } } status_t WriterImplBase::Init(BPositionIO* file, bool keepFile, const char* fileName, const BPackageWriterParameters& parameters) { fParameters = parameters; if (fPackageStringCache.Init() != B_OK) throw std::bad_alloc(); if (file == NULL) { if (fileName == NULL) return B_BAD_VALUE; // open file (don't truncate in update mode) int openMode = O_RDWR; if ((parameters.Flags() & B_HPKG_WRITER_UPDATE_PACKAGE) == 0) openMode |= O_CREAT | O_TRUNC; BFile* newFile = new BFile; status_t error = newFile->SetTo(fileName, openMode); if (error != B_OK) { fErrorOutput->PrintError("Failed to open %s file \"%s\": %s\n", fFileType, fileName, strerror(errno)); delete newFile; return error; } fFile = newFile; fOwnsFile = true; } else { fFile = file; fOwnsFile = keepFile; } fFileName = fileName; return B_OK; } status_t WriterImplBase::InitHeapReader(size_t headerSize) { // allocate the compression/decompression algorithm CompressionAlgorithmOwner* compressionAlgorithm = NULL; BReference compressionAlgorithmReference; DecompressionAlgorithmOwner* decompressionAlgorithm = NULL; BReference decompressionAlgorithmReference; switch (fParameters.Compression()) { case B_HPKG_COMPRESSION_NONE: break; case B_HPKG_COMPRESSION_ZLIB: compressionAlgorithm = CompressionAlgorithmOwner::Create( new(std::nothrow) BZlibCompressionAlgorithm, new(std::nothrow) BZlibCompressionParameters( (fParameters.CompressionLevel() / float(B_HPKG_COMPRESSION_LEVEL_BEST)) * B_ZLIB_COMPRESSION_BEST)); compressionAlgorithmReference.SetTo(compressionAlgorithm, true); decompressionAlgorithm = DecompressionAlgorithmOwner::Create( new(std::nothrow) BZlibCompressionAlgorithm, new(std::nothrow) BZlibDecompressionParameters); decompressionAlgorithmReference.SetTo(decompressionAlgorithm, true); if (compressionAlgorithm == NULL || compressionAlgorithm->algorithm == NULL || compressionAlgorithm->parameters == NULL || decompressionAlgorithm == NULL || decompressionAlgorithm->algorithm == NULL || decompressionAlgorithm->parameters == NULL) { throw std::bad_alloc(); } break; case B_HPKG_COMPRESSION_ZSTD: compressionAlgorithm = CompressionAlgorithmOwner::Create( new(std::nothrow) BZstdCompressionAlgorithm, new(std::nothrow) BZstdCompressionParameters( (fParameters.CompressionLevel() / float(B_HPKG_COMPRESSION_LEVEL_BEST)) * B_ZSTD_COMPRESSION_BEST)); compressionAlgorithmReference.SetTo(compressionAlgorithm, true); decompressionAlgorithm = DecompressionAlgorithmOwner::Create( new(std::nothrow) BZstdCompressionAlgorithm, new(std::nothrow) BZstdDecompressionParameters); decompressionAlgorithmReference.SetTo(decompressionAlgorithm, true); if (compressionAlgorithm == NULL || compressionAlgorithm->algorithm == NULL || compressionAlgorithm->parameters == NULL || decompressionAlgorithm == NULL || decompressionAlgorithm->algorithm == NULL || decompressionAlgorithm->parameters == NULL) { throw std::bad_alloc(); } break; default: fErrorOutput->PrintError("Error: Invalid heap compression\n"); return B_BAD_VALUE; } // create heap writer fHeapWriter = new PackageFileHeapWriter(fErrorOutput, fFile, headerSize, compressionAlgorithm, decompressionAlgorithm); fHeapWriter->Init(); return B_OK; } void WriterImplBase::SetCompression(uint32 compression) { fParameters.SetCompression(compression); } void WriterImplBase::RegisterPackageInfo(PackageAttributeList& attributeList, const BPackageInfo& packageInfo) { // name AddStringAttribute(B_HPKG_ATTRIBUTE_ID_PACKAGE_NAME, packageInfo.Name(), attributeList); // summary AddStringAttribute(B_HPKG_ATTRIBUTE_ID_PACKAGE_SUMMARY, packageInfo.Summary(), attributeList); // description AddStringAttribute(B_HPKG_ATTRIBUTE_ID_PACKAGE_DESCRIPTION, packageInfo.Description(), attributeList); // vendor AddStringAttribute(B_HPKG_ATTRIBUTE_ID_PACKAGE_VENDOR, packageInfo.Vendor(), attributeList); // packager AddStringAttribute(B_HPKG_ATTRIBUTE_ID_PACKAGE_PACKAGER, packageInfo.Packager(), attributeList); // base package (optional) _AddStringAttributeIfNotEmpty(B_HPKG_ATTRIBUTE_ID_PACKAGE_BASE_PACKAGE, packageInfo.BasePackage(), attributeList); // flags PackageAttribute* flags = new PackageAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_FLAGS, B_HPKG_ATTRIBUTE_TYPE_UINT, B_HPKG_ATTRIBUTE_ENCODING_INT_32_BIT); flags->unsignedInt = packageInfo.Flags(); attributeList.Add(flags); // architecture PackageAttribute* architecture = new PackageAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_ARCHITECTURE, B_HPKG_ATTRIBUTE_TYPE_UINT, B_HPKG_ATTRIBUTE_ENCODING_INT_8_BIT); architecture->unsignedInt = packageInfo.Architecture(); attributeList.Add(architecture); // version RegisterPackageVersion(attributeList, packageInfo.Version()); // copyright list _AddStringAttributeList(B_HPKG_ATTRIBUTE_ID_PACKAGE_COPYRIGHT, packageInfo.CopyrightList(), attributeList); // license list _AddStringAttributeList(B_HPKG_ATTRIBUTE_ID_PACKAGE_LICENSE, packageInfo.LicenseList(), attributeList); // URL list _AddStringAttributeList(B_HPKG_ATTRIBUTE_ID_PACKAGE_URL, packageInfo.URLList(), attributeList); // source URL list _AddStringAttributeList(B_HPKG_ATTRIBUTE_ID_PACKAGE_SOURCE_URL, packageInfo.SourceURLList(), attributeList); // provides list const BObjectList& providesList = packageInfo.ProvidesList(); for (int i = 0; i < providesList.CountItems(); ++i) { BPackageResolvable* resolvable = providesList.ItemAt(i); bool hasVersion = resolvable->Version().InitCheck() == B_OK; bool hasCompatibleVersion = resolvable->CompatibleVersion().InitCheck() == B_OK; PackageAttribute* provides = AddStringAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_PROVIDES, resolvable->Name(), attributeList); if (hasVersion) RegisterPackageVersion(provides->children, resolvable->Version()); if (hasCompatibleVersion) { RegisterPackageVersion(provides->children, resolvable->CompatibleVersion(), B_HPKG_ATTRIBUTE_ID_PACKAGE_PROVIDES_COMPATIBLE); } } // requires list RegisterPackageResolvableExpressionList(attributeList, packageInfo.RequiresList(), B_HPKG_ATTRIBUTE_ID_PACKAGE_REQUIRES); // supplements list RegisterPackageResolvableExpressionList(attributeList, packageInfo.SupplementsList(), B_HPKG_ATTRIBUTE_ID_PACKAGE_SUPPLEMENTS); // conflicts list RegisterPackageResolvableExpressionList(attributeList, packageInfo.ConflictsList(), B_HPKG_ATTRIBUTE_ID_PACKAGE_CONFLICTS); // freshens list RegisterPackageResolvableExpressionList(attributeList, packageInfo.FreshensList(), B_HPKG_ATTRIBUTE_ID_PACKAGE_FRESHENS); // replaces list _AddStringAttributeList(B_HPKG_ATTRIBUTE_ID_PACKAGE_REPLACES, packageInfo.ReplacesList(), attributeList); // global writable file info list const BObjectList& globalWritableFileInfos = packageInfo.GlobalWritableFileInfos(); for (int32 i = 0; i < globalWritableFileInfos.CountItems(); ++i) { BGlobalWritableFileInfo* info = globalWritableFileInfos.ItemAt(i); PackageAttribute* attribute = AddStringAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_GLOBAL_WRITABLE_FILE, info->Path(), attributeList); if (info->IsDirectory()) { PackageAttribute* isDirectoryAttribute = new PackageAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_IS_WRITABLE_DIRECTORY, B_HPKG_ATTRIBUTE_TYPE_UINT, B_HPKG_ATTRIBUTE_ENCODING_INT_8_BIT); isDirectoryAttribute->unsignedInt = 1; attribute->children.Add(isDirectoryAttribute); } if (info->IsIncluded()) { PackageAttribute* updateTypeAttribute = new PackageAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_WRITABLE_FILE_UPDATE_TYPE, B_HPKG_ATTRIBUTE_TYPE_UINT, B_HPKG_ATTRIBUTE_ENCODING_INT_8_BIT); updateTypeAttribute->unsignedInt = info->UpdateType(); attribute->children.Add(updateTypeAttribute); } } // user settings file info list const BObjectList& userSettingsFileInfos = packageInfo.UserSettingsFileInfos(); for (int32 i = 0; i < userSettingsFileInfos.CountItems(); ++i) { BUserSettingsFileInfo* info = userSettingsFileInfos.ItemAt(i); PackageAttribute* attribute = AddStringAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_USER_SETTINGS_FILE, info->Path(), attributeList); if (info->IsDirectory()) { PackageAttribute* isDirectoryAttribute = new PackageAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_IS_WRITABLE_DIRECTORY, B_HPKG_ATTRIBUTE_TYPE_UINT, B_HPKG_ATTRIBUTE_ENCODING_INT_8_BIT); isDirectoryAttribute->unsignedInt = 1; attribute->children.Add(isDirectoryAttribute); } else { _AddStringAttributeIfNotEmpty( B_HPKG_ATTRIBUTE_ID_PACKAGE_SETTINGS_FILE_TEMPLATE, info->TemplatePath(), attribute->children); } } // user list const BObjectList& users = packageInfo.Users(); for (int32 i = 0; i < users.CountItems(); ++i) { const BUser* user = users.ItemAt(i); PackageAttribute* attribute = AddStringAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_USER, user->Name(), attributeList); _AddStringAttributeIfNotEmpty( B_HPKG_ATTRIBUTE_ID_PACKAGE_USER_REAL_NAME, user->RealName(), attribute->children); _AddStringAttributeIfNotEmpty( B_HPKG_ATTRIBUTE_ID_PACKAGE_USER_HOME, user->Home(), attribute->children); _AddStringAttributeIfNotEmpty( B_HPKG_ATTRIBUTE_ID_PACKAGE_USER_SHELL, user->Shell(), attribute->children); for (int32 k = 0; k < user->Groups().CountStrings(); k++) { AddStringAttribute(B_HPKG_ATTRIBUTE_ID_PACKAGE_USER_GROUP, user->Groups().StringAt(k), attribute->children); } } // group list _AddStringAttributeList(B_HPKG_ATTRIBUTE_ID_PACKAGE_GROUP, packageInfo.Groups(), attributeList); // post install script list _AddStringAttributeList(B_HPKG_ATTRIBUTE_ID_PACKAGE_POST_INSTALL_SCRIPT, packageInfo.PostInstallScripts(), attributeList); // pre uninstall script list _AddStringAttributeList(B_HPKG_ATTRIBUTE_ID_PACKAGE_PRE_UNINSTALL_SCRIPT, packageInfo.PreUninstallScripts(), attributeList); // checksum (optional, only exists in repositories) _AddStringAttributeIfNotEmpty(B_HPKG_ATTRIBUTE_ID_PACKAGE_CHECKSUM, packageInfo.Checksum(), attributeList); // install path (optional) _AddStringAttributeIfNotEmpty(B_HPKG_ATTRIBUTE_ID_PACKAGE_INSTALL_PATH, packageInfo.InstallPath(), attributeList); } void WriterImplBase::RegisterPackageVersion(PackageAttributeList& attributeList, const BPackageVersion& version, BHPKGAttributeID attributeID) { PackageAttribute* versionMajor = AddStringAttribute(attributeID, version.Major(), attributeList); if (!version.Minor().IsEmpty()) { AddStringAttribute(B_HPKG_ATTRIBUTE_ID_PACKAGE_VERSION_MINOR, version.Minor(), versionMajor->children); _AddStringAttributeIfNotEmpty( B_HPKG_ATTRIBUTE_ID_PACKAGE_VERSION_MICRO, version.Micro(), versionMajor->children); } _AddStringAttributeIfNotEmpty( B_HPKG_ATTRIBUTE_ID_PACKAGE_VERSION_PRE_RELEASE, version.PreRelease(), versionMajor->children); if (version.Revision() != 0) { PackageAttribute* versionRevision = new PackageAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_VERSION_REVISION, B_HPKG_ATTRIBUTE_TYPE_UINT, B_HPKG_ATTRIBUTE_ENCODING_INT_32_BIT); versionRevision->unsignedInt = version.Revision(); versionMajor->children.Add(versionRevision); } } void WriterImplBase::RegisterPackageResolvableExpressionList( PackageAttributeList& attributeList, const BObjectList& expressionList, uint8 id) { for (int i = 0; i < expressionList.CountItems(); ++i) { BPackageResolvableExpression* resolvableExpr = expressionList.ItemAt(i); PackageAttribute* name = AddStringAttribute((BHPKGAttributeID)id, resolvableExpr->Name(), attributeList); if (resolvableExpr->Version().InitCheck() == B_OK) { PackageAttribute* op = new PackageAttribute( B_HPKG_ATTRIBUTE_ID_PACKAGE_RESOLVABLE_OPERATOR, B_HPKG_ATTRIBUTE_TYPE_UINT, B_HPKG_ATTRIBUTE_ENCODING_INT_8_BIT); op->unsignedInt = resolvableExpr->Operator(); name->children.Add(op); RegisterPackageVersion(name->children, resolvableExpr->Version()); } } } WriterImplBase::PackageAttribute* WriterImplBase::AddStringAttribute(BHPKGAttributeID id, const BString& value, DoublyLinkedList& list) { PackageAttribute* attribute = new PackageAttribute(id, B_HPKG_ATTRIBUTE_TYPE_STRING, B_HPKG_ATTRIBUTE_ENCODING_STRING_TABLE); attribute->string = fPackageStringCache.Get(value); list.Add(attribute); return attribute; } int32 WriterImplBase::WriteCachedStrings(const StringCache& cache, uint32 minUsageCount) { // create an array of the cached strings int32 count = cache.CountElements(); CachedString** cachedStrings = new CachedString*[count]; ArrayDeleter cachedStringsDeleter(cachedStrings); int32 index = 0; for (CachedStringTable::Iterator it = cache.GetIterator(); CachedString* string = it.Next();) { cachedStrings[index++] = string; } // sort it by descending usage count std::sort(cachedStrings, cachedStrings + count, CachedStringUsageGreater()); // assign the indices and write entries to disk int32 stringsWritten = 0; for (int32 i = 0; i < count; i++) { CachedString* cachedString = cachedStrings[i]; // empty strings must be stored inline, as they can't be distinguished // from the end-marker! if (strlen(cachedString->string) == 0) continue; // strings that are used only once are better stored inline if (cachedString->usageCount < minUsageCount) break; WriteString(cachedString->string); cachedString->index = stringsWritten++; } // write a terminating 0 byte Write(0); return stringsWritten; } int32 WriterImplBase::WritePackageAttributes( const PackageAttributeList& packageAttributes, uint32& _stringsLengthUncompressed) { // write the cached strings uint64 startOffset = fHeapWriter->UncompressedHeapSize(); uint32 stringsCount = WriteCachedStrings(fPackageStringCache, 2); _stringsLengthUncompressed = fHeapWriter->UncompressedHeapSize() - startOffset; _WritePackageAttributes(packageAttributes); return stringsCount; } void WriterImplBase::WriteAttributeValue(const AttributeValue& value, uint8 encoding) { switch (value.type) { case B_HPKG_ATTRIBUTE_TYPE_INT: case B_HPKG_ATTRIBUTE_TYPE_UINT: { uint64 intValue = value.type == B_HPKG_ATTRIBUTE_TYPE_INT ? (uint64)value.signedInt : value.unsignedInt; switch (encoding) { case B_HPKG_ATTRIBUTE_ENCODING_INT_8_BIT: Write((uint8)intValue); break; case B_HPKG_ATTRIBUTE_ENCODING_INT_16_BIT: Write( B_HOST_TO_BENDIAN_INT16((uint16)intValue)); break; case B_HPKG_ATTRIBUTE_ENCODING_INT_32_BIT: Write( B_HOST_TO_BENDIAN_INT32((uint32)intValue)); break; case B_HPKG_ATTRIBUTE_ENCODING_INT_64_BIT: Write( B_HOST_TO_BENDIAN_INT64((uint64)intValue)); break; default: { fErrorOutput->PrintError("WriteAttributeValue(): invalid " "encoding %d for int value type %d\n", encoding, value.type); throw status_t(B_BAD_VALUE); } } break; } case B_HPKG_ATTRIBUTE_TYPE_STRING: { if (encoding == B_HPKG_ATTRIBUTE_ENCODING_STRING_TABLE) WriteUnsignedLEB128(value.string->index); else WriteString(value.string->string); break; } case B_HPKG_ATTRIBUTE_TYPE_RAW: { WriteUnsignedLEB128(value.data.size); if (encoding == B_HPKG_ATTRIBUTE_ENCODING_RAW_HEAP) WriteUnsignedLEB128(value.data.offset); else fHeapWriter->AddDataThrows(value.data.raw, value.data.size); break; } default: fErrorOutput->PrintError( "WriteAttributeValue(): invalid value type: %d\n", value.type); throw status_t(B_BAD_VALUE); } } void WriterImplBase::WriteUnsignedLEB128(uint64 value) { uint8 bytes[10]; int32 count = 0; do { uint8 byte = value & 0x7f; value >>= 7; bytes[count++] = byte | (value != 0 ? 0x80 : 0); } while (value != 0); fHeapWriter->AddDataThrows(bytes, count); } void WriterImplBase::RawWriteBuffer(const void* buffer, size_t size, off_t offset) { status_t error = fFile->WriteAtExactly(offset, buffer, size); if (error != B_OK) { fErrorOutput->PrintError( "RawWriteBuffer(%p, %lu) failed to write data: %s\n", buffer, size, strerror(error)); throw error; } } void WriterImplBase::_AddStringAttributeList(BHPKGAttributeID id, const BStringList& value, DoublyLinkedList& list) { for (int32 i = 0; i < value.CountStrings(); i++) AddStringAttribute(id, value.StringAt(i), list); } void WriterImplBase::_WritePackageAttributes( const PackageAttributeList& packageAttributes) { DoublyLinkedList::ConstIterator it = packageAttributes.GetIterator(); while (PackageAttribute* attribute = it.Next()) { uint8 encoding = attribute->ApplicableEncoding(); // write tag WriteUnsignedLEB128(compose_attribute_tag( attribute->id, attribute->type, encoding, !attribute->children.IsEmpty())); // write value WriteAttributeValue(*attribute, encoding); if (!attribute->children.IsEmpty()) _WritePackageAttributes(attribute->children); } WriteUnsignedLEB128(0); } } // namespace BPrivate } // namespace BHPKG } // namespace BPackageKit