⛏️ index : haiku.git

/*
 * Copyright 2013-2021, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Ingo Weinhold <ingo_weinhold@gmx.de>
 *		Andrew Lindesay <apl@lindesay.co.nz>
 */


#include "Volume.h"

#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <Looper.h>
#include <MessageRunner.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <Roster.h>
#include <SymLink.h>

#include <package/CommitTransactionResult.h>
#include <package/PackageRoster.h>
#include <package/solver/Solver.h>
#include <package/solver/SolverPackage.h>
#include <package/solver/SolverProblem.h>
#include <package/solver/SolverProblemSolution.h>
#include <package/solver/SolverRepository.h>
#include <package/solver/SolverResult.h>

#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <NotOwningEntryRef.h>
#include <package/DaemonDefs.h>
#include <RosterPrivate.h>

#include "CommitTransactionHandler.h"
#include "Constants.h"
#include "DebugSupport.h"
#include "Exception.h"
#include "PackageFileManager.h"
#include "Root.h"
#include "VolumeState.h"


using namespace BPackageKit::BPrivate;


// #pragma mark - Listener


Volume::Listener::~Listener()
{
}


// #pragma mark - NodeMonitorEvent


struct Volume::NodeMonitorEvent
	: public DoublyLinkedListLinkImpl<NodeMonitorEvent> {
public:
	NodeMonitorEvent(const BString& entryName, bool created)
		:
		fEntryName(entryName),
		fCreated(created)
	{
	}

	const BString& EntryName() const
	{
		return fEntryName;
	}

	bool WasCreated() const
	{
		return fCreated;
	}

private:
	BString	fEntryName;
	bool	fCreated;
};


// #pragma mark - PackagesDirectory


struct Volume::PackagesDirectory {
public:
	PackagesDirectory()
		:
		fNodeRef(),
		fName()
	{
	}

	void Init(const node_ref& nodeRef, bool isPackagesDir)
	{
		fNodeRef = nodeRef;

		if (isPackagesDir)
			return;

		BDirectory directory;
		BEntry entry;
		if (directory.SetTo(&fNodeRef) == B_OK
			&& directory.GetEntry(&entry) == B_OK) {
			fName = entry.Name();
		}

		if (fName.IsEmpty())
			fName = "unknown state";
	}

	const node_ref& NodeRef() const
	{
		return fNodeRef;
	}

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

private:
	node_ref	fNodeRef;
	BString		fName;
};


// #pragma mark - Volume


Volume::Volume(BLooper* looper)
	:
	BHandler(),
	fPath(),
	fMountType(PACKAGE_FS_MOUNT_TYPE_CUSTOM),
	fRootDirectoryRef(),
	fPackagesDirectories(NULL),
	fPackagesDirectoryCount(0),
	fRoot(NULL),
	fListener(NULL),
	fPackageFileManager(NULL),
	fLatestState(NULL),
	fActiveState(NULL),
	fChangeCount(0),
	fLock("volume"),
	fPendingNodeMonitorEventsLock("pending node monitor events"),
	fPendingNodeMonitorEvents(),
	fNodeMonitorEventHandleTime(0),
	fPackagesToBeActivated(),
	fPackagesToBeDeactivated(),
	fLocationInfoReply(B_MESSAGE_GET_INSTALLATION_LOCATION_INFO_REPLY),
	fPendingPackageJobCount(0)
{
	looper->AddHandler(this);
}


Volume::~Volume()
{
	Unmounted();
		// needed for error case in InitPackages()

	_SetLatestState(NULL, true);

	delete[] fPackagesDirectories;
	delete fPackageFileManager;
}


status_t
Volume::Init(const node_ref& rootDirectoryRef, node_ref& _packageRootRef)
{
	status_t error = fLock.InitCheck();
	if (error != B_OK)
		return error;

	error = fPendingNodeMonitorEventsLock.InitCheck();
	if (error != B_OK)
		return error;

	fLatestState = new(std::nothrow) VolumeState;
	if (fLatestState == NULL || !fLatestState->Init())
		RETURN_ERROR(B_NO_MEMORY);

	fPackageFileManager = new(std::nothrow) PackageFileManager(fLock);
	if (fPackageFileManager == NULL)
		RETURN_ERROR(B_NO_MEMORY);

	error = fPackageFileManager->Init();
	if (error != B_OK)
		RETURN_ERROR(error);

	fRootDirectoryRef = rootDirectoryRef;

	// open the root directory
	BDirectory directory;
	error = directory.SetTo(&fRootDirectoryRef);
	if (error != B_OK) {
		ERROR("Volume::Init(): failed to open root directory: %s\n",
			strerror(error));
		RETURN_ERROR(error);
	}

	// get the directory path
	BEntry entry;
	error = directory.GetEntry(&entry);

	BPath path;
	if (error == B_OK)
		error = entry.GetPath(&path);

	if (error != B_OK) {
		ERROR("Volume::Init(): failed to get root directory path: %s\n",
			strerror(error));
		RETURN_ERROR(error);
	}

	fPath = path.Path();
	if (fPath.IsEmpty())
		RETURN_ERROR(B_NO_MEMORY);

	// get a volume info from the FS
	FileDescriptorCloser fd(directory.Dup());
	if (!fd.IsSet()) {
		ERROR("Volume::Init(): failed to get root directory FD: %s\n",
			strerror(fd.Get()));
		RETURN_ERROR(fd.Get());
	}

	// get the volume info from packagefs
	uint32 maxPackagesDirCount = 16;
	PackageFSVolumeInfo* info = NULL;
	MemoryDeleter infoDeleter;
	size_t bufferSize;
	for (;;) {
		bufferSize = sizeof(PackageFSVolumeInfo)
			+ (maxPackagesDirCount - 1) * sizeof(PackageFSDirectoryInfo);
		info = (PackageFSVolumeInfo*)malloc(bufferSize);
		if (info == NULL)
			RETURN_ERROR(B_NO_MEMORY);
		infoDeleter.SetTo(info);

		if (ioctl(fd.Get(), PACKAGE_FS_OPERATION_GET_VOLUME_INFO, info,
				bufferSize) != 0) {
			ERROR("Volume::Init(): failed to get volume info: %s\n",
				strerror(errno));
			RETURN_ERROR(errno);
		}

		if (info->packagesDirectoryCount <= maxPackagesDirCount)
			break;

		maxPackagesDirCount = info->packagesDirectoryCount;
		infoDeleter.Unset();
	}

	if (info->packagesDirectoryCount < 1) {
		ERROR("Volume::Init(): got invalid volume info from packagefs\n");
		RETURN_ERROR(B_BAD_VALUE);
	}

	fMountType = info->mountType;

	fPackagesDirectories = new(std::nothrow) PackagesDirectory[
		info->packagesDirectoryCount];
	if (fPackagesDirectories == NULL)
		RETURN_ERROR(B_NO_MEMORY);

	fPackagesDirectoryCount = info->packagesDirectoryCount;

	for (uint32 i = 0; i < info->packagesDirectoryCount; i++) {
		fPackagesDirectories[i].Init(
			node_ref(info->packagesDirectoryInfos[i].deviceID,
				info->packagesDirectoryInfos[i].nodeID),
			i == 0);
	}

	_packageRootRef.device = info->rootDeviceID;
	_packageRootRef.node = info->rootDirectoryID;

	return B_OK;
}


