diff --git a/src/servers/package/CommitTransactionHandler.cpp b/src/servers/package/CommitTransactionHandler.cpp new file mode 100644 index 0000000000..0f672ccf87 --- /dev/null +++ b/src/servers/package/CommitTransactionHandler.cpp @@ -0,0 +1,1275 @@ +/* + * Copyright 2013-2014, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ + + +#include "CommitTransactionHandler.h" + +#include +#include + +#include + +#include +#include +#include +#include + +#include "Constants.h" +#include "DebugSupport.h" +#include "Exception.h" +#include "VolumeState.h" + + +using namespace BPackageKit::BPrivate; + + +CommitTransactionHandler::CommitTransactionHandler(Volume* volume, + const PackageSet& packagesAlreadyAdded, + const PackageSet& packagesAlreadyRemoved) + : + fVolume(volume), + fPackagesToActivate(), + fPackagesToDeactivate(), + fAddedPackages(), + fRemovedPackages(), + fPackagesAlreadyAdded(packagesAlreadyAdded), + fPackagesAlreadyRemoved(packagesAlreadyRemoved), + fAddedGroups(), + fAddedUsers(), + fFSTransaction() +{ +} + + +CommitTransactionHandler::~CommitTransactionHandler() +{ + // Delete Package objects we created in case of error (on success + // fPackagesToActivate will be empty). + int32 count = fPackagesToActivate.CountItems(); + for (int32 i = 0; i < count; i++) { + Package* package = fPackagesToActivate.ItemAt(i); + if (fPackagesAlreadyAdded.find(package) + == fPackagesAlreadyAdded.end()) { + delete package; + } + } +} + + +void +CommitTransactionHandler::HandleRequest(BMessage* request, BMessage* reply) +{ + status_t error; + BActivationTransaction transaction(request, &error); + if (error == B_OK) + error = transaction.InitCheck(); + if (error != B_OK) { + if (error == B_NO_MEMORY) + throw Exception(B_NO_MEMORY); + throw Exception(B_DAEMON_BAD_REQUEST); + } + + HandleRequest(transaction, reply); +} + + +void +CommitTransactionHandler::HandleRequest( + const BActivationTransaction& transaction, BMessage* reply) +{ + // check the change count + if (transaction.ChangeCount() != fVolume->fState->ChangeCount()) + throw Exception(B_DAEMON_CHANGE_COUNT_MISMATCH); + + // collect the packages to deactivate + _GetPackagesToDeactivate(transaction); + + // read the packages to activate + _ReadPackagesToActivate(transaction); + + // anything to do at all? + if (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.empty()) { + throw Exception(B_DAEMON_BAD_REQUEST, + "no packages to activate or deactivate"); + } + + _ApplyChanges(reply); +} + + +void +CommitTransactionHandler::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(); + } + + fPackagesToDeactivate = packagesRemoved; + + _ApplyChanges(NULL); +} + + +void +CommitTransactionHandler::Revert() +{ + // move packages to activate back to transaction directory + _RevertAddPackagesToActivate(); + + // move packages to deactivate back to packages directory + _RevertRemovePackagesToDeactivate(); + + // revert user and group changes + _RevertUserGroupChanges(); + + // Revert all other FS operations, i.e. the writable files changes as + // well as the creation of the old state directory. + fFSTransaction.RollBack(); +} + + +void +CommitTransactionHandler::_GetPackagesToDeactivate( + const BActivationTransaction& transaction) +{ + // get the number of packages to deactivate + const BStringList& packagesToDeactivate + = transaction.PackagesToDeactivate(); + int32 packagesToDeactivateCount = packagesToDeactivate.CountStrings(); + if (packagesToDeactivateCount == 0) + return; + + for (int32 i = 0; i < packagesToDeactivateCount; i++) { + BString packageName = packagesToDeactivate.StringAt(i); + Package* package = fVolume->fState->FindPackage(packageName); + if (package == NULL) { + throw Exception(B_DAEMON_NO_SUCH_PACKAGE, "no such package", + packageName); + } + + fPackagesToDeactivate.insert(package); + + if (fPackagesAlreadyRemoved.find(package) + == fPackagesAlreadyRemoved.end()) { + package->IncrementEntryRemovedIgnoreLevel(); + } + } +} + + +void +CommitTransactionHandler::_ReadPackagesToActivate( + const BActivationTransaction& transaction) +{ + // get the number of packages to activate + const BStringList& packagesToActivate + = transaction.PackagesToActivate(); + int32 packagesToActivateCount = packagesToActivate.CountStrings(); + if (packagesToActivateCount == 0) + return; + + // check the transaction directory name -- we only allow a simple + // subdirectory of the admin directory + const BString& transactionDirectoryName + = transaction.TransactionDirectoryName(); + 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++) { + BString packageName = packagesToActivate.StringAt(i); + + // make sure it doesn't clash with an already existing package + Package* package = fVolume->fState->FindPackage(packageName); + if (package != NULL) { + if (fPackagesAlreadyAdded.find(package) + != fPackagesAlreadyAdded.end()) { + if (!fPackagesToActivate.AddItem(package)) + throw Exception(B_NO_MEMORY); + continue; + } + + if (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 +CommitTransactionHandler::_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); + + // run post-installation scripts + _RunPostInstallScripts(); + + // removed packages have been deleted, new packages shall not be deleted + fAddedPackages.clear(); + fRemovedPackages.clear(); + fPackagesToActivate.MakeEmpty(false); + fPackagesToDeactivate.clear(); +} + + +void +CommitTransactionHandler::_CreateOldStateDirectory(BMessage* reply) +{ + // 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 + FSTransaction::CreateOperation createOldStateDirectoryOperation( + &fFSTransaction, FSUtils::Entry(adminDirectory, directoryName)); + + error = adminDirectory.CreateDirectory(directoryName, + &fOldStateDirectory); + if (error != B_OK) + throw Exception(error, "failed to create old state directory"); + + createOldStateDirectoryOperation.Finished(); + + 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 + if (reply != NULL) { + error = reply->AddString("old state", fOldStateDirectoryName); + if (error != B_OK) + throw Exception(error, "failed to add field to reply"); + } +} + + +void +CommitTransactionHandler::_RemovePackagesToDeactivate() +{ + if (fPackagesToDeactivate.empty()) + return; + + for (PackageSet::const_iterator it = fPackagesToDeactivate.begin(); + it != fPackagesToDeactivate.end(); ++it) { + Package* package = *it; + if (fPackagesAlreadyRemoved.find(package) + != fPackagesAlreadyRemoved.end()) { + fRemovedPackages.insert(package); + continue; + } + + // get a BEntry for the package + NotOwningEntryRef entryRef(fVolume->fPackagesDirectoryRef, + package->FileName()); + + 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 +CommitTransactionHandler::_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++) { + Package* package = fPackagesToActivate.ItemAt(i); + if (fPackagesAlreadyAdded.find(package) + != fPackagesAlreadyAdded.end()) { + fAddedPackages.insert(package); + _PreparePackageToActivate(package); + continue; + } + + // get a BEntry for the package + 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); + + _PreparePackageToActivate(package); + } +} + + +void +CommitTransactionHandler::_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& users = package->Info().Users(); + for (int32 i = 0; const BUser* user = users.ItemAt(i); i++) + _AddUser(package, *user); + + // handle global writable files + _AddGlobalWritableFiles(package); +} + + +void +CommitTransactionHandler::_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 += FSUtils::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 +CommitTransactionHandler::_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 ") + + FSUtils::ShellEscapeString(user.RealName()).String() + " "; + } + + if (!user.Home().IsEmpty()) { + commandLine += std::string("-d ") + + FSUtils::ShellEscapeString(user.Home()).String() + " "; + } + + if (!user.Shell().IsEmpty()) { + commandLine += std::string("-s ") + + FSUtils::ShellEscapeString(user.Shell()).String() + " "; + } + + if (!user.Groups().IsEmpty()) { + commandLine += std::string("-g ") + + FSUtils::ShellEscapeString(user.Groups().First()).String() + + " "; + } + + commandLine += FSUtils::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 ") + + FSUtils::ShellEscapeString(user.Name()).String() + + " " + + FSUtils::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 +CommitTransactionHandler::_AddGlobalWritableFiles(Package* package) +{ + // get the list of included files + const BObjectList& files + = package->Info().GlobalWritableFileInfos(); + BStringList contentPaths; + for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i); + i++) { + if (file->IsIncluded() && !contentPaths.Add(file->Path())) + throw std::bad_alloc(); + } + + if (contentPaths.IsEmpty()) + return; + + // Open the root directory of the installation location where we will + // extract the files -- that's the volume's root directory. + BDirectory rootDirectory; + status_t error = rootDirectory.SetTo(&fVolume->fRootDirectoryRef); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to get the root directory " + "for writable files"), + package->FileName()); + } + + // Open writable-files directory in the administrative directory. + if (fWritableFilesDirectory.InitCheck() != B_OK) { + error = fVolume->_OpenPackagesSubDirectory( + RelativePath(kAdminDirectoryName, kWritableFilesDirectoryName), + true, fWritableFilesDirectory); + + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to get the backup directory " + "for writable files"), + package->FileName()); + } + } + + // extract files into a subdir of the writable-files directory + BDirectory extractedFilesDirectory; + _ExtractPackageContent(package, contentPaths, + fWritableFilesDirectory, extractedFilesDirectory); + + for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i); + i++) { + if (file->IsIncluded()) { + _AddGlobalWritableFile(package, *file, rootDirectory, + extractedFilesDirectory); + } + } +} + + +void +CommitTransactionHandler::_AddGlobalWritableFile(Package* package, + const BGlobalWritableFileInfo& file, const BDirectory& rootDirectory, + const BDirectory& extractedFilesDirectory) +{ + // Map the path name to the actual target location. Currently this only + // concerns "settings/", which is mapped to "settings/global/". + BString targetPath(file.Path()); + if (fVolume->fMountType == PACKAGE_FS_MOUNT_TYPE_HOME) { + if (targetPath == "settings" + || targetPath.StartsWith("settings/")) { + targetPath.Insert("/global", 8); + if (targetPath.Length() == file.Path().Length()) + throw std::bad_alloc(); + } + } + + // open parent directory of the source entry + const char* lastSlash = strrchr(file.Path(), '/'); + const BDirectory* sourceDirectory; + BDirectory stackSourceDirectory; + if (lastSlash != NULL) { + sourceDirectory = &stackSourceDirectory; + BString sourceParentPath(file.Path(), + lastSlash - file.Path().String()); + if (sourceParentPath.Length() == 0) + throw std::bad_alloc(); + + status_t error = stackSourceDirectory.SetTo( + &extractedFilesDirectory, sourceParentPath); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open directory \"%s\"", + _GetPath( + FSUtils::Entry(extractedFilesDirectory, + sourceParentPath), + sourceParentPath).String()), + package->FileName()); + } + } else { + sourceDirectory = &extractedFilesDirectory; + } + + // open parent directory of the target entry -- create, if necessary + FSUtils::Path relativeSourcePath(file.Path()); + lastSlash = strrchr(targetPath, '/'); + if (lastSlash != NULL) { + BString targetParentPath(targetPath, + lastSlash - targetPath.String()); + if (targetParentPath.Length() == 0) + throw std::bad_alloc(); + + BDirectory targetDirectory; + status_t error = FSUtils::OpenSubDirectory(rootDirectory, + RelativePath(targetParentPath), true, targetDirectory); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open/create directory " + "\"%s\"", + _GetPath( + FSUtils::Entry(rootDirectory,targetParentPath), + targetParentPath).String()), + package->FileName()); + } + _AddGlobalWritableFileRecurse(package, *sourceDirectory, + relativeSourcePath, targetDirectory, lastSlash + 1, + file.UpdateType()); + } else { + _AddGlobalWritableFileRecurse(package, *sourceDirectory, + relativeSourcePath, rootDirectory, targetPath, + file.UpdateType()); + } +} + + +void +CommitTransactionHandler::_AddGlobalWritableFileRecurse(Package* package, + const BDirectory& sourceDirectory, FSUtils::Path& relativeSourcePath, + const BDirectory& targetDirectory, const char* targetName, + BWritableFileUpdateType updateType) +{ + // * If the file doesn't exist, just copy the extracted one. + // * If the file does exist, compare with the previous original version: + // * If unchanged, just overwrite it. + // * If changed, leave it to the user for now. When we support merging + // first back the file up, then try the merge. + + // Check whether the target location exists and what type the entry at + // both locations are. + struct stat targetStat; + if (targetDirectory.GetStatFor(targetName, &targetStat) != B_OK) { + // target doesn't exist -- just copy + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "couldn't get stat for writable file, copying...\n"); + FSTransaction::CreateOperation copyOperation(&fFSTransaction, + FSUtils::Entry(targetDirectory, targetName)); + status_t error = BCopyEngine(BCopyEngine::COPY_RECURSIVELY) + .CopyEntry( + FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), + FSUtils::Entry(targetDirectory, targetName)); + if (error != B_OK) { + if (targetDirectory.GetStatFor(targetName, &targetStat) == B_OK) + copyOperation.Finished(); + + throw Exception(error, + BString().SetToFormat("failed to copy entry \"%s\"", + _GetPath( + FSUtils::Entry(sourceDirectory, + relativeSourcePath.Leaf()), + relativeSourcePath).String()), + package->FileName()); + } + copyOperation.Finished(); + return; + } + + struct stat sourceStat; + status_t error = sourceDirectory.GetStatFor(relativeSourcePath.Leaf(), + &sourceStat); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to get stat data for entry " + "\"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, targetName), + targetName).String()), + package->FileName()); + } + + if ((sourceStat.st_mode & S_IFMT) != (targetStat.st_mode & S_IFMT) + || (!S_ISDIR(sourceStat.st_mode) && !S_ISREG(sourceStat.st_mode) + && !S_ISLNK(sourceStat.st_mode))) { + // Source and target entry types don't match or this is an entry + // we cannot handle. The user must handle this manually. + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "writable file exists, but type doesn't match previous type\n"); +// TODO: Notify user! + return; + } + + if (S_ISDIR(sourceStat.st_mode)) { + // entry is a directory -- recurse + BDirectory sourceSubDirectory; + error = sourceSubDirectory.SetTo(&sourceDirectory, + relativeSourcePath.Leaf()); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open directory \"%s\"", + _GetPath( + FSUtils::Entry(sourceDirectory, + relativeSourcePath.Leaf()), + relativeSourcePath).String()), + package->FileName()); + } + + BDirectory targetSubDirectory; + error = targetSubDirectory.SetTo(&targetDirectory, targetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open directory \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, targetName), + targetName).String()), + package->FileName()); + } + + entry_ref entry; + while (sourceSubDirectory.GetNextRef(&entry) == B_OK) { + relativeSourcePath.AppendComponent(entry.name); + _AddGlobalWritableFileRecurse(package, sourceSubDirectory, + relativeSourcePath, targetSubDirectory, entry.name, + updateType); + relativeSourcePath.RemoveLastComponent(); + } + + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "writable directory, recursion done\n"); + return; + } + + // get the package the target file originated from + BString originalPackage; + if (BNode(&targetDirectory, targetName).ReadAttrString( + kPackageFileAttribute, &originalPackage) != B_OK) { + // Can't determine the original package. The user must handle this + // manually. +// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "failed to get SYS:PACKAGE attribute\n"); + return; + } + + // If that's our package, we're happy. + if (originalPackage == package->RevisionedNameThrows()) { + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "file tagged with same package version we're activating\n"); + return; + } + + // Check, whether the writable-files directory for the original package + // exists. + BString originalRelativeSourcePath = BString().SetToFormat("%s/%s", + originalPackage.String(), relativeSourcePath.ToCString()); + if (originalRelativeSourcePath.IsEmpty()) + throw std::bad_alloc(); + + struct stat originalPackageStat; + if (fWritableFilesDirectory.GetStatFor(originalRelativeSourcePath, + &originalPackageStat) != B_OK + || (sourceStat.st_mode & S_IFMT) + != (originalPackageStat.st_mode & S_IFMT)) { + // Original entry doesn't exist (either we don't have the data from + // the original package or the entry really didn't exist) or its + // type differs from the expected one. The user must handle this + // manually. + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "original \"%s\" doesn't exist or has other type\n", + _GetPath(FSUtils::Entry(fWritableFilesDirectory, + originalRelativeSourcePath), + originalRelativeSourcePath).String()); + return; +// TODO: Notify user! + } + + if (S_ISREG(sourceStat.st_mode)) { + // compare file content + bool equal; + error = FSUtils::CompareFileContent( + FSUtils::Entry(fWritableFilesDirectory, + originalRelativeSourcePath), + FSUtils::Entry(targetDirectory, targetName), + equal); + // TODO: Merge support! + if (error != B_OK || !equal) { + // The comparison failed or the files differ. The user must + // handle this manually. + PRINT("Volume::CommitTransactionHandler::" + "_AddGlobalWritableFile(): " + "file comparison failed (%s) or files aren't equal\n", + strerror(error)); + return; +// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! + } + } else { + // compare symlinks + bool equal; + error = FSUtils::CompareSymLinks( + FSUtils::Entry(fWritableFilesDirectory, + originalRelativeSourcePath), + FSUtils::Entry(targetDirectory, targetName), + equal); + if (error != B_OK || !equal) { + // The comparison failed or the symlinks differ. The user must + // handle this manually. + PRINT("Volume::CommitTransactionHandler::" + "_AddGlobalWritableFile(): " + "symlink comparison failed (%s) or symlinks aren't equal\n", + strerror(error)); + return; +// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! + } + } + + // Replace the existing file/symlink. We do that in two steps: First + // copy the new file to a neighoring location, then move-replace the + // old file. + BString tempTargetName; + tempTargetName.SetToFormat("%s.%s", targetName, + package->RevisionedNameThrows().String()); + if (tempTargetName.IsEmpty()) + throw std::bad_alloc(); + + // copy + FSTransaction::CreateOperation copyOperation(&fFSTransaction, + FSUtils::Entry(targetDirectory, tempTargetName)); + + error = BCopyEngine(BCopyEngine::UNLINK_DESTINATION).CopyEntry( + FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), + FSUtils::Entry(targetDirectory, tempTargetName)); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to copy entry \"%s\"", + _GetPath( + FSUtils::Entry(sourceDirectory, + relativeSourcePath.Leaf()), + relativeSourcePath).String()), + package->FileName()); + } + + copyOperation.Finished(); + + // rename + FSTransaction::RemoveOperation renameOperation(&fFSTransaction, + FSUtils::Entry(targetDirectory, targetName), + FSUtils::Entry(fWritableFilesDirectory, + originalRelativeSourcePath)); + + BEntry targetEntry; + error = targetEntry.SetTo(&targetDirectory, tempTargetName); + if (error == B_OK) + error = targetEntry.Rename(targetName, true); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to rename entry \"%s\" to \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, tempTargetName), + tempTargetName).String(), + targetName), + package->FileName()); + } + + renameOperation.Finished(); + copyOperation.Unregister(); +} + + +void +CommitTransactionHandler::_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; + + if (fPackagesAlreadyAdded.find(package) + != fPackagesAlreadyAdded.end()) { + continue; + } + + fVolume->_RemovePackage(package); + + if (transactionDirectory.InitCheck() != B_OK) + continue; + + // get BEntry for the package + NotOwningEntryRef entryRef(fVolume->fPackagesDirectoryRef, + package->FileName()); + + 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 +CommitTransactionHandler::_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) { + Package* package = *it; + if (fPackagesAlreadyRemoved.find(package) + != fPackagesAlreadyRemoved.end()) { + continue; + } + + // get a BEntry for the package + 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 +CommitTransactionHandler::_RevertUserGroupChanges() +{ + // delete users + for (StringSet::const_iterator it = fAddedUsers.begin(); + it != fAddedUsers.end(); ++it) { + std::string commandLine("userdel "); + commandLine += FSUtils::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 += FSUtils::ShellEscapeString(it->c_str()).String(); + if (system(commandLine.c_str()) != 0) + ERROR("failed to remove group \"%s\"\n", it->c_str()); + } +} + + +void +CommitTransactionHandler::_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 +CommitTransactionHandler::_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! + } +} + + +/*static*/ BString +CommitTransactionHandler::_GetPath(const FSUtils::Entry& entry, + const BString& fallback) +{ + BString path = entry.Path(); + return path.IsEmpty() ? fallback : path; +} + + +void +CommitTransactionHandler::_ExtractPackageContent(Package* package, + const BStringList& contentPaths, BDirectory& targetDirectory, + BDirectory& _extractedFilesDirectory) +{ + // check whether the subdirectory already exists + BString targetName(package->RevisionedNameThrows()); + + BEntry targetEntry; + status_t error = targetEntry.SetTo(&targetDirectory, targetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to init entry \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, targetName), + targetName).String()), + package->FileName()); + } + if (targetEntry.Exists()) { + // nothing to do -- the very same version of the package has already + // been extracted + error = _extractedFilesDirectory.SetTo(&targetDirectory, + targetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open directory \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, targetName), + targetName).String()), + package->FileName()); + } + return; + } + + // create the subdirectory with a temporary name (remove, if it already + // exists) + BString temporaryTargetName = BString().SetToFormat("%s.tmp", + targetName.String()); + if (temporaryTargetName.IsEmpty()) + throw std::bad_alloc(); + + error = targetEntry.SetTo(&targetDirectory, temporaryTargetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to init entry \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, temporaryTargetName), + temporaryTargetName).String()), + package->FileName()); + } + + if (targetEntry.Exists()) { + // remove pre-existing + error = BRemoveEngine().RemoveEntry(FSUtils::Entry(targetEntry)); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to remove directory \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, + temporaryTargetName), + temporaryTargetName).String()), + package->FileName()); + } + } + + BDirectory& subDirectory = _extractedFilesDirectory; + FSTransaction::CreateOperation createSubDirectoryOperation( + &fFSTransaction, + FSUtils::Entry(targetDirectory, temporaryTargetName)); + error = targetDirectory.CreateDirectory(temporaryTargetName, + &subDirectory); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to create directory \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, temporaryTargetName), + temporaryTargetName).String()), + package->FileName()); + } + + createSubDirectoryOperation.Finished(); + + // extract + NotOwningEntryRef packageRef(fVolume->fPackagesDirectoryRef, + package->FileName()); + + int32 contentPathCount = contentPaths.CountStrings(); + for (int32 i = 0; i < contentPathCount; i++) { + const char* contentPath = contentPaths.StringAt(i); + + error = FSUtils::ExtractPackageContent(FSUtils::Entry(packageRef), + contentPath, FSUtils::Entry(subDirectory)); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat( + "failed to extract \"%s\" from package", contentPath), + package->FileName()); + } + } + + // tag all entries with the package attribute + error = _TagPackageEntriesRecursively(subDirectory, targetName, true); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to tag extract files in \"%s\" " + "with package attribute", + _GetPath( + FSUtils::Entry(targetDirectory, temporaryTargetName), + temporaryTargetName).String()), + package->FileName()); + } + + // rename the subdirectory + error = targetEntry.Rename(targetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to rename entry \"%s\" to \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, temporaryTargetName), + temporaryTargetName).String(), + targetName.String()), + package->FileName()); + } + + // keep the directory, regardless of whether the transaction is rolled + // back + createSubDirectoryOperation.Unregister(); +} + + +/*static*/ status_t +CommitTransactionHandler::_TagPackageEntriesRecursively(BDirectory& directory, + const BString& value, bool nonDirectoriesOnly) +{ + char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH]; + dirent *entry = (dirent*)buffer; + while (directory.GetNextDirents(entry, sizeof(buffer), 1) == 1) { + if (strcmp(entry->d_name, ".") == 0 + || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // determine type + struct stat st; + status_t error = directory.GetStatFor(entry->d_name, &st); + if (error != B_OK) + return error; + bool isDirectory = S_ISDIR(st.st_mode); + + // open the node and set the attribute + BNode stackNode; + BDirectory stackDirectory; + BNode* node; + if (isDirectory) { + node = &stackDirectory; + error = stackDirectory.SetTo(&directory, entry->d_name); + } else { + node = &stackNode; + error = stackNode.SetTo(&directory, entry->d_name); + } + + if (error != B_OK) + return error; + + if (!isDirectory || !nonDirectoriesOnly) { + error = node->WriteAttrString(kPackageFileAttribute, &value); + if (error != B_OK) + return error; + } + + // recurse + if (isDirectory) { + error = _TagPackageEntriesRecursively(stackDirectory, value, + nonDirectoriesOnly); + if (error != B_OK) + return error; + } + } + + return B_OK; +} diff --git a/src/servers/package/CommitTransactionHandler.h b/src/servers/package/CommitTransactionHandler.h new file mode 100644 index 0000000000..d19131a9d4 --- /dev/null +++ b/src/servers/package/CommitTransactionHandler.h @@ -0,0 +1,113 @@ +/* + * Copyright 2013-2014, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ +#ifndef COMMIT_TRANSACTION_HANDLER_H +#define COMMIT_TRANSACTION_HANDLER_H + + +#include +#include + +#include + +#include "FSTransaction.h" +#include "FSUtils.h" +#include "Volume.h" + + +typedef std::set StringSet; + + +class CommitTransactionHandler { +public: + CommitTransactionHandler(Volume* volume, + const PackageSet& packagesAlreadyAdded, + const PackageSet& packagesAlreadyRemoved); + ~CommitTransactionHandler(); + + void HandleRequest(BMessage* request, + BMessage* reply); + void HandleRequest( + const BActivationTransaction& transaction, + BMessage* reply); + void HandleRequest(const PackageSet& packagesAdded, + const PackageSet& packagesRemoved); + + void Revert(); + + const BString& OldStateDirectoryName() const + { return fOldStateDirectoryName; } + +private: + typedef BObjectList PackageList; + typedef FSUtils::RelativePath RelativePath; + +private: + void _GetPackagesToDeactivate( + const BActivationTransaction& transaction); + void _ReadPackagesToActivate( + const BActivationTransaction& transaction); + void _ApplyChanges(BMessage* reply); + void _CreateOldStateDirectory(BMessage* reply); + void _RemovePackagesToDeactivate(); + void _AddPackagesToActivate(); + + void _PreparePackageToActivate(Package* package); + void _AddGroup(Package* package, + const BString& groupName); + void _AddUser(Package* package, const BUser& user); + void _AddGlobalWritableFiles(Package* package); + void _AddGlobalWritableFile(Package* package, + const BGlobalWritableFileInfo& file, + const BDirectory& rootDirectory, + const BDirectory& extractedFilesDirectory); + void _AddGlobalWritableFileRecurse(Package* package, + const BDirectory& sourceDirectory, + FSUtils::Path& relativeSourcePath, + const BDirectory& targetDirectory, + const char* targetName, + BWritableFileUpdateType updateType); + + void _RevertAddPackagesToActivate(); + void _RevertRemovePackagesToDeactivate(); + void _RevertUserGroupChanges(); + + void _RunPostInstallScripts(); + void _RunPostInstallScript(Package* package, + const BString& script); + + static BString _GetPath(const FSUtils::Entry& entry, + const BString& fallback); + + void _ExtractPackageContent(Package* package, + const BStringList& contentPaths, + BDirectory& targetDirectory, + BDirectory& _extractedFilesDirectory); + + static status_t _TagPackageEntriesRecursively( + BDirectory& directory, const BString& value, + bool nonDirectoriesOnly); + +private: + Volume* fVolume; + PackageList fPackagesToActivate; + PackageSet fPackagesToDeactivate; + PackageSet fAddedPackages; + PackageSet fRemovedPackages; + const PackageSet& fPackagesAlreadyAdded; + const PackageSet& fPackagesAlreadyRemoved; + BDirectory fOldStateDirectory; + BString fOldStateDirectoryName; + node_ref fTransactionDirectoryRef; + BDirectory fWritableFilesDirectory; + StringSet fAddedGroups; + StringSet fAddedUsers; + FSTransaction fFSTransaction; +}; + + +#endif // COMMIT_TRANSACTION_HANDLER_H diff --git a/src/servers/package/Constants.cpp b/src/servers/package/Constants.cpp new file mode 100644 index 0000000000..ed0f7c5b11 --- /dev/null +++ b/src/servers/package/Constants.cpp @@ -0,0 +1,18 @@ +/* + * Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "Constants.h" + +#include + + +const char* const kPackageFileNameExtension = ".hpkg"; +const char* const kAdminDirectoryName = PACKAGES_DIRECTORY_ADMIN_DIRECTORY; +const char* const kActivationFileName = PACKAGES_DIRECTORY_ACTIVATION_FILE; +const char* const kTemporaryActivationFileName + = PACKAGES_DIRECTORY_ACTIVATION_FILE ".tmp"; +const char* const kWritableFilesDirectoryName = "writable-files"; +const char* const kPackageFileAttribute = "SYS:PACKAGE"; diff --git a/src/servers/package/Constants.h b/src/servers/package/Constants.h new file mode 100644 index 0000000000..3fcbde9fcb --- /dev/null +++ b/src/servers/package/Constants.h @@ -0,0 +1,25 @@ +/* + * Copyright 2014, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef CONSTANTS_H +#define CONSTANTS_H + + +#include + + +extern const char* const kPackageFileNameExtension; +extern const char* const kAdminDirectoryName; +extern const char* const kActivationFileName; +extern const char* const kTemporaryActivationFileName; +extern const char* const kWritableFilesDirectoryName; +extern const char* const kPackageFileAttribute; + +static const bigtime_t kHandleNodeMonitorEvents = 'nmon'; + +static const bigtime_t kNodeMonitorEventHandlingDelay = 500000; +static const bigtime_t kCommunicationTimeout = 1000000; + + +#endif // CONSTANTS_H diff --git a/src/servers/package/Jamfile b/src/servers/package/Jamfile index cd61ea06f8..3db078b7ec 100644 --- a/src/servers/package/Jamfile +++ b/src/servers/package/Jamfile @@ -5,6 +5,8 @@ UsePrivateHeaders app interface kernel shared storage ; Server package_daemon : + CommitTransactionHandler.cpp + Constants.cpp DebugSupport.cpp Exception.cpp FSTransaction.cpp diff --git a/src/servers/package/Root.cpp b/src/servers/package/Root.cpp index b43346558f..426b4a0be0 100644 --- a/src/servers/package/Root.cpp +++ b/src/servers/package/Root.cpp @@ -22,6 +22,7 @@ #include #include +#include "Constants.h" #include "DebugSupport.h" #include "PackageManager.h" @@ -30,9 +31,6 @@ using namespace BPackageKit::BPrivate; using namespace BPackageKit::BManager::BPrivate; -static const bigtime_t kCommunicationTimeout = 1000000; - - // #pragma mark - AbstractVolumeJob diff --git a/src/servers/package/Volume.cpp b/src/servers/package/Volume.cpp index 0dc63f368c..fc896b2ba4 100644 --- a/src/servers/package/Volume.cpp +++ b/src/servers/package/Volume.cpp @@ -10,15 +10,11 @@ #include "Volume.h" #include -#include -#include #include #include #include #include -#include - #include #include #include @@ -36,38 +32,17 @@ #include #include -#include -#include #include -#include -#include +#include "CommitTransactionHandler.h" +#include "Constants.h" #include "DebugSupport.h" #include "Exception.h" -#include "FSTransaction.h" #include "VolumeState.h" using namespace BPackageKit::BPrivate; -typedef std::set StringSet; - - -static const char* const kPackageFileNameExtension = ".hpkg"; -static const char* const kAdminDirectoryName - = PACKAGES_DIRECTORY_ADMIN_DIRECTORY; -static const char* const kActivationFileName - = PACKAGES_DIRECTORY_ACTIVATION_FILE; -static const char* const kTemporaryActivationFileName - = PACKAGES_DIRECTORY_ACTIVATION_FILE ".tmp"; -static const char* const kWritableFilesDirectoryName = "writable-files"; -static const char* const kPackageFileAttribute = "SYS:PACKAGE"; - -static const bigtime_t kHandleNodeMonitorEvents = 'nmon'; - -static const bigtime_t kNodeMonitorEventHandlingDelay = 500000; -static const bigtime_t kCommunicationTimeout = 1000000; - // #pragma mark - Listener @@ -106,1230 +81,6 @@ private: }; -// #pragma mark - CommitTransactionHandler - - -struct Volume::CommitTransactionHandler { - CommitTransactionHandler(Volume* volume, - const PackageSet& packagesAlreadyAdded, - const PackageSet& packagesAlreadyRemoved) - : - fVolume(volume), - fPackagesToActivate(), - fPackagesToDeactivate(), - fAddedPackages(), - fRemovedPackages(), - fPackagesAlreadyAdded(packagesAlreadyAdded), - fPackagesAlreadyRemoved(packagesAlreadyRemoved), - fAddedGroups(), - fAddedUsers(), - fFSTransaction() - { - } - - ~CommitTransactionHandler() - { - // Delete Package objects we created in case of error (on success - // fPackagesToActivate will be empty). - int32 count = fPackagesToActivate.CountItems(); - for (int32 i = 0; i < count; i++) { - Package* package = fPackagesToActivate.ItemAt(i); - if (fPackagesAlreadyAdded.find(package) - == fPackagesAlreadyAdded.end()) { - delete package; - } - } - } - - void HandleRequest(BMessage* request, BMessage* reply) - { - status_t error; - BActivationTransaction transaction(request, &error); - if (error == B_OK) - error = transaction.InitCheck(); - if (error != B_OK) { - if (error == B_NO_MEMORY) - throw Exception(B_NO_MEMORY); - throw Exception(B_DAEMON_BAD_REQUEST); - } - - HandleRequest(transaction, reply); - } - - void HandleRequest(const BActivationTransaction& transaction, - BMessage* reply) - { - // check the change count - if (transaction.ChangeCount() != fVolume->fState->ChangeCount()) - throw Exception(B_DAEMON_CHANGE_COUNT_MISMATCH); - - // collect the packages to deactivate - _GetPackagesToDeactivate(transaction); - - // read the packages to activate - _ReadPackagesToActivate(transaction); - - // anything to do at all? - if (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.empty()) { - throw Exception(B_DAEMON_BAD_REQUEST, - "no packages to activate or deactivate"); - } - - _ApplyChanges(reply); - } - - 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(); - } - - fPackagesToDeactivate = packagesRemoved; - - _ApplyChanges(NULL); - } - - void Revert() - { - // move packages to activate back to transaction directory - _RevertAddPackagesToActivate(); - - // move packages to deactivate back to packages directory - _RevertRemovePackagesToDeactivate(); - - // revert user and group changes - _RevertUserGroupChanges(); - - // Revert all other FS operations, i.e. the writable files changes as - // well as the creation of the old state directory. - fFSTransaction.RollBack(); - } - - const BString& OldStateDirectoryName() const - { - return fOldStateDirectoryName; - } - -private: - typedef BObjectList PackageList; - - void _GetPackagesToDeactivate(const BActivationTransaction& transaction) - { - // get the number of packages to deactivate - const BStringList& packagesToDeactivate - = transaction.PackagesToDeactivate(); - int32 packagesToDeactivateCount = packagesToDeactivate.CountStrings(); - if (packagesToDeactivateCount == 0) - return; - - for (int32 i = 0; i < packagesToDeactivateCount; i++) { - BString packageName = packagesToDeactivate.StringAt(i); - Package* package = fVolume->fState->FindPackage(packageName); - if (package == NULL) { - throw Exception(B_DAEMON_NO_SUCH_PACKAGE, "no such package", - packageName); - } - - fPackagesToDeactivate.insert(package); - - if (fPackagesAlreadyRemoved.find(package) - == fPackagesAlreadyRemoved.end()) { - package->IncrementEntryRemovedIgnoreLevel(); - } - } - } - - void _ReadPackagesToActivate(const BActivationTransaction& transaction) - { - // get the number of packages to activate - const BStringList& packagesToActivate - = transaction.PackagesToActivate(); - int32 packagesToActivateCount = packagesToActivate.CountStrings(); - if (packagesToActivateCount == 0) - return; - - // check the transaction directory name -- we only allow a simple - // subdirectory of the admin directory - const BString& transactionDirectoryName - = transaction.TransactionDirectoryName(); - 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++) { - BString packageName = packagesToActivate.StringAt(i); - - // make sure it doesn't clash with an already existing package - Package* package = fVolume->fState->FindPackage(packageName); - if (package != NULL) { - if (fPackagesAlreadyAdded.find(package) - != fPackagesAlreadyAdded.end()) { - if (!fPackagesToActivate.AddItem(package)) - throw Exception(B_NO_MEMORY); - continue; - } - - if (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 _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); - - // run post-installation scripts - _RunPostInstallScripts(); - - // 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 - 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 - FSTransaction::CreateOperation createOldStateDirectoryOperation( - &fFSTransaction, FSUtils::Entry(adminDirectory, directoryName)); - - error = adminDirectory.CreateDirectory(directoryName, - &fOldStateDirectory); - if (error != B_OK) - throw Exception(error, "failed to create old state directory"); - - createOldStateDirectoryOperation.Finished(); - - 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 - if (reply != NULL) { - error = reply->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) { - Package* package = *it; - if (fPackagesAlreadyRemoved.find(package) - != fPackagesAlreadyRemoved.end()) { - fRemovedPackages.insert(package); - continue; - } - - // get a BEntry for the package - NotOwningEntryRef entryRef(fVolume->fPackagesDirectoryRef, - package->FileName()); - - 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++) { - Package* package = fPackagesToActivate.ItemAt(i); - if (fPackagesAlreadyAdded.find(package) - != fPackagesAlreadyAdded.end()) { - fAddedPackages.insert(package); - _PreparePackageToActivate(package); - continue; - } - - // get a BEntry for the package - 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); - - _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& users = package->Info().Users(); - for (int32 i = 0; const BUser* user = users.ItemAt(i); i++) - _AddUser(package, *user); - - // handle global writable files - _AddGlobalWritableFiles(package); - } - - 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 += FSUtils::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 ") - + FSUtils::ShellEscapeString(user.RealName()).String() + " "; - } - - if (!user.Home().IsEmpty()) { - commandLine += std::string("-d ") - + FSUtils::ShellEscapeString(user.Home()).String() + " "; - } - - if (!user.Shell().IsEmpty()) { - commandLine += std::string("-s ") - + FSUtils::ShellEscapeString(user.Shell()).String() + " "; - } - - if (!user.Groups().IsEmpty()) { - commandLine += std::string("-g ") - + FSUtils::ShellEscapeString(user.Groups().First()).String() - + " "; - } - - commandLine += FSUtils::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 ") - + FSUtils::ShellEscapeString(user.Name()).String() - + " " - + FSUtils::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 _AddGlobalWritableFiles(Package* package) - { - // get the list of included files - const BObjectList& files - = package->Info().GlobalWritableFileInfos(); - BStringList contentPaths; - for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i); - i++) { - if (file->IsIncluded() && !contentPaths.Add(file->Path())) - throw std::bad_alloc(); - } - - if (contentPaths.IsEmpty()) - return; - - // Open the root directory of the installation location where we will - // extract the files -- that's the volume's root directory. - BDirectory rootDirectory; - status_t error = rootDirectory.SetTo(&fVolume->fRootDirectoryRef); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to get the root directory " - "for writable files"), - package->FileName()); - } - - // Open writable-files directory in the administrative directory. - if (fWritableFilesDirectory.InitCheck() != B_OK) { - error = fVolume->_OpenPackagesSubDirectory( - RelativePath(kAdminDirectoryName, kWritableFilesDirectoryName), - true, fWritableFilesDirectory); - - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to get the backup directory " - "for writable files"), - package->FileName()); - } - } - - // extract files into a subdir of the writable-files directory - BDirectory extractedFilesDirectory; - _ExtractPackageContent(package, contentPaths, - fWritableFilesDirectory, extractedFilesDirectory); - - for (int32 i = 0; const BGlobalWritableFileInfo* file = files.ItemAt(i); - i++) { - if (file->IsIncluded()) { - _AddGlobalWritableFile(package, *file, rootDirectory, - extractedFilesDirectory); - } - } - } - - void _AddGlobalWritableFile(Package* package, - const BGlobalWritableFileInfo& file, const BDirectory& rootDirectory, - const BDirectory& extractedFilesDirectory) - { - // Map the path name to the actual target location. Currently this only - // concerns "settings/", which is mapped to "settings/global/". - BString targetPath(file.Path()); - if (fVolume->fMountType == PACKAGE_FS_MOUNT_TYPE_HOME) { - if (targetPath == "settings" - || targetPath.StartsWith("settings/")) { - targetPath.Insert("/global", 8); - if (targetPath.Length() == file.Path().Length()) - throw std::bad_alloc(); - } - } - - // open parent directory of the source entry - const char* lastSlash = strrchr(file.Path(), '/'); - const BDirectory* sourceDirectory; - BDirectory stackSourceDirectory; - if (lastSlash != NULL) { - sourceDirectory = &stackSourceDirectory; - BString sourceParentPath(file.Path(), - lastSlash - file.Path().String()); - if (sourceParentPath.Length() == 0) - throw std::bad_alloc(); - - status_t error = stackSourceDirectory.SetTo( - &extractedFilesDirectory, sourceParentPath); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to open directory \"%s\"", - _GetPath( - FSUtils::Entry(extractedFilesDirectory, - sourceParentPath), - sourceParentPath).String()), - package->FileName()); - } - } else { - sourceDirectory = &extractedFilesDirectory; - } - - // open parent directory of the target entry -- create, if necessary - FSUtils::Path relativeSourcePath(file.Path()); - lastSlash = strrchr(targetPath, '/'); - if (lastSlash != NULL) { - BString targetParentPath(targetPath, - lastSlash - targetPath.String()); - if (targetParentPath.Length() == 0) - throw std::bad_alloc(); - - BDirectory targetDirectory; - status_t error = FSUtils::OpenSubDirectory(rootDirectory, - RelativePath(targetParentPath), true, targetDirectory); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to open/create directory " - "\"%s\"", - _GetPath( - FSUtils::Entry(rootDirectory,targetParentPath), - targetParentPath).String()), - package->FileName()); - } - _AddGlobalWritableFileRecurse(package, *sourceDirectory, - relativeSourcePath, targetDirectory, lastSlash + 1, - file.UpdateType()); - } else { - _AddGlobalWritableFileRecurse(package, *sourceDirectory, - relativeSourcePath, rootDirectory, targetPath, - file.UpdateType()); - } - } - - void _AddGlobalWritableFileRecurse(Package* package, - const BDirectory& sourceDirectory, FSUtils::Path& relativeSourcePath, - const BDirectory& targetDirectory, const char* targetName, - BWritableFileUpdateType updateType) - { - // * If the file doesn't exist, just copy the extracted one. - // * If the file does exist, compare with the previous original version: - // * If unchanged, just overwrite it. - // * If changed, leave it to the user for now. When we support merging - // first back the file up, then try the merge. - - // Check whether the target location exists and what type the entry at - // both locations are. - struct stat targetStat; - if (targetDirectory.GetStatFor(targetName, &targetStat) != B_OK) { - // target doesn't exist -- just copy - PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " - "couldn't get stat for writable file, copying...\n"); - FSTransaction::CreateOperation copyOperation(&fFSTransaction, - FSUtils::Entry(targetDirectory, targetName)); - status_t error = BCopyEngine(BCopyEngine::COPY_RECURSIVELY) - .CopyEntry( - FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), - FSUtils::Entry(targetDirectory, targetName)); - if (error != B_OK) { - if (targetDirectory.GetStatFor(targetName, &targetStat) == B_OK) - copyOperation.Finished(); - - throw Exception(error, - BString().SetToFormat("failed to copy entry \"%s\"", - _GetPath( - FSUtils::Entry(sourceDirectory, - relativeSourcePath.Leaf()), - relativeSourcePath).String()), - package->FileName()); - } - copyOperation.Finished(); - return; - } - - struct stat sourceStat; - status_t error = sourceDirectory.GetStatFor(relativeSourcePath.Leaf(), - &sourceStat); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to get stat data for entry " - "\"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, targetName), - targetName).String()), - package->FileName()); - } - - if ((sourceStat.st_mode & S_IFMT) != (targetStat.st_mode & S_IFMT) - || (!S_ISDIR(sourceStat.st_mode) && !S_ISREG(sourceStat.st_mode) - && !S_ISLNK(sourceStat.st_mode))) { - // Source and target entry types don't match or this is an entry - // we cannot handle. The user must handle this manually. - PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " - "writable file exists, but type doesn't match previous type\n"); -// TODO: Notify user! - return; - } - - if (S_ISDIR(sourceStat.st_mode)) { - // entry is a directory -- recurse - BDirectory sourceSubDirectory; - error = sourceSubDirectory.SetTo(&sourceDirectory, - relativeSourcePath.Leaf()); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to open directory \"%s\"", - _GetPath( - FSUtils::Entry(sourceDirectory, - relativeSourcePath.Leaf()), - relativeSourcePath).String()), - package->FileName()); - } - - BDirectory targetSubDirectory; - error = targetSubDirectory.SetTo(&targetDirectory, targetName); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to open directory \"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, targetName), - targetName).String()), - package->FileName()); - } - - entry_ref entry; - while (sourceSubDirectory.GetNextRef(&entry) == B_OK) { - relativeSourcePath.AppendComponent(entry.name); - _AddGlobalWritableFileRecurse(package, sourceSubDirectory, - relativeSourcePath, targetSubDirectory, entry.name, - updateType); - relativeSourcePath.RemoveLastComponent(); - } - - PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " - "writable directory, recursion done\n"); - return; - } - - // get the package the target file originated from - BString originalPackage; - if (BNode(&targetDirectory, targetName).ReadAttrString( - kPackageFileAttribute, &originalPackage) != B_OK) { - // Can't determine the original package. The user must handle this - // manually. -// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! - PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " - "failed to get SYS:PACKAGE attribute\n"); - return; - } - - // If that's our package, we're happy. - if (originalPackage == package->RevisionedNameThrows()) { - PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " - "file tagged with same package version we're activating\n"); - return; - } - - // Check, whether the writable-files directory for the original package - // exists. - BString originalRelativeSourcePath = BString().SetToFormat("%s/%s", - originalPackage.String(), relativeSourcePath.ToCString()); - if (originalRelativeSourcePath.IsEmpty()) - throw std::bad_alloc(); - - struct stat originalPackageStat; - if (fWritableFilesDirectory.GetStatFor(originalRelativeSourcePath, - &originalPackageStat) != B_OK - || (sourceStat.st_mode & S_IFMT) - != (originalPackageStat.st_mode & S_IFMT)) { - // Original entry doesn't exist (either we don't have the data from - // the original package or the entry really didn't exist) or its - // type differs from the expected one. The user must handle this - // manually. - PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " - "original \"%s\" doesn't exist or has other type\n", - _GetPath(FSUtils::Entry(fWritableFilesDirectory, - originalRelativeSourcePath), - originalRelativeSourcePath).String()); - return; -// TODO: Notify user! - } - - if (S_ISREG(sourceStat.st_mode)) { - // compare file content - bool equal; - error = FSUtils::CompareFileContent( - FSUtils::Entry(fWritableFilesDirectory, - originalRelativeSourcePath), - FSUtils::Entry(targetDirectory, targetName), - equal); - // TODO: Merge support! - if (error != B_OK || !equal) { - // The comparison failed or the files differ. The user must - // handle this manually. - PRINT("Volume::CommitTransactionHandler::" - "_AddGlobalWritableFile(): " - "file comparison failed (%s) or files aren't equal\n", - strerror(error)); - return; -// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! - } - } else { - // compare symlinks - bool equal; - error = FSUtils::CompareSymLinks( - FSUtils::Entry(fWritableFilesDirectory, - originalRelativeSourcePath), - FSUtils::Entry(targetDirectory, targetName), - equal); - if (error != B_OK || !equal) { - // The comparison failed or the symlinks differ. The user must - // handle this manually. - PRINT("Volume::CommitTransactionHandler::" - "_AddGlobalWritableFile(): " - "symlink comparison failed (%s) or symlinks aren't equal\n", - strerror(error)); - return; -// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! - } - } - - // Replace the existing file/symlink. We do that in two steps: First - // copy the new file to a neighoring location, then move-replace the - // old file. - BString tempTargetName; - tempTargetName.SetToFormat("%s.%s", targetName, - package->RevisionedNameThrows().String()); - if (tempTargetName.IsEmpty()) - throw std::bad_alloc(); - - // copy - FSTransaction::CreateOperation copyOperation(&fFSTransaction, - FSUtils::Entry(targetDirectory, tempTargetName)); - - error = BCopyEngine(BCopyEngine::UNLINK_DESTINATION).CopyEntry( - FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), - FSUtils::Entry(targetDirectory, tempTargetName)); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to copy entry \"%s\"", - _GetPath( - FSUtils::Entry(sourceDirectory, - relativeSourcePath.Leaf()), - relativeSourcePath).String()), - package->FileName()); - } - - copyOperation.Finished(); - - // rename - FSTransaction::RemoveOperation renameOperation(&fFSTransaction, - FSUtils::Entry(targetDirectory, targetName), - FSUtils::Entry(fWritableFilesDirectory, - originalRelativeSourcePath)); - - BEntry targetEntry; - error = targetEntry.SetTo(&targetDirectory, tempTargetName); - if (error == B_OK) - error = targetEntry.Rename(targetName, true); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to rename entry \"%s\" to \"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, tempTargetName), - tempTargetName).String(), - targetName), - package->FileName()); - } - - renameOperation.Finished(); - copyOperation.Unregister(); - } - - 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; - - if (fPackagesAlreadyAdded.find(package) - != fPackagesAlreadyAdded.end()) { - continue; - } - - fVolume->_RemovePackage(package); - - if (transactionDirectory.InitCheck() != B_OK) - continue; - - // get BEntry for the package - NotOwningEntryRef entryRef(fVolume->fPackagesDirectoryRef, - package->FileName()); - - 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) { - Package* package = *it; - if (fPackagesAlreadyRemoved.find(package) - != fPackagesAlreadyRemoved.end()) { - continue; - } - - // get a BEntry for the package - 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 _RevertUserGroupChanges() - { - // delete users - for (StringSet::const_iterator it = fAddedUsers.begin(); - it != fAddedUsers.end(); ++it) { - std::string commandLine("userdel "); - commandLine += FSUtils::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 += FSUtils::ShellEscapeString(it->c_str()).String(); - if (system(commandLine.c_str()) != 0) - ERROR("failed to remove group \"%s\"\n", it->c_str()); - } - } - - 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! - } - } - - static BString _GetPath(const FSUtils::Entry& entry, - const BString& fallback) - { - BString path = entry.Path(); - return path.IsEmpty() ? fallback : path; - } - - void _ExtractPackageContent(Package* package, - const BStringList& contentPaths, BDirectory& targetDirectory, - BDirectory& _extractedFilesDirectory) - { - // check whether the subdirectory already exists - BString targetName(package->RevisionedNameThrows()); - - BEntry targetEntry; - status_t error = targetEntry.SetTo(&targetDirectory, targetName); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to init entry \"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, targetName), - targetName).String()), - package->FileName()); - } - if (targetEntry.Exists()) { - // nothing to do -- the very same version of the package has already - // been extracted - error = _extractedFilesDirectory.SetTo(&targetDirectory, - targetName); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to open directory \"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, targetName), - targetName).String()), - package->FileName()); - } - return; - } - - // create the subdirectory with a temporary name (remove, if it already - // exists) - BString temporaryTargetName = BString().SetToFormat("%s.tmp", - targetName.String()); - if (temporaryTargetName.IsEmpty()) - throw std::bad_alloc(); - - error = targetEntry.SetTo(&targetDirectory, temporaryTargetName); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to init entry \"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, temporaryTargetName), - temporaryTargetName).String()), - package->FileName()); - } - - if (targetEntry.Exists()) { - // remove pre-existing - error = BRemoveEngine().RemoveEntry(FSUtils::Entry(targetEntry)); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to remove directory \"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, - temporaryTargetName), - temporaryTargetName).String()), - package->FileName()); - } - } - - BDirectory& subDirectory = _extractedFilesDirectory; - FSTransaction::CreateOperation createSubDirectoryOperation( - &fFSTransaction, - FSUtils::Entry(targetDirectory, temporaryTargetName)); - error = targetDirectory.CreateDirectory(temporaryTargetName, - &subDirectory); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to create directory \"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, temporaryTargetName), - temporaryTargetName).String()), - package->FileName()); - } - - createSubDirectoryOperation.Finished(); - - // extract - NotOwningEntryRef packageRef(fVolume->fPackagesDirectoryRef, - package->FileName()); - - int32 contentPathCount = contentPaths.CountStrings(); - for (int32 i = 0; i < contentPathCount; i++) { - const char* contentPath = contentPaths.StringAt(i); - - error = FSUtils::ExtractPackageContent(FSUtils::Entry(packageRef), - contentPath, FSUtils::Entry(subDirectory)); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat( - "failed to extract \"%s\" from package", contentPath), - package->FileName()); - } - } - - // tag all entries with the package attribute - error = _TagPackageEntriesRecursively(subDirectory, targetName, true); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to tag extract files in \"%s\" " - "with package attribute", - _GetPath( - FSUtils::Entry(targetDirectory, temporaryTargetName), - temporaryTargetName).String()), - package->FileName()); - } - - // rename the subdirectory - error = targetEntry.Rename(targetName); - if (error != B_OK) { - throw Exception(error, - BString().SetToFormat("failed to rename entry \"%s\" to \"%s\"", - _GetPath( - FSUtils::Entry(targetDirectory, temporaryTargetName), - temporaryTargetName).String(), - targetName.String()), - package->FileName()); - } - - // keep the directory, regardless of whether the transaction is rolled - // back - createSubDirectoryOperation.Unregister(); - } - - static status_t _TagPackageEntriesRecursively(BDirectory& directory, - const BString& value, bool nonDirectoriesOnly) - { - char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH]; - dirent *entry = (dirent*)buffer; - while (directory.GetNextDirents(entry, sizeof(buffer), 1) == 1) { - if (strcmp(entry->d_name, ".") == 0 - || strcmp(entry->d_name, "..") == 0) { - continue; - } - - // determine type - struct stat st; - status_t error = directory.GetStatFor(entry->d_name, &st); - if (error != B_OK) - return error; - bool isDirectory = S_ISDIR(st.st_mode); - - // open the node and set the attribute - BNode stackNode; - BDirectory stackDirectory; - BNode* node; - if (isDirectory) { - node = &stackDirectory; - error = stackDirectory.SetTo(&directory, entry->d_name); - } else { - node = &stackNode; - error = stackNode.SetTo(&directory, entry->d_name); - } - - if (error != B_OK) - return error; - - if (!isDirectory || !nonDirectoriesOnly) { - error = node->WriteAttrString(kPackageFileAttribute, &value); - if (error != B_OK) - return error; - } - - // recurse - if (isDirectory) { - error = _TagPackageEntriesRecursively(stackDirectory, value, - nonDirectoriesOnly); - if (error != B_OK) - return error; - } - } - - return B_OK; - } - -private: - Volume* fVolume; - PackageList fPackagesToActivate; - PackageSet fPackagesToDeactivate; - PackageSet fAddedPackages; - PackageSet fRemovedPackages; - const PackageSet& fPackagesAlreadyAdded; - const PackageSet& fPackagesAlreadyRemoved; - BDirectory fOldStateDirectory; - BString fOldStateDirectoryName; - node_ref fTransactionDirectoryRef; - BDirectory fWritableFilesDirectory; - StringSet fAddedGroups; - StringSet fAddedUsers; - FSTransaction fFSTransaction; -}; - - // #pragma mark - Volume diff --git a/src/servers/package/Volume.h b/src/servers/package/Volume.h index 4aaf5e36c0..2f3e03f9f0 100644 --- a/src/servers/package/Volume.h +++ b/src/servers/package/Volume.h @@ -43,6 +43,7 @@ using BPackageKit::BPrivate::BDaemonClient; class BDirectory; +class CommitTransactionHandler; class Root; class VolumeState; @@ -138,9 +139,8 @@ public: private: struct NodeMonitorEvent; - struct CommitTransactionHandler; - friend struct CommitTransactionHandler; + friend class CommitTransactionHandler; typedef FSUtils::RelativePath RelativePath; typedef DoublyLinkedList NodeMonitorEventList;