* Copyright 2013-2014, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold <ingo_weinhold@gmx.de>
*/
#include "CommitTransactionHandler.h"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <File.h>
#include <Path.h>
#include <SymLink.h>
#include <AutoDeleter.h>
#include <CopyEngine.h>
#include <NotOwningEntryRef.h>
#include <package/CommitTransactionResult.h>
#include <package/DaemonDefs.h>
#include <RemoveEngine.h>
#include "Constants.h"
#include "DebugSupport.h"
#include "Exception.h"
#include "PackageFileManager.h"
#include "VolumeState.h"
using namespace BPackageKit::BPrivate;
using BPackageKit::BTransactionIssue;
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;
};
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()
{
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)
{
if (transaction.ChangeCount() != fVolume->ChangeCount())
throw Exception(B_TRANSACTION_CHANGE_COUNT_MISMATCH);
fFirstBootProcessing = transaction.FirstBootProcessing();
_GetPackagesToDeactivate(transaction);
_ReadPackagesToActivate(transaction);
if (fPackagesToActivate.IsEmpty() && fPackagesToDeactivate.empty()) {
WARN("Bad package activation request: no packages to activate or"
" deactivate\n");
throw Exception(B_TRANSACTION_BAD_REQUEST);
}
_ApplyChanges();
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();
}
}
}
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()
{
_RevertAddPackagesToActivate();
_RevertRemovePackagesToDeactivate();
_RevertUserGroupChanges();
fFSTransaction.RollBack();
}
VolumeState*
CommitTransactionHandler::DetachVolumeState()
{
VolumeState* result = fVolumeState;
fVolumeState = NULL;
return result;
}
void
CommitTransactionHandler::_GetPackagesToDeactivate(
const BActivationTransaction& transaction)
{
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)
{
const BStringList& packagesToActivate
= transaction.PackagesToActivate();
int32 packagesToActivateCount = packagesToActivate.CountStrings();
if (packagesToActivateCount == 0)
return;
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);
}
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);
}
for (int32 i = 0; i < packagesToActivateCount; i++) {
BString packageName = packagesToActivate.StringAt(i);
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);
}
}
}
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)
{
_CreateOldStateDirectory();
_RemovePackagesToDeactivate();
_AddPackagesToActivate();
_RunPreUninstallScripts();
_ChangePackageActivation(fAddedPackages, fRemovedPackages);
} else
_PrepareFirstBootPackages();
if (fVolumeStateIsActive || fFirstBootProcessing) {
_RunPostInstallScripts();
} else {
_QueuePostInstallScripts();
}
fAddedPackages.clear();
fRemovedPackages.clear();
fPackagesToActivate.MakeEmpty(false);
fPackagesToDeactivate.clear();
}
void
CommitTransactionHandler::_CreateOldStateDirectory()
{
time_t stateTime = 0;
{
BFile oldActivationFile;
BEntry oldActivationEntry;
if (_OpenPackagesFile(RelativePath(kAdminDirectoryName), kActivationFileName,
B_READ_ONLY, oldActivationFile, &oldActivationEntry) != B_OK
|| oldActivationEntry.GetModificationTime(&stateTime) != B_OK) {
stateTime = time(NULL);
}
}
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);
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);
}
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);
}
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;
if (_IsSystemPackage(package))
fVolumeStateIsActive = false;
if (fPackagesAlreadyRemoved.find(package)
!= fPackagesAlreadyRemoved.end()) {
fRemovedPackages.insert(package);
continue;
}
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);
}
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;
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("<packages>")
.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;
}
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);
}
fAddedPackages.insert(package);
error = entry.MoveTo(&packagesDirectory);
if (error == B_FILE_EXISTS) {
error = _AssertEntriesAreEqual(entry, &packagesDirectory);
if (error == B_OK) {
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));
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();
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;
const BStringList& groups = package->Info().Groups();
int32 count = groups.CountStrings();
for (int32 i = 0; i < count; i++)
_AddGroup(package, groups.StringAt(i));
const BObjectList<BUser, true>& users = package->Info().Users();
for (int32 i = 0; const BUser* user = users.ItemAt(i); i++)
_AddUser(package, *user);
_AddGlobalWritableFiles(package);
fCurrentPackage = NULL;
}
void
CommitTransactionHandler::_AddGroup(Package* package, const BString& groupName)
{
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;
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)
{
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;
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());
}
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)
{
const BObjectList<BGlobalWritableFileInfo, true>& 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;
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()),
"<packagefs root>"))
.SetSystemError(error);
}
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);
}
}
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)
{
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;
}
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)
{
struct stat targetStat;
if (targetDirectory.GetStatFor(targetName, &targetStat) != B_OK) {
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))) {
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)) {
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;
}
BString originalPackage;
if (BNode(&targetDirectory, targetName).ReadAttrString(
kPackageFileAttribute, &originalPackage) != B_OK) {
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 (originalPackage == package->RevisionedNameThrows()) {
PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): "
"file \"%s\" tagged with same package version we're activating\n",
targetName);
return;
}
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)) {
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)) {
bool equal;
error = FSUtils::CompareFileContent(
FSUtils::Entry(fWritableFilesDirectory,
originalRelativeSourcePath),
FSUtils::Entry(targetDirectory, targetName),
equal);
if (error != B_OK || !equal) {
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 {
bool equal;
error = FSUtils::CompareSymLinks(
FSUtils::Entry(fWritableFilesDirectory,
originalRelativeSourcePath),
FSUtils::Entry(targetDirectory, targetName),
equal);
if (error != B_OK || !equal) {
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;
}
}
BString tempTargetName;
tempTargetName.SetToFormat("%s.%s", targetName,
package->RevisionedNameThrows().String());
if (tempTargetName.IsEmpty())
throw std::bad_alloc();
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();
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;
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) {
Package* package = *it;
if (fPackagesAlreadyAdded.find(package)
!= fPackagesAlreadyAdded.end()) {
continue;
}
fVolumeState->RemovePackage(package);
if (transactionDirectory.InitCheck() != B_OK)
continue;
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;
}
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;
BDirectory packagesDirectory;
status_t error
= packagesDirectory.SetTo(&fVolume->PackagesDirectoryRef());
if (error != B_OK) {
throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
.SetPath1("<packages>")
.SetSystemError(error);
}
for (PackageSet::iterator it = fRemovedPackages.begin();
it != fRemovedPackages.end(); ++it) {
Package* package = *it;
if (fPackagesAlreadyRemoved.find(package)
!= fPackagesAlreadyRemoved.end()) {
continue;
}
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;
}
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()
{
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());
}
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()
{
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) {
_AddIssue(TransactionIssueBuilder(postNotPre
? BTransactionIssue::B_STARTING_POST_INSTALL_SCRIPT_FAILED
: BTransactionIssue::B_STARTING_PRE_UNINSTALL_SCRIPT_FAILED)
.SetPath1(BString(scriptPath.Path()))
.SetSystemError(errno));
} else {
_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;
}
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)
{
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()) {
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;
}
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()) {
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();
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);
}
}
_TagPackageEntriesRecursively(subDirectory, targetName, true);
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);
}
createSubDirectoryOperation.Unregister();
}
status_t
CommitTransactionHandler::_OpenPackagesSubDirectory(const RelativePath& path,
bool create, BDirectory& _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)
{
BString activationFileContent;
_CreateActivationFileContent(toActivate, toDeactivate,
activationFileContent);
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());
BEntry activationFileEntry;
_WriteActivationFile(RelativePath(kAdminDirectoryName),
kTemporaryActivationFileName, packagesToActivate, packagesToDeactivate,
activationFileEntry);
if (fVolumeStateIsActive) {
_ChangePackageActivationIOCtl(packagesToActivate, packagesToDeactivate);
} else {
}
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);
}
fVolumeState->ActivationChanged(packagesToActivate, packagesToDeactivate);
}
void
CommitTransactionHandler::_ChangePackageActivationIOCtl(
const PackageSet& packagesToActivate,
const PackageSet& packagesToDeactivate)
{
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;
}
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);
}
FileDescriptorCloser fd(fVolume->OpenRootDirectory());
if (!fd.IsSet()) {
throw Exception(B_TRANSACTION_FAILED_TO_OPEN_DIRECTORY)
.SetPath1(_GetPath(
FSUtils::Entry(fVolume->RootDirectoryRef()),
"<packagefs root>"))
.SetSystemError(fd.Get());
}
if (ioctl(fd.Get(), PACKAGE_FS_OPERATION_CHANGE_ACTIVATION, request,
requestSize) != 0) {
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)
{
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));
}
BString
CommitTransactionHandler::_GetPath(const FSUtils::Entry& entry,
const BString& fallback)
{
BString path = entry.Path();
return path.IsEmpty() ? fallback : path;
}
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;
}
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);
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);
}
}
if (isDirectory) {
_TagPackageEntriesRecursively(stackDirectory, value,
nonDirectoriesOnly);
}
}
}
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;
}