From 85d2badf007cb152215485db7916578ed6700504 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Sat, 20 Apr 2013 01:28:18 +0200 Subject: [PATCH] package daemon: Add support for activation change request * daemon: Handle new request B_MESSAGE_COMMIT_TRANSACTION. It activates and deactivates given sets of packages. The new packages must be placed in a directory in the administrative directory. The daemon moves them to the packages directory and the deactivated packages to a subdirectory it creates. It also save the old activation state there. * Add private BActivationTransaction, describing an activation change transaction. * BDaemonClient: Add CommitTransaction(), which sends a given BActivationTransaction as a B_MESSAGE_COMMIT_TRANSACTION request to the daemon. Completely untested yet. --- .../private/package/ActivationTransaction.h | 1 + .../private/package/ActivationTransaction.h | 70 ++ headers/private/package/DaemonClient.h | 49 + headers/private/package/DaemonDefs.h | 41 +- src/build/libpackage/Jamfile | 1 + src/kits/package/ActivationTransaction.cpp | 158 +++ src/kits/package/DaemonClient.cpp | 149 ++- src/kits/package/Jamfile | 1 + src/servers/package/Package.cpp | 4 +- src/servers/package/Package.h | 16 + src/servers/package/PackageDaemon.cpp | 7 +- src/servers/package/Root.cpp | 64 +- src/servers/package/Root.h | 12 +- src/servers/package/Volume.cpp | 993 +++++++++++++++--- src/servers/package/Volume.h | 38 +- 15 files changed, 1434 insertions(+), 170 deletions(-) create mode 100644 headers/build/private/package/ActivationTransaction.h create mode 100644 headers/private/package/ActivationTransaction.h create mode 100644 src/kits/package/ActivationTransaction.cpp diff --git a/headers/build/private/package/ActivationTransaction.h b/headers/build/private/package/ActivationTransaction.h new file mode 100644 index 0000000000..39e231882e --- /dev/null +++ b/headers/build/private/package/ActivationTransaction.h @@ -0,0 +1 @@ +#include <../private/package/ActivationTransaction.h> diff --git a/headers/private/package/ActivationTransaction.h b/headers/private/package/ActivationTransaction.h new file mode 100644 index 0000000000..11b5094325 --- /dev/null +++ b/headers/private/package/ActivationTransaction.h @@ -0,0 +1,70 @@ +/* + * Copyright 2013, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ +#ifndef _PACKAGE__PRIVATE__ACTIVATION_TRANSACTION_H_ +#define _PACKAGE__PRIVATE__ACTIVATION_TRANSACTION_H_ + + +#include +#include + + +namespace BPackageKit { +namespace BPrivate { + + +class BActivationTransaction { +public: + BActivationTransaction(); + BActivationTransaction( + BPackageInstallationLocation location, + int64 changeCount, + const BString& directoryName, + const BStringList& packagesToActivate, + const BStringList& packagesToDeactivate); + ~BActivationTransaction(); + + status_t InitCheck() const; + status_t SetTo(BPackageInstallationLocation location, + int64 changeCount, + const BString& directoryName, + const BStringList& packagesToActivate, + const BStringList& packagesToDeactivate); + + BPackageInstallationLocation Location() const; + void SetLocation( + BPackageInstallationLocation location); + + int64 ChangeCount() const; + void SetChangeCount(int64 changeCount); + + const BString& TransactionDirectoryName() const; + void SetTransactionDirectoryName( + const BString& directoryName); + + const BStringList& PackagesToActivate() const; + void SetPackagesToActivate( + const BStringList& packages); + + const BStringList& PackagesToDeactivate() const; + void SetPackagesToDeactivate( + const BStringList& packages); + +private: + BPackageInstallationLocation fLocation; + int64 fChangeCount; + BString fTransactionDirectoryName; + BStringList fPackagesToActivate; + BStringList fPackagesToDeactivate; +}; + + +} // namespace BPrivate +} // namespace BPackageKit + + +#endif // _PACKAGE__PRIVATE__ACTIVATION_TRANSACTION_H_ diff --git a/headers/private/package/DaemonClient.h b/headers/private/package/DaemonClient.h index e16b3e3499..bcad84ad59 100644 --- a/headers/private/package/DaemonClient.h +++ b/headers/private/package/DaemonClient.h @@ -11,6 +11,9 @@ #include #include +#include + +#include namespace BPackageKit { @@ -23,7 +26,13 @@ class BPackageInfoSet; namespace BPrivate { +class BActivationTransaction; + + class BDaemonClient { +public: + class BCommitTransactionResult; + public: BDaemonClient(); ~BDaemonClient(); @@ -31,17 +40,57 @@ public: status_t GetInstallationLocationInfo( BPackageInstallationLocation location, BInstallationLocationInfo& _info); + status_t CommitTransaction( + const BActivationTransaction& transaction, + BCommitTransactionResult& _result); private: status_t _InitMessenger(); status_t _ExtractPackageInfoSet(const BMessage& message, const char* field, BPackageInfoSet& _infos); + status_t _CommitTransaction( + const BActivationTransaction& transaction, + BCommitTransactionResult& _result); + private: BMessenger fDaemonMessenger; }; +class BDaemonClient::BCommitTransactionResult { +public: + BCommitTransactionResult(); + BCommitTransactionResult(int32 error, + const BString& errorMessage, + const BString& errorPackage, + const BString& oldStateDirectory); + ~BCommitTransactionResult(); + + void SetTo(int32 error, const BString& errorMessage, + const BString& errorPackage, + const BString& oldStateDirectory); + + status_t Error() const; + BDaemonError DaemonError() const; + // may be B_DAEMON_OK, even if Error() is + // != B_OK, then Error() is as specific as + // is known + const BString& ErrorMessage() const; + // may be empty, even on error + const BString& ErrorPackage() const; + // may be empty, even on error + + const BString& OldStateDirectory() const; + +private: + int32 fError; + BString fErrorMessage; + BString fErrorPackage; + BString fOldStateDirectory; +}; + + } // namespace BPrivate } // namespace BPackageKit diff --git a/headers/private/package/DaemonDefs.h b/headers/private/package/DaemonDefs.h index 3bde3b742b..76a323133a 100644 --- a/headers/private/package/DaemonDefs.h +++ b/headers/private/package/DaemonDefs.h @@ -17,7 +17,11 @@ namespace BPrivate { enum BDaemonError { - B_DAEMON_OK + B_DAEMON_OK = 0, + B_DAEMON_CHANGE_COUNT_MISMATCH, + B_DAEMON_BAD_REQUEST, + B_DAEMON_NO_SUCH_PACKAGE, + B_DAEMON_PACKAGE_ALREADY_EXISTS }; @@ -36,11 +40,40 @@ enum { // archived BPackageInfos of the active packages // "inactive packages": message[] // archived BPackageInfos of the inactive packages + + B_MESSAGE_COMMIT_TRANSACTION = 'PKTC', + // "location": int32 + // the respective installation location constant + // "change count": int64 + // the expected change count of the installation location; fail, + // if something has changed in the meantime + // "transaction": string + // name of the transaction directory (subdirectory of the + // administrative directory) where to-be-activated packages live + // "activate": string[] + // file names of the packages to activate; must be in the + // transaction directory + // "deactivate": string[] + // file names of the packages to activate; must be in the + // transaction directory + B_MESSAGE_COMMIT_TRANSACTION_REPLY = 'PKTR' + // "error": int32 + // regular error code or BDaemonError describing how committing + // the transaction went + // "error message": string + // [error case only] gives some additional information what went + // wrong; optional + // "error package": string + // [error case only] file name of the package causing the error, + // if any in particarly; optional + // "old state": string + // name of the directory (subdirectory of the administrative + // directory) containing the deactivated packages }; -#endif // _PACKAGE__PRIVATE__DAEMON_DEFS_H_ - - } // namespace BPrivate } // namespace BPackageKit + + +#endif // _PACKAGE__PRIVATE__DAEMON_DEFS_H_ diff --git a/src/build/libpackage/Jamfile b/src/build/libpackage/Jamfile index 65a24be213..1d99a84d1b 100644 --- a/src/build/libpackage/Jamfile +++ b/src/build/libpackage/Jamfile @@ -67,6 +67,7 @@ BuildPlatformSharedLibrary libpackage_build.so : ActivateRepositoryCacheJob.cpp ActivateRepositoryConfigJob.cpp + ActivationTransaction.cpp AddRepositoryRequest.cpp Attributes.cpp BlockBufferCacheNoLock.cpp diff --git a/src/kits/package/ActivationTransaction.cpp b/src/kits/package/ActivationTransaction.cpp new file mode 100644 index 0000000000..838b071774 --- /dev/null +++ b/src/kits/package/ActivationTransaction.cpp @@ -0,0 +1,158 @@ +/* + * Copyright 2013, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ + + +#include + + +namespace BPackageKit { +namespace BPrivate { + + +BActivationTransaction::BActivationTransaction() + : + fLocation(B_PACKAGE_INSTALLATION_LOCATION_ENUM_COUNT), + fChangeCount(0), + fTransactionDirectoryName(), + fPackagesToActivate(), + fPackagesToDeactivate() +{ +} + + +BActivationTransaction::BActivationTransaction( + BPackageInstallationLocation location, int64 changeCount, + const BString& directoryName, const BStringList& packagesToActivate, + const BStringList& packagesToDeactivate) + : + fLocation(location), + fChangeCount(changeCount), + fTransactionDirectoryName(directoryName), + fPackagesToActivate(packagesToActivate), + fPackagesToDeactivate(packagesToDeactivate) +{ +} + + +BActivationTransaction::~BActivationTransaction() +{ +} + + +status_t +BActivationTransaction::InitCheck() const +{ + if (fLocation < 0 || fLocation >= B_PACKAGE_INSTALLATION_LOCATION_ENUM_COUNT + || fTransactionDirectoryName.IsEmpty() + || (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.IsEmpty())) { + return B_BAD_VALUE; + } + return B_OK; +} + +status_t +BActivationTransaction::SetTo(BPackageInstallationLocation location, + int64 changeCount, const BString& directoryName, + const BStringList& packagesToActivate, + const BStringList& packagesToDeactivate) +{ + if (location < 0 || location >= B_PACKAGE_INSTALLATION_LOCATION_ENUM_COUNT + || directoryName.IsEmpty() + || (packagesToActivate.IsEmpty() && packagesToDeactivate.IsEmpty())) { + return B_BAD_VALUE; + } + + fLocation = location; + fChangeCount = changeCount; + fTransactionDirectoryName = directoryName; + fPackagesToActivate = packagesToActivate; + fPackagesToDeactivate = packagesToDeactivate; + + if (fPackagesToActivate.CountStrings() != packagesToActivate.CountStrings() + || fPackagesToDeactivate.CountStrings() + != packagesToDeactivate.CountStrings()) { + return B_NO_MEMORY; + } + + return B_OK; +} + + +BPackageInstallationLocation +BActivationTransaction::Location() const +{ + return fLocation; +} + + +void +BActivationTransaction::SetLocation(BPackageInstallationLocation location) +{ + fLocation = location; +} + + +int64 +BActivationTransaction::ChangeCount() const +{ + return fChangeCount; +} + + +void +BActivationTransaction::SetChangeCount(int64 changeCount) +{ + fChangeCount = changeCount; +} + + +const BString& +BActivationTransaction::TransactionDirectoryName() const +{ + return fTransactionDirectoryName; +} + + +void +BActivationTransaction::SetTransactionDirectoryName( + const BString& directoryName) +{ + fTransactionDirectoryName = directoryName; +} + + +const BStringList& +BActivationTransaction::PackagesToActivate() const +{ + return fPackagesToActivate; +} + + +void +BActivationTransaction::SetPackagesToActivate(const BStringList& packages) +{ + fPackagesToActivate = packages; +} + + +const BStringList& +BActivationTransaction::PackagesToDeactivate() const +{ + return fPackagesToDeactivate; +} + + +void +BActivationTransaction::SetPackagesToDeactivate(const BStringList& packages) +{ + fPackagesToDeactivate = packages; +} + + +} // namespace BPrivate +} // namespace BPackageKit diff --git a/src/kits/package/DaemonClient.cpp b/src/kits/package/DaemonClient.cpp index f14801f51e..8d2c1bf310 100644 --- a/src/kits/package/DaemonClient.cpp +++ b/src/kits/package/DaemonClient.cpp @@ -12,13 +12,16 @@ #include #include -#include +#include namespace BPackageKit { namespace BPrivate { +// #pragma mark - BCommitTransactionResult + + BDaemonClient::BDaemonClient() : fDaemonMessenger() @@ -87,6 +90,19 @@ BDaemonClient::GetInstallationLocationInfo( } +status_t +BDaemonClient::CommitTransaction(const BActivationTransaction& transaction, + BCommitTransactionResult& _result) +{ + status_t error = _CommitTransaction(transaction, _result); + // B_OK just indicates that _result has been initialized. + if (error != B_OK) + _result.SetTo(error, BString(), BString(), BString()); + + return _result.Error(); +} + + status_t BDaemonClient::_InitMessenger() { @@ -133,5 +149,136 @@ BDaemonClient::_ExtractPackageInfoSet(const BMessage& message, } +status_t +BDaemonClient::_CommitTransaction(const BActivationTransaction& transaction, + BCommitTransactionResult& _result) +{ + if (transaction.InitCheck() != B_OK) + return B_BAD_VALUE; + + status_t error = _InitMessenger(); + if (error != B_OK) + return error; + + // send the request + BMessage request(B_MESSAGE_COMMIT_TRANSACTION); + if ((error = request.AddInt32("location", transaction.Location())) != B_OK + || (error = request.AddInt64("change count", + transaction.ChangeCount())) != B_OK + || (error = request.AddString("transaction", + transaction.TransactionDirectoryName())) != B_OK + || (error = request.AddStrings("activate", + transaction.PackagesToActivate())) != B_OK + || (error = request.AddStrings("deactivate", + transaction.PackagesToDeactivate())) != B_OK) { + return error; + } + + BMessage reply; + fDaemonMessenger.SendMessage(&request, &reply); + if (reply.what != B_MESSAGE_COMMIT_TRANSACTION_REPLY) + return B_ERROR; + + // extract the result + int32 requestError; + error = reply.FindInt32("error", &requestError); + if (error != B_OK) + return error; + + BString errorMessage; + BString errorPackage; + BString oldStateDirectory; + if (requestError == 0) { + error = reply.FindString("old state", &oldStateDirectory); + if (error != B_OK) + return error; + } else { + reply.FindString("error message", &errorMessage); + reply.FindString("error package", &errorPackage); + } + + _result.SetTo(requestError, errorMessage, errorPackage, oldStateDirectory); + return B_OK; + // Even on error. B_OK just indicates that we have initialized _result. +} + + +// #pragma mark - BCommitTransactionResult + + +BDaemonClient::BCommitTransactionResult::BCommitTransactionResult() + : + fError(B_NO_INIT), + fErrorMessage(), + fErrorPackage(), + fOldStateDirectory() +{ +} + + +BDaemonClient::BCommitTransactionResult::BCommitTransactionResult(int32 error, + const BString& errorMessage, const BString& errorPackage, + const BString& oldStateDirectory) + : + fError(error), + fErrorMessage(errorMessage), + fErrorPackage(errorPackage), + fOldStateDirectory(oldStateDirectory) +{ +} + + +BDaemonClient::BCommitTransactionResult::~BCommitTransactionResult() +{ +} + + +void +BDaemonClient::BCommitTransactionResult::SetTo(int32 error, + const BString& errorMessage, const BString& errorPackage, + const BString& oldStateDirectory) +{ + fError = error; + fErrorMessage = errorMessage; + fErrorPackage = errorPackage; + fOldStateDirectory = oldStateDirectory; +} + + +status_t +BDaemonClient::BCommitTransactionResult::Error() const +{ + return fError <= 0 ? fError : B_ERROR; +} + + +BDaemonError +BDaemonClient::BCommitTransactionResult::DaemonError() const +{ + return fError > 0 ? (BDaemonError)fError : B_DAEMON_OK; +} + + +const BString& +BDaemonClient::BCommitTransactionResult::ErrorMessage() const +{ + return fErrorMessage; +} + + +const BString& +BDaemonClient::BCommitTransactionResult::ErrorPackage() const +{ + return fErrorPackage; +} + + +const BString& +BDaemonClient::BCommitTransactionResult::OldStateDirectory() const +{ + return fOldStateDirectory; +} + + } // namespace BPrivate } // namespace BPackageKit diff --git a/src/kits/package/Jamfile b/src/kits/package/Jamfile index 8ebcc9ce2f..6df66e5cb2 100644 --- a/src/kits/package/Jamfile +++ b/src/kits/package/Jamfile @@ -46,6 +46,7 @@ SharedLibrary libpackage.so : ActivateRepositoryCacheJob.cpp ActivateRepositoryConfigJob.cpp + ActivationTransaction.cpp AddRepositoryRequest.cpp Attributes.cpp BlockBufferCacheNoLock.cpp diff --git a/src/servers/package/Package.cpp b/src/servers/package/Package.cpp index c88767ee0d..74d6740aae 100644 --- a/src/servers/package/Package.cpp +++ b/src/servers/package/Package.cpp @@ -25,7 +25,9 @@ Package::Package() fInfo(), fActive(false), fFileNameHashTableNext(NULL), - fNodeRefHashTableNext(NULL) + fNodeRefHashTableNext(NULL), + fIgnoreEntryCreated(0), + fIgnoreEntryRemoved(0) { } diff --git a/src/servers/package/Package.h b/src/servers/package/Package.h index b93fed8a2f..e2ac3dd7f0 100644 --- a/src/servers/package/Package.h +++ b/src/servers/package/Package.h @@ -39,6 +39,20 @@ public: void SetActive(bool active) { fActive = active; } + int32 EntryCreatedIgnoreLevel() const + { return fIgnoreEntryCreated; } + void IncrementEntryCreatedIgnoreLevel() + { fIgnoreEntryCreated++; } + void DecrementEntryCreatedIgnoreLevel() + { fIgnoreEntryCreated--; } + + int32 EntryRemovedIgnoreLevel() const + { return fIgnoreEntryRemoved; } + void IncrementEntryRemovedIgnoreLevel() + { fIgnoreEntryRemoved++; } + void DecrementEntryRemovedIgnoreLevel() + { fIgnoreEntryRemoved--; } + Package*& FileNameHashTableNext() { return fFileNameHashTableNext; } Package*& NodeRefHashTableNext() @@ -51,6 +65,8 @@ private: bool fActive; Package* fFileNameHashTableNext; Package* fNodeRefHashTableNext; + int32 fIgnoreEntryCreated; + int32 fIgnoreEntryRemoved; }; diff --git a/src/servers/package/PackageDaemon.cpp b/src/servers/package/PackageDaemon.cpp index 58d76c045f..426d5e3592 100644 --- a/src/servers/package/PackageDaemon.cpp +++ b/src/servers/package/PackageDaemon.cpp @@ -86,11 +86,10 @@ PackageDaemon::MessageReceived(BMessage* message) } case B_MESSAGE_GET_INSTALLATION_LOCATION_INFO: + case B_MESSAGE_COMMIT_TRANSACTION: { - if (fSystemRoot == NULL) - break; - - fSystemRoot->HandleGetLocationInfoRequest(DetachCurrentMessage()); + if (fSystemRoot != NULL) + fSystemRoot->HandleRequest(DetachCurrentMessage()); break; } diff --git a/src/servers/package/Root.cpp b/src/servers/package/Root.cpp index fd0c4565c0..bb3ac96aad 100644 --- a/src/servers/package/Root.cpp +++ b/src/servers/package/Root.cpp @@ -17,9 +17,14 @@ #include #include +#include + #include "DebugSupport.h" +using namespace BPackageKit::BPrivate; + + // #pragma mark - VolumeJob @@ -42,11 +47,11 @@ private: }; -// #pragma mark - HandleGetLocationInfoRequestJob +// #pragma mark - RequestJob -struct Root::HandleGetLocationInfoRequestJob : public Job { - HandleGetLocationInfoRequestJob(Root* root, BMessage* message) +struct Root::RequestJob : public Job { + RequestJob(Root* root, BMessage* message) : fRoot(root), fMessage(message) @@ -55,7 +60,7 @@ struct Root::HandleGetLocationInfoRequestJob : public Job { virtual void Do() { - fRoot->_HandleGetLocationInfoRequest(fMessage.Get()); + fRoot->_HandleRequest(fMessage.Get()); } private: @@ -209,10 +214,9 @@ Root::FindVolume(dev_t deviceID) const void -Root::HandleGetLocationInfoRequest(BMessage* message) +Root::HandleRequest(BMessage* message) { - HandleGetLocationInfoRequestJob* job - = new(std::nothrow) HandleGetLocationInfoRequestJob(this, message); + RequestJob* job = new(std::nothrow) RequestJob(this, message); if (job == NULL) { delete message; return; @@ -253,6 +257,22 @@ Root::_GetVolume(PackageFSMountType mountType) } +Volume* +Root::_GetVolume(BPackageInstallationLocation location) +{ + switch ((BPackageInstallationLocation)location) { + case B_PACKAGE_INSTALLATION_LOCATION_SYSTEM: + return fSystemVolume; + case B_PACKAGE_INSTALLATION_LOCATION_COMMON: + return fCommonVolume; + case B_PACKAGE_INSTALLATION_LOCATION_HOME: + return fHomeVolume; + default: + return NULL; + } +} + + Volume* Root::_NextVolumeFor(Volume* volume) { @@ -314,7 +334,7 @@ Root::_ProcessNodeMonitorEvents(Volume* volume) void -Root::_HandleGetLocationInfoRequest(BMessage* message) +Root::_HandleRequest(BMessage* message) { int32 location; if (message->FindInt32("location", &location) != B_OK @@ -325,23 +345,19 @@ Root::_HandleGetLocationInfoRequest(BMessage* message) // get the volume and let it handle the message AutoLocker locker(fLock); - Volume* volume; - switch ((BPackageInstallationLocation)location) { - case B_PACKAGE_INSTALLATION_LOCATION_SYSTEM: - volume = fSystemVolume; - break; - case B_PACKAGE_INSTALLATION_LOCATION_COMMON: - volume = fCommonVolume; - break; - case B_PACKAGE_INSTALLATION_LOCATION_HOME: - volume = fHomeVolume; - break; - default: - return; - } + Volume* volume = _GetVolume((BPackageInstallationLocation)location); + locker.Unlock(); - if (volume != NULL) - volume->HandleGetLocationInfoRequest(message); + if (volume != NULL) { + switch (message->what) { + case B_MESSAGE_GET_INSTALLATION_LOCATION_INFO: + volume->HandleGetLocationInfoRequest(message); + break; + case B_MESSAGE_COMMIT_TRANSACTION: + volume->HandleCommitTransactionRequest(message); + break; + } + } } diff --git a/src/servers/package/Root.h b/src/servers/package/Root.h index 62aba1b8d3..512cd23abe 100644 --- a/src/servers/package/Root.h +++ b/src/servers/package/Root.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -41,7 +42,7 @@ public: Volume* FindVolume(dev_t deviceID) const; - void HandleGetLocationInfoRequest(BMessage* message); + void HandleRequest(BMessage* message); private: // Volume::Listener @@ -52,19 +53,20 @@ protected: private: struct VolumeJob; - struct HandleGetLocationInfoRequestJob; + struct RequestJob; - friend struct HandleGetLocationInfoRequestJob; + friend struct RequestJob; private: Volume** _GetVolume(PackageFSMountType mountType); + Volume* _GetVolume( + BPackageInstallationLocation location); Volume* _NextVolumeFor(Volume* volume); void _InitPackages(Volume* volume); void _DeleteVolume(Volume* volume); void _ProcessNodeMonitorEvents(Volume* volume); - void _HandleGetLocationInfoRequest( - BMessage* message); + void _HandleRequest(BMessage* message); status_t _QueueJob(Job* job); diff --git a/src/servers/package/Volume.cpp b/src/servers/package/Volume.cpp index 401d760430..80225c4556 100644 --- a/src/servers/package/Volume.cpp +++ b/src/servers/package/Volume.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -87,6 +88,608 @@ private: }; +// #pragma mark - RelativePath + + +struct Volume::RelativePath { + RelativePath(const char* component1 = NULL, const char* component2 = NULL, + const char* component3 = NULL) + : + fComponentCount(kMaxComponentCount) + { + fComponents[0] = component1; + fComponents[1] = component2; + fComponents[2] = component3; + + for (size_t i = 0; i < kMaxComponentCount; i++) { + if (fComponents[i] == NULL) { + fComponentCount = i; + break; + } + } + } + + bool IsEmpty() const + { + return fComponentCount == 0; + } + + RelativePath HeadPath(size_t componentsToDropCount = 1) + { + RelativePath result; + if (componentsToDropCount < fComponentCount) { + result.fComponentCount = fComponentCount - componentsToDropCount; + for (size_t i = 0; i < result.fComponentCount; i++) + result.fComponents[i] = fComponents[i]; + } + + return result; + } + + const char* LastComponent() const + { + return fComponentCount > 0 ? fComponents[fComponentCount - 1] : NULL; + } + + BString ToString() const + { + if (fComponentCount == 0) + return BString(); + + size_t length = fComponentCount - 1; + for (size_t i = 0; i < fComponentCount; i++) + length += strlen(fComponents[i]); + + BString result; + char* buffer = result.LockBuffer(length + 1); + if (buffer == NULL) + return BString(); + + for (size_t i = 0; i < fComponentCount; i++) { + if (i > 0) { + *buffer = '/'; + buffer++; + } + strcpy(buffer, fComponents[i]); + buffer += strlen(buffer); + } + + return result.UnlockBuffer(); + } + +private: + static const size_t kMaxComponentCount = 3; + + const char* fComponents[kMaxComponentCount]; + size_t fComponentCount; +}; + + +// #pragma mark - Exception + + +struct Volume::Exception { + Exception(int32 error, const char* errorMessage = NULL, + const char* packageName = NULL) + : + fError(error), + fErrorMessage(errorMessage), + fPackageName(packageName) + { + } + + int32 Error() const + { + return fError; + } + + const BString& ErrorMessage() const + { + return fErrorMessage; + } + + const BString& PackageName() const + { + return fPackageName; + } + + BString ToString() const + { + const char* error; + if (fError >= 0) { + switch (fError) { + case B_DAEMON_OK: + error = "no error"; + break; + case B_DAEMON_CHANGE_COUNT_MISMATCH: + error = "transaction out of date"; + break; + case B_DAEMON_BAD_REQUEST: + error = "invalid transaction"; + break; + case B_DAEMON_NO_SUCH_PACKAGE: + error = "no such package"; + break; + case B_DAEMON_PACKAGE_ALREADY_EXISTS: + error = "package already exists"; + break; + default: + error = "unknown error"; + break; + } + } else + error = strerror(fError); + + BString string; + if (!fErrorMessage.IsEmpty()) { + string = fErrorMessage; + string << ": "; + } + + string << error; + + if (!fPackageName.IsEmpty()) + string << ", package: \"" << fPackageName << '"'; + + return string; + } + +private: + int32 fError; + BString fErrorMessage; + BString fPackageName; +}; + + +// #pragma mark - CommitTransactionHandler + + +struct Volume::CommitTransactionHandler { + CommitTransactionHandler(Volume* volume, BMessage* request, BMessage& reply) + : + fVolume(volume), + fRequest(request), + fReply(reply), + fPackagesToActivate(20, true), + fPackagesToDeactivate(), + fAddedPackages(), + fRemovedPackages() + { + } + + void HandleRequest() + { + // check the change count + int64 changeCount; + if (fRequest->FindInt64("change count", &changeCount) != B_OK) + throw Exception(B_DAEMON_CHANGE_COUNT_MISMATCH); + + // collect the packages to deactivate + _GetPackagesToDeactivate(); + + // read the packages to activate + _ReadPackagesToActivate(); + + // anything to do at all? + if (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.empty()) { + throw Exception(B_DAEMON_BAD_REQUEST, + "no packages to activate or deactivate"); + } + + // create an old state directory + _CreateOldStateDirectory(); + + // move packages to deactivate to old state directory + _RemovePackagesToDeactivate(); + + // move packages to activate to packages directory + _AddPackagesToActivate(); + + // activate/deactivate packages + fVolume->_ChangePackageActivation(fAddedPackages, fRemovedPackages); + + // removed packages have been deleted, new packages shall not be deleted + fAddedPackages.clear(); + fRemovedPackages.clear(); + fPackagesToActivate.MakeEmpty(false); + fPackagesToDeactivate.clear(); + } + + void Revert() + { + // move packages to activate back to transaction directory + _RevertAddPackagesToActivate(); + + // move packages to deactivate back to packages directory + _RevertRemovePackagesToDeactivate(); + + // remove old state directory + _RemoveOldStateDirectory(); + } + +private: + typedef BObjectList PackageList; + + void _GetPackagesToDeactivate() + { + static const char* const kPackagesToDeactivateFieldName = "deactivate"; + + // get the number of packages to activate + type_code type; + int32 packagesToDeactivateCount; + if (fRequest->GetInfo(kPackagesToDeactivateFieldName, &type, + &packagesToDeactivateCount) != B_OK) { + // the field is missing, i.e. no packages shall be deactivated + return; + } + + for (int32 i = 0; i < packagesToDeactivateCount; i++) { + const char* packageName; + status_t error = fRequest->FindString( + kPackagesToDeactivateFieldName, i, &packageName); + if (error != B_OK) + throw Exception(error); + + Package* package = fVolume->fPackagesByFileName.Lookup(packageName); + if (package == NULL) { + throw Exception(B_DAEMON_NO_SUCH_PACKAGE, "no such package", + packageName); + } + + fPackagesToDeactivate.insert(package); + + package->IncrementEntryRemovedIgnoreLevel(); + } + } + + void _ReadPackagesToActivate() + { + static const char* const kPackagesToActivateFieldName = "activate"; + + // get the number of packages to activate + type_code type; + int32 packagesToActivateCount; + if (fRequest->GetInfo(kPackagesToActivateFieldName, &type, + &packagesToActivateCount) != B_OK) { + // the field is missing, i.e. no packages shall be activated + return; + } + + // get the name of the transaction directory + BString transactionDirectoryName; + if (packagesToActivateCount > 0) { + if (fRequest->FindString("transaction", &transactionDirectoryName) + != B_OK) { + throw Exception(B_DAEMON_BAD_REQUEST); + } + } + + // check the name -- we only allow a simple subdirectory of the admin + // directory + if (transactionDirectoryName.IsEmpty() + || transactionDirectoryName.FindFirst('/') >= 0 + || transactionDirectoryName == "." + || transactionDirectoryName == "..") { + throw Exception(B_DAEMON_BAD_REQUEST); + } + + // open the directory + RelativePath directoryPath(kAdminDirectoryName, + transactionDirectoryName); + BDirectory directory; + status_t error = fVolume->_OpenPackagesSubDirectory(directoryPath, + false, directory); + if (error != B_OK) + throw Exception(error, "failed to open transaction directory"); + + error = directory.GetNodeRef(&fTransactionDirectoryRef); + if (error != B_OK) { + throw Exception(error, + "failed to get transaction directory node ref"); + } + + // read the packages + for (int32 i = 0; i < packagesToActivateCount; i++) { + const char* packageName; + error = fRequest->FindString(kPackagesToActivateFieldName, i, + &packageName); + if (error != B_OK) + throw Exception(error); + + // make sure it doesn't clash with an already existing package + Package* package = fVolume->fPackagesByFileName.Lookup(packageName); + if (package != NULL + && fPackagesToDeactivate.find(package) + == fPackagesToDeactivate.end()) { + throw Exception(B_DAEMON_PACKAGE_ALREADY_EXISTS, NULL, + packageName); + } + + // read the package + entry_ref entryRef; + entryRef.device = fTransactionDirectoryRef.device; + entryRef.directory = fTransactionDirectoryRef.node; + if (entryRef.set_name(packageName) != B_OK) + throw Exception(B_NO_MEMORY); + + package = new(std::nothrow) Package; + if (package == NULL || !fPackagesToActivate.AddItem(package)) { + delete package; + throw Exception(B_NO_MEMORY); + } + + error = package->Init(entryRef); + if (error != B_OK) + throw Exception(error, "failed to read package", packageName); + + package->IncrementEntryCreatedIgnoreLevel(); + } + } + + void _CreateOldStateDirectory() + { + // construct a nice name from the current date and time + time_t nowSeconds = time(NULL); + struct tm now; + BString baseName; + if (localtime_r(&nowSeconds, &now) != NULL) { + baseName.SetToFormat("state_%d-%02d-%02d_%02d:%02d:%02d", + 1900 + now.tm_year, now.tm_mon + 1, now.tm_mday, now.tm_hour, + now.tm_min, now.tm_sec); + } else + baseName = "state"; + + if (baseName.IsEmpty()) + throw Exception(B_NO_MEMORY); + + // make sure the directory doesn't exist yet + BDirectory adminDirectory; + status_t error = fVolume->_OpenPackagesSubDirectory( + RelativePath(kAdminDirectoryName), true, adminDirectory); + if (error != B_OK) + throw Exception(error, "failed to open administrative directory"); + + int uniqueId = 1; + BString directoryName = baseName; + while (BEntry(&adminDirectory, directoryName).Exists()) { + directoryName.SetToFormat("%s-%d", baseName.String(), uniqueId++); + if (directoryName.IsEmpty()) + throw Exception(B_NO_MEMORY); + } + + // create the directory + error = adminDirectory.CreateDirectory(directoryName, + &fOldStateDirectory); + if (error != B_OK) + throw Exception(error, "failed to create old state directory"); + + fOldStateDirectoryName = directoryName; + + // write the old activation file + BEntry activationFile; + error = fVolume->_WriteActivationFile( + RelativePath(kAdminDirectoryName, directoryName), + kActivationFileName, PackageSet(), PackageSet(), activationFile); + if (error != B_OK) + throw Exception(error, "failed to write old activation file"); + + // add the old state directory to the reply + error = fReply.AddString("old state", fOldStateDirectoryName); + if (error != B_OK) + throw Exception(error, "failed to add field to reply"); + } + + void _RemovePackagesToDeactivate() + { + if (fPackagesToDeactivate.empty()) + return; + + for (PackageSet::const_iterator it = fPackagesToDeactivate.begin(); + it != fPackagesToDeactivate.end(); ++it) { + // get an BEntry for the package + Package* package = *it; + entry_ref entryRef; + entryRef.device = fVolume->fPackagesDirectoryRef.device; + entryRef.directory = fVolume->fPackagesDirectoryRef.node; + if (entryRef.set_name(package->FileName()) != B_OK) + throw Exception(B_NO_MEMORY); + + BEntry entry; + status_t error = entry.SetTo(&entryRef); + if (error != B_OK) { + throw Exception(error, "failed to get package entry", + package->FileName()); + } + + // move entry + fRemovedPackages.insert(package); + + error = entry.MoveTo(&fOldStateDirectory); + if (error != B_OK) { + fRemovedPackages.erase(package); + throw Exception(error, + "failed to move old package from packages directory", + package->FileName()); + } + } + } + + void _AddPackagesToActivate() + { + if (fPackagesToActivate.IsEmpty()) + return; + + // open packages directory + BDirectory packagesDirectory; + status_t error + = packagesDirectory.SetTo(&fVolume->fPackagesDirectoryRef); + if (error != B_OK) + throw Exception(error, "failed to open packages directory"); + + int32 count = fPackagesToActivate.CountItems(); + for (int32 i = 0; i < count; i++) { + // get an BEntry for the package + Package* package = fPackagesToActivate.ItemAt(i); + entry_ref entryRef; + entryRef.device = fTransactionDirectoryRef.device; + entryRef.directory = fTransactionDirectoryRef.node; + if (entryRef.set_name(package->FileName()) != B_OK) + throw Exception(B_NO_MEMORY); + + BEntry entry; + error = entry.SetTo(&entryRef); + if (error != B_OK) { + throw Exception(error, "failed to get package entry", + package->FileName()); + } + + // move entry + fAddedPackages.insert(package); + + error = entry.MoveTo(&packagesDirectory); + if (error != B_OK) { + fAddedPackages.erase(package); + throw Exception(error, + "failed to move new package to packages directory", + package->FileName()); + } + + // also add the package to the volume + fVolume->_AddPackage(package); + } + } + + void _RevertAddPackagesToActivate() + { + if (fAddedPackages.empty()) + return; + + // open transaction directory + BDirectory transactionDirectory; + status_t error = transactionDirectory.SetTo(&fTransactionDirectoryRef); + if (error != B_OK) { + ERROR("failed to open transaction directory: %s\n", + strerror(error)); + } + + for (PackageSet::iterator it = fAddedPackages.begin(); + it != fAddedPackages.end(); ++it) { + // remove package from the volume + Package* package = *it; + fVolume->_RemovePackage(package); + + if (transactionDirectory.InitCheck() != B_OK) + continue; + + // get BEntry for the package + entry_ref entryRef; + entryRef.device = fVolume->fPackagesDirectoryRef.device; + entryRef.directory = fVolume->fPackagesDirectoryRef.node; + if (entryRef.set_name(package->FileName()) != B_OK) { + ERROR("out of memory\n"); + continue; + } + + BEntry entry; + error = entry.SetTo(&entryRef); + if (error != B_OK) { + ERROR("failed to get entry for package \"%s\": %s\n", + package->FileName().String(), strerror(error)); + continue; + } + + // move entry + error = entry.MoveTo(&transactionDirectory); + if (error != B_OK) { + ERROR("failed to move new package \"%s\" back to transaction " + "directory: %s\n", package->FileName().String(), + strerror(error)); + continue; + } + } + } + + void _RevertRemovePackagesToDeactivate() + { + if (fRemovedPackages.empty()) + return; + + // open packages directory + BDirectory packagesDirectory; + status_t error + = packagesDirectory.SetTo(&fVolume->fPackagesDirectoryRef); + if (error != B_OK) { + throw Exception(error, "failed to open packages directory"); + ERROR("failed to open packages directory: %s\n", + strerror(error)); + return; + } + + for (PackageSet::iterator it = fRemovedPackages.begin(); + it != fRemovedPackages.end(); ++it) { + // get an BEntry for the package + Package* package = *it; + BEntry entry; + status_t error = entry.SetTo(&fOldStateDirectory, + package->FileName()); + if (error != B_OK) { + ERROR("failed to get entry for package \"%s\": %s\n", + package->FileName().String(), strerror(error)); + continue; + } + + // move entry + error = entry.MoveTo(&packagesDirectory); + if (error != B_OK) { + ERROR("failed to move old package \"%s\" back to packages " + "directory: %s\n", package->FileName().String(), + strerror(error)); + continue; + } + } + } + + void _RemoveOldStateDirectory() + { + if (fOldStateDirectory.InitCheck() != B_OK) + return; + + // remove the old activation file (it won't exist, if creating it + // failed) + BEntry(&fOldStateDirectory, kActivationFileName).Remove(); + + // Now the directory should be empty. If it isn't, it still contains + // some old package file, which we failed to move back. + BEntry entry; + status_t error = fOldStateDirectory.GetEntry(&entry); + if (error != B_OK) { + ERROR("failed to get entry for old state directory: %s\n", + strerror(error)); + return; + } + + error = entry.Remove(); + if (error != B_OK) { + ERROR("failed to remove old state directory: %s\n", + strerror(error)); + return; + } + } + +private: + Volume* fVolume; + BMessage* fRequest; + BMessage& fReply; + PackageList fPackagesToActivate; + PackageSet fPackagesToDeactivate; + PackageSet fAddedPackages; + PackageSet fRemovedPackages; + BDirectory fOldStateDirectory; + BString fOldStateDirectoryName; + node_ref fTransactionDirectoryRef; +}; + + // #pragma mark - Volume @@ -383,6 +986,42 @@ Volume::HandleGetLocationInfoRequest(BMessage* message) } +void +Volume::HandleCommitTransactionRequest(BMessage* message) +{ + // Prepare the reply in so far that we can at least set the error code + // without risk of failure. + BMessage reply(B_MESSAGE_COMMIT_TRANSACTION_REPLY); + if (reply.AddInt32("error", B_ERROR) != B_OK) + return; + + // perform the request + CommitTransactionHandler handler(this, message, reply); + int32 error; + try { + handler.HandleRequest(); + error = B_DAEMON_OK; + } catch (Exception& exception) { + error = exception.Error(); + + if (!exception.ErrorMessage().IsEmpty()) + reply.AddString("error message", exception.ErrorMessage()); + if (!exception.PackageName().IsEmpty()) + reply.AddString("error package", exception.PackageName()); + } catch (std::bad_alloc& exception) { + error = B_NO_MEMORY; + } + + // revert on error + if (error != B_DAEMON_OK) + handler.Revert(); + + // send the reply + reply.ReplaceInt32("error", error); + message->SendReply(&reply, (BHandler*)NULL, kCommunicationTimeout); +} + + void Volume::Unmounted() { @@ -477,127 +1116,19 @@ Volume::ProcessPendingPackageActivationChanges() { if (!HasPendingPackageActivationChanges()) return; -INFORM("Volume::ProcessPendingPackageActivationChanges(): activating %zu, deactivating %zu packages\n", -fPackagesToBeActivated.size(), fPackagesToBeDeactivated.size()); - // compute the size of the allocation we need for the activation change - // request - int32 itemCount - = fPackagesToBeActivated.size() + fPackagesToBeDeactivated.size(); - size_t requestSize = sizeof(PackageFSActivationChangeRequest) - + itemCount * sizeof(PackageFSActivationChangeItem); - - for (PackageSet::iterator it = fPackagesToBeActivated.begin(); - it != fPackagesToBeActivated.end(); ++it) { - requestSize += (*it)->FileName().Length() + 1; - } - - for (PackageSet::iterator it = fPackagesToBeDeactivated.begin(); - it != fPackagesToBeDeactivated.end(); ++it) { - requestSize += (*it)->FileName().Length() + 1; - } - - // allocate and prepare the request - PackageFSActivationChangeRequest* request - = (PackageFSActivationChangeRequest*)malloc(requestSize); - if (request == NULL) { - ERROR("out of memory\n"); - return; - } - MemoryDeleter requestDeleter(request); - - request->itemCount = itemCount; - - PackageFSActivationChangeItem* item = &request->items[0]; - char* nameBuffer = (char*)(item + itemCount); - - for (PackageSet::iterator it = fPackagesToBeActivated.begin(); - it != fPackagesToBeActivated.end(); ++it, item++) { - _FillInActivationChangeItem(item, PACKAGE_FS_ACTIVATE_PACKAGE, *it, - nameBuffer); - } - - for (PackageSet::iterator it = fPackagesToBeDeactivated.begin(); - it != fPackagesToBeDeactivated.end(); ++it, item++) { - _FillInActivationChangeItem(item, PACKAGE_FS_DEACTIVATE_PACKAGE, *it, - nameBuffer); - } - - // issue the request - int fd = OpenRootDirectory(); - if (fd < 0) { - ERROR("Volume::ProcessPendingPackageActivationChanges(): failed to " - "open root directory: %s", strerror(fd)); - return; - } - FileDescriptorCloser fdCloser(fd); - - if (ioctl(fd, PACKAGE_FS_OPERATION_CHANGE_ACTIVATION, request, requestSize) - != 0) { -// TODO: We need more error information and error handling! - ERROR("Volume::ProcessPendingPackageActivationChanges(): failed to " - "activate packages: %s\n", strerror(errno)); - return; - } - - // Update our state, i.e. remove deactivated packages and mark activated - // packages accordingly. - for (PackageSet::iterator it = fPackagesToBeActivated.begin(); - it != fPackagesToBeActivated.end(); ++it) { - (*it)->SetActive(true); - fChangeCount++; - } - - for (PackageSet::iterator it = fPackagesToBeDeactivated.begin(); - it != fPackagesToBeDeactivated.end(); ++it) { - Package* package = *it; - _RemovePackage(package); - delete package; + try { + _ChangePackageActivation(fPackagesToBeActivated, + fPackagesToBeDeactivated); + } catch (Exception& exception) { + ERROR("Volume::ProcessPendingPackageActivationChanges(): package " + "activation failed: %s\n", exception.ToString().String()); +// TODO: Notify the user! } + // clear the activation/deactivation sets in any event fPackagesToBeActivated.clear(); fPackagesToBeDeactivated.clear(); - - // write the package activation file - - // create the content - BString activationFileContent; - for (PackageFileNameHashTable::Iterator it - = fPackagesByFileName.GetIterator(); it.HasNext();) { - Package* package = it.Next(); - if (package->IsActive()) - activationFileContent << package->FileName() << '\n'; - } - - // open and write the temporary file - BFile activationFile; - BEntry activationFileEntry; - status_t error = _OpenPackagesFile(kAdminDirectoryName, - kTemporaryActivationFileName, - B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE, activationFile, - &activationFileEntry); - if (error != B_OK) { - ERROR("Volume::ProcessPendingPackageActivationChanges(): failed to " - "create activation file: %s\n", strerror(error)); - return; - } - - ssize_t bytesWritten = activationFile.Write(activationFileContent.String(), - activationFileContent.Length()); - if (bytesWritten < 0) { - ERROR("Volume::ProcessPendingPackageActivationChanges(): failed to " - "write activation file: %s\n", strerror(bytesWritten)); - return; - } - - // rename the temporary file to the final file - error = activationFileEntry.Rename(kActivationFileName, true); - if (error != B_OK) { - ERROR("Volume::ProcessPendingPackageActivationChanges(): failed to " - "rename temporary activation file to final file: %s\n", - strerror(error)); - return; - } } @@ -681,6 +1212,13 @@ void Volume::_PackagesEntryCreated(const char* name) { INFORM("Volume::_PackagesEntryCreated(\"%s\")\n", name); + // Ignore the event, if we generated it ourselves. + Package* package = fPackagesByFileName.Lookup(name); + if (package->EntryCreatedIgnoreLevel() > 0) { + package->DecrementEntryCreatedIgnoreLevel(); + return; + } + entry_ref entry; entry.device = fPackagesDirectoryRef.device; entry.directory = fPackagesDirectoryRef.node; @@ -690,7 +1228,7 @@ INFORM("Volume::_PackagesEntryCreated(\"%s\")\n", name); return; } - Package* package = new(std::nothrow) Package; + package = new(std::nothrow) Package; if (package == NULL) { ERROR("out of memory\n"); return; @@ -723,6 +1261,12 @@ INFORM("Volume::_PackagesEntryRemoved(\"%s\")\n", name); if (package == NULL) return; + // Ignore the event, if we generated it ourselves. + if (package->EntryRemovedIgnoreLevel() > 0) { + package->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); @@ -907,16 +1451,17 @@ Volume::_AddRepository(BSolver* solver, BSolverRepository& repository, status_t -Volume::_OpenPackagesFile(const char* subDirectoryPath, const char* fileName, - uint32 openMode, BFile& _file, BEntry* _entry) +Volume::_OpenPackagesFile(const RelativePath& subDirectoryPath, + const char* fileName, uint32 openMode, BFile& _file, BEntry* _entry) { BDirectory directory; - if (subDirectoryPath != NULL) { + if (!subDirectoryPath.IsEmpty()) { status_t error = _OpenPackagesSubDirectory(subDirectoryPath, (openMode & B_CREATE_FILE) != 0, directory); if (error != B_OK) { ERROR("Volume::_OpenPackagesFile(): failed to open packages " - "subdirectory \"%s\": %s\n", subDirectoryPath, strerror(error)); + "subdirectory \"%s\": %s\n", + subDirectoryPath.ToString().String(), strerror(error)); RETURN_ERROR(error); } } else { @@ -942,7 +1487,7 @@ Volume::_OpenPackagesFile(const char* subDirectoryPath, const char* fileName, status_t -Volume::_OpenPackagesSubDirectory(const char* path, bool create, +Volume::_OpenPackagesSubDirectory(const RelativePath& path, bool create, BDirectory& _directory) { // open the packages directory @@ -954,16 +1499,22 @@ Volume::_OpenPackagesSubDirectory(const char* path, bool create, RETURN_ERROR(error); } + // get a string for the path + BString pathString = path.ToString(); + if (pathString.IsEmpty()) + RETURN_ERROR(B_NO_MEMORY); + // If creating is not allowed, just try to open it. if (!create) - RETURN_ERROR(_directory.SetTo(&directory, path)); + RETURN_ERROR(_directory.SetTo(&directory, pathString)); // get an absolute path and create the subdirectory BPath absolutePath; - error = absolutePath.SetTo(&directory, path); + error = absolutePath.SetTo(&directory, pathString); if (error != B_OK) { ERROR("Volume::_OpenConfigSubDirectory(): failed to get absolute path " - "for subdirectory \"%s\": %s\n", path, strerror(error)); + "for subdirectory \"%s\": %s\n", pathString.String(), + strerror(error)); RETURN_ERROR(error); } @@ -971,9 +1522,197 @@ Volume::_OpenPackagesSubDirectory(const char* path, bool create, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); if (error != B_OK) { ERROR("Volume::_OpenConfigSubDirectory(): failed to create packages " - "subdirectory \"%s\": %s\n", path, strerror(error)); + "subdirectory \"%s\": %s\n", pathString.String(), + strerror(error)); RETURN_ERROR(error); } - RETURN_ERROR(_directory.SetTo(&directory, path)); + RETURN_ERROR(_directory.SetTo(&directory, pathString)); +} + + +status_t +Volume::_CreateActivationFileContent(const PackageSet& toActivate, + const PackageSet& toDeactivate, BString& _content) +{ + BString activationFileContent; + for (PackageFileNameHashTable::Iterator it + = fPackagesByFileName.GetIterator(); it.HasNext();) { + Package* package = it.Next(); + if (package->IsActive() + && toDeactivate.find(package) == toDeactivate.end()) { + int32 length = activationFileContent.Length(); + activationFileContent << package->FileName() << '\n'; + if (activationFileContent.Length() + < length + package->FileName().Length() + 1) { + return B_NO_MEMORY; + } + } + } + + for (PackageSet::const_iterator it = toActivate.begin(); + it != toActivate.end(); ++it) { + Package* package = *it; + int32 length = activationFileContent.Length(); + activationFileContent << package->FileName() << '\n'; + if (activationFileContent.Length() + < length + package->FileName().Length() + 1) { + return B_NO_MEMORY; + } + } + + _content = activationFileContent; + return B_OK; +} + + +status_t +Volume::_WriteActivationFile(const RelativePath& directoryPath, + const char* fileName, const PackageSet& toActivate, + const PackageSet& toDeactivate, + BEntry& _entry) +{ + // create the content + BString activationFileContent; + status_t error = _CreateActivationFileContent(fPackagesToBeActivated, + fPackagesToBeDeactivated, activationFileContent); + if (error != B_OK) + return error; + + // write the file + BEntry activationFileEntry; + error = _WriteTextFile(directoryPath, fileName, activationFileContent, + activationFileEntry); + if (error != B_OK) { + ERROR("Volume::_WriteActivationFile(): failed to write activation " + "file \"%s/%s\": %s\n", directoryPath.ToString().String(), fileName, + strerror(error)); + return error; + } + + return B_OK; +} + + +status_t +Volume::_WriteTextFile(const RelativePath& directoryPath, const char* fileName, + const BString& content, BEntry& _entry) +{ + BFile file; + status_t error = _OpenPackagesFile(directoryPath, + fileName, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE, file, &_entry); + if (error != B_OK) { + ERROR("Volume::_WriteTextFile(): failed to create file \"%s/%s\": %s\n", + directoryPath.ToString().String(), fileName, strerror(error)); + return error; + } + + ssize_t bytesWritten = file.Write(content.String(), + content.Length()); + if (bytesWritten < 0) { + ERROR("Volume::_WriteTextFile(): failed to write file \"%s/%s\": %s\n", + directoryPath.ToString().String(), fileName, + strerror(bytesWritten)); + return bytesWritten; + } + + return B_OK; +} + + +void +Volume::_ChangePackageActivation(const PackageSet& packagesToActivate, + const PackageSet& packagesToDeactivate) +{ +INFORM("Volume::ProcessPendingPackageActivationChanges(): activating %zu, deactivating %zu packages\n", +packagesToActivate.size(), packagesToDeactivate.size()); + + // write the temporary package activation file + BEntry activationFileEntry; + status_t error = _WriteActivationFile(RelativePath(kAdminDirectoryName), + kTemporaryActivationFileName, packagesToActivate, packagesToDeactivate, + activationFileEntry); + if (error != B_OK) + throw Exception(error, "failed to write activation file"); + + // compute the size of the allocation we need for the activation change + // request + int32 itemCount = packagesToActivate.size() + packagesToDeactivate.size(); + size_t requestSize = sizeof(PackageFSActivationChangeRequest) + + itemCount * sizeof(PackageFSActivationChangeItem); + + for (PackageSet::iterator it = packagesToActivate.begin(); + it != packagesToActivate.end(); ++it) { + requestSize += (*it)->FileName().Length() + 1; + } + + for (PackageSet::iterator it = packagesToDeactivate.begin(); + it != packagesToDeactivate.end(); ++it) { + requestSize += (*it)->FileName().Length() + 1; + } + + // allocate and prepare the request + PackageFSActivationChangeRequest* request + = (PackageFSActivationChangeRequest*)malloc(requestSize); + if (request == NULL) + throw Exception(B_NO_MEMORY); + MemoryDeleter requestDeleter(request); + + request->itemCount = itemCount; + + PackageFSActivationChangeItem* item = &request->items[0]; + char* nameBuffer = (char*)(item + itemCount); + + for (PackageSet::iterator it = packagesToActivate.begin(); + it != packagesToActivate.end(); ++it, item++) { + _FillInActivationChangeItem(item, PACKAGE_FS_ACTIVATE_PACKAGE, *it, + nameBuffer); + } + + for (PackageSet::iterator it = packagesToDeactivate.begin(); + it != packagesToDeactivate.end(); ++it, item++) { + _FillInActivationChangeItem(item, PACKAGE_FS_DEACTIVATE_PACKAGE, *it, + nameBuffer); + } + + // issue the request + int fd = OpenRootDirectory(); + if (fd < 0) + throw Exception(fd, "failed to open root directory"); + FileDescriptorCloser fdCloser(fd); + + if (ioctl(fd, PACKAGE_FS_OPERATION_CHANGE_ACTIVATION, request, requestSize) + != 0) { +// TODO: We need more error information and error handling! + throw Exception(errno, "ioctl() to de-/activate packages failed"); + } + + // rename the temporary activation file to the final file + error = activationFileEntry.Rename(kActivationFileName, true); + if (error != B_OK) { + throw Exception(error, + "failed to rename temporary activation file to final file"); +// TODO: We should probably try to reverse the activation changes, though that +// will fail, if this method has been called in response to node monitoring +// events. Alternatively moving the package activation file could be made part +// of the ioctl(), since packagefs should be able to undo package changes until +// the very end, unless running out of memory. In the end the situation would be +// bad anyway, though, since the activation file may refer to removed packages +// and things would be in an inconsistent state after rebooting. + } + + // Update our state, i.e. remove deactivated packages and mark activated + // packages accordingly. + for (PackageSet::iterator it = packagesToActivate.begin(); + it != packagesToActivate.end(); ++it) { + (*it)->SetActive(true); + fChangeCount++; + } + + for (PackageSet::iterator it = fPackagesToBeDeactivated.begin(); + it != fPackagesToBeDeactivated.end(); ++it) { + Package* package = *it; + _RemovePackage(package); + delete package; + } } diff --git a/src/servers/package/Volume.h b/src/servers/package/Volume.h index 8e65ff3fe1..51b644b78b 100644 --- a/src/servers/package/Volume.h +++ b/src/servers/package/Volume.h @@ -50,6 +50,8 @@ public: void InitialVerify(Volume* nextVolume, Volume* nextNextVolume); void HandleGetLocationInfoRequest(BMessage* message); + void HandleCommitTransactionRequest( + BMessage* message); void Unmounted(); @@ -88,8 +90,13 @@ public: private: struct NodeMonitorEvent; - typedef DoublyLinkedList NodeMonitorEventList; + struct RelativePath; + struct Exception; + struct CommitTransactionHandler; + friend struct CommitTransactionHandler; + + typedef DoublyLinkedList NodeMonitorEventList; typedef std::set PackageSet; private: @@ -117,11 +124,34 @@ private: BSolverRepository& repository, bool activeOnly, bool installed); - status_t _OpenPackagesFile(const char* subDirectoryPath, + status_t _OpenPackagesFile( + const RelativePath& subDirectoryPath, const char* fileName, uint32 openMode, BFile& _file, BEntry* _entry = NULL); - status_t _OpenPackagesSubDirectory(const char* path, - bool create, BDirectory& _directory); + status_t _OpenPackagesSubDirectory( + const RelativePath& path, bool create, + BDirectory& _directory); + + status_t _CreateActivationFileContent( + const PackageSet& toActivate, + const PackageSet& toDeactivate, + BString& _content); + status_t _WriteActivationFile( + const RelativePath& directoryPath, + const char* fileName, + const PackageSet& toActivate, + const PackageSet& toDeactivate, + BEntry& _entry); + + status_t _WriteTextFile( + const RelativePath& directoryPath, + const char* fileName, + const BString& content, BEntry& _entry); + + void _ChangePackageActivation( + const PackageSet& packagesToActivate, + const PackageSet& packagesToDeactivate); + // throws Exception private: BString fPath;