added 2 changesets to branch 'refs/remotes/HaikuPM-github/package-management' old head: 976b547224ff78d2f15a4df1413f790cba9bc98f new head: be2254e30db907dc1a8b26dd3c3b9dde014bb385 overview: https://github.com/haiku/HaikuPM/compare/976b547...be2254e ---------------------------------------------------------------------------- 8b600ba: package daemon: Use CommitTransactionHandler in all cases ... also when only activating/deactivating already moved packages. be2254e: package daemon: Handle post-installation scripts, users/groups ... specified by a package when it is going to be activated. We don't try to remove users/groups when deactivating packages yet, nor is the user properly identified in all error cases. [ Ingo Weinhold <ingo_weinhold@xxxxxx> ] ---------------------------------------------------------------------------- 1 file changed, 258 insertions(+), 17 deletions(-) src/servers/package/Volume.cpp | 275 ++++++++++++++++++++++++++++++++++--- ############################################################################ Commit: 8b600ba48deafbe63747e28b705551b00adb00a7 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Sun Sep 15 10:59:20 2013 UTC package daemon: Use CommitTransactionHandler in all cases ... also when only activating/deactivating already moved packages. ---------------------------------------------------------------------------- diff --git a/src/servers/package/Volume.cpp b/src/servers/package/Volume.cpp index 7007b0d..61ca65f 100644 --- a/src/servers/package/Volume.cpp +++ b/src/servers/package/Volume.cpp @@ -297,23 +297,25 @@ struct Volume::CommitTransactionHandler { "no packages to activate or deactivate"); } - // create an old state directory - _CreateOldStateDirectory(reply); - - // move packages to deactivate to old state directory - _RemovePackagesToDeactivate(); + _ApplyChanges(reply); + } - // move packages to activate to packages directory - _AddPackagesToActivate(); + void HandleRequest(const PackageSet& packagesAdded, + const PackageSet& packagesRemoved) + { + // Copy package sets to fPackagesToActivate/fPackagesToDeactivate. The + // given sets are assumed to be identical to the ones specified in the + // constructor invocation (fPackagesAlreadyAdded, + // fPackagesAlreadyRemoved). + for (PackageSet::const_iterator it = packagesAdded.begin(); + it != packagesAdded.end(); ++it) { + if (!fPackagesToActivate.AddItem(*it)) + throw std::bad_alloc(); + } - // activate/deactivate packages - fVolume->_ChangePackageActivation(fAddedPackages, fRemovedPackages); + fPackagesToDeactivate = packagesRemoved; - // removed packages have been deleted, new packages shall not be deleted - fAddedPackages.clear(); - fRemovedPackages.clear(); - fPackagesToActivate.MakeEmpty(false); - fPackagesToDeactivate.clear(); + _ApplyChanges(NULL); } void Revert() @@ -439,6 +441,27 @@ private: } } + void _ApplyChanges(BMessage* reply) + { + // create an old state directory + _CreateOldStateDirectory(reply); + + // 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 _CreateOldStateDirectory(BMessage* reply) { // construct a nice name from the current date and time @@ -1187,15 +1210,29 @@ Volume::ProcessPendingPackageActivationChanges() if (!HasPendingPackageActivationChanges()) return; + // perform the request + CommitTransactionHandler handler(this, fPackagesToBeActivated, + fPackagesToBeDeactivated); + int32 error; try { - _ChangePackageActivation(fPackagesToBeActivated, - fPackagesToBeDeactivated); + handler.HandleRequest(fPackagesToBeActivated, fPackagesToBeDeactivated); + error = B_DAEMON_OK; } catch (Exception& exception) { + error = exception.Error(); ERROR("Volume::ProcessPendingPackageActivationChanges(): package " "activation failed: %s\n", exception.ToString().String()); // TODO: Notify the user! + } catch (std::bad_alloc& exception) { + error = B_NO_MEMORY; + ERROR("Volume::ProcessPendingPackageActivationChanges(): package " + "activation failed: out of memory\n"); +// TODO: Notify the user! } + // revert on error + if (error != B_DAEMON_OK) + handler.Revert(); + // clear the activation/deactivation sets in any event fPackagesToBeActivated.clear(); fPackagesToBeDeactivated.clear(); ############################################################################ Commit: be2254e30db907dc1a8b26dd3c3b9dde014bb385 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Wed Sep 18 21:55:13 2013 UTC package daemon: Handle post-installation scripts, users/groups ... specified by a package when it is going to be activated. We don't try to remove users/groups when deactivating packages yet, nor is the user properly identified in all error cases. ---------------------------------------------------------------------------- diff --git a/src/servers/package/Volume.cpp b/src/servers/package/Volume.cpp index 61ca65f..0e732f8 100644 --- a/src/servers/package/Volume.cpp +++ b/src/servers/package/Volume.cpp @@ -10,11 +10,15 @@ #include "Volume.h" #include <errno.h> +#include <grp.h> +#include <pwd.h> #include <stdlib.h> #include <sys/stat.h> #include <time.h> #include <unistd.h> +#include <string> + #include <Directory.h> #include <Entry.h> #include <File.h> @@ -40,6 +44,8 @@ using namespace BPackageKit::BPrivate; +typedef std::set<std::string> StringSet; + static const char* const kPackageFileNameExtension = ".hpkg"; static const char* const kAdminDirectoryName @@ -54,6 +60,8 @@ static const bigtime_t kHandleNodeMonitorEvents = 'nmon'; static const bigtime_t kNodeMonitorEventHandlingDelay = 500000; static const bigtime_t kCommunicationTimeout = 1000000; +const char* const kShellEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!"; + // #pragma mark - Listener @@ -259,7 +267,9 @@ struct Volume::CommitTransactionHandler { fAddedPackages(), fRemovedPackages(), fPackagesAlreadyAdded(packagesAlreadyAdded), - fPackagesAlreadyRemoved(packagesAlreadyRemoved) + fPackagesAlreadyRemoved(packagesAlreadyRemoved), + fAddedGroups(), + fAddedUsers() { } @@ -326,6 +336,9 @@ struct Volume::CommitTransactionHandler { // move packages to deactivate back to packages directory _RevertRemovePackagesToDeactivate(); + // revert user and group changes + _RevertUserGroupChanges(); + // remove old state directory _RemoveOldStateDirectory(); } @@ -455,6 +468,9 @@ private: // activate/deactivate packages fVolume->_ChangePackageActivation(fAddedPackages, fRemovedPackages); + // run post-installation scripts + _RunPostInstallScripts(); + // removed packages have been deleted, new packages shall not be deleted fAddedPackages.clear(); fRemovedPackages.clear(); @@ -576,6 +592,7 @@ private: if (fPackagesAlreadyAdded.find(package) != fPackagesAlreadyAdded.end()) { fAddedPackages.insert(package); + _PreparePackageToActivate(package); continue; } @@ -606,9 +623,129 @@ private: // also add the package to the volume fVolume->_AddPackage(package); + + _PreparePackageToActivate(package); + } + } + + void _PreparePackageToActivate(Package* package) + { + // add groups + const BStringList& groups = package->Info().Groups(); + int32 count = groups.CountStrings(); + for (int32 i = 0; i < count; i++) + _AddGroup(package, groups.StringAt(i)); + + // add users + const BObjectList<BUser>& users = package->Info().Users(); + for (int32 i = 0; const BUser* user = users.ItemAt(i); i++) + _AddUser(package, *user); + + // handle global writable files + const BObjectList<BGlobalWritableFileInfo>& files + = package->Info().GlobalWritableFileInfos(); + for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i); + i++) { + _AddGlobalWritableFile(package, *file); + } + } + + void _AddGroup(Package* package, const BString& groupName) + { + // Check whether the group already exists. + char buffer[256]; + struct group groupBuffer; + struct group* groupFound; + int error = getgrnam_r(groupName, &groupBuffer, buffer, sizeof(buffer), + &groupFound); + if ((error == 0 && groupFound != NULL) || error == ERANGE) + return; + + // add it + fAddedGroups.insert(groupName.String()); + + std::string commandLine("groupadd "); + commandLine += _ShellEscapeString(groupName).String(); + + if (system(commandLine.c_str()) != 0) { + fAddedGroups.erase(groupName.String()); + throw Exception(error, + BString().SetToFormat("failed to add group \%s\"", + groupName.String()), + package->FileName()); + } + } + + void _AddUser(Package* package, const BUser& user) + { + // Check whether the user already exists. + char buffer[256]; + struct passwd passwdBuffer; + struct passwd* passwdFound; + int error = getpwnam_r(user.Name(), &passwdBuffer, buffer, + sizeof(buffer), &passwdFound); + if ((error == 0 && passwdFound != NULL) || error == ERANGE) + return; + + // add it + fAddedUsers.insert(user.Name().String()); + + std::string commandLine("useradd "); + + if (!user.RealName().IsEmpty()) { + commandLine += std::string("-n ") + + _ShellEscapeString(user.RealName()).String() + " "; + } + + if (!user.Home().IsEmpty()) { + commandLine += std::string("-d ") + + _ShellEscapeString(user.Home()).String() + " "; + } + + if (!user.Shell().IsEmpty()) { + commandLine += std::string("-s ") + + _ShellEscapeString(user.Shell()).String() + " "; + } + + if (!user.Groups().IsEmpty()) { + commandLine += std::string("-g ") + + _ShellEscapeString(user.Groups().First()).String() + " "; + } + + commandLine += _ShellEscapeString(user.Name()).String(); + + if (system(commandLine.c_str()) != 0) { + fAddedUsers.erase(user.Name().String()); + throw Exception(error, + BString().SetToFormat("failed to add user \%s\"", + user.Name().String()), + package->FileName()); + } + + // add the supplementary groups + int32 groupCount = user.Groups().CountStrings(); + for (int32 i = 1; i < groupCount; i++) { + commandLine = std::string("groupmod -A ") + + _ShellEscapeString(user.Name()).String() + + " " + + _ShellEscapeString(user.Groups().StringAt(i)).String(); + if (system(commandLine.c_str()) != 0) { + fAddedUsers.erase(user.Name().String()); + throw Exception(error, + BString().SetToFormat("failed to add user \%s\" to group " + "\"%s\"", user.Name().String(), + user.Groups().StringAt(i).String()), + package->FileName()); + } } } + void _AddGlobalWritableFile(Package* package, + const BGlobalWritableFileInfo& file) + { +// TODO:... + } + void _RevertAddPackagesToActivate() { if (fAddedPackages.empty()) @@ -709,6 +846,27 @@ private: } } + void _RevertUserGroupChanges() + { + // delete users + for (StringSet::const_iterator it = fAddedUsers.begin(); + it != fAddedUsers.end(); ++it) { + std::string commandLine("userdel "); + commandLine += _ShellEscapeString(it->c_str()).String(); + if (system(commandLine.c_str()) != 0) + ERROR("failed to remove user \"%s\"\n", it->c_str()); + } + + // delete groups + for (StringSet::const_iterator it = fAddedGroups.begin(); + it != fAddedGroups.end(); ++it) { + std::string commandLine("groupdel "); + commandLine += _ShellEscapeString(it->c_str()).String(); + if (system(commandLine.c_str()) != 0) + ERROR("failed to remove group \"%s\"\n", it->c_str()); + } + } + void _RemoveOldStateDirectory() { if (fOldStateDirectory.InitCheck() != B_OK) @@ -736,6 +894,50 @@ private: } } + void _RunPostInstallScripts() + { + for (PackageSet::iterator it = fAddedPackages.begin(); + it != fAddedPackages.end(); ++it) { + Package* package = *it; + const BStringList& scripts = package->Info().PostInstallScripts(); + int32 count = scripts.CountStrings(); + for (int32 i = 0; i < count; i++) + _RunPostInstallScript(package, scripts.StringAt(i)); + } + } + + void _RunPostInstallScript(Package* package, const BString& script) + { + BDirectory rootDir(&fVolume->fRootDirectoryRef); + BPath scriptPath(&rootDir, script); + status_t error = scriptPath.InitCheck(); + if (error != B_OK) { + ERROR("Volume::CommitTransactionHandler::_RunPostInstallScript(): " + "failed get path of post-installation script \"%s\" of package " + "%s: %s\n", script.String(), package->FileName().String(), + strerror(error)); +// TODO: Notify the user! + return; + } + + if (system(scriptPath.Path()) != 0) { + ERROR("Volume::CommitTransactionHandler::_RunPostInstallScript(): " + "running post-installation script \"%s\" of package %s " + "failed: %s\n", script.String(), package->FileName().String(), + strerror(error)); +// TODO: Notify the user! + } + } + + BString _ShellEscapeString(const BString& string) + { + BString result(string); + result.CharacterEscape(kShellEscapeCharacters, '\\'); + if (result.IsEmpty()) + throw std::bad_alloc(); + return result; + } + private: Volume* fVolume; PackageList fPackagesToActivate; @@ -747,6 +949,8 @@ private: BDirectory fOldStateDirectory; BString fOldStateDirectoryName; node_ref fTransactionDirectoryRef; + StringSet fAddedGroups; + StringSet fAddedUsers; };