⛏️ index : haiku.git

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


#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <map>

#include <Entry.h>
#include <ObjectList.h>
#include <Path.h>
#include <String.h>

#include <package/hpkg/HPKGDefs.h>
#include <package/hpkg/PackageInfoAttributeValue.h>
#include <package/hpkg/RepositoryContentHandler.h>
#include <package/hpkg/RepositoryReader.h>
#include <package/hpkg/RepositoryWriter.h>
#include <package/hpkg/StandardErrorOutput.h>
#include <package/PackageInfo.h>
#include <package/PackageInfoContentHandler.h>
#include <package/RepositoryInfo.h>

#include "package_repo.h"


using BPackageKit::BHPKG::BRepositoryWriterListener;
using BPackageKit::BHPKG::BRepositoryWriter;
using namespace BPackageKit::BHPKG;
using namespace BPackageKit;


static bool sTrustFilenames = false;


bool operator< (const BPackageInfo & left, const BPackageInfo & right)
{
	if (sTrustFilenames)
		return left.FileName().Compare(right.FileName()) < 0;

	if (left.Name() != right.Name())
		return left.Name() < right.Name();

	return left.Version().Compare(right.Version()) < 0;
}


namespace
{


typedef std::map<BPackageInfo, bool> PackageInfos;


static status_t
parsePackageListFile(const char* packageListFileName,
	BObjectList<BString, true>* packageFileNames)
{
	FILE* packageListFile = fopen(packageListFileName, "r");
	if (packageListFile == NULL) {
		printf("Error: Unable to open %s\n", packageListFileName);
		return B_ENTRY_NOT_FOUND;
	}
	char buffer[128];
	while (fgets(buffer, sizeof(buffer), packageListFile) != NULL) {
		BString* packageFileName = new(std::nothrow) BString(buffer);
		if (packageFileName == NULL) {
			printf("Error: Out of memory when reading from %s\n",
				packageListFileName);
			fclose(packageListFile);
			return B_NO_MEMORY;
		}
		packageFileName->Trim();
		packageFileNames->AddItem(packageFileName);
	}
	fclose(packageListFile);
	return B_OK;
}


struct PackageInfosCollector : BRepositoryContentHandler {
	PackageInfosCollector(PackageInfos& packageInfos,
		BHPKG::BErrorOutput* errorOutput)
		:
		fPackageInfos(packageInfos),
		fErrorOutput(errorOutput),
		fRepositoryInfo(),
		fPackageInfo(),
		fPackageInfoContentHandler(fPackageInfo, fErrorOutput)
	{
	}

	virtual status_t HandlePackage(const char* packageName)
	{
		fPackageInfo.Clear();
		return B_OK;
	}

	virtual status_t HandlePackageAttribute(
		const BPackageInfoAttributeValue& value)
	{
		return fPackageInfoContentHandler.HandlePackageAttribute(value);
	}

	virtual status_t HandlePackageDone(const char* packageName)
	{
		if (fPackageInfo.InitCheck() != B_OK) {
			const BString& name = fPackageInfo.Name();
			printf("Error: package-info in repository for '%s' is incomplete\n",
				name.IsEmpty() ? "<unknown-package>" : name.String());
			return B_BAD_DATA;
		}

		if (sTrustFilenames) {
			// Cache the canonical name to avoid repeated fallbacks
			fPackageInfo.SetFileName(fPackageInfo.CanonicalFileName());
		}

		fPackageInfos[fPackageInfo] = false;
		return B_OK;
	}

	virtual status_t HandleRepositoryInfo(const BRepositoryInfo& repositoryInfo)
	{
		fRepositoryInfo = repositoryInfo;
		return B_OK;
	}

	virtual void HandleErrorOccurred()
	{
	}

	const BRepositoryInfo& RepositoryInfo() const
	{
		return fRepositoryInfo;
	}

private:
	PackageInfos& fPackageInfos;
	BHPKG::BErrorOutput* fErrorOutput;
	BRepositoryInfo fRepositoryInfo;
	BPackageInfo fPackageInfo;
	BPackageInfoContentHandler fPackageInfoContentHandler;
};


class RepositoryWriterListener	: public BRepositoryWriterListener {
public:
	RepositoryWriterListener(bool verbose, bool quiet)
		: fVerbose(verbose), fQuiet(quiet)
	{
	}