status_t
Volume::InitPackages(Listener* listener)
{
	// node-monitor the volume's packages directory
	status_t error = watch_node(&PackagesDirectoryRef(), B_WATCH_DIRECTORY,
		BMessenger(this));
	if (error == B_OK) {
		fListener = listener;
	} else {
		ERROR("Volume::InitPackages(): failed to start watching the packages "
			"directory of the volume at \"%s\": %s\n",
			fPath.String(), strerror(error));
		// Not good, but not fatal. Only the manual package operations in the
		// packages directory won't work correctly.
	}

	// read the packages directory and get the active packages
	FileDescriptorCloser fd(OpenRootDirectory());
	if (!fd.IsSet()) {
		ERROR("Volume::InitPackages(): failed to open root directory: %s\n",
			strerror(fd.Get()));
		RETURN_ERROR(fd.Get());
	}

	error = _ReadPackagesDirectory();
	if (error != B_OK)
		RETURN_ERROR(error);

	error = _InitLatestState();
	if (error != B_OK)
		RETURN_ERROR(error);

	error = _GetActivePackages(fd.Get());
	if (error != B_OK)
		RETURN_ERROR(error);

	// create the admin directory, if it doesn't exist yet
	BDirectory packagesDirectory;
	bool createdAdminDirectory = false;
	if (packagesDirectory.SetTo(&PackagesDirectoryRef()) == B_OK) {
		if (!BEntry(&packagesDirectory, kAdminDirectoryName).Exists()) {
			packagesDirectory.CreateDirectory(kAdminDirectoryName, NULL);
			createdAdminDirectory = true;
		}
	}
	BDirectory adminDirectory(&packagesDirectory, kAdminDirectoryName);
	error = adminDirectory.InitCheck();
	if (error != B_OK)
		RETURN_ERROR(error);

	// First boot processing requested by a magic file left by the OS installer?
	BEntry flagFileEntry(&adminDirectory, kFirstBootProcessingNeededFileName);
	if (createdAdminDirectory || flagFileEntry.Exists()) {
		INFORM("Volume::InitPackages Requesting delayed first boot processing "
			"for packages dir %s.\n", BPath(&packagesDirectory).Path());
		if (flagFileEntry.Exists())
			flagFileEntry.Remove(); // Remove early on to avoid an error loop.

		// Are there any packages needing processing?  Don't want to create an
		// empty transaction directory and then never have it cleaned up when
		// the empty transaction gets rejected.
		bool anyPackages = false;
		for (PackageNodeRefHashTable::Iterator it =
				fActiveState->ByNodeRefIterator(); it.HasNext();) {
			Package* package = it.Next();
			if (package->IsActive()) {
				anyPackages = true;
				break;
			}
		}

		if (anyPackages) {
			// Create first boot processing special transaction for current
			// volume, which also creates an empty transaction directory.
			BPackageInstallationLocation location = Location();
			BDirectory transactionDirectory;
			BActivationTransaction transaction;
			error = CreateTransaction(location, transaction,
				transactionDirectory);
			if (error != B_OK)
				RETURN_ERROR(error);

			// Add all package files in currently active state to transaction.
			for (PackageNodeRefHashTable::Iterator it =
					fActiveState->ByNodeRefIterator(); it.HasNext();) {
				Package* package = it.Next();
				if (package->IsActive()) {
					if (!transaction.AddPackageToActivate(
							package->FileName().String()))
						RETURN_ERROR(B_NO_MEMORY);
				}
			}
			transaction.SetFirstBootProcessing(true);

			// Queue up the transaction as a BMessage for processing a bit
			// later, once the package daemon has finished initialising.
			BMessage commitMessage(B_MESSAGE_COMMIT_TRANSACTION);
			error = transaction.Archive(&commitMessage);
			if (error != B_OK)
				RETURN_ERROR(error);
			BLooper *myLooper = Looper() ;
			if (myLooper == NULL)
				RETURN_ERROR(B_NOT_INITIALIZED);
			error = myLooper->PostMessage(&commitMessage);
			if (error != B_OK)
				RETURN_ERROR(error);
		}
	}

	return B_OK;
}


