text: /*
   * Copyright 2013-2014, Haiku, Inc. All Rights Reserved.
   * Distributed under the terms of the MIT License.
   *
   * Authors:
   *		Ingo Weinhold <ingo_weinhold@gmx.de>
   */
  
  
  #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
  	int fd = directory.Dup();
  	if (fd < 0) {
  		ERROR("Volume::Init(): failed to get root directory FD: %s\n",
  			strerror(fd));
  		RETURN_ERROR(fd);
  	}
  	FileDescriptorCloser fdCloser(fd);
  
  	// 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, 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
  	int fd = OpenRootDirectory();
  	if (fd < 0) {
  		ERROR("Volume::InitPackages(): failed to open root directory: %s\n",
  			strerror(fd));
  		RETURN_ERROR(fd);
  	}
  	FileDescriptorCloser fdCloser(fd);
  
  	error = _ReadPackagesDirectory();
  	if (error != B_OK)
  		RETURN_ERROR(error);
  
  	error = _InitLatestState();
  	if (error != B_OK)
  		RETURN_ERROR(error);
  
  	error = _GetActivePackages(fd);
  	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.MoveFrom(&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();
  	}
  
  	INFORM("latest volume state:\n");
  	_DumpState(fLatestState);
  
  	// 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);
  	}
  
  	INFORM("currently active volume state:\n");
  	_DumpState(state);
  
  	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);
  	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 (fRoot->IsSystemRoot()) {
  		BMessage message(B_PACKAGE_UPDATE);
  		if (message.AddInt32("event",
  				(int32)B_INSTALLATION_LOCATION_PACKAGES_CHANGED) == B_OK
  			&& message.AddInt32("location", (int32)Location()) == B_OK
  			&& message.AddInt64("change count", fChangeCount) == B_OK) {
  			BRoster::Private().SendTo(&message, NULL, false);
  		}
  	}
  }
  
  
  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();
  }
  