⛏️ index : haiku.git

/*
 * Copyright 2011, Oliver Tappe <zooey@hirschkaefer.de>
 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include <package/PackageInfo.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <new>

#include <File.h>
#include <Entry.h>
#include <Message.h>
#include <package/hpkg/NoErrorOutput.h>
#include <package/hpkg/PackageReader.h>
#include <package/hpkg/v1/PackageInfoContentHandler.h>
#include <package/hpkg/v1/PackageReader.h>
#include <package/PackageInfoContentHandler.h>

#include "PackageInfoParser.h"
#include "PackageInfoStringBuilder.h"


namespace BPackageKit {


const char* const BPackageInfo::kElementNames[B_PACKAGE_INFO_ENUM_COUNT] = {
	"name",
	"summary",
	"description",
	"vendor",
	"packager",
	"architecture",
	"version",
	"copyrights",
	"licenses",
	"provides",
	"requires",
	"supplements",
	"conflicts",
	"freshens",
	"replaces",
	"flags",
	"urls",
	"source-urls",
	"checksum",		// not being parsed, computed externally
	NULL,			// install-path -- not settable via .PackageInfo
	"base-package",
	"global-writable-files",
	"user-settings-files",
	"users",
	"groups",
	"post-install-scripts",
	"pre-uninstall-scripts"
};


const char* const
BPackageInfo::kArchitectureNames[B_PACKAGE_ARCHITECTURE_ENUM_COUNT] = {
	"any",
	"x86",
	"x86_gcc2",
	"source",
	"x86_64",
	"ppc",
	"arm",
	"m68k",
	"sparc",
	"arm64",
	"riscv64"
};


const char* const BPackageInfo::kWritableFileUpdateTypes[
		B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT] = {
	"keep-old",
	"manual",
	"auto-merge",
};


// #pragma mark - FieldName


struct BPackageInfo::FieldName {
	FieldName(const char* prefix, const char* suffix)
	{
		size_t prefixLength = strlen(prefix);
		size_t suffixLength = strlen(suffix);
		if (prefixLength + suffixLength >= sizeof(fFieldName)) {
			fFieldName[0] = '\0';
			return;
		}

		memcpy(fFieldName, prefix, prefixLength);
		memcpy(fFieldName + prefixLength, suffix, suffixLength);
		fFieldName[prefixLength + suffixLength] = '\0';
	}

	bool ReplaceSuffix(size_t prefixLength, const char* suffix)
	{
		size_t suffixLength = strlen(suffix);
		if (prefixLength + suffixLength >= sizeof(fFieldName)) {
			fFieldName[0] = '\0';
			return false;
		}

		memcpy(fFieldName + prefixLength, suffix, suffixLength);
		fFieldName[prefixLength + suffixLength] = '\0';
		return true;
	}

	bool IsValid() const
	{
		return fFieldName[0] != '\0';
	}

	operator const char*()
	{
		return fFieldName;
	}

private:
	char	fFieldName[64];
};


// #pragma mark - PackageFileLocation


struct BPackageInfo::PackageFileLocation {
	PackageFileLocation(const char* path)
		:
		fPath(path),
		fFD(-1)
	{
	}

	PackageFileLocation(int fd)
		:
		fPath(NULL),
		fFD(fd)
	{
	}

	const char* Path() const
	{
		return fPath;
	}

	int FD() const
	{
		return fFD;
	}

private:
	const char*	fPath;
	int			fFD;
};


// #pragma mark - BPackageInfo


BPackageInfo::BPackageInfo()
	:
	BArchivable(),
	fFlags(0),
	fArchitecture(B_PACKAGE_ARCHITECTURE_ENUM_COUNT),
	fCopyrightList(4),
	fLicenseList(4),
	fURLList(4),
	fSourceURLList(4),
	fGlobalWritableFileInfos(4),
	fUserSettingsFileInfos(4),
	fUsers(4),
	fGroups(4),
	fPostInstallScripts(4),
	fPreUninstallScripts(4),
	fProvidesList(20),
	fRequiresList(20),
	fSupplementsList(20),
	fConflictsList(4),
	fFreshensList(4),
	fReplacesList(4)
{
}


BPackageInfo::BPackageInfo(BMessage* archive, status_t* _error)
	:
	BArchivable(archive),
	fFlags(0),
	fArchitecture(B_PACKAGE_ARCHITECTURE_ENUM_COUNT),
	fCopyrightList(4),
	fLicenseList(4),
	fURLList(4),
	fSourceURLList(4),
	fGlobalWritableFileInfos(4),
	fUserSettingsFileInfos(4),
	fUsers(4),
	fGroups(4),
	fPostInstallScripts(4),
	fPreUninstallScripts(4),
	fProvidesList(20),
	fRequiresList(20),
	fSupplementsList(20),
	fConflictsList(4),
	fFreshensList(4),
	fReplacesList(4)
{
	status_t error;
	int32 architecture;
	if ((error = archive->FindString("name", &fName)) == B_OK
		&& (error = archive->FindString("summary", &fSummary)) == B_OK
		&& (error = archive->FindString("description", &fDescription)) == B_OK
		&& (error = archive->FindString("vendor", &fVendor)) == B_OK
		&& (error = archive->FindString("packager", &fPackager)) == B_OK
		&& (error = archive->FindString("basePackage", &fBasePackage)) == B_OK
		&& (error = archive->FindUInt32("flags", &fFlags)) == B_OK
		&& (error = archive->FindInt32("architecture", &architecture)) == B_OK
		&& (error = _ExtractVersion(archive, "version", 0, fVersion)) == B_OK
		&& (error = _ExtractStringList(archive, "copyrights", fCopyrightList))
			== B_OK
		&& (error = _ExtractStringList(archive, "licenses", fLicenseList))
			== B_OK
		&& (error = _ExtractStringList(archive, "urls", fURLList)) == B_OK
		&& (error = _ExtractStringList(archive, "source-urls", fSourceURLList))
			== B_OK
		&& (error = _ExtractGlobalWritableFileInfos(archive,
			"global-writable-files", fGlobalWritableFileInfos)) == B_OK
		&& (error = _ExtractUserSettingsFileInfos(archive, "user-settings-files",
			fUserSettingsFileInfos)) == B_OK
		&& (error = _ExtractUsers(archive, "users", fUsers)) == B_OK
		&& (error = _ExtractStringList(archive, "groups", fGroups)) == B_OK
		&& (error = _ExtractStringList(archive, "post-install-scripts",
			fPostInstallScripts)) == B_OK
		&& (error = _ExtractStringList(archive, "pre-uninstall-scripts",
			fPreUninstallScripts)) == B_OK
		&& (error = _ExtractResolvables(archive, "provides", fProvidesList))
			== B_OK
		&& (error = _ExtractResolvableExpressions(archive, "requires",
			fRequiresList)) == B_OK
		&& (error = _ExtractResolvableExpressions(archive, "supplements",
			fSupplementsList)) == B_OK
		&& (error = _ExtractResolvableExpressions(archive, "conflicts",
			fConflictsList)) == B_OK
		&& (error = _ExtractResolvableExpressions(archive, "freshens",
			fFreshensList)) == B_OK
		&& (error = _ExtractStringList(archive, "replaces", fReplacesList))
			== B_OK
		&& (error = archive->FindString("checksum", &fChecksum)) == B_OK
		&& (error = archive->FindString("install-path", &fInstallPath)) == B_OK
		&& (error = archive->FindString("file-name", &fFileName)) == B_OK) {
		if (architecture >= 0
			&& architecture <= B_PACKAGE_ARCHITECTURE_ENUM_COUNT) {
			fArchitecture = (BPackageArchitecture)architecture;
		} else
			error = B_BAD_DATA;
	}

	if (_error != NULL)
		*_error = error;
}


BPackageInfo::~BPackageInfo()
{
}


status_t
BPackageInfo::ReadFromConfigFile(const BEntry& packageInfoEntry,
	ParseErrorListener* listener)
{
	status_t result = packageInfoEntry.InitCheck();
	if (result != B_OK)
		return result;

	BFile file(&packageInfoEntry, B_READ_ONLY);
	if ((result = file.InitCheck()) != B_OK)
		return result;

	return ReadFromConfigFile(file, listener);
}


status_t
BPackageInfo::ReadFromConfigFile(BFile& packageInfoFile,
	ParseErrorListener* listener)
{
	off_t size;
	status_t result = packageInfoFile.GetSize(&size);
	if (result != B_OK)
		return result;

	BString packageInfoString;
	char* buffer = packageInfoString.LockBuffer(size);
	if (buffer == NULL)
		return B_NO_MEMORY;

	if ((result = packageInfoFile.Read(buffer, size)) < size) {
		packageInfoString.UnlockBuffer(0);
		return result >= 0 ? B_IO_ERROR : result;
	}

	buffer[size] = '\0';
	packageInfoString.UnlockBuffer(size);

	return ReadFromConfigString(packageInfoString, listener);
}


status_t
BPackageInfo::ReadFromConfigString(const BString& packageInfoString,
	ParseErrorListener* listener)
{
	Clear();

	Parser parser(listener);
	return parser.Parse(packageInfoString, this);
}


status_t
BPackageInfo::ReadFromPackageFile(const char* path)
{
	return _ReadFromPackageFile(PackageFileLocation(path));
}


status_t
BPackageInfo::ReadFromPackageFile(int fd)
{
	return _ReadFromPackageFile(PackageFileLocation(fd));
}


status_t
BPackageInfo::InitCheck() const
{
	if (fName.Length() == 0 || fSummary.Length() == 0
		|| fDescription.Length() == 0 || fVendor.Length() == 0
		|| fPackager.Length() == 0
		|| fArchitecture == B_PACKAGE_ARCHITECTURE_ENUM_COUNT
		|| fVersion.InitCheck() != B_OK
		|| fCopyrightList.IsEmpty() || fLicenseList.IsEmpty()
		|| fProvidesList.IsEmpty())
		return B_NO_INIT;

	// check global writable files
	int32 globalWritableFileCount = fGlobalWritableFileInfos.CountItems();
	for (int32 i = 0; i < globalWritableFileCount; i++) {
		const BGlobalWritableFileInfo* info
			= fGlobalWritableFileInfos.ItemAt(i);
		status_t error = info->InitCheck();
		if (error != B_OK)
			return error;
	}

	// check user settings files
	int32 userSettingsFileCount = fUserSettingsFileInfos.CountItems();
	for (int32 i = 0; i < userSettingsFileCount; i++) {
		const BUserSettingsFileInfo* info = fUserSettingsFileInfos.ItemAt(i);
		status_t error = info->InitCheck();
		if (error != B_OK)
			return error;
	}

	// check users
	int32 userCount = fUsers.CountItems();
	for (int32 i = 0; i < userCount; i++) {
		const BUser* user = fUsers.ItemAt(i);
		status_t error = user->InitCheck();
		if (error != B_OK)
			return B_NO_INIT;

		// make sure the user's groups are specified as groups
		const BStringList& userGroups = user->Groups();
		int32 groupCount = userGroups.CountStrings();
		for (int32 k = 0; k < groupCount; k++) {
			const BString& group = userGroups.StringAt(k);
			if (!fGroups.HasString(group))
				return B_BAD_VALUE;
		}
	}

	// check groups
	int32 groupCount = fGroups.CountStrings();
	for (int32 i = 0; i< groupCount; i++) {
		if (!BUser::IsValidUserName(fGroups.StringAt(i)))
			return B_BAD_VALUE;
	}

	return B_OK;
}


const BString&
BPackageInfo::Name() const
{
	return fName;
}


const BString&
BPackageInfo::Summary() const
{
	return fSummary;
}


const BString&
BPackageInfo::Description() const
{
	return fDescription;
}


const BString&
BPackageInfo::Vendor() const
{
	return fVendor;
}


const BString&
BPackageInfo::Packager() const
{
	return fPackager;
}


const BString&
BPackageInfo::BasePackage() const
{
	return fBasePackage;
}


const BString&
BPackageInfo::Checksum() const
{
	return fChecksum;
}


const BString&
BPackageInfo::InstallPath() const
{
	return fInstallPath;
}


BString
BPackageInfo::FileName() const
{
	return fFileName.IsEmpty() ? CanonicalFileName() : fFileName;
}


uint32
BPackageInfo::Flags() const
{
	return fFlags;
}


BPackageArchitecture
BPackageInfo::Architecture() const
{
	return fArchitecture;
}


const char*
BPackageInfo::ArchitectureName() const
{
	if ((int)fArchitecture < 0
		|| fArchitecture >= B_PACKAGE_ARCHITECTURE_ENUM_COUNT) {
		return NULL;
	}
	return kArchitectureNames[fArchitecture];
}


const BPackageVersion&
BPackageInfo::Version() const
{
	return fVersion;
}


const BStringList&
BPackageInfo::CopyrightList() const
{
	return fCopyrightList;
}


const BStringList&
BPackageInfo::LicenseList() const
{
	return fLicenseList;
}


const BStringList&
BPackageInfo::URLList() const
{
	return fURLList;
}


const BStringList&
BPackageInfo::SourceURLList() const
{
	return fSourceURLList;
}


const BObjectList<BGlobalWritableFileInfo, true>&
BPackageInfo::GlobalWritableFileInfos() const
{
	return fGlobalWritableFileInfos;
}


const BObjectList<BUserSettingsFileInfo, true>&
BPackageInfo::UserSettingsFileInfos() const
{
	return fUserSettingsFileInfos;
}


const BObjectList<BUser, true>&
BPackageInfo::Users() const
{
	return fUsers;
}


const BStringList&
BPackageInfo::Groups() const
{
	return fGroups;
}


const BStringList&
BPackageInfo::PostInstallScripts() const
{
	return fPostInstallScripts;
}


const BStringList&
BPackageInfo::PreUninstallScripts() const
{
	return fPreUninstallScripts;
}


const BObjectList<BPackageResolvable, true>&
BPackageInfo::ProvidesList() const
{
	return fProvidesList;
}


const BObjectList<BPackageResolvableExpression, true>&
BPackageInfo::RequiresList() const
{
	return fRequiresList;
}


const BObjectList<BPackageResolvableExpression, true>&
BPackageInfo::SupplementsList() const
{
	return fSupplementsList;
}


const BObjectList<BPackageResolvableExpression, true>&
BPackageInfo::ConflictsList() const
{
	return fConflictsList;
}


const BObjectList<BPackageResolvableExpression, true>&
BPackageInfo::FreshensList() const
{
	return fFreshensList;
}


const BStringList&
BPackageInfo::ReplacesList() const
{
	return fReplacesList;
}


BString
BPackageInfo::CanonicalFileName() const
{
	if (InitCheck() != B_OK)
		return BString();

	return BString().SetToFormat("%s-%s-%s.hpkg", fName.String(),
		fVersion.ToString().String(), kArchitectureNames[fArchitecture]);
}


bool
BPackageInfo::Matches(const BPackageResolvableExpression& expression) const
{
	// check for an explicit match on the package
	if (expression.Name().StartsWith("pkg:")) {
		return fName == expression.Name().String() + 4
			&& expression.Matches(fVersion, fVersion);
	}

	// search for a matching provides
	int32 count = fProvidesList.CountItems();
	for (int32 i = 0; i < count; i++) {
		const BPackageResolvable* provides = fProvidesList.ItemAt(i);
		if (expression.Matches(*provides))
			return true;
	}

	return false;
}


void
BPackageInfo::SetName(const BString& name)
{
	fName = name;
	fName.ToLower();
}


void
BPackageInfo::SetSummary(const BString& summary)
{
	fSummary = summary;
}


void
BPackageInfo::SetDescription(const BString& description)
{
	fDescription = description;
}


void
BPackageInfo::SetVendor(const BString& vendor)
{
	fVendor = vendor;
}


void
BPackageInfo::SetPackager(const BString& packager)
{
	fPackager = packager;
}


void
BPackageInfo::SetBasePackage(const BString& basePackage)
{
	fBasePackage = basePackage;
}


void
BPackageInfo::SetChecksum(const BString& checksum)
{
	fChecksum = checksum;
}


void
BPackageInfo::SetInstallPath(const BString& installPath)
{
	fInstallPath = installPath;
}


void
BPackageInfo::SetFileName(const BString& fileName)
{
	fFileName = fileName;
}


void
BPackageInfo::SetVersion(const BPackageVersion& version)
{
	fVersion = version;
}


void
BPackageInfo::SetFlags(uint32 flags)
{
	fFlags = flags;
}


void
BPackageInfo::SetArchitecture(BPackageArchitecture architecture)
{
	fArchitecture = architecture;
}


void
BPackageInfo::ClearCopyrightList()
{
	fCopyrightList.MakeEmpty();
}


status_t
BPackageInfo::AddCopyright(const BString& copyright)
{
	return fCopyrightList.Add(copyright) ? B_OK : B_ERROR;
}


void
BPackageInfo::ClearLicenseList()
{
	fLicenseList.MakeEmpty();
}


status_t
BPackageInfo::AddLicense(const BString& license)
{
	return fLicenseList.Add(license) ? B_OK : B_ERROR;
}


void
BPackageInfo::ClearURLList()
{
	fURLList.MakeEmpty();
}


status_t
BPackageInfo::AddURL(const BString& url)
{
	return fURLList.Add(url) ? B_OK : B_NO_MEMORY;
}


void
BPackageInfo::ClearSourceURLList()
{
	fSourceURLList.MakeEmpty();
}


status_t
BPackageInfo::AddSourceURL(const BString& url)
{
	return fSourceURLList.Add(url) ? B_OK : B_NO_MEMORY;
}


void
BPackageInfo::ClearGlobalWritableFileInfos()
{
	fGlobalWritableFileInfos.MakeEmpty();
}


status_t
BPackageInfo::AddGlobalWritableFileInfo(const BGlobalWritableFileInfo& info)
{
	BGlobalWritableFileInfo* newInfo
		= new (std::nothrow) BGlobalWritableFileInfo(info);
	if (newInfo == NULL || !fGlobalWritableFileInfos.AddItem(newInfo)) {
		delete newInfo;
		return B_NO_MEMORY;
	}

	return B_OK;
}


void
BPackageInfo::ClearUserSettingsFileInfos()
{
	fUserSettingsFileInfos.MakeEmpty();
}


status_t
BPackageInfo::AddUserSettingsFileInfo(const BUserSettingsFileInfo& info)
{
	BUserSettingsFileInfo* newInfo
		= new (std::nothrow) BUserSettingsFileInfo(info);
	if (newInfo == NULL || !fUserSettingsFileInfos.AddItem(newInfo)) {
		delete newInfo;
		return B_NO_MEMORY;
	}

	return B_OK;
}


void
BPackageInfo::ClearUsers()
{
	fUsers.MakeEmpty();
}


status_t
BPackageInfo::AddUser(const BUser& user)
{
	BUser* newUser = new (std::nothrow) BUser(user);
	if (newUser == NULL || !fUsers.AddItem(newUser)) {
		delete newUser;
		return B_NO_MEMORY;
	}

	return B_OK;
}


void
BPackageInfo::ClearGroups()
{
	fGroups.MakeEmpty();
}


status_t
BPackageInfo::AddGroup(const BString& group)
{
	return fGroups.Add(group) ? B_OK : B_NO_MEMORY;
}


void
BPackageInfo::ClearPostInstallScripts()
{
	fPostInstallScripts.MakeEmpty();
}


void
BPackageInfo::ClearPreUninstallScripts()
{
	fPreUninstallScripts.MakeEmpty();
}


status_t
BPackageInfo::AddPostInstallScript(const BString& path)
{
	return fPostInstallScripts.Add(path) ? B_OK : B_NO_MEMORY;
}


status_t
BPackageInfo::AddPreUninstallScript(const BString& path)
{
	return fPreUninstallScripts.Add(path) ? B_OK : B_NO_MEMORY;
}


void
BPackageInfo::ClearProvidesList()
{
	fProvidesList.MakeEmpty();
}


status_t
BPackageInfo::AddProvides(const BPackageResolvable& provides)
{
	BPackageResolvable* newProvides
		= new (std::nothrow) BPackageResolvable(provides);
	if (newProvides == NULL)
		return B_NO_MEMORY;

	return fProvidesList.AddItem(newProvides) ? B_OK : B_ERROR;
}


void
BPackageInfo::ClearRequiresList()
{
	fRequiresList.MakeEmpty();
}


status_t
BPackageInfo::AddRequires(const BPackageResolvableExpression& packageRequires)
{
	BPackageResolvableExpression* newRequires
		= new (std::nothrow) BPackageResolvableExpression(packageRequires);
	if (newRequires == NULL)
		return B_NO_MEMORY;

	return fRequiresList.AddItem(newRequires) ? B_OK : B_ERROR;
}


void
BPackageInfo::ClearSupplementsList()
{
	fSupplementsList.MakeEmpty();
}


status_t
BPackageInfo::AddSupplements(const BPackageResolvableExpression& supplements)
{
	BPackageResolvableExpression* newSupplements
		= new (std::nothrow) BPackageResolvableExpression(supplements);
	if (newSupplements == NULL)
		return B_NO_MEMORY;

	return fSupplementsList.AddItem(newSupplements) ? B_OK : B_ERROR;
}


void
BPackageInfo::ClearConflictsList()
{
	fConflictsList.MakeEmpty();
}


status_t
BPackageInfo::AddConflicts(const BPackageResolvableExpression& conflicts)
{
	BPackageResolvableExpression* newConflicts
		= new (std::nothrow) BPackageResolvableExpression(conflicts);
	if (newConflicts == NULL)
		return B_NO_MEMORY;

	return fConflictsList.AddItem(newConflicts) ? B_OK : B_ERROR;
}


void
BPackageInfo::ClearFreshensList()
{
	fFreshensList.MakeEmpty();
}


status_t
BPackageInfo::AddFreshens(const BPackageResolvableExpression& freshens)
{
	BPackageResolvableExpression* newFreshens
		= new (std::nothrow) BPackageResolvableExpression(freshens);
	if (newFreshens == NULL)
		return B_NO_MEMORY;

	return fFreshensList.AddItem(newFreshens) ? B_OK : B_ERROR;
}


void
BPackageInfo::ClearReplacesList()
{
	fReplacesList.MakeEmpty();
}


status_t
BPackageInfo::AddReplaces(const BString& replaces)
{
	return fReplacesList.Add(BString(replaces).ToLower()) ? B_OK : B_ERROR;
}


void
BPackageInfo::Clear()
{
	fName.Truncate(0);
	fSummary.Truncate(0);
	fDescription.Truncate(0);
	fVendor.Truncate(0);
	fPackager.Truncate(0);
	fBasePackage.Truncate(0);
	fChecksum.Truncate(0);
	fInstallPath.Truncate(0);
	fFileName.Truncate(0);
	fFlags = 0;
	fArchitecture = B_PACKAGE_ARCHITECTURE_ENUM_COUNT;
	fVersion.Clear();
	fCopyrightList.MakeEmpty();
	fLicenseList.MakeEmpty();
	fURLList.MakeEmpty();
	fSourceURLList.MakeEmpty();
	fGlobalWritableFileInfos.MakeEmpty();
	fUserSettingsFileInfos.MakeEmpty();
	fUsers.MakeEmpty();
	fGroups.MakeEmpty();
	fPostInstallScripts.MakeEmpty();
	fPreUninstallScripts.MakeEmpty();
	fRequiresList.MakeEmpty();
	fProvidesList.MakeEmpty();
	fSupplementsList.MakeEmpty();
	fConflictsList.MakeEmpty();
	fFreshensList.MakeEmpty();
	fReplacesList.MakeEmpty();
}


status_t
BPackageInfo::Archive(BMessage* archive, bool deep) const
{
	status_t error = BArchivable::Archive(archive, deep);
	if (error != B_OK)
		return error;

	if ((error = archive->AddString("name", fName)) != B_OK
		|| (error = archive->AddString("summary", fSummary)) != B_OK
		|| (error = archive->AddString("description", fDescription)) != B_OK
		|| (error = archive->AddString("vendor", fVendor)) != B_OK
		|| (error = archive->AddString("packager", fPackager)) != B_OK
		|| (error = archive->AddString("basePackage", fBasePackage)) != B_OK
		|| (error = archive->AddUInt32("flags", fFlags)) != B_OK
		|| (error = archive->AddInt32("architecture", fArchitecture)) != B_OK
		|| (error = _AddVersion(archive, "version", fVersion)) != B_OK
		|| (error = archive->AddStrings("copyrights", fCopyrightList))
			!= B_OK
		|| (error = archive->AddStrings("licenses", fLicenseList)) != B_OK
		|| (error = archive->AddStrings("urls", fURLList)) != B_OK
		|| (error = archive->AddStrings("source-urls", fSourceURLList))
			!= B_OK
		|| (error = _AddGlobalWritableFileInfos(archive,
			"global-writable-files", fGlobalWritableFileInfos)) != B_OK
		|| (error = _AddUserSettingsFileInfos(archive,
			"user-settings-files", fUserSettingsFileInfos)) != B_OK
		|| (error = _AddUsers(archive, "users", fUsers)) != B_OK
		|| (error = archive->AddStrings("groups", fGroups)) != B_OK
		|| (error = archive->AddStrings("post-install-scripts",
			fPostInstallScripts)) != B_OK
		|| (error = archive->AddStrings("pre-uninstall-scripts",
			fPreUninstallScripts)) != B_OK
		|| (error = _AddResolvables(archive, "provides", fProvidesList)) != B_OK
		|| (error = _AddResolvableExpressions(archive, "requires",
			fRequiresList)) != B_OK
		|| (error = _AddResolvableExpressions(archive, "supplements",
			fSupplementsList)) != B_OK
		|| (error = _AddResolvableExpressions(archive, "conflicts",
			fConflictsList)) != B_OK
		|| (error = _AddResolvableExpressions(archive, "freshens",
			fFreshensList)) != B_OK
		|| (error = archive->AddStrings("replaces", fReplacesList)) != B_OK
		|| (error = archive->AddString("checksum", fChecksum)) != B_OK
		|| (error = archive->AddString("install-path", fInstallPath)) != B_OK
		|| (error = archive->AddString("file-name", fFileName)) != B_OK) {
		return error;
	}

	return B_OK;
}


/*static*/ BArchivable*
BPackageInfo::Instantiate(BMessage* archive)
{
	if (validate_instantiation(archive, "BPackageInfo"))
		return new(std::nothrow) BPackageInfo(archive);
	return NULL;
}