	virtual void PrintErrorVarArgs(const char* format, va_list args)
	{
		vfprintf(stderr, format, args);
	}

	virtual void OnPackageAdded(const BPackageInfo& packageInfo)
	{
	}

	virtual void OnRepositoryInfoSectionDone(uint32 uncompressedSize)
	{
		if (fQuiet || !fVerbose)
			return;

		printf("----- Repository Info Section --------------------\n");
		printf("repository info size:    %10" B_PRIu32 " (uncompressed)\n",
			uncompressedSize);
	}

	virtual void OnPackageAttributesSectionDone(uint32 stringCount,
		uint32 uncompressedSize)
	{
		if (fQuiet || !fVerbose)
			return;

		printf("----- Package Attribute Section -------------------\n");
		printf("string count:            %10" B_PRIu32 "\n", stringCount);
		printf("package attributes size: %10" B_PRIu32 " (uncompressed)\n",
			uncompressedSize);
	}

	virtual void OnRepositoryDone(uint32 headerSize, uint32 repositoryInfoSize,
		uint32 licenseCount, uint32 packageCount, uint32 packageAttributesSize,
		uint64 totalSize)
	{
		if (fQuiet || !fVerbose)
			return;

		printf("----- Package Repository Info -----\n");
		if (fVerbose)
			printf("embedded license count   %10" B_PRIu32 "\n", licenseCount);
		printf("package count            %10" B_PRIu32 "\n", packageCount);
		printf("-----------------------------------\n");
		printf("header size:             %10" B_PRIu32 "\n", headerSize);
		printf("repository header size:  %10" B_PRIu32 "\n",
			repositoryInfoSize);
		printf("package attributes size: %10" B_PRIu32 "\n",
			packageAttributesSize);
		printf("total size:              %10" B_PRIu64 "\n", totalSize);
		printf("-----------------------------------\n");
	}

private:
	bool fVerbose;
	bool fQuiet;
};


}	// anonymous namespace