status_t
Volume::AddPackagesToRepository(BSolverRepository& repository, bool activeOnly)
{
	for (PackageFileNameHashTable::Iterator it
			= fLatestState->ByFileNameIterator(); it.HasNext();) {
		Package* package = it.Next();
		if (activeOnly && !package->IsActive())
			continue;

		status_t error = repository.AddPackage(package->Info());
		if (error != B_OK) {
			ERROR("Volume::AddPackagesToRepository(): failed to add package %s "
				"to repository: %s\n", package->FileName().String(),
				strerror(error));
			return error;
		}
	}

	return B_OK;
}


void
Volume::InitialVerify(Volume* nextVolume, Volume* nextNextVolume)
{
INFORM("Volume::InitialVerify(%p, %p)\n", nextVolume, nextNextVolume);
	// create the solver
	BSolver* solver;
	status_t error = BSolver::Create(solver);
	if (error != B_OK) {
		ERROR("Volume::InitialVerify(): failed to create solver: %s\n",
			strerror(error));
		return;
	}
	ObjectDeleter<BSolver> solverDeleter(solver);

	// add a repository with all active packages
	BSolverRepository repository;
	error = _AddRepository(solver, repository, true, true);
	if (error != B_OK) {
		ERROR("Volume::InitialVerify(): failed to add repository: %s\n",
			strerror(error));
		return;
	}

	// add a repository for the next volume
	BSolverRepository nextRepository;
	if (nextVolume != NULL) {
		nextRepository.SetPriority(1);
		error = nextVolume->_AddRepository(solver, nextRepository, true, false);
		if (error != B_OK) {
			ERROR("Volume::InitialVerify(): failed to add repository: %s\n",
				strerror(error));
			return;
		}
	}

	// add a repository for the next next volume
	BSolverRepository nextNextRepository;
	if (nextNextVolume != NULL) {
		nextNextRepository.SetPriority(2);
		error = nextNextVolume->_AddRepository(solver, nextNextRepository, true,
			false);
		if (error != B_OK) {
			ERROR("Volume::InitialVerify(): failed to add repository: %s\n",
				strerror(error));
			return;
		}
	}

	// verify
	error = solver->VerifyInstallation();
	if (error != B_OK) {
		ERROR("Volume::InitialVerify(): failed to verify: %s\n",
			strerror(error));
		return;
	}

	if (!solver->HasProblems()) {
		INFORM("Volume::InitialVerify(): volume at \"%s\" is consistent\n",
			Path().String());
		return;
	}

	// print the problems
// TODO: Notify the user ...
	INFORM("Volume::InitialVerify(): volume at \"%s\" has problems:\n",
		Path().String());

	int32 problemCount = solver->CountProblems();
	for (int32 i = 0; i < problemCount; i++) {
		BSolverProblem* problem = solver->ProblemAt(i);
		INFORM("  %" B_PRId32 ": %s\n", i + 1, problem->ToString().String());
		int32 solutionCount = problem->CountSolutions();
		for (int32 k = 0; k < solutionCount; k++) {
			const BSolverProblemSolution* solution = problem->SolutionAt(k);
			INFORM("    solution %" B_PRId32 ":\n", k + 1);
			int32 elementCount = solution->CountElements();
			for (int32 l = 0; l < elementCount; l++) {
				const BSolverProblemSolutionElement* element
					= solution->ElementAt(l);
				INFORM("      - %s\n", element->ToString().String());
			}
		}
	}
}


void
Volume::HandleGetLocationInfoRequest(BMessage* message)
{
	AutoLocker<BLocker> locker(fLock);

	// If the cached reply message is up-to-date, just send it.
	int64 changeCount;
	if (fLocationInfoReply.FindInt64("change count", &changeCount) == B_OK
		&& changeCount == fChangeCount) {
		locker.Unlock();
		message->SendReply(&fLocationInfoReply, (BHandler*)NULL,
			kCommunicationTimeout);
		return;
	}

	// rebuild the reply message
	fLocationInfoReply.MakeEmpty();

	if (fLocationInfoReply.AddInt32("base directory device",
			fRootDirectoryRef.device) != B_OK
		|| fLocationInfoReply.AddInt64("base directory node",
			fRootDirectoryRef.node) != B_OK
		|| fLocationInfoReply.AddInt32("packages directory device",
			PackagesDeviceID()) != B_OK
		|| fLocationInfoReply.AddInt64("packages directory node",
			PackagesDirectoryID()) != B_OK) {
		return;
	}

	for (PackageFileNameHashTable::Iterator it
			= fLatestState->ByFileNameIterator(); it.HasNext();) {
		Package* package = it.Next();
		const char* fieldName = package->IsActive()
			? "latest active packages" : "latest inactive packages";
		BMessage packageArchive;
		if (package->Info().Archive(&packageArchive) != B_OK
			|| fLocationInfoReply.AddMessage(fieldName, &packageArchive)
				!= B_OK) {
			return;
		}
	}

	if (fActiveState != fLatestState) {
		if (fPackagesDirectoryCount > 1) {
			fLocationInfoReply.AddString("old state",
				fPackagesDirectories[fPackagesDirectoryCount - 1].Name());
		}

		for (PackageFileNameHashTable::Iterator it
				= fActiveState->ByFileNameIterator(); it.HasNext();) {
			Package* package = it.Next();
			if (!package->IsActive())
				continue;

			BMessage packageArchive;
			if (package->Info().Archive(&packageArchive) != B_OK
				|| fLocationInfoReply.AddMessage("currently active packages",
					&packageArchive) != B_OK) {
				return;
			}
		}
	}

	if (fLocationInfoReply.AddInt64("change count", fChangeCount) != B_OK)
		return;

	locker.Unlock();

	message->SendReply(&fLocationInfoReply, (BHandler*)NULL,
		kCommunicationTimeout);
}


