root/src/servers/package/Volume.cpp
/*
 * 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("active 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();
}