int
command_update(int argc, const char* const* argv)
{
	const char* changeToDirectory = NULL;
	bool quiet = false;
	bool verbose = false;

	while (true) {
		static struct option sLongOptions[] = {
			{ "help", no_argument, 0, 'h' },
			{ "quiet", no_argument, 0, 'q' },
			{ "verbose", no_argument, 0, 'v' },
			{ "trust-filenames", no_argument, 0, 't' },
			{ 0, 0, 0, 0 }
		};

		opterr = 0; // don't print errors
		int c = getopt_long(argc, (char**)argv, "+C:hqvt", sLongOptions, NULL);
		if (c == -1)
			break;

		switch (c) {
			case 'C':
				changeToDirectory = optarg;
				break;

			case 'h':
				print_usage_and_exit(false);
				break;

			case 'q':
				quiet = true;
				break;

			case 'v':
				verbose = true;
				break;

			case 't':
				sTrustFilenames = true;
				break;

			default:
				print_usage_and_exit(true);
				break;
		}
	}

	// The remaining three arguments are the source and target repository file
	// plus the package list file.
	if (optind + 3 != argc)
		print_usage_and_exit(true);

	const char* sourceRepositoryFileName = argv[optind++];
	const char* targetRepositoryFileName = argv[optind++];
	const char* packageListFileName = argv[optind++];

	BStandardErrorOutput errorOutput;
	RepositoryWriterListener listener(verbose, quiet);

	BEntry sourceRepositoryEntry(sourceRepositoryFileName);
	if (!sourceRepositoryEntry.Exists()) {
		listener.PrintError(
			"Error: given source repository file '%s' doesn't exist!\n",
			sourceRepositoryFileName);
		return 1;
	}
	// determine path for 'repo.info' file from given new repository file
	BString repositoryInfoFileName(targetRepositoryFileName);
	repositoryInfoFileName.Append(".info");
	BEntry repositoryInfoEntry(repositoryInfoFileName.String());
	BRepositoryInfo repositoryInfo(repositoryInfoEntry);
	status_t result = repositoryInfo.InitCheck();
	if (result != B_OK) {
		listener.PrintError(
			"Error: can't parse/read repository-info file %s : %s\n",
			repositoryInfoFileName.String(), strerror(result));
		return 1;
	}

	// open source repository
	BRepositoryReader repositoryReader(&errorOutput);
	result = repositoryReader.Init(sourceRepositoryFileName);
	if (result != B_OK) {
		listener.PrintError(
			"Error: can't read from old repository file : %s\n",
			strerror(result));
		return 1;
	}

	// collect package infos from source repository
	PackageInfos packageInfos;
	PackageInfosCollector packageInfosCollector(packageInfos, &errorOutput);
	result = repositoryReader.ParseContent(&packageInfosCollector);
	if (result != B_OK) {
		listener.PrintError(
			"Error: couldn't fetch package infos from old repository : %s\n",
			strerror(result));
		return 1;
	}

	// create new repository
	BRepositoryWriter repositoryWriter(&listener, &repositoryInfo);
	BString tempRepositoryFileName(targetRepositoryFileName);
	tempRepositoryFileName += ".___new___";
	if ((result = repositoryWriter.Init(tempRepositoryFileName.String()))
			!= B_OK) {
		listener.PrintError("Error: can't initialize repository-writer : %s\n",
			strerror(result));
		return 1;
	}

	BEntry tempRepositoryFile(tempRepositoryFileName.String());
	BPath targetRepositoryFilePath(targetRepositoryFileName);

	BObjectList<BString, true> packageNames(100);
	if ((result = parsePackageListFile(packageListFileName, &packageNames))
			!= B_OK) {
		listener.PrintError(
			"Error: Failed to read package-list-file \"%s\": %s\n",
			packageListFileName, strerror(result));
		return 1;
	}

	// change directory, if requested
	if (changeToDirectory != NULL) {
		if (chdir(changeToDirectory) != 0) {
			listener.PrintError(
				"Error: Failed to change the current working directory to "
				"\"%s\": %s\n", changeToDirectory, strerror(errno));
			return 1;
		}
	}

	// add all given package files
	for (int i = 0; i < packageNames.CountItems(); ++i) {
		BPackageInfo packageInfo;

		if (sTrustFilenames)
			packageInfo.SetFileName(*packageNames.ItemAt(i));
		else {
			if ((result = packageInfo.ReadFromPackageFile(
					packageNames.ItemAt(i)->String())) != B_OK) {
				listener.PrintError(
					"Error: Failed to read package-info from \"%s\": %s\n",
					packageNames.ItemAt(i)->String(), strerror(result));
				return 1;
			}
		}

		PackageInfos::iterator infoIter = packageInfos.find(packageInfo);
		if (infoIter != packageInfos.end()) {
			infoIter->second = true;
			if ((result = repositoryWriter.AddPackageInfo(infoIter->first))
					!= B_OK)
				return 1;
			if (verbose) {
				printf("keeping '%s-%s'\n", infoIter->first.Name().String(),
					infoIter->first.Version().ToString().String());
			}
		} else {
			BEntry entry(packageNames.ItemAt(i)->String());
			if ((result = repositoryWriter.AddPackage(entry)) != B_OK)
				return 1;
			if (!quiet) {
				printf("added '%s' ...\n",
					packageNames.ItemAt(i)->String());
			}
		}
	}

	// tell about packages dropped from repository
	PackageInfos::const_iterator infoIter;
	for (infoIter = packageInfos.begin(); infoIter != packageInfos.end();
			++infoIter) {
		if (!infoIter->second) {
			printf("dropped '%s-%s'\n", infoIter->first.Name().String(),
				infoIter->first.Version().ToString().String());
		}
	}


	// write the repository
	result = repositoryWriter.Finish();
	if (result != B_OK)
		return 1;

	result = tempRepositoryFile.Rename(targetRepositoryFilePath.Leaf(), true);
	if (result != B_OK) {
		printf("Error: unable to rename repository %s to %s - %s\n",
			tempRepositoryFileName.String(), targetRepositoryFileName,
			strerror(result));
		return 1;
	}

	if (verbose) {
		printf("\nsuccessfully created repository '%s'\n",
			targetRepositoryFileName);
	}

	return 0;
}