void
Volume::HandleCommitTransactionRequest(BMessage* message)
{
	BCommitTransactionResult result;
	PackageSet dummy;
	_CommitTransaction(message, NULL, dummy, dummy, result);

	BMessage reply(B_MESSAGE_COMMIT_TRANSACTION_REPLY);
	status_t error = result.AddToMessage(reply);
	if (error != B_OK) {
		ERROR("Volume::HandleCommitTransactionRequest(): Failed to add "
			"transaction result to reply: %s\n", strerror(error));
		return;
	}

	message->SendReply(&reply, (BHandler*)NULL, kCommunicationTimeout);
}


void
Volume::PackageJobPending()
{
	atomic_add(&fPendingPackageJobCount, 1);
}


void
Volume::PackageJobFinished()
{
	atomic_add(&fPendingPackageJobCount, -1);
}


bool
Volume::IsPackageJobPending() const
{
	return fPendingPackageJobCount != 0;
}


void
Volume::Unmounted()
{
	if (fListener != NULL) {
		stop_watching(BMessenger(this));
		fListener = NULL;
	}

	if (BLooper* looper = Looper())
		looper->RemoveHandler(this);
}


void
Volume::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case B_NODE_MONITOR:
		{
			int32 opcode;
			if (message->FindInt32("opcode", &opcode) != B_OK)
				break;

			switch (opcode) {
				case B_ENTRY_CREATED:
					_HandleEntryCreatedOrRemoved(message, true);
					break;
				case B_ENTRY_REMOVED:
					_HandleEntryCreatedOrRemoved(message, false);
					break;
				case B_ENTRY_MOVED:
					_HandleEntryMoved(message);
					break;
				default:
					break;
			}
			break;
		}

		case kHandleNodeMonitorEvents:
			if (fListener != NULL) {
				if (system_time() >= fNodeMonitorEventHandleTime)
					fListener->VolumeNodeMonitorEventOccurred(this);
			}
			break;

		default:
			BHandler::MessageReceived(message);
			break;
	}
}


BPackageInstallationLocation
Volume::Location() const
{
	switch (fMountType) {
		case PACKAGE_FS_MOUNT_TYPE_SYSTEM:
			return B_PACKAGE_INSTALLATION_LOCATION_SYSTEM;
		case PACKAGE_FS_MOUNT_TYPE_HOME:
			return B_PACKAGE_INSTALLATION_LOCATION_HOME;
		case PACKAGE_FS_MOUNT_TYPE_CUSTOM:
		default:
			return B_PACKAGE_INSTALLATION_LOCATION_ENUM_COUNT;
	}
}


const node_ref&
Volume::PackagesDirectoryRef() const
{
	return fPackagesDirectories[0].NodeRef();
}


PackageFileNameHashTable::Iterator
Volume::PackagesByFileNameIterator() const
{
	return fLatestState->ByFileNameIterator();
}


int
Volume::OpenRootDirectory() const
{
	BDirectory directory;
	status_t error = directory.SetTo(&fRootDirectoryRef);
	if (error != B_OK) {
		ERROR("Volume::OpenRootDirectory(): failed to open root directory: "
			"%s\n", strerror(error));
		RETURN_ERROR(error);
	}

	return directory.Dup();
}


void
Volume::ProcessPendingNodeMonitorEvents()
{
	// get the events
	NodeMonitorEventList events;
	{
		AutoLocker<BLocker> eventsLock(fPendingNodeMonitorEventsLock);
		events.TakeFrom(&fPendingNodeMonitorEvents);
	}

	// process them
	while (NodeMonitorEvent* event = events.RemoveHead()) {
		ObjectDeleter<NodeMonitorEvent> eventDeleter(event);
		if (event->WasCreated())
			_PackagesEntryCreated(event->EntryName());
		else
			_PackagesEntryRemoved(event->EntryName());
	}
}


bool
Volume::HasPendingPackageActivationChanges() const
{
	return !fPackagesToBeActivated.empty() || !fPackagesToBeDeactivated.empty();
}


void
Volume::ProcessPendingPackageActivationChanges()
{
	if (!HasPendingPackageActivationChanges())
		return;

	// perform the request
	BCommitTransactionResult result;
	_CommitTransaction(NULL, NULL, fPackagesToBeActivated,
		fPackagesToBeDeactivated, result);

	if (result.Error() != B_TRANSACTION_OK) {
		ERROR("Volume::ProcessPendingPackageActivationChanges(): package "
			"activation failed: %s\n", result.FullErrorMessage().String());
// TODO: Notify the user!
	}

	// clear the activation/deactivation sets in any event
	fPackagesToBeActivated.clear();
	fPackagesToBeDeactivated.clear();
}


void
Volume::ClearPackageActivationChanges()
{
	fPackagesToBeActivated.clear();
	fPackagesToBeDeactivated.clear();
}


status_t
Volume::CreateTransaction(BPackageInstallationLocation location,
	BActivationTransaction& _transaction, BDirectory& _transactionDirectory)
{
	// open admin directory
	BDirectory adminDirectory;
	status_t error = _OpenPackagesSubDirectory(
		RelativePath(kAdminDirectoryName), true, adminDirectory);
	if (error != B_OK)
		return error;

	// create a transaction directory
	int uniqueId = 1;
	BString directoryName;
	for (;; uniqueId++) {
		directoryName.SetToFormat("transaction-%d", uniqueId);
		if (directoryName.IsEmpty())
			return B_NO_MEMORY;

		error = adminDirectory.CreateDirectory(directoryName,
			&_transactionDirectory);
		if (error == B_OK)
			break;
		if (error != B_FILE_EXISTS)
			return error;
	}

	// init the transaction
	error = _transaction.SetTo(location, fChangeCount, directoryName);
	if (error != B_OK) {
		BEntry entry;
		_transactionDirectory.GetEntry(&entry);
		_transactionDirectory.Unset();
		if (entry.InitCheck() == B_OK)
			entry.Remove();
		return error;
	}

	return B_OK;
}


void
Volume::CommitTransaction(const BActivationTransaction& transaction,
	const PackageSet& packagesAlreadyAdded,
	const PackageSet& packagesAlreadyRemoved, BCommitTransactionResult& _result)
{
	_CommitTransaction(NULL, &transaction, packagesAlreadyAdded,
		packagesAlreadyRemoved, _result);
}


void
Volume::_HandleEntryCreatedOrRemoved(const BMessage* message, bool created)
{
	// only moves to or from our packages directory are interesting
	int32 deviceID;
	int64 directoryID;
	const char* name;
	if (message->FindInt32("device", &deviceID) != B_OK
		|| message->FindInt64("directory", &directoryID) != B_OK
		|| message->FindString("name", &name) != B_OK
		|| node_ref(deviceID, directoryID) != PackagesDirectoryRef()) {
		return;
	}

	_QueueNodeMonitorEvent(name, created);
}


void
Volume::_HandleEntryMoved(const BMessage* message)
{
	int32 deviceID;
	int64 fromDirectoryID;
	int64 toDirectoryID;
	const char* fromName;
	const char* toName;
	if (message->FindInt32("device", &deviceID) != B_OK
		|| message->FindInt64("from directory", &fromDirectoryID) != B_OK
		|| message->FindInt64("to directory", &toDirectoryID) != B_OK
		|| message->FindString("from name", &fromName) != B_OK
		|| message->FindString("name", &toName) != B_OK
		|| deviceID != PackagesDeviceID()
		|| (fromDirectoryID != PackagesDirectoryID()
			&& toDirectoryID != PackagesDirectoryID())) {
		return;
	}

	AutoLocker<BLocker> eventsLock(fPendingNodeMonitorEventsLock);
		// make sure for a move the two events cannot get split

	if (fromDirectoryID == PackagesDirectoryID())
		_QueueNodeMonitorEvent(fromName, false);
	if (toDirectoryID == PackagesDirectoryID())
		_QueueNodeMonitorEvent(toName, true);
}


void
Volume::_QueueNodeMonitorEvent(const BString& name, bool wasCreated)
{
	if (name.IsEmpty()) {
		ERROR("Volume::_QueueNodeMonitorEvent(): got empty name.\n");
		return;
	}

	// ignore entries that don't have the ".hpkg" extension
	if (!name.EndsWith(kPackageFileNameExtension))
		return;

	NodeMonitorEvent* event
		= new(std::nothrow) NodeMonitorEvent(name, wasCreated);
	if (event == NULL) {
		ERROR("Volume::_QueueNodeMonitorEvent(): out of memory.\n");
		return;
	}

	AutoLocker<BLocker> eventsLock(fPendingNodeMonitorEventsLock);
	fPendingNodeMonitorEvents.Add(event);
	eventsLock.Unlock();

	fNodeMonitorEventHandleTime
		= system_time() + kNodeMonitorEventHandlingDelay;
	BMessage message(kHandleNodeMonitorEvents);
	BMessageRunner::StartSending(this, &message, kNodeMonitorEventHandlingDelay,
		1);
}


void
Volume::_PackagesEntryCreated(const char* name)
{
INFORM("Volume::_PackagesEntryCreated(\"%s\")\n", name);
	// Ignore the event, if the package is already known.
	Package* package = fLatestState->FindPackage(name);
	if (package != NULL) {
		if (package->File()->EntryCreatedIgnoreLevel() > 0) {
			package->File()->DecrementEntryCreatedIgnoreLevel();
		} else {
			WARN("node monitoring created event for already known entry "
				"\"%s\"\n", name);
		}

		// Remove the package from the packages-to-be-deactivated set, if it is in
		// there (unlikely, unless we see a remove-create sequence).
		PackageSet::iterator it = fPackagesToBeDeactivated.find(package);
		if (it != fPackagesToBeDeactivated.end())
			fPackagesToBeDeactivated.erase(it);

		return;
	}

	status_t error = fPackageFileManager->CreatePackage(
		NotOwningEntryRef(PackagesDirectoryRef(), name),
		package);
	if (error != B_OK) {
		ERROR("failed to init package for file \"%s\"\n", name);
		return;
	}

	fLock.Lock();
	fLatestState->AddPackage(package);
	fChangeCount++;
	fLock.Unlock();

	try {
		fPackagesToBeActivated.insert(package);
	} catch (std::bad_alloc& exception) {
		ERROR("out of memory\n");
		return;
	}
}


void
Volume::_PackagesEntryRemoved(const char* name)
{
INFORM("Volume::_PackagesEntryRemoved(\"%s\")\n", name);
	Package* package = fLatestState->FindPackage(name);
	if (package == NULL)
		return;

	// Ignore the event, if we generated it ourselves.
	if (package->File()->EntryRemovedIgnoreLevel() > 0) {
		package->File()->DecrementEntryRemovedIgnoreLevel();
		return;
	}

	// Remove the package from the packages-to-be-activated set, if it is in
	// there (unlikely, unless we see a create-remove-create sequence).
	PackageSet::iterator it = fPackagesToBeActivated.find(package);
	if (it != fPackagesToBeActivated.end())
		fPackagesToBeActivated.erase(it);

	// If the package isn't active, just remove it for good.
	if (!package->IsActive()) {
		AutoLocker<BLocker> locker(fLock);
		fLatestState->RemovePackage(package);
		fChangeCount++;
		delete package;
		return;
	}

	// The package must be deactivated.
	try {
		fPackagesToBeDeactivated.insert(package);
	} catch (std::bad_alloc& exception) {
		ERROR("out of memory\n");
		return;
	}
}


status_t
Volume::_ReadPackagesDirectory()
{
	BDirectory directory;
	status_t error = directory.SetTo(&PackagesDirectoryRef());
	if (error != B_OK) {
		ERROR("Volume::_ReadPackagesDirectory(): failed to open packages "
			"directory: %s\n", strerror(error));
		RETURN_ERROR(error);
	}

	entry_ref entry;
	while (directory.GetNextRef(&entry) == B_OK) {
		if (!BString(entry.name).EndsWith(kPackageFileNameExtension))
			continue;

		Package* package;
		status_t error = fPackageFileManager->CreatePackage(entry, package);
		if (error == B_OK) {
			AutoLocker<BLocker> locker(fLock);
			fLatestState->AddPackage(package);
			fChangeCount++;
		}
	}

	return B_OK;
}


status_t
Volume::_InitLatestState()
{
	if (_InitLatestStateFromActivatedPackages() == B_OK)
		return B_OK;

	INFORM("Failed to get activated packages info from activated packages file."
		" Assuming all package files in package directory are activated.\n");

	AutoLocker<BLocker> locker(fLock);

	for (PackageFileNameHashTable::Iterator it
				= fLatestState->ByFileNameIterator();
			Package* package = it.Next();) {
		fLatestState->SetPackageActive(package, true);
		fChangeCount++;
	}

	return B_OK;
}


status_t
Volume::_InitLatestStateFromActivatedPackages()
{
	// open admin directory
	BDirectory adminDirectory;
	status_t error = _OpenPackagesSubDirectory(
		RelativePath(kAdminDirectoryName), false, adminDirectory);
	if (error != B_OK)
		RETURN_ERROR(error);

	node_ref adminNode;
	error = adminDirectory.GetNodeRef(&adminNode);
	if (error != B_OK)
		RETURN_ERROR(error);

	// try reading the activation file
	NotOwningEntryRef entryRef(adminNode, kActivationFileName);
	BFile file;
	error = file.SetTo(&entryRef, B_READ_ONLY);
	if (error != B_OK) {
		BEntry activationEntry(&entryRef);
		BPath activationPath;
		const char *activationFilePathName = "Unknown due to errors";
		if (activationEntry.InitCheck() == B_OK &&
		activationEntry.GetPath(&activationPath) == B_OK)
			activationFilePathName = activationPath.Path();
		INFORM("Failed to open packages activation file %s: %s\n",
			activationFilePathName, strerror(error));
		RETURN_ERROR(error);
	}

	// read the whole file into memory to simplify things
	off_t size;
	error = file.GetSize(&size);
	if (error != B_OK) {
		ERROR("Failed to packages activation file size: %s\n",
			strerror(error));
		RETURN_ERROR(error);
	}

	if (size > (off_t)kMaxActivationFileSize) {
		ERROR("The packages activation file is too big.\n");
		RETURN_ERROR(B_BAD_DATA);
	}

	char* fileContent = (char*)malloc(size + 1);
	if (fileContent == NULL)
		RETURN_ERROR(B_NO_MEMORY);
	MemoryDeleter fileContentDeleter(fileContent);

	ssize_t bytesRead = file.Read(fileContent, size);
	if (bytesRead < 0) {
		ERROR("Failed to read packages activation file: %s\n",
			strerror(bytesRead));
		RETURN_ERROR(errno);
	}

	if (bytesRead != size) {
		ERROR("Failed to read whole packages activation file.\n");
		RETURN_ERROR(B_ERROR);
	}

	// null-terminate to simplify parsing
	fileContent[size] = '\0';

	AutoLocker<BLocker> locker(fLock);

	// parse the file and mark the respective packages active
	const char* packageName = fileContent;
	char* const fileContentEnd = fileContent + size;
	while (packageName < fileContentEnd) {
		char* packageNameEnd = strchr(packageName, '\n');
		if (packageNameEnd == NULL)
			packageNameEnd = fileContentEnd;

		// skip empty lines
		if (packageName == packageNameEnd) {
			packageName++;
			continue;
		}
		*packageNameEnd = '\0';

		if (packageNameEnd - packageName >= B_FILE_NAME_LENGTH) {
			ERROR("Invalid packages activation file content.\n");
			RETURN_ERROR(B_BAD_DATA);
		}

		Package* package = fLatestState->FindPackage(packageName);
		if (package != NULL) {
			fLatestState->SetPackageActive(package, true);
			fChangeCount++;
		} else {
			WARN("Package \"%s\" from activation file not in packages "
				"directory.\n", packageName);
		}

		packageName = packageNameEnd + 1;
	}

	return B_OK;
}


status_t
Volume::_GetActivePackages(int fd)
{
	// get the info from packagefs
	PackageFSGetPackageInfosRequest* request = NULL;
	MemoryDeleter requestDeleter;
	size_t bufferSize = 64 * 1024;
	for (;;) {
		request = (PackageFSGetPackageInfosRequest*)malloc(bufferSize);
		if (request == NULL)
			RETURN_ERROR(B_NO_MEMORY);
		requestDeleter.SetTo(request);

		if (ioctl(fd, PACKAGE_FS_OPERATION_GET_PACKAGE_INFOS, request,
				bufferSize) != 0) {
			ERROR("Volume::_GetActivePackages(): failed to get active package "
				"info from package FS: %s\n", strerror(errno));
			RETURN_ERROR(errno);
		}

		if (request->bufferSize <= bufferSize)
			break;

		bufferSize = request->bufferSize;
		requestDeleter.Unset();
	}

#if 0
	INFORM("latest volume state:\n");
	_DumpState(fLatestState);
#endif

	// check whether that matches the expected state
	if (_CheckActivePackagesMatchLatestState(request)) {
		INFORM("The latest volume state is also the currently active one\n");
		fActiveState = fLatestState;
		return B_OK;
	}

	// There's a mismatch. We need a new state that reflects the actual
	// activation situation.
	VolumeState* state = new(std::nothrow) VolumeState;
	if (state == NULL)
		RETURN_ERROR(B_NO_MEMORY);
	ObjectDeleter<VolumeState> stateDeleter(state);

	for (uint32 i = 0; i < request->packageCount; i++) {
		const PackageFSPackageInfo& info = request->infos[i];
		NotOwningEntryRef entryRef(info.directoryDeviceID, info.directoryNodeID,
			info.name);
		Package* package;
		status_t error = fPackageFileManager->CreatePackage(entryRef, package);
		if (error != B_OK) {
			WARN("Failed to create package (dev: %" B_PRIdDEV ", node: %"
				B_PRIdINO ", \"%s\"): %s\n", info.directoryDeviceID,
				info.directoryNodeID, info.name, strerror(error));
			continue;
		}

		state->AddPackage(package);
		state->SetPackageActive(package, true);
	}

#if 0
	INFORM("currently active volume state:\n");
	_DumpState(state);
#endif

	fActiveState = stateDeleter.Detach();
	return B_OK;
}


void
Volume::_RunQueuedScripts()
{
	BDirectory adminDirectory;
	status_t error = _OpenPackagesSubDirectory(
		RelativePath(kAdminDirectoryName), false, adminDirectory);
	if (error != B_OK)
		return;

	BDirectory scriptsDirectory;
	error = scriptsDirectory.SetTo(&adminDirectory, kQueuedScriptsDirectoryName);
	if (error != B_OK)
		return;

	// enumerate all the symlinks in the queued scripts directory
	BEntry scriptEntry;
	while (scriptsDirectory.GetNextEntry(&scriptEntry, false) == B_OK) {
		BPath scriptPath;
		scriptEntry.GetPath(&scriptPath);
		error = scriptPath.InitCheck();
		if (error != B_OK) {
			INFORM("failed to get path of post-installation script \"%s\"\n",
				strerror(error));
			continue;
		}

		errno = 0;
		int result = system(scriptPath.Path());
		if (result != 0) {
			INFORM("running post-installation script \"%s\" "
				"failed: %d (errno: %s)\n", scriptPath.Leaf(), errno, strerror(errno));
		}

		// remove the symlink, now that we've run the post-installation script
		error = scriptEntry.Remove();
		if (error != B_OK) {
			INFORM("removing queued post-install script failed \"%s\"\n",
				strerror(error));
		}
	}
}


bool
Volume::_CheckActivePackagesMatchLatestState(
	PackageFSGetPackageInfosRequest* request)
{
	if (fPackagesDirectoryCount != 1) {
		INFORM("An old packages state (\"%s\") seems to be active.\n",
			fPackagesDirectories[fPackagesDirectoryCount - 1].Name().String());
		return false;
	}

	const node_ref packagesDirRef(PackagesDirectoryRef());

	// mark the returned packages active
	for (uint32 i = 0; i < request->packageCount; i++) {
		const PackageFSPackageInfo& info = request->infos[i];
		if (node_ref(info.directoryDeviceID, info.directoryNodeID)
				!= packagesDirRef) {
			WARN("active package \"%s\" (dev: %" B_PRIdDEV ", node: %" B_PRIdINO
				") not in packages directory\n", info.name,
				info.packageDeviceID, info.packageNodeID);
			return false;
		}

		Package* package = fLatestState->FindPackage(
			node_ref(info.packageDeviceID, info.packageNodeID));
		if (package == NULL || !package->IsActive()) {
			WARN("active package \"%s\" (dev: %" B_PRIdDEV ", node: %" B_PRIdINO
				") not %s\n", info.name,
				info.packageDeviceID, info.packageNodeID,
				package == NULL
					? "found in packages directory" : "supposed to be active");
			return false;
		}
	}

	// Check whether there are packages that aren't active but should be.
	uint32 count = 0;
	for (PackageNodeRefHashTable::Iterator it
			= fLatestState->ByNodeRefIterator(); it.HasNext();) {
		Package* package = it.Next();
		if (package->IsActive())
			count++;
	}

	if (count != request->packageCount) {
		INFORM("There seem to be packages in the packages directory that "
			"should be active.\n");
		return false;
	}

	return true;
}


void
Volume::_SetLatestState(VolumeState* state, bool isActive)
{
	AutoLocker<BLocker> locker(fLock);

	bool sendNotification = fRoot->IsSystemRoot();
		// Send a notification, if this is a system root volume.
	BStringList addedPackageNames;
	BStringList removedPackageNames;

	// If a notification should be sent then assemble the latest and incoming
	// set of the packages' names.  This can be used to figure out which
	// packages are added and which are removed.

	if (sendNotification) {
		_CollectPackageNamesAdded(fLatestState, state, addedPackageNames);
		_CollectPackageNamesAdded(state, fLatestState, removedPackageNames);
	}

	if (isActive) {
		if (fLatestState != fActiveState)
			delete fActiveState;
		fActiveState = state;
	}

	if (fLatestState != fActiveState)
		delete fLatestState;
	fLatestState = state;
	fChangeCount++;

	locker.Unlock();

	// Send a notification, if this is a system root volume.
	if (sendNotification) {
		BMessage message(B_PACKAGE_UPDATE);
		if (message.AddInt32("event",
				(int32)B_INSTALLATION_LOCATION_PACKAGES_CHANGED) == B_OK
			&& message.AddStrings("added package names",
				addedPackageNames) == B_OK
			&& message.AddStrings("removed package names",
				removedPackageNames) == B_OK
			&& message.AddInt32("location", (int32)Location()) == B_OK
			&& message.AddInt64("change count", fChangeCount) == B_OK) {
			BRoster::Private().SendTo(&message, NULL, false);
		}
	}
}


/*static*/ void
Volume::_CollectPackageNamesAdded(const VolumeState* oldState,
	const VolumeState* newState, BStringList& addedPackageNames)
{
	if (newState == NULL)
		return;

	for (PackageFileNameHashTable::Iterator it
			= newState->ByFileNameIterator(); it.HasNext();) {
		Package* package = it.Next();
		BString packageName = package->Info().Name();
		if (oldState == NULL)
			addedPackageNames.Add(packageName);
		else {
			Package* oldStatePackage = oldState->FindPackage(
				package->FileName());
			if (oldStatePackage == NULL)
				addedPackageNames.Add(packageName);
		}
	}
}


void
Volume::_DumpState(VolumeState* state)
{
	uint32 inactiveCount = 0;
	for (PackageNodeRefHashTable::Iterator it = state->ByNodeRefIterator();
			it.HasNext();) {
		Package* package = it.Next();
		if (package->IsActive()) {
			INFORM("active package: \"%s\"\n", package->FileName().String());
		} else
			inactiveCount++;
	}

	if (inactiveCount == 0)
		return;

	for (PackageNodeRefHashTable::Iterator it = state->ByNodeRefIterator();
			it.HasNext();) {
		Package* package = it.Next();
		if (!package->IsActive())
			INFORM("inactive package: \"%s\"\n", package->FileName().String());
	}
}


status_t
Volume::_AddRepository(BSolver* solver, BSolverRepository& repository,
	bool activeOnly, bool installed)
{
	status_t error = repository.SetTo(Path());
	if (error != B_OK) {
		ERROR("Volume::_AddRepository(): failed to init repository: %s\n",
			strerror(error));
		return error;
	}

	repository.SetInstalled(installed);

	error = AddPackagesToRepository(repository, true);
	if (error != B_OK) {
		ERROR("Volume::_AddRepository(): failed to add packages to "
			"repository: %s\n", strerror(error));
		return error;
	}

	error = solver->AddRepository(&repository);
	if (error != B_OK) {
		ERROR("Volume::_AddRepository(): failed to add repository to solver: "
			"%s\n", strerror(error));
		return error;
	}

	return B_OK;
}


status_t
Volume::_OpenPackagesSubDirectory(const RelativePath& path, bool create,
	BDirectory& _directory)
{
	// open the packages directory
	BDirectory directory;
	status_t error = directory.SetTo(&PackagesDirectoryRef());
	if (error != B_OK) {
		ERROR("Volume::_OpenPackagesSubDirectory(): failed to open packages "
			"directory: %s\n", strerror(error));
		RETURN_ERROR(error);
	}

	return FSUtils::OpenSubDirectory(directory, path, create, _directory);
}


void
Volume::_CommitTransaction(BMessage* message,
	const BActivationTransaction* transaction,
	const PackageSet& packagesAlreadyAdded,
	const PackageSet& packagesAlreadyRemoved, BCommitTransactionResult& _result)
{
	_result.Unset();

	// perform the request
	CommitTransactionHandler handler(this, fPackageFileManager, _result);
	BTransactionError error = B_TRANSACTION_INTERNAL_ERROR;
	try {
		handler.Init(fLatestState, fLatestState == fActiveState,
			packagesAlreadyAdded, packagesAlreadyRemoved);

		if (message != NULL)
			handler.HandleRequest(message);
		else if (transaction != NULL)
			handler.HandleRequest(*transaction);
		else
			handler.HandleRequest();

		_SetLatestState(handler.DetachVolumeState(),
			handler.IsActiveVolumeState());
		error = B_TRANSACTION_OK;
	} catch (Exception& exception) {
		error = exception.Error();
		exception.SetOnResult(_result);
		if (_result.ErrorPackage().IsEmpty()
			&& handler.CurrentPackage() != NULL) {
			_result.SetErrorPackage(handler.CurrentPackage()->FileName());
		}
	} catch (std::bad_alloc& exception) {
		error = B_TRANSACTION_NO_MEMORY;
	}

	_result.SetError(error);

	// revert on error
	if (error != B_TRANSACTION_OK)
		handler.Revert();
}