/* * 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 #include #include #include #include #include "Constants.h" #include "DebugSupport.h" #include "Exception.h" #include "PackageFileManager.h" #include "VolumeState.h" using namespace BPackageKit::BPrivate; using BPackageKit::BTransactionIssue; // #pragma mark - TransactionIssueBuilder struct CommitTransactionHandler::TransactionIssueBuilder { TransactionIssueBuilder(BTransactionIssue::BType type, Package* package = NULL) : fType(type), fPackageName(package != NULL ? package->FileName() : BString()), fPath1(), fPath2(), fSystemError(B_OK), fExitCode(0) { } TransactionIssueBuilder& SetPath1(const BString& path) { fPath1 = path; return *this; } TransactionIssueBuilder& SetPath1(const FSUtils::Entry& entry) { return SetPath1(entry.Path()); } TransactionIssueBuilder& SetPath2(const BString& path) { fPath2 = path; return *this; } TransactionIssueBuilder& SetPath2(const FSUtils::Entry& entry) { return SetPath2(entry.Path()); } TransactionIssueBuilder& SetSystemError(status_t error) { fSystemError = error; return *this; } TransactionIssueBuilder& SetExitCode(int exitCode) { fExitCode = exitCode; return *this; } BTransactionIssue BuildIssue(Package* package) const { BString packageName(fPackageName); if (packageName.IsEmpty() && package != NULL) packageName = package->FileName(); return BTransactionIssue(fType, packageName, fPath1, fPath2, fSystemError, fExitCode); } private: BTransactionIssue::BType fType; BString fPackageName; BString fPath1; BString fPath2; status_t fSystemError; int fExitCode; }; // #pragma mark - CommitTransactionHandler CommitTransactionHandler::CommitTransactionHandler(Volume* volume, PackageFileManager* packageFileManager, BCommitTransactionResult& result) : fVolume(volume), fPackageFileManager(packageFileManager), fVolumeState(NULL), fVolumeStateIsActive(false), fPackagesToActivate(), fPackagesToDeactivate(), fAddedPackages(), fRemovedPackages(), fPackagesAlreadyAdded(), fPackagesAlreadyRemoved(), fOldStateDirectory(), fOldStateDirectoryRef(), fOldStateDirectoryName(), fTransactionDirectoryRef(), fFirstBootProcessing(false), fWritableFilesDirectory(), fAddedGroups(), fAddedUsers(), fFSTransaction(), fResult(result), fCurrentPackage(NULL) { } 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; } } delete fVolumeState; } void CommitTransactionHandler::Init(VolumeState* volumeState, bool isActiveVolumeState, const PackageSet& packagesAlreadyAdded, const PackageSet& packagesAlreadyRemoved) { fVolumeState = volumeState->Clone(); if (fVolumeState == NULL) throw std::bad_alloc(); fVolumeStateIsActive = isActiveVolumeState; for (PackageSet::const_iterator it = packagesAlreadyAdded.begin(); it != packagesAlreadyAdded.end(); ++it) { Package* package = fVolumeState->FindPackage((*it)->FileName()); fPackagesAlreadyAdded.insert(package); } for (PackageSet::const_iterator it = packagesAlreadyRemoved.begin(); it != packagesAlreadyRemoved.end(); ++it) { Package* package = fVolumeState->FindPackage((*it)->FileName()); fPackagesAlreadyRemoved.insert(package); } } void CommitTransactionHandler::HandleRequest(BMessage* request) { 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_TRANSACTION_NO_MEMORY); throw Exception(B_TRANSACTION_BAD_REQUEST); } HandleRequest(transaction); } void CommitTransactionHandler::HandleRequest( const BActivationTransaction& transaction) { // check the change count if (transaction.ChangeCount() != fVolume->ChangeCount()) throw Exception(B_TRANSACTION_CHANGE_COUNT_MISMATCH); fFirstBootProcessing = transaction.FirstBootProcessing(); // collect the packages to deactivate _GetPackagesToDeactivate(transaction); // read the packages to activate _ReadPackagesToActivate(transaction); // anything to do at all? if (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.empty()) { WARN("Bad package activation request: no packages to activate or" " deactivate\n"); throw Exception(B_TRANSACTION_BAD_REQUEST); } _ApplyChanges(); // Clean up the unused empty transaction directory for first boot // processing, since it's usually an internal to package_daemon // operation and there is no external client to clean it up. if (fFirstBootProcessing) { RelativePath directoryPath(kAdminDirectoryName, transaction.TransactionDirectoryName().String()); BDirectory transactionDir; status_t error = _OpenPackagesSubDirectory(directoryPath, false, transactionDir); if (error == B_OK) { BEntry transactionDirEntry; error = transactionDir.GetEntry(&transactionDirEntry); if (error == B_OK) transactionDirEntry.Remove(); // Okay to fail when non-empty. } } } void CommitTransactionHandler::HandleRequest() { for (PackageSet::const_iterator it = fPackagesAlreadyAdded.begin(); it != fPackagesAlreadyAdded.end(); ++it) { if (!fPackagesToActivate.AddItem(*it)) throw std::bad_alloc(); } fPackagesToDeactivate = fPackagesAlreadyRemoved; _ApplyChanges(); } 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(); } VolumeState* CommitTransactionHandler::DetachVolumeState() { VolumeState* result = fVolumeState; fVolumeState = NULL; return result; } 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 = fVolumeState->FindPackage(packageName); if (package == NULL) { throw Exception(B_TRANSACTION_NO_SUCH_PACKAGE) .SetPackageName(packageName); } fPackagesToDeactivate.insert(package); } } 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 == "..") { WARN("Bad package activation request: malformed transaction" " directory name: \"%s\"\n", transactionDirectoryName.String()); throw Exception(B_TRANSACTION_BAD_REQUEST); } // open the directory RelativePath directoryPath(kAdminDirectoryName, transactionDirectoryName); BDirectory directory; status_t error = _OpenPackagesSubDirectory(directoryPath, false, directory); if (error == B_OK) { error = directory.GetNodeRef(&fTransactionDirectoryRef); if (error != B_OK) { ERROR("Failed to get transaction directory node ref: %s\n", strerror(error)); } } else ERROR("Failed to open transaction directory: %s\n", strerror(error)); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(fVolume->PackagesDirectoryRef(), directoryPath.ToString()), directoryPath.ToString())) .SetSystemError(error); } // 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, // except in first boot mode where it should always clash. Package* package = fVolumeState->FindPackage(packageName); if (fFirstBootProcessing) { if (package == NULL) { throw Exception(B_TRANSACTION_NO_SUCH_PACKAGE) .SetPackageName(packageName); } if (!fPackagesToActivate.AddItem(package)) throw Exception(B_TRANSACTION_NO_MEMORY); continue; } else { if (package != NULL) { if (fPackagesAlreadyAdded.find(package) != fPackagesAlreadyAdded.end()) { if (!fPackagesToActivate.AddItem(package)) throw Exception(B_TRANSACTION_NO_MEMORY); continue; } if (fPackagesToDeactivate.find(package) == fPackagesToDeactivate.end()) { throw Exception(B_TRANSACTION_PACKAGE_ALREADY_EXISTS) .SetPackageName(packageName); } } } // read the package error = fPackageFileManager->CreatePackage( NotOwningEntryRef(fTransactionDirectoryRef, packageName), package); if (error != B_OK) { if (error == B_NO_MEMORY) throw Exception(B_TRANSACTION_NO_MEMORY); throw Exception(B_TRANSACTION_FAILED_TO_READ_PACKAGE_FILE) .SetPackageName(packageName) .SetPath1(_GetPath( FSUtils::Entry( NotOwningEntryRef(fTransactionDirectoryRef, packageName)), packageName)) .SetSystemError(error); } if (!fPackagesToActivate.AddItem(package)) { delete package; throw Exception(B_TRANSACTION_NO_MEMORY); } } } void CommitTransactionHandler::_ApplyChanges() { if (!fFirstBootProcessing) { // create an old state directory _CreateOldStateDirectory(); // move packages to deactivate to old state directory _RemovePackagesToDeactivate(); // move packages to activate to packages directory _AddPackagesToActivate(); // run pre-uninstall scripts, before their packages vanish. _RunPreUninstallScripts(); // activate/deactivate packages and create users, groups, settings files. _ChangePackageActivation(fAddedPackages, fRemovedPackages); } else // FirstBootProcessing, skip several steps and just do package setup. _PrepareFirstBootPackages(); // run post-install scripts now that the new packages are visible in the // package file system. if (fVolumeStateIsActive || fFirstBootProcessing) { _RunPostInstallScripts(); } else { // Do post-install scripts later after a reboot, for Haiku OS packages. _QueuePostInstallScripts(); } // removed packages have been deleted, new packages shall not be deleted fAddedPackages.clear(); fRemovedPackages.clear(); fPackagesToActivate.MakeEmpty(false); fPackagesToDeactivate.clear(); } void CommitTransactionHandler::_CreateOldStateDirectory() { time_t stateTime = 0; { // use the modification time of the old activations file, if possible BFile oldActivationFile; BEntry oldActivationEntry; if (_OpenPackagesFile(RelativePath(kAdminDirectoryName), kActivationFileName, B_READ_ONLY, oldActivationFile, &oldActivationEntry) != B_OK || oldActivationEntry.GetModificationTime(&stateTime) != B_OK) { stateTime = time(NULL); } } // construct a nice name from the date and time struct tm now; BString baseName; if (localtime_r(&stateTime, &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_TRANSACTION_NO_MEMORY); // make sure the directory doesn't exist yet BDirectory adminDirectory; status_t error = _OpenPackagesSubDirectory( RelativePath(kAdminDirectoryName), true, adminDirectory); if (error != B_OK) { ERROR("Failed to open administrative directory: %s\n", strerror(error)); throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(fVolume->PackagesDirectoryRef(), kAdminDirectoryName), kAdminDirectoryName)) .SetSystemError(error); } int uniqueId = 1; BString directoryName = baseName; while (BEntry(&adminDirectory, directoryName).Exists()) { directoryName.SetToFormat("%s-%d", baseName.String(), uniqueId++); if (directoryName.IsEmpty()) throw Exception(B_TRANSACTION_NO_MEMORY); } // create the directory FSTransaction::CreateOperation createOldStateDirectoryOperation( &fFSTransaction, FSUtils::Entry(adminDirectory, directoryName)); error = adminDirectory.CreateDirectory(directoryName, &fOldStateDirectory); if (error == B_OK) { createOldStateDirectoryOperation.Finished(); fOldStateDirectoryName = directoryName; error = fOldStateDirectory.GetNodeRef(&fOldStateDirectoryRef); if (error != B_OK) ERROR("Failed get old state directory ref: %s\n", strerror(error)); } else ERROR("Failed to create old state directory: %s\n", strerror(error)); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_CREATE_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(adminDirectory, directoryName), directoryName)) .SetSystemError(error); } // write the old activation file BEntry activationFile; _WriteActivationFile(RelativePath(kAdminDirectoryName, directoryName), kActivationFileName, PackageSet(), PackageSet(), activationFile); fResult.SetOldStateDirectory(fOldStateDirectoryName); } void CommitTransactionHandler::_RemovePackagesToDeactivate() { if (fPackagesToDeactivate.empty()) return; for (PackageSet::const_iterator it = fPackagesToDeactivate.begin(); it != fPackagesToDeactivate.end(); ++it) { Package* package = *it; // When deactivating (or updating) a system package, don't do that live. if (_IsSystemPackage(package)) fVolumeStateIsActive = false; if (fPackagesAlreadyRemoved.find(package) != fPackagesAlreadyRemoved.end()) { fRemovedPackages.insert(package); continue; } // get a BEntry for the package NotOwningEntryRef entryRef(package->EntryRef()); BEntry entry; status_t error = entry.SetTo(&entryRef); if (error != B_OK) { ERROR("Failed to get package entry for %s: %s\n", package->FileName().String(), strerror(error)); throw Exception(B_TRANSACTION_FAILED_TO_GET_ENTRY_PATH) .SetPath1(package->FileName()) .SetPackageName(package->FileName()) .SetSystemError(error); } // move entry fRemovedPackages.insert(package); error = entry.MoveTo(&fOldStateDirectory); if (error != B_OK) { fRemovedPackages.erase(package); ERROR("Failed to move old package %s from packages directory: %s\n", package->FileName().String(), strerror(error)); throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) .SetPath1( _GetPath(FSUtils::Entry(entryRef), package->FileName())) .SetPath2(_GetPath( FSUtils::Entry(fOldStateDirectory), fOldStateDirectoryName)) .SetSystemError(error); } fPackageFileManager->PackageFileMoved(package->File(), fOldStateDirectoryRef); package->File()->IncrementEntryRemovedIgnoreLevel(); } } void CommitTransactionHandler::_AddPackagesToActivate() { if (fPackagesToActivate.IsEmpty()) return; // open packages directory BDirectory packagesDirectory; status_t error = packagesDirectory.SetTo(&fVolume->PackagesDirectoryRef()); if (error != B_OK) { ERROR("Failed to open packages directory: %s\n", strerror(error)); throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1("") .SetSystemError(error); } 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 NotOwningEntryRef entryRef(fTransactionDirectoryRef, package->FileName()); BEntry entry; error = entry.SetTo(&entryRef); if (error != B_OK) { ERROR("Failed to get package entry for %s: %s\n", package->FileName().String(), strerror(error)); throw Exception(B_TRANSACTION_FAILED_TO_GET_ENTRY_PATH) .SetPath1(package->FileName()) .SetPackageName(package->FileName()) .SetSystemError(error); } // move entry fAddedPackages.insert(package); error = entry.MoveTo(&packagesDirectory); if (error == B_FILE_EXISTS) { error = _AssertEntriesAreEqual(entry, &packagesDirectory); if (error == B_OK) { // Packages are identical, no need to move. // If the entry is not removed however, it will prevent // the transaction directory from being removed later. // We ignore failure to Remove() here, though. entry.Remove(); } else if (error != B_FILE_EXISTS) { ERROR("Failed to compare new package %s to existing file in " "packages directory: %s\n", package->FileName().String(), strerror(error)); // Restore original error to avoid confusion error = B_FILE_EXISTS; } } if (error != B_OK) { fAddedPackages.erase(package); ERROR("Failed to move new package %s to packages directory: %s\n", package->FileName().String(), strerror(error)); throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) .SetPath1( _GetPath(FSUtils::Entry(entryRef), package->FileName())) .SetPath2(_GetPath( FSUtils::Entry(packagesDirectory), "packages")) .SetSystemError(error); } fPackageFileManager->PackageFileMoved(package->File(), fVolume->PackagesDirectoryRef()); package->File()->IncrementEntryCreatedIgnoreLevel(); // also add the package to the volume fVolumeState->AddPackage(package); _PreparePackageToActivate(package); } } void CommitTransactionHandler::_PrepareFirstBootPackages() { int32 count = fPackagesToActivate.CountItems(); BDirectory transactionDir(&fTransactionDirectoryRef); BEntry transactionEntry; BPath transactionPath; if (transactionDir.InitCheck() == B_OK && transactionDir.GetEntry(&transactionEntry) == B_OK && transactionEntry.GetPath(&transactionPath) == B_OK) { INFORM("Starting First Boot Processing for %d packages in %s.\n", (int) count, transactionPath.Path()); } for (int32 i = 0; i < count; i++) { Package* package = fPackagesToActivate.ItemAt(i); fAddedPackages.insert(package); INFORM("Doing first boot processing #%d for package %s.\n", (int) i, package->FileName().String()); _PreparePackageToActivate(package); } } void CommitTransactionHandler::_PreparePackageToActivate(Package* package) { fCurrentPackage = 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); fCurrentPackage = NULL; } 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()); ERROR("Failed to add group \"%s\".\n", groupName.String()); throw Exception(B_TRANSACTION_FAILED_TO_ADD_GROUP) .SetPackageName(package->FileName()) .SetString1(groupName); } } 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()); ERROR("Failed to add user \"%s\".\n", user.Name().String()); throw Exception(B_TRANSACTION_FAILED_TO_ADD_USER) .SetPackageName(package->FileName()) .SetString1(user.Name()); } // 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()); ERROR("Failed to add user \"%s\" to group \"%s\".\n", user.Name().String(), user.Groups().StringAt(i).String()); throw Exception(B_TRANSACTION_FAILED_TO_ADD_USER_TO_GROUP) .SetPackageName(package->FileName()) .SetString1(user.Name()) .SetString2(user.Groups().StringAt(i)); } } } 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->RootDirectoryRef()); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(fVolume->RootDirectoryRef()), "")) .SetSystemError(error); } // Open writable-files directory in the administrative directory. if (fWritableFilesDirectory.InitCheck() != B_OK) { RelativePath directoryPath(kAdminDirectoryName, kWritableFilesDirectoryName); error = _OpenPackagesSubDirectory(directoryPath, true, fWritableFilesDirectory); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(fVolume->PackagesDirectoryRef(), directoryPath.ToString()), directoryPath.ToString())) .SetPackageName(package->FileName()) .SetSystemError(error); } } // 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) { // 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(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(extractedFilesDirectory, sourceParentPath), sourceParentPath)) .SetPackageName(package->FileName()) .SetSystemError(error); } } else { sourceDirectory = &extractedFilesDirectory; } // open parent directory of the target entry -- create, if necessary BString targetPath(file.Path()); 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(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(rootDirectory, targetParentPath), targetParentPath)) .SetPackageName(package->FileName()) .SetSystemError(error); } _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 \"%s\", copying...\n", targetName); 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(B_TRANSACTION_FAILED_TO_COPY_FILE) .SetPath1(_GetPath( FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), relativeSourcePath)) .SetPath2(_GetPath( FSUtils::Entry(targetDirectory, targetName), targetName)) .SetSystemError(error); } copyOperation.Finished(); return; } struct stat sourceStat; status_t error = sourceDirectory.GetStatFor(relativeSourcePath.Leaf(), &sourceStat); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY) .SetPath1(_GetPath( FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), relativeSourcePath)) .SetSystemError(error); } 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 \"%s\" exists, but type doesn't match previous " "type\n", targetName); _AddIssue(TransactionIssueBuilder( BTransactionIssue::B_WRITABLE_FILE_TYPE_MISMATCH) .SetPath1(FSUtils::Entry(targetDirectory, targetName)) .SetPath2(FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()))); 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(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), relativeSourcePath)) .SetPackageName(package->FileName()) .SetSystemError(error); } BDirectory targetSubDirectory; error = targetSubDirectory.SetTo(&targetDirectory, targetName); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(targetDirectory, targetName), targetName)) .SetPackageName(package->FileName()) .SetSystemError(error); } 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. PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " "failed to get SYS:PACKAGE attribute for \"%s\", can't tell if " "file needs to be updated\n", targetName); if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) { _AddIssue(TransactionIssueBuilder( BTransactionIssue::B_WRITABLE_FILE_NO_PACKAGE_ATTRIBUTE) .SetPath1(FSUtils::Entry(targetDirectory, targetName))); } return; } // If that's our package, we're happy. if (originalPackage == package->RevisionedNameThrows()) { PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " "file \"%s\" tagged with same package version we're activating\n", targetName); 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; error = fWritableFilesDirectory.GetStatFor(originalRelativeSourcePath, &originalPackageStat); if (error != 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()); if (error != B_OK) { _AddIssue(TransactionIssueBuilder( BTransactionIssue ::B_WRITABLE_FILE_OLD_ORIGINAL_FILE_MISSING) .SetPath1(FSUtils::Entry(targetDirectory, targetName)) .SetPath2(FSUtils::Entry(fWritableFilesDirectory, originalRelativeSourcePath))); } else { _AddIssue(TransactionIssueBuilder( BTransactionIssue ::B_WRITABLE_FILE_OLD_ORIGINAL_FILE_TYPE_MISMATCH) .SetPath1(FSUtils::Entry(targetDirectory, targetName)) .SetPath2(FSUtils::Entry(fWritableFilesDirectory, originalRelativeSourcePath))); } return; } 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 \"%s\" failed (%s) or files aren't equal\n", targetName, strerror(error)); if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) { if (error != B_OK) { _AddIssue(TransactionIssueBuilder( BTransactionIssue ::B_WRITABLE_FILE_COMPARISON_FAILED) .SetPath1(FSUtils::Entry(targetDirectory, targetName)) .SetPath2(FSUtils::Entry(fWritableFilesDirectory, originalRelativeSourcePath)) .SetSystemError(error)); } else { _AddIssue(TransactionIssueBuilder( BTransactionIssue ::B_WRITABLE_FILE_NOT_EQUAL) .SetPath1(FSUtils::Entry(targetDirectory, targetName)) .SetPath2(FSUtils::Entry(fWritableFilesDirectory, originalRelativeSourcePath))); } } return; } } 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 \"%s\" failed (%s) or symlinks aren't " "equal\n", targetName, strerror(error)); if (updateType != B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD) { if (error != B_OK) { _AddIssue(TransactionIssueBuilder( BTransactionIssue ::B_WRITABLE_SYMLINK_COMPARISON_FAILED) .SetPath1(FSUtils::Entry(targetDirectory, targetName)) .SetPath2(FSUtils::Entry(fWritableFilesDirectory, originalRelativeSourcePath)) .SetSystemError(error)); } else { _AddIssue(TransactionIssueBuilder( BTransactionIssue ::B_WRITABLE_SYMLINK_NOT_EQUAL) .SetPath1(FSUtils::Entry(targetDirectory, targetName)) .SetPath2(FSUtils::Entry(fWritableFilesDirectory, originalRelativeSourcePath))); } } return; } } // 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(B_TRANSACTION_FAILED_TO_COPY_FILE) .SetPath1(_GetPath( FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), relativeSourcePath)) .SetPath2(_GetPath( FSUtils::Entry(targetDirectory, tempTargetName), tempTargetName)) .SetSystemError(error); } 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(B_TRANSACTION_FAILED_TO_MOVE_FILE) .SetPath1(_GetPath( FSUtils::Entry(targetDirectory, tempTargetName), tempTargetName)) .SetPath2(targetName) .SetSystemError(error); } renameOperation.Finished(); copyOperation.Unregister(); } void CommitTransactionHandler::_RevertAddPackagesToActivate() { if (fAddedPackages.empty() || fFirstBootProcessing) 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; } fVolumeState->RemovePackage(package); if (transactionDirectory.InitCheck() != B_OK) continue; // get BEntry for the package NotOwningEntryRef entryRef(package->EntryRef()); 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; } fPackageFileManager->PackageFileMoved(package->File(), fTransactionDirectoryRef); package->File()->IncrementEntryRemovedIgnoreLevel(); } } void CommitTransactionHandler::_RevertRemovePackagesToDeactivate() { if (fRemovedPackages.empty() || fFirstBootProcessing) return; // open packages directory BDirectory packagesDirectory; status_t error = packagesDirectory.SetTo(&fVolume->PackagesDirectoryRef()); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1("") .SetSystemError(error); } 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; } fPackageFileManager->PackageFileMoved(package->File(), fVolume->PackagesDirectoryRef()); package->File()->IncrementEntryCreatedIgnoreLevel(); } } 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; fCurrentPackage = package; const BStringList& scripts = package->Info().PostInstallScripts(); int32 count = scripts.CountStrings(); for (int32 i = 0; i < count; i++) _RunPostOrPreScript(package, scripts.StringAt(i), true); } fCurrentPackage = NULL; } void CommitTransactionHandler::_RunPreUninstallScripts() { // Note this runs in the correct order, so dependents get uninstalled before // the packages they depend on. No need for a reversed loop. for (PackageSet::iterator it = fPackagesToDeactivate.begin(); it != fPackagesToDeactivate.end(); ++it) { Package* package = *it; fCurrentPackage = package; const BStringList& scripts = package->Info().PreUninstallScripts(); int32 count = scripts.CountStrings(); for (int32 i = 0; i < count; i++) _RunPostOrPreScript(package, scripts.StringAt(i), false); } fCurrentPackage = NULL; } void CommitTransactionHandler::_RunPostOrPreScript(Package* package, const BString& script, bool postNotPre) { const char *postOrPreInstallWording = postNotPre ? "post-installation" : "pre-uninstall"; BDirectory rootDir(&fVolume->RootDirectoryRef()); BPath scriptPath(&rootDir, script); status_t error = scriptPath.InitCheck(); if (error != B_OK) { ERROR("Volume::CommitTransactionHandler::_RunPostOrPreScript(): " "failed get path of %s script \"%s\" of package " "%s: %s\n", postOrPreInstallWording, script.String(), package->FileName().String(), strerror(error)); _AddIssue(TransactionIssueBuilder(postNotPre ? BTransactionIssue::B_POST_INSTALL_SCRIPT_NOT_FOUND : BTransactionIssue::B_PRE_UNINSTALL_SCRIPT_NOT_FOUND) .SetPath1(script) .SetSystemError(error)); return; } errno = 0; int result = system(scriptPath.Path()); if (result != 0) { ERROR("Volume::CommitTransactionHandler::_RunPostOrPreScript(): " "running %s script \"%s\" of package %s " "failed: %d (errno: %s)\n", postOrPreInstallWording, script.String(), package->FileName().String(), result, strerror(errno)); if (result < 0 || result == 127) { // bash shell returns 127 on failure. _AddIssue(TransactionIssueBuilder(postNotPre ? BTransactionIssue::B_STARTING_POST_INSTALL_SCRIPT_FAILED : BTransactionIssue::B_STARTING_PRE_UNINSTALL_SCRIPT_FAILED) .SetPath1(BString(scriptPath.Path())) .SetSystemError(errno)); } else { // positive is an exit code from the script itself. _AddIssue(TransactionIssueBuilder(postNotPre ? BTransactionIssue::B_POST_INSTALL_SCRIPT_FAILED : BTransactionIssue::B_PRE_UNINSTALL_SCRIPT_FAILED) .SetPath1(BString(scriptPath.Path())) .SetExitCode(result)); } } } void CommitTransactionHandler::_QueuePostInstallScripts() { BDirectory adminDirectory; status_t error = _OpenPackagesSubDirectory( RelativePath(kAdminDirectoryName), true, adminDirectory); if (error != B_OK) { ERROR("Failed to open administrative directory: %s\n", strerror(error)); return; } BDirectory scriptsDirectory; error = scriptsDirectory.SetTo(&adminDirectory, kQueuedScriptsDirectoryName); if (error == B_ENTRY_NOT_FOUND) error = adminDirectory.CreateDirectory(kQueuedScriptsDirectoryName, &scriptsDirectory); if (error != B_OK) { ERROR("Failed to open queued scripts directory: %s\n", strerror(error)); return; } BDirectory rootDir(&fVolume->RootDirectoryRef()); for (PackageSet::iterator it = fAddedPackages.begin(); it != fAddedPackages.end(); ++it) { Package* package = *it; const BStringList& scripts = package->Info().PostInstallScripts(); for (int32 i = 0; i < scripts.CountStrings(); ++i) { BPath scriptPath(&rootDir, scripts.StringAt(i)); status_t error = scriptPath.InitCheck(); if (error != B_OK) { ERROR("Can't find script: %s\n", scripts.StringAt(i).String()); continue; } // symlink to the script BSymLink scriptLink; scriptsDirectory.CreateSymLink(scriptPath.Leaf(), scriptPath.Path(), &scriptLink); if (scriptLink.InitCheck() != B_OK) { ERROR("Creating symlink failed: %s\n", strerror(scriptLink.InitCheck())); continue; } } } } 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(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY) .SetPath1(_GetPath( FSUtils::Entry(targetDirectory, targetName), targetName)) .SetPackageName(package->FileName()) .SetSystemError(error); } 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(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(targetDirectory, targetName), targetName)) .SetPackageName(package->FileName()) .SetSystemError(error); } 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(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY) .SetPath1(_GetPath( FSUtils::Entry(targetDirectory, temporaryTargetName), temporaryTargetName)) .SetPackageName(package->FileName()) .SetSystemError(error); } if (targetEntry.Exists()) { // remove pre-existing error = BRemoveEngine().RemoveEntry(FSUtils::Entry(targetEntry)); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_REMOVE_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(targetDirectory, temporaryTargetName), temporaryTargetName)) .SetPackageName(package->FileName()) .SetSystemError(error); } } BDirectory& subDirectory = _extractedFilesDirectory; FSTransaction::CreateOperation createSubDirectoryOperation( &fFSTransaction, FSUtils::Entry(targetDirectory, temporaryTargetName)); error = targetDirectory.CreateDirectory(temporaryTargetName, &subDirectory); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_CREATE_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(targetDirectory, temporaryTargetName), temporaryTargetName)) .SetPackageName(package->FileName()) .SetSystemError(error); } createSubDirectoryOperation.Finished(); // extract NotOwningEntryRef packageRef(package->EntryRef()); 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(B_TRANSACTION_FAILED_TO_EXTRACT_PACKAGE_FILE) .SetPath1(contentPath) .SetPackageName(package->FileName()) .SetSystemError(error); } } // tag all entries with the package attribute _TagPackageEntriesRecursively(subDirectory, targetName, true); // rename the subdirectory error = targetEntry.Rename(targetName); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) .SetPath1(_GetPath( FSUtils::Entry(targetDirectory, temporaryTargetName), temporaryTargetName)) .SetPath2(targetName) .SetPackageName(package->FileName()) .SetSystemError(error); } // keep the directory, regardless of whether the transaction is rolled // back createSubDirectoryOperation.Unregister(); } status_t CommitTransactionHandler::_OpenPackagesSubDirectory(const RelativePath& path, bool create, BDirectory& _directory) { // open the packages directory BDirectory directory; status_t error = directory.SetTo(&fVolume->PackagesDirectoryRef()); if (error != B_OK) { ERROR("CommitTransactionHandler::_OpenPackagesSubDirectory(): failed " "to open packages directory: %s\n", strerror(error)); RETURN_ERROR(error); } return FSUtils::OpenSubDirectory(directory, path, create, _directory); } status_t CommitTransactionHandler::_OpenPackagesFile( const RelativePath& subDirectoryPath, const char* fileName, uint32 openMode, BFile& _file, BEntry* _entry) { BDirectory directory; if (!subDirectoryPath.IsEmpty()) { status_t error = _OpenPackagesSubDirectory(subDirectoryPath, (openMode & B_CREATE_FILE) != 0, directory); if (error != B_OK) { ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to " "open packages subdirectory \"%s\": %s\n", subDirectoryPath.ToString().String(), strerror(error)); RETURN_ERROR(error); } } else { status_t error = directory.SetTo(&fVolume->PackagesDirectoryRef()); if (error != B_OK) { ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to " "open packages directory: %s\n", strerror(error)); RETURN_ERROR(error); } } BEntry stackEntry; BEntry& entry = _entry != NULL ? *_entry : stackEntry; status_t error = entry.SetTo(&directory, fileName); if (error != B_OK) { ERROR("CommitTransactionHandler::_OpenPackagesFile(): failed to get " "entry for file: %s", strerror(error)); RETURN_ERROR(error); } return _file.SetTo(&entry, openMode); } void CommitTransactionHandler::_WriteActivationFile( const RelativePath& directoryPath, const char* fileName, const PackageSet& toActivate, const PackageSet& toDeactivate, BEntry& _entry) { // create the content BString activationFileContent; _CreateActivationFileContent(toActivate, toDeactivate, activationFileContent); // write the file status_t error = _WriteTextFile(directoryPath, fileName, activationFileContent, _entry); if (error != B_OK) { BString filePath = directoryPath.ToString() << '/' << fileName; throw Exception(B_TRANSACTION_FAILED_TO_WRITE_ACTIVATION_FILE) .SetPath1(_GetPath( FSUtils::Entry(fVolume->PackagesDirectoryRef(), filePath), filePath)) .SetSystemError(error); } } void CommitTransactionHandler::_CreateActivationFileContent( const PackageSet& toActivate, const PackageSet& toDeactivate, BString& _content) { BString activationFileContent; for (PackageFileNameHashTable::Iterator it = fVolumeState->ByFileNameIterator(); Package* package = it.Next();) { if (package->IsActive() && toDeactivate.find(package) == toDeactivate.end()) { int32 length = activationFileContent.Length(); activationFileContent << package->FileName() << '\n'; if (activationFileContent.Length() < length + package->FileName().Length() + 1) { throw Exception(B_TRANSACTION_NO_MEMORY); } } } for (PackageSet::const_iterator it = toActivate.begin(); it != toActivate.end(); ++it) { Package* package = *it; int32 length = activationFileContent.Length(); activationFileContent << package->FileName() << '\n'; if (activationFileContent.Length() < length + package->FileName().Length() + 1) { throw Exception(B_TRANSACTION_NO_MEMORY); } } _content = activationFileContent; } status_t CommitTransactionHandler::_WriteTextFile(const RelativePath& directoryPath, const char* fileName, const BString& content, BEntry& _entry) { BFile file; status_t error = _OpenPackagesFile(directoryPath, fileName, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE, file, &_entry); if (error != B_OK) { ERROR("CommitTransactionHandler::_WriteTextFile(): failed to create " "file \"%s/%s\": %s\n", directoryPath.ToString().String(), fileName, strerror(error)); return error; } ssize_t bytesWritten = file.Write(content.String(), content.Length()); if (bytesWritten < 0) { ERROR("CommitTransactionHandler::_WriteTextFile(): failed to write " "file \"%s/%s\": %s\n", directoryPath.ToString().String(), fileName, strerror(bytesWritten)); return bytesWritten; } return B_OK; } void CommitTransactionHandler::_ChangePackageActivation( const PackageSet& packagesToActivate, const PackageSet& packagesToDeactivate) { INFORM("CommitTransactionHandler::_ChangePackageActivation(): activating " "%zu, deactivating %zu packages\n", packagesToActivate.size(), packagesToDeactivate.size()); // write the temporary package activation file BEntry activationFileEntry; _WriteActivationFile(RelativePath(kAdminDirectoryName), kTemporaryActivationFileName, packagesToActivate, packagesToDeactivate, activationFileEntry); // notify packagefs if (fVolumeStateIsActive) { _ChangePackageActivationIOCtl(packagesToActivate, packagesToDeactivate); } else { // TODO: Notify packagefs that active packages have been moved or do // node monitoring in packagefs! } // rename the temporary activation file to the final file status_t error = activationFileEntry.Rename(kActivationFileName, true); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_MOVE_FILE) .SetPath1(_GetPath( FSUtils::Entry(activationFileEntry), activationFileEntry.Name())) .SetPath2(kActivationFileName) .SetSystemError(error); // TODO: We should probably try to revert the activation changes, though that // will fail, if this method has been called in response to node monitoring // events. Alternatively moving the package activation file could be made part // of the ioctl(), since packagefs should be able to undo package changes until // the very end, unless running out of memory. In the end the situation would be // bad anyway, though, since the activation file may refer to removed packages // and things would be in an inconsistent state after rebooting. } // Update our state, i.e. remove deactivated packages and mark activated // packages accordingly. fVolumeState->ActivationChanged(packagesToActivate, packagesToDeactivate); } void CommitTransactionHandler::_ChangePackageActivationIOCtl( const PackageSet& packagesToActivate, const PackageSet& packagesToDeactivate) { // compute the size of the allocation we need for the activation change // request int32 itemCount = packagesToActivate.size() + packagesToDeactivate.size(); size_t requestSize = sizeof(PackageFSActivationChangeRequest) + itemCount * sizeof(PackageFSActivationChangeItem); for (PackageSet::iterator it = packagesToActivate.begin(); it != packagesToActivate.end(); ++it) { requestSize += (*it)->FileName().Length() + 1; } for (PackageSet::iterator it = packagesToDeactivate.begin(); it != packagesToDeactivate.end(); ++it) { requestSize += (*it)->FileName().Length() + 1; } // allocate and prepare the request PackageFSActivationChangeRequest* request = (PackageFSActivationChangeRequest*)malloc(requestSize); if (request == NULL) throw Exception(B_TRANSACTION_NO_MEMORY); MemoryDeleter requestDeleter(request); request->itemCount = itemCount; PackageFSActivationChangeItem* item = &request->items[0]; char* nameBuffer = (char*)(item + itemCount); for (PackageSet::iterator it = packagesToActivate.begin(); it != packagesToActivate.end(); ++it, item++) { _FillInActivationChangeItem(item, PACKAGE_FS_ACTIVATE_PACKAGE, *it, nameBuffer); } for (PackageSet::iterator it = packagesToDeactivate.begin(); it != packagesToDeactivate.end(); ++it, item++) { _FillInActivationChangeItem(item, PACKAGE_FS_DEACTIVATE_PACKAGE, *it, nameBuffer); } // issue the request FileDescriptorCloser fd(fVolume->OpenRootDirectory()); if (!fd.IsSet()) { throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY) .SetPath1(_GetPath( FSUtils::Entry(fVolume->RootDirectoryRef()), "")) .SetSystemError(fd.Get()); } if (ioctl(fd.Get(), PACKAGE_FS_OPERATION_CHANGE_ACTIVATION, request, requestSize) != 0) { // TODO: We need more error information and error handling! throw Exception(B_TRANSACTION_FAILED_TO_CHANGE_PACKAGE_ACTIVATION) .SetSystemError(errno); } } void CommitTransactionHandler::_FillInActivationChangeItem( PackageFSActivationChangeItem* item, PackageFSActivationChangeType type, Package* package, char*& nameBuffer) { item->type = type; item->packageDeviceID = package->NodeRef().device; item->packageNodeID = package->NodeRef().node; item->nameLength = package->FileName().Length(); item->parentDeviceID = fVolume->PackagesDeviceID(); item->parentDirectoryID = fVolume->PackagesDirectoryID(); item->name = nameBuffer; strcpy(nameBuffer, package->FileName()); nameBuffer += package->FileName().Length() + 1; } bool CommitTransactionHandler::_IsSystemPackage(Package* package) { // package name should be "haiku[_]" const BString& name = package->Info().Name(); if (!name.StartsWith("haiku")) return false; if (name.Length() == 5) return true; if (name[5] != '_') return false; BPackageArchitecture architecture; return BPackageInfo::GetArchitectureByName(name.String() + 6, architecture) == B_OK; } void CommitTransactionHandler::_AddIssue(const TransactionIssueBuilder& builder) { fResult.AddIssue(builder.BuildIssue(fCurrentPackage)); } /*static*/ BString CommitTransactionHandler::_GetPath(const FSUtils::Entry& entry, const BString& fallback) { BString path = entry.Path(); return path.IsEmpty() ? fallback : path; } /*static*/ void CommitTransactionHandler::_TagPackageEntriesRecursively(BDirectory& directory, const BString& value, bool nonDirectoriesOnly) { char buffer[offsetof(struct dirent, d_name) + 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) { throw Exception(B_TRANSACTION_FAILED_TO_ACCESS_ENTRY) .SetPath1(_GetPath( FSUtils::Entry(directory, entry->d_name), entry->d_name)) .SetSystemError(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) { throw Exception(isDirectory ? B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY : B_TRANSACTION_FAILED_TO_OPEN_FILE) .SetPath1(_GetPath( FSUtils::Entry(directory, entry->d_name), entry->d_name)) .SetSystemError(error); } if (!isDirectory || !nonDirectoriesOnly) { error = node->WriteAttrString(kPackageFileAttribute, &value); if (error != B_OK) { throw Exception(B_TRANSACTION_FAILED_TO_WRITE_FILE_ATTRIBUTE) .SetPath1(_GetPath( FSUtils::Entry(directory, entry->d_name), entry->d_name)) .SetSystemError(error); } } // recurse if (isDirectory) { _TagPackageEntriesRecursively(stackDirectory, value, nonDirectoriesOnly); } } } /*static*/ status_t CommitTransactionHandler::_AssertEntriesAreEqual(const BEntry& entry, const BDirectory* directory) { BFile a; status_t status = a.SetTo(&entry, B_READ_ONLY); if (status != B_OK) return status; BFile b; status = b.SetTo(directory, entry.Name(), B_READ_ONLY); if (status != B_OK) return status; off_t aSize; status = a.GetSize(&aSize); if (status != B_OK) return status; off_t bSize; status = b.GetSize(&bSize); if (status != B_OK) return status; if (aSize != bSize) return B_FILE_EXISTS; const size_t bufferSize = 4096; uint8 aBuffer[bufferSize]; uint8 bBuffer[bufferSize]; while (aSize > 0) { ssize_t aRead = a.Read(aBuffer, bufferSize); ssize_t bRead = b.Read(bBuffer, bufferSize); if (aRead < 0 || aRead != bRead) return B_FILE_EXISTS; if (memcmp(aBuffer, bBuffer, aRead) != 0) return B_FILE_EXISTS; aSize -= aRead; } INFORM("CommitTransactionHandler::_AssertEntriesAreEqual(): " "Package file '%s' already exists in target folder " "with equal contents\n", entry.Name()); return B_OK; }