haiku/src/servers/package/Volume.cpp

1509 lines
37 KiB
C++

/*
* 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
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);
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();
}