status_t
BPackageInfo::GetConfigString(BString& _string) const
{
	return StringBuilder()
		.Write("name", fName)
		.Write("version", fVersion)
		.Write("summary", fSummary)
		.Write("description", fDescription)
		.Write("vendor", fVendor)
		.Write("packager", fPackager)
		.Write("architecture", kArchitectureNames[fArchitecture])
		.Write("copyrights", fCopyrightList)
		.Write("licenses", fLicenseList)
		.Write("urls", fURLList)
		.Write("source-urls", fSourceURLList)
		.Write("global-writable-files", fGlobalWritableFileInfos)
		.Write("user-settings-files", fUserSettingsFileInfos)
		.Write("users", fUsers)
		.Write("groups", fGroups)
		.Write("post-install-scripts", fPostInstallScripts)
		.Write("pre-uninstall-scripts", fPreUninstallScripts)
		.Write("provides", fProvidesList)
		.BeginRequires(fBasePackage)
			.Write("requires", fRequiresList)
		.EndRequires()
		.Write("supplements", fSupplementsList)
		.Write("conflicts", fConflictsList)
		.Write("freshens", fFreshensList)
		.Write("replaces", fReplacesList)
		.WriteFlags("flags", fFlags)
		.Write("checksum", fChecksum)
		.GetString(_string);
	// Note: fInstallPath and fFileName can not be specified via .PackageInfo.
}


BString
BPackageInfo::ToString() const
{
	BString string;
	GetConfigString(string);
	return string;
}


/*static*/ status_t
BPackageInfo::GetArchitectureByName(const BString& name,
	BPackageArchitecture& _architecture)
{
	for (int i = 0; i < B_PACKAGE_ARCHITECTURE_ENUM_COUNT; ++i) {
		if (name.ICompare(kArchitectureNames[i]) == 0) {
			_architecture = (BPackageArchitecture)i;
			return B_OK;
		}
	}
	return B_NAME_NOT_FOUND;
}


/*static*/ status_t
BPackageInfo::ParseVersionString(const BString& string, bool revisionIsOptional,
	BPackageVersion& _version, ParseErrorListener* listener)
{
	return Parser(listener).ParseVersion(string, revisionIsOptional, _version);
}


/*static*/ status_t
BPackageInfo::ParseResolvableString(const BString& string,
	BPackageResolvable& _expression, ParseErrorListener* listener)
{
	return Parser(listener).ParseResolvable(string, _expression);
}


/*static*/ status_t
BPackageInfo::ParseResolvableExpressionString(const BString& string,
	BPackageResolvableExpression& _expression, ParseErrorListener* listener)
{
	return Parser(listener).ParseResolvableExpression(string, _expression);
}


status_t
BPackageInfo::_ReadFromPackageFile(const PackageFileLocation& fileLocation)
{
	BHPKG::BNoErrorOutput errorOutput;

	// try current package file format version
	{
		BHPKG::BPackageReader packageReader(&errorOutput);
		status_t error = fileLocation.Path() != NULL
			? packageReader.Init(fileLocation.Path())
			: packageReader.Init(fileLocation.FD(), false);
		if (error == B_OK) {
			BPackageInfoContentHandler handler(*this);
			return packageReader.ParseContent(&handler);
		}

		if (error != B_MISMATCHED_VALUES)
			return error;
	}

	// try package file format version 1
	BHPKG::V1::BPackageReader packageReader(&errorOutput);
	status_t error = fileLocation.Path() != NULL
		? packageReader.Init(fileLocation.Path())
		: packageReader.Init(fileLocation.FD(), false);
	if (error != B_OK)
		return error;

	BHPKG::V1::BPackageInfoContentHandler handler(*this);
	return packageReader.ParseContent(&handler);
}


/*static*/ status_t
BPackageInfo::_AddVersion(BMessage* archive, const char* field,
	const BPackageVersion& version)
{
	// Storing BPackageVersion::ToString() would be nice, but the corresponding
	// constructor only works for valid versions and we might want to store
	// invalid versions as well.

	// major
	size_t fieldLength = strlen(field);
	FieldName fieldName(field, ":major");
	if (!fieldName.IsValid())
		return B_BAD_VALUE;

	status_t error = archive->AddString(fieldName, version.Major());
	if (error != B_OK)
		return error;

	// minor
	if (!fieldName.ReplaceSuffix(fieldLength, ":minor"))
		return B_BAD_VALUE;

	error = archive->AddString(fieldName, version.Minor());
	if (error != B_OK)
		return error;

	// micro
	if (!fieldName.ReplaceSuffix(fieldLength, ":micro"))
		return B_BAD_VALUE;

	error = archive->AddString(fieldName, version.Micro());
	if (error != B_OK)
		return error;

	// pre-release
	if (!fieldName.ReplaceSuffix(fieldLength, ":pre"))
		return B_BAD_VALUE;

	error = archive->AddString(fieldName, version.PreRelease());
	if (error != B_OK)
		return error;

	// revision
	if (!fieldName.ReplaceSuffix(fieldLength, ":revision"))
		return B_BAD_VALUE;

	return archive->AddUInt32(fieldName, version.Revision());
}


/*static*/ status_t
BPackageInfo::_AddResolvables(BMessage* archive, const char* field,
	const ResolvableList& resolvables)
{
	// construct the field names we need
	FieldName nameField(field, ":name");
	FieldName typeField(field, ":type");
	FieldName versionField(field, ":version");
	FieldName compatibleVersionField(field, ":compat");

	if (!nameField.IsValid() || !typeField.IsValid() || !versionField.IsValid()
		|| !compatibleVersionField.IsValid()) {
		return B_BAD_VALUE;
	}

	// add fields
	int32 count = resolvables.CountItems();
	for (int32 i = 0; i < count; i++) {
		const BPackageResolvable* resolvable = resolvables.ItemAt(i);
		status_t error;
		if ((error = archive->AddString(nameField, resolvable->Name())) != B_OK
			|| (error = _AddVersion(archive, versionField,
				resolvable->Version())) != B_OK
			|| (error = _AddVersion(archive, compatibleVersionField,
				resolvable->CompatibleVersion())) != B_OK) {
			return error;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_AddResolvableExpressions(BMessage* archive, const char* field,
	const ResolvableExpressionList& expressions)
{
	// construct the field names we need
	FieldName nameField(field, ":name");
	FieldName operatorField(field, ":operator");
	FieldName versionField(field, ":version");

	if (!nameField.IsValid() || !operatorField.IsValid()
		|| !versionField.IsValid()) {
		return B_BAD_VALUE;
	}

	// add fields
	int32 count = expressions.CountItems();
	for (int32 i = 0; i < count; i++) {
		const BPackageResolvableExpression* expression = expressions.ItemAt(i);
		status_t error;
		if ((error = archive->AddString(nameField, expression->Name())) != B_OK
			|| (error = archive->AddInt32(operatorField,
				expression->Operator())) != B_OK
			|| (error = _AddVersion(archive, versionField,
				expression->Version())) != B_OK) {
			return error;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_AddGlobalWritableFileInfos(BMessage* archive, const char* field,
	const GlobalWritableFileInfoList& infos)
{
	// construct the field names we need
	FieldName pathField(field, ":path");
	FieldName updateTypeField(field, ":updateType");
	FieldName isDirectoryField(field, ":isDirectory");

	if (!pathField.IsValid() || !updateTypeField.IsValid()
		|| !isDirectoryField.IsValid()) {
		return B_BAD_VALUE;
	}

	// add fields
	int32 count = infos.CountItems();
	for (int32 i = 0; i < count; i++) {
		const BGlobalWritableFileInfo* info = infos.ItemAt(i);
		status_t error;
		if ((error = archive->AddString(pathField, info->Path())) != B_OK
			|| (error = archive->AddInt32(updateTypeField, info->UpdateType()))
				!= B_OK
			|| (error = archive->AddBool(isDirectoryField,
				info->IsDirectory())) != B_OK) {
			return error;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_AddUserSettingsFileInfos(BMessage* archive, const char* field,
	const UserSettingsFileInfoList& infos)
{
	// construct the field names we need
	FieldName pathField(field, ":path");
	FieldName templatePathField(field, ":templatePath");
	FieldName isDirectoryField(field, ":isDirectory");

	if (!pathField.IsValid() || !templatePathField.IsValid()
		|| !isDirectoryField.IsValid()) {
		return B_BAD_VALUE;
	}

	// add fields
	int32 count = infos.CountItems();
	for (int32 i = 0; i < count; i++) {
		const BUserSettingsFileInfo* info = infos.ItemAt(i);
		status_t error;
		if ((error = archive->AddString(pathField, info->Path())) != B_OK
			|| (error = archive->AddString(templatePathField,
				info->TemplatePath())) != B_OK
			|| (error = archive->AddBool(isDirectoryField,
				info->IsDirectory())) != B_OK) {
			return error;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_AddUsers(BMessage* archive, const char* field,
	const UserList& users)
{
	// construct the field names we need
	FieldName nameField(field, ":name");
	FieldName realNameField(field, ":realName");
	FieldName homeField(field, ":home");
	FieldName shellField(field, ":shell");
	FieldName groupsField(field, ":groups");

	if (!nameField.IsValid() || !realNameField.IsValid() || !homeField.IsValid()
		|| !shellField.IsValid() || !groupsField.IsValid())
		return B_BAD_VALUE;

	// add fields
	int32 count = users.CountItems();
	for (int32 i = 0; i < count; i++) {
		const BUser* user = users.ItemAt(i);
		BString groups = user->Groups().Join(" ");
		if (groups.IsEmpty() && !user->Groups().IsEmpty())
			return B_NO_MEMORY;

		status_t error;
		if ((error = archive->AddString(nameField, user->Name())) != B_OK
			|| (error = archive->AddString(realNameField, user->RealName()))
				!= B_OK
			|| (error = archive->AddString(homeField, user->Home())) != B_OK
			|| (error = archive->AddString(shellField, user->Shell())) != B_OK
			|| (error = archive->AddString(groupsField, groups)) != B_OK) {
			return error;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_ExtractVersion(BMessage* archive, const char* field, int32 index,
	BPackageVersion& _version)
{
	// major
	size_t fieldLength = strlen(field);
	FieldName fieldName(field, ":major");
	if (!fieldName.IsValid())
		return B_BAD_VALUE;

	BString major;
	status_t error = archive->FindString(fieldName, index, &major);
	if (error != B_OK)
		return error;

	// minor
	if (!fieldName.ReplaceSuffix(fieldLength, ":minor"))
		return B_BAD_VALUE;

	BString minor;
	error = archive->FindString(fieldName, index, &minor);
	if (error != B_OK)
		return error;

	// micro
	if (!fieldName.ReplaceSuffix(fieldLength, ":micro"))
		return B_BAD_VALUE;

	BString micro;
	error = archive->FindString(fieldName, index, &micro);
	if (error != B_OK)
		return error;

	// pre-release
	if (!fieldName.ReplaceSuffix(fieldLength, ":pre"))
		return B_BAD_VALUE;

	BString preRelease;
	error = archive->FindString(fieldName, index, &preRelease);
	if (error != B_OK)
		return error;

	// revision
	if (!fieldName.ReplaceSuffix(fieldLength, ":revision"))
		return B_BAD_VALUE;

	uint32 revision;
	error = archive->FindUInt32(fieldName, index, &revision);
	if (error != B_OK)
		return error;

	_version.SetTo(major, minor, micro, preRelease, revision);
	return B_OK;
}


/*static*/ status_t
BPackageInfo::_ExtractStringList(BMessage* archive, const char* field,
	BStringList& _list)
{
	status_t error = archive->FindStrings(field, &_list);
	return error == B_NAME_NOT_FOUND ? B_OK : error;
		// If the field doesn't exist, that's OK.
}


/*static*/ status_t
BPackageInfo::_ExtractResolvables(BMessage* archive, const char* field,
	ResolvableList& _resolvables)
{
	// construct the field names we need
	FieldName nameField(field, ":name");
	FieldName typeField(field, ":type");
	FieldName versionField(field, ":version");
	FieldName compatibleVersionField(field, ":compat");

	if (!nameField.IsValid() || !typeField.IsValid() || !versionField.IsValid()
		|| !compatibleVersionField.IsValid()) {
		return B_BAD_VALUE;
	}

	// get the number of items
	type_code type;
	int32 count;
	if (archive->GetInfo(nameField, &type, &count) != B_OK) {
		// the field is missing
		return B_OK;
	}

	// extract fields
	for (int32 i = 0; i < count; i++) {
		BString name;
		status_t error = archive->FindString(nameField, i, &name);
		if (error != B_OK)
			return error;

		BPackageVersion version;
		error = _ExtractVersion(archive, versionField, i, version);
		if (error != B_OK)
			return error;

		BPackageVersion compatibleVersion;
		error = _ExtractVersion(archive, compatibleVersionField, i,
			compatibleVersion);
		if (error != B_OK)
			return error;

		BPackageResolvable* resolvable = new(std::nothrow) BPackageResolvable(
			name, version, compatibleVersion);
		if (resolvable == NULL || !_resolvables.AddItem(resolvable)) {
			delete resolvable;
			return B_NO_MEMORY;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_ExtractResolvableExpressions(BMessage* archive,
	const char* field, ResolvableExpressionList& _expressions)
{
	// construct the field names we need
	FieldName nameField(field, ":name");
	FieldName operatorField(field, ":operator");
	FieldName versionField(field, ":version");

	if (!nameField.IsValid() || !operatorField.IsValid()
		|| !versionField.IsValid()) {
		return B_BAD_VALUE;
	}

	// get the number of items
	type_code type;
	int32 count;
	if (archive->GetInfo(nameField, &type, &count) != B_OK) {
		// the field is missing
		return B_OK;
	}

	// extract fields
	for (int32 i = 0; i < count; i++) {
		BString name;
		status_t error = archive->FindString(nameField, i, &name);
		if (error != B_OK)
			return error;

		int32 operatorType;
		error = archive->FindInt32(operatorField, i, &operatorType);
		if (error != B_OK)
			return error;
		if (operatorType < 0
			|| operatorType > B_PACKAGE_RESOLVABLE_OP_ENUM_COUNT) {
			return B_BAD_DATA;
		}

		BPackageVersion version;
		error = _ExtractVersion(archive, versionField, i, version);
		if (error != B_OK)
			return error;

		BPackageResolvableExpression* expression
			= new(std::nothrow) BPackageResolvableExpression(name,
				(BPackageResolvableOperator)operatorType, version);
		if (expression == NULL || !_expressions.AddItem(expression)) {
			delete expression;
			return B_NO_MEMORY;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_ExtractGlobalWritableFileInfos(BMessage* archive,
	const char* field, GlobalWritableFileInfoList& _infos)
{
	// construct the field names we need
	FieldName pathField(field, ":path");
	FieldName updateTypeField(field, ":updateType");
	FieldName isDirectoryField(field, ":isDirectory");

	if (!pathField.IsValid() || !updateTypeField.IsValid()
		|| !isDirectoryField.IsValid()) {
		return B_BAD_VALUE;
	}

	// get the number of items
	type_code type;
	int32 count;
	if (archive->GetInfo(pathField, &type, &count) != B_OK) {
		// the field is missing
		return B_OK;
	}

	// extract fields
	for (int32 i = 0; i < count; i++) {
		BString path;
		status_t error = archive->FindString(pathField, i, &path);
		if (error != B_OK)
			return error;

		int32 updateType;
		error = archive->FindInt32(updateTypeField, i, &updateType);
		if (error != B_OK)
			return error;
		if (updateType < 0
			|| updateType > B_WRITABLE_FILE_UPDATE_TYPE_ENUM_COUNT) {
			return B_BAD_DATA;
		}

		bool isDirectory;
		error = archive->FindBool(isDirectoryField, i, &isDirectory);
		if (error != B_OK)
			return error;

		BGlobalWritableFileInfo* info
			= new(std::nothrow) BGlobalWritableFileInfo(path,
				(BWritableFileUpdateType)updateType, isDirectory);
		if (info == NULL || !_infos.AddItem(info)) {
			delete info;
			return B_NO_MEMORY;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_ExtractUserSettingsFileInfos(BMessage* archive,
	const char* field, UserSettingsFileInfoList& _infos)
{
	// construct the field names we need
	FieldName pathField(field, ":path");
	FieldName templatePathField(field, ":templatePath");
	FieldName isDirectoryField(field, ":isDirectory");

	if (!pathField.IsValid() || !templatePathField.IsValid()
		|| !isDirectoryField.IsValid()) {
		return B_BAD_VALUE;
	}

	// get the number of items
	type_code type;
	int32 count;
	if (archive->GetInfo(pathField, &type, &count) != B_OK) {
		// the field is missing
		return B_OK;
	}

	// extract fields
	for (int32 i = 0; i < count; i++) {
		BString path;
		status_t error = archive->FindString(pathField, i, &path);
		if (error != B_OK)
			return error;

		BString templatePath;
		error = archive->FindString(templatePathField, i, &templatePath);
		if (error != B_OK)
			return error;

		bool isDirectory;
		error = archive->FindBool(isDirectoryField, i, &isDirectory);
		if (error != B_OK)
			return error;

		BUserSettingsFileInfo* info = isDirectory
			? new(std::nothrow) BUserSettingsFileInfo(path, true)
			: new(std::nothrow) BUserSettingsFileInfo(path, templatePath);
		if (info == NULL || !_infos.AddItem(info)) {
			delete info;
			return B_NO_MEMORY;
		}
	}

	return B_OK;
}


/*static*/ status_t
BPackageInfo::_ExtractUsers(BMessage* archive, const char* field,
	UserList& _users)
{
	// construct the field names we need
	FieldName nameField(field, ":name");
	FieldName realNameField(field, ":realName");
	FieldName homeField(field, ":home");
	FieldName shellField(field, ":shell");
	FieldName groupsField(field, ":groups");

	if (!nameField.IsValid() || !realNameField.IsValid() || !homeField.IsValid()
		|| !shellField.IsValid() || !groupsField.IsValid())
		return B_BAD_VALUE;

	// get the number of items
	type_code type;
	int32 count;
	if (archive->GetInfo(nameField, &type, &count) != B_OK) {
		// the field is missing
		return B_OK;
	}

	// extract fields
	for (int32 i = 0; i < count; i++) {
		BString name;
		status_t error = archive->FindString(nameField, i, &name);
		if (error != B_OK)
			return error;

		BString realName;
		error = archive->FindString(realNameField, i, &realName);
		if (error != B_OK)
			return error;

		BString home;
		error = archive->FindString(homeField, i, &home);
		if (error != B_OK)
			return error;

		BString shell;
		error = archive->FindString(shellField, i, &shell);
		if (error != B_OK)
			return error;

		BString groupsString;
		error = archive->FindString(groupsField, i, &groupsString);
		if (error != B_OK)
			return error;

		BStringList groups;
		if (!groupsString.IsEmpty() && !groupsString.Split(" ", false, groups))
			return B_NO_MEMORY;

		BUser* user = new(std::nothrow) BUser(name, realName, home, shell,
			groups);
		if (user == NULL || !_users.AddItem(user)) {
			delete user;
			return B_NO_MEMORY;
		}
	}

	return B_OK;
}


}	// namespace BPackageKit