From 23733521a7b2d6c16027654c606c6069ef026357 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Fri, 27 Sep 2013 00:47:09 +0200 Subject: [PATCH] package daemon: Implement writable files handling * Move Volume::Exception to top level and into own files. * Add utility class FSUtils, move Volume::RelativePath there and add a bunch of FS utility functionality. * Add FSTransaction, a helper class to record FS operations and revert them. * When activating a package we now extract the writable files/directories it declares. The handling is not quite complete: - We don't handle merges yet. I.e. the user will have to do that manually for now. - We don't propagate issues/infos regarding the writable files (e.g. that a manual intervention is required) to the user yet. --- src/servers/package/Exception.cpp | 63 ++ src/servers/package/Exception.h | 38 ++ src/servers/package/FSTransaction.cpp | 232 ++++++++ src/servers/package/FSTransaction.h | 132 +++++ src/servers/package/FSUtils.cpp | 248 ++++++++ src/servers/package/FSUtils.h | 229 +++++++ src/servers/package/Jamfile | 5 +- src/servers/package/Root.cpp | 13 + src/servers/package/Root.h | 3 + src/servers/package/Volume.cpp | 825 ++++++++++++++++++-------- src/servers/package/Volume.h | 15 +- 11 files changed, 1560 insertions(+), 243 deletions(-) create mode 100644 src/servers/package/Exception.cpp create mode 100644 src/servers/package/Exception.h create mode 100644 src/servers/package/FSTransaction.cpp create mode 100644 src/servers/package/FSTransaction.h create mode 100644 src/servers/package/FSUtils.cpp create mode 100644 src/servers/package/FSUtils.h diff --git a/src/servers/package/Exception.cpp b/src/servers/package/Exception.cpp new file mode 100644 index 0000000000..fd282d8025 --- /dev/null +++ b/src/servers/package/Exception.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "Exception.h" + + +using namespace BPackageKit::BPrivate; + + +Exception::Exception(int32 error, const char* errorMessage, + const char* packageName) + : + fError(error), + fErrorMessage(errorMessage), + fPackageName(packageName) +{ +} + + +BString +Exception::ToString() const +{ + const char* error; + if (fError >= 0) { + switch (fError) { + case B_DAEMON_OK: + error = "no error"; + break; + case B_DAEMON_CHANGE_COUNT_MISMATCH: + error = "transaction out of date"; + break; + case B_DAEMON_BAD_REQUEST: + error = "invalid transaction"; + break; + case B_DAEMON_NO_SUCH_PACKAGE: + error = "no such package"; + break; + case B_DAEMON_PACKAGE_ALREADY_EXISTS: + error = "package already exists"; + break; + default: + error = "unknown error"; + break; + } + } else + error = strerror(fError); + + BString string; + if (!fErrorMessage.IsEmpty()) { + string = fErrorMessage; + string << ": "; + } + + string << error; + + if (!fPackageName.IsEmpty()) + string << ", package: \"" << fPackageName << '"'; + + return string; +} diff --git a/src/servers/package/Exception.h b/src/servers/package/Exception.h new file mode 100644 index 0000000000..dc5f5f84db --- /dev/null +++ b/src/servers/package/Exception.h @@ -0,0 +1,38 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef EXCEPTION_H +#define EXCEPTION_H + + +#include + +#include + + +class Exception { +public: + Exception(int32 error, + const char* errorMessage = NULL, + const char* packageName = NULL); + + int32 Error() const + { return fError; } + + const BString& ErrorMessage() const + { return fErrorMessage; } + + const BString& PackageName() const + { return fPackageName; } + + BString ToString() const; + +private: + int32 fError; + BString fErrorMessage; + BString fPackageName; +}; + + +#endif // EXCEPTION_H diff --git a/src/servers/package/FSTransaction.cpp b/src/servers/package/FSTransaction.cpp new file mode 100644 index 0000000000..dfba68dcda --- /dev/null +++ b/src/servers/package/FSTransaction.cpp @@ -0,0 +1,232 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "FSTransaction.h" + +#include +#include + +#include +#include + +#include "DebugSupport.h" +#include "Exception.h" + + +// #pragma mark - OperationInfo + + +struct FSTransaction::OperationInfo { +public: + enum Type { + TYPE_CREATE, + TYPE_REMOVE, + TYPE_MOVE, + }; + +public: + OperationInfo(Type type, const std::string& fromPath, + const std::string& toPath, int32 modifiedOperation) + : + fType(type), + fFromPath(fromPath), + fToPath(toPath), + fModifiedOperation(modifiedOperation), + fEnabled(true) + { + } + + const std::string& FromPath() const + { + return fFromPath; + } + + const std::string& ToPath() const + { + return fToPath; + } + + int32 ModifiedOperation() const + { + return fModifiedOperation; + } + + void SetModifiedOperation(int32 modifiedOperation) + { + fModifiedOperation = modifiedOperation; + } + + bool IsEnabled() const + { + return fEnabled; + } + + void SetEnabled(bool enabled) + { + fEnabled = enabled; + } + + status_t RollBack() const + { + switch (fType) { + case TYPE_CREATE: + { + status_t error = BRemoveEngine().RemoveEntry( + Entry(fFromPath.c_str())); + if (error != B_OK) { + ERROR("Failed to remove \"%s\": %s\n", fFromPath.c_str(), + strerror(error)); + } + return error; + } + + case TYPE_REMOVE: + { + if (fToPath.empty()) + return B_NOT_SUPPORTED; + + status_t error = BCopyEngine( + BCopyEngine::COPY_RECURSIVELY + | BCopyEngine::UNLINK_DESTINATION) + .CopyEntry(fToPath.c_str(), fFromPath.c_str()); + if (error != B_OK) { + ERROR("Failed to copy \"%s\" to \"%s\": %s\n", + fToPath.c_str(), fFromPath.c_str(), strerror(error)); + } + return error; + } + + case TYPE_MOVE: + { + BEntry entry; + status_t error = entry.SetTo(fToPath.c_str()); + if (error != B_OK) { + ERROR("Failed to init entry for \"%s\": %s\n", + fToPath.c_str(), strerror(error)); + return error; + } + + error = entry.Rename(fFromPath.c_str(), true); + if (error != B_OK) { + ERROR("Failed to move \"%s\" to \"%s\": %s\n", + fToPath.c_str(), fFromPath.c_str(), strerror(error)); + return error; + } + return error; + } + } + + return B_ERROR; + } + +private: + Type fType; + std::string fFromPath; + std::string fToPath; + int32 fModifiedOperation; + bool fEnabled; +}; + + +// #pragma mark - FSTransaction + + +FSTransaction::FSTransaction() +{ +} + + +FSTransaction::~FSTransaction() +{ +} + + +void +FSTransaction::RollBack() +{ + int32 count = (int32)fOperations.size(); + for (int32 i = count - 1; i >= 0; i--) { + const OperationInfo& operation = fOperations[i]; + bool rolledBack = false; + if (operation.IsEnabled()) + rolledBack = operation.RollBack() == B_OK; + + if (!rolledBack && operation.ModifiedOperation() >= 0) + fOperations[operation.ModifiedOperation()].SetEnabled(false); + } +} + + +int32 +FSTransaction::CreateEntry(const Entry& entry, int32 modifiedOperation) +{ + fOperations.push_back( + OperationInfo(OperationInfo::TYPE_CREATE, _GetPath(entry), + std::string(), modifiedOperation)); + return (int32)fOperations.size() - 1; +} + + +int32 +FSTransaction::RemoveEntry(const Entry& entry, const Entry& backupEntry, + int32 modifiedOperation) +{ + fOperations.push_back( + OperationInfo(OperationInfo::TYPE_REMOVE, _GetPath(entry), + _GetPath(backupEntry), modifiedOperation)); + return (int32)fOperations.size() - 1; +} + + +int32 +FSTransaction::MoveEntry(const Entry& fromEntry, const Entry& toEntry, + int32 modifiedOperation) +{ + fOperations.push_back( + OperationInfo(OperationInfo::TYPE_MOVE, _GetPath(fromEntry), + _GetPath(toEntry), modifiedOperation)); + return (int32)fOperations.size() - 1; +} + + +void +FSTransaction::RemoveOperationAt(int32 index) +{ + int32 count = fOperations.size(); + if (index < 0 || index >= count) { + throw Exception(B_ERROR, + BString().SetToFormat("FSTransaction::RemoveOperationAt(): invalid " + "operation index %" B_PRId32 "/%" B_PRId32, index, count)); + } + + fOperations.erase(fOperations.begin() + index); + + for (int32 i = index; i < count; i++) { + int32 modifiedOperation = fOperations[i].ModifiedOperation(); + if (modifiedOperation == index) + fOperations[i].SetModifiedOperation(-1); + else if (modifiedOperation > index) + fOperations[i].SetModifiedOperation(modifiedOperation - 1); + } +} + + +/*static*/ std::string +FSTransaction::_GetPath(const Entry& entry) +{ + BPath pathBuffer; + const char* path; + status_t error = entry.GetPath(pathBuffer, path); + if (error == B_OK && path[0] != '/') { + // make absolute + error = pathBuffer.SetTo(path); + } + + if (error != B_OK) + throw Exception(error); + + return path; +} diff --git a/src/servers/package/FSTransaction.h b/src/servers/package/FSTransaction.h new file mode 100644 index 0000000000..4885599a2c --- /dev/null +++ b/src/servers/package/FSTransaction.h @@ -0,0 +1,132 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef FS_TRANSACTION_H +#define FS_TRANSACTION_H + + +#include +#include + +#include "FSUtils.h" + + +class FSTransaction { +public: + typedef FSUtils::Entry Entry; + + class Operation; + class CreateOperation; + class RemoveOperation; + class MoveOperation; + +public: + FSTransaction(); + ~FSTransaction(); + + void RollBack(); + + int32 CreateEntry(const Entry& entry, + int32 modifiedOperation = -1); + int32 RemoveEntry(const Entry& entry, + const Entry& backupEntry, + int32 modifiedOperation = -1); + int32 MoveEntry(const Entry& fromEntry, + const Entry& toEntry, + int32 modifiedOperation = -1); + + void RemoveOperationAt(int32 index); + +private: + struct OperationInfo; + typedef std::vector OperationList; + +private: + static std::string _GetPath(const Entry& entry); + +private: + OperationList fOperations; +}; + + +class FSTransaction::Operation { +public: + Operation(FSTransaction* transaction, int32 operation) + : + fTransaction(transaction), + fOperation(operation) + { + } + + ~Operation() + { + if (fTransaction != NULL && fOperation >= 0 && !fIsFinished) + fTransaction->RemoveOperationAt(fOperation); + } + + /*! Arms the operation rollback, i.e. rolling back the transaction will + revert this operation. + */ + void Finished() + { + fIsFinished = true; + } + + /*! Unregisters the operation rollback, i.e. rolling back the transaction + will not revert this operation. + */ + void Unregister() + { + if (fTransaction != NULL && fOperation >= 0) { + fTransaction->RemoveOperationAt(fOperation); + fIsFinished = false; + fTransaction = NULL; + fOperation = -1; + } + } + +private: + FSTransaction* fTransaction; + int32 fOperation; + bool fIsFinished; +}; + + +class FSTransaction::CreateOperation : public FSTransaction::Operation { +public: + CreateOperation(FSTransaction* transaction, const Entry& entry, + int32 modifiedOperation = -1) + : + Operation(transaction, + transaction->CreateEntry(entry, modifiedOperation)) + { + } +}; + + +class FSTransaction::RemoveOperation : public FSTransaction::Operation { +public: + RemoveOperation(FSTransaction* transaction, const Entry& entry, + const Entry& backupEntry, int32 modifiedOperation = -1) + : + Operation(transaction, + transaction->RemoveEntry(entry, backupEntry, modifiedOperation)) + { + } +}; + + +class FSTransaction::MoveOperation : public FSTransaction::Operation { +public: + MoveOperation(FSTransaction* transaction, const Entry& fromEntry, + const Entry& toEntry, int32 modifiedOperation = -1) + : + Operation(transaction, + transaction->MoveEntry(fromEntry, toEntry, modifiedOperation)) + { + } +}; + + +#endif // FS_TRANSACTION_H diff --git a/src/servers/package/FSUtils.cpp b/src/servers/package/FSUtils.cpp new file mode 100644 index 0000000000..e3aa83f043 --- /dev/null +++ b/src/servers/package/FSUtils.cpp @@ -0,0 +1,248 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "FSUtils.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include "DebugSupport.h" + + +static const size_t kCompareDataBufferSize = 64 * 1024; +const char* const kShellEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!"; + + +/*static*/ BString +FSUtils::ShellEscapeString(const BString& string) +{ + BString result(string); + result.CharacterEscape(kShellEscapeCharacters, '\\'); + if (result.IsEmpty()) + throw std::bad_alloc(); + return result; +} + + +/*static*/ status_t +FSUtils::OpenSubDirectory(BDirectory& baseDirectory, const RelativePath& path, + bool create, BDirectory& _directory) +{ + // get a string for the path + BString pathString = path.ToString(); + if (pathString.IsEmpty()) + RETURN_ERROR(B_NO_MEMORY); + + // If creating is not allowed, just try to open it. + if (!create) + RETURN_ERROR(_directory.SetTo(&baseDirectory, pathString)); + + // get an absolute path and create the subdirectory + BPath absolutePath; + status_t error = absolutePath.SetTo(&baseDirectory, pathString); + if (error != B_OK) { + ERROR("Volume::OpenSubDirectory(): failed to get absolute path " + "for subdirectory \"%s\": %s\n", pathString.String(), + strerror(error)); + RETURN_ERROR(error); + } + + error = create_directory(absolutePath.Path(), + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (error != B_OK) { + ERROR("Volume::OpenSubDirectory(): failed to create " + "subdirectory \"%s\": %s\n", pathString.String(), + strerror(error)); + RETURN_ERROR(error); + } + + RETURN_ERROR(_directory.SetTo(&baseDirectory, pathString)); +} + + +/*static*/ status_t +FSUtils::CompareFileContent(const Entry& entry1, const Entry& entry2, + bool& _equal) +{ + BFile file1; + status_t error = _OpenFile(entry1, file1); + if (error != B_OK) + return error; + + BFile file2; + error = _OpenFile(entry2, file2); + if (error != B_OK) + return error; + + return CompareFileContent(file1, file2, _equal); +} + + +/*static*/ status_t +FSUtils::CompareFileContent(BPositionIO& content1, BPositionIO& content2, + bool& _equal) +{ + // get and compare content size + off_t size1; + status_t error = content1.GetSize(&size1); + if (error != B_OK) + return error; + + off_t size2; + error = content2.GetSize(&size2); + if (error != B_OK) + return error; + + if (size1 != size2) { + _equal = false; + return B_OK; + } + + if (size1 == 0) { + _equal = true; + return B_OK; + } + + // allocate a data buffer + uint8* buffer1 = new(std::nothrow) uint8[2 * kCompareDataBufferSize]; + if (buffer1 == NULL) + return B_NO_MEMORY; + MemoryDeleter bufferDeleter(buffer1); + uint8* buffer2 = buffer1 + kCompareDataBufferSize; + + // compare the data + off_t offset = 0; + while (offset < size1) { + size_t toCompare = std::min(size_t(size1 - offset), + kCompareDataBufferSize); + ssize_t bytesRead = content1.ReadAt(offset, buffer1, toCompare); + if (bytesRead < 0) + return bytesRead; + if ((size_t)bytesRead != toCompare) + return B_ERROR; + + bytesRead = content2.ReadAt(offset, buffer2, toCompare); + if (bytesRead < 0) + return bytesRead; + if ((size_t)bytesRead != toCompare) + return B_ERROR; + + if (memcmp(buffer1, buffer2, toCompare) != 0) { + _equal = false; + return B_OK; + } + + offset += bytesRead; + } + + _equal = true; + return B_OK; +} + + +/*static*/ status_t +FSUtils::CompareSymLinks(const Entry& entry1, const Entry& entry2, bool& _equal) +{ + BSymLink symLink1; + status_t error = _OpenSymLink(entry1, symLink1); + if (error != B_OK) + return error; + + BSymLink symLink2; + error = _OpenSymLink(entry2, symLink2); + if (error != B_OK) + return error; + + return CompareSymLinks(symLink1, symLink2, _equal); +} + + +/*static*/ status_t +FSUtils::CompareSymLinks(BSymLink& symLink1, BSymLink& symLink2, bool& _equal) +{ + char buffer1[B_PATH_NAME_LENGTH]; + ssize_t bytesRead1 = symLink1.ReadLink(buffer1, sizeof(buffer1)); + if (bytesRead1 < 0) + return bytesRead1; + + char buffer2[B_PATH_NAME_LENGTH]; + ssize_t bytesRead2 = symLink2.ReadLink(buffer2, sizeof(buffer2)); + if (bytesRead2 < 0) + return bytesRead2; + + _equal = bytesRead1 == bytesRead2 + && memcmp(buffer1, buffer2, bytesRead1) == 0; + return B_OK; +} + + +/*static*/ status_t +FSUtils::ExtractPackageContent(const Entry& packageEntry, + const char* contentPath, const Entry& targetDirectoryEntry) +{ + BPath packagePathBuffer; + const char* packagePath; + status_t error = packageEntry.GetPath(packagePathBuffer, packagePath); + if (error != B_OK) + return error; + + BPath targetPathBuffer; + const char* targetPath; + error = targetDirectoryEntry.GetPath(targetPathBuffer, targetPath); + if (error != B_OK) + return error; + + return ExtractPackageContent(packagePath, contentPath, targetPath); +} + + +/*static*/ status_t +FSUtils::ExtractPackageContent(const char* packagePath, const char* contentPath, + const char* targetDirectoryPath) +{ + std::string commandLine = std::string("package extract -C ") + + ShellEscapeString(targetDirectoryPath).String() + + " " + + ShellEscapeString(packagePath).String(); + if (system(commandLine.c_str()) != 0) + return B_ERROR; + return B_OK; +} + + +/*static*/ status_t +FSUtils::_OpenFile(const Entry& entry, BFile& file) +{ + BPath pathBuffer; + const char* path; + status_t error = entry.GetPath(pathBuffer, path); + if (error != B_OK) + return error; + + return file.SetTo(path, B_READ_ONLY); +} + + +/*static*/ status_t +FSUtils::_OpenSymLink(const Entry& entry, BSymLink& symLink) +{ + BPath pathBuffer; + const char* path; + status_t error = entry.GetPath(pathBuffer, path); + if (error != B_OK) + return error; + + return symLink.SetTo(path); +} diff --git a/src/servers/package/FSUtils.h b/src/servers/package/FSUtils.h new file mode 100644 index 0000000000..b1c1c3868c --- /dev/null +++ b/src/servers/package/FSUtils.h @@ -0,0 +1,229 @@ +/* + * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef FS_UTILS_H +#define FS_UTILS_H + + +#include + +#include + +#include + + +class BDirectory; +class BFile; +class BPositionIO; +class BSymLink; + + +class FSUtils { +public: + typedef ::BPrivate::BEntryOperationEngineBase::Entry Entry; + +public: + struct RelativePath { + RelativePath(const char* component1 = NULL, + const char* component2 = NULL, const char* component3 = NULL) + : + fComponentCount(kMaxComponentCount) + { + fComponents[0] = component1; + fComponents[1] = component2; + fComponents[2] = component3; + + for (size_t i = 0; i < kMaxComponentCount; i++) { + if (fComponents[i] == NULL) { + fComponentCount = i; + break; + } + } + } + + bool IsEmpty() const + { + return fComponentCount == 0; + } + + RelativePath HeadPath(size_t componentsToDropCount = 1) + { + RelativePath result; + if (componentsToDropCount < fComponentCount) { + result.fComponentCount + = fComponentCount - componentsToDropCount; + for (size_t i = 0; i < result.fComponentCount; i++) + result.fComponents[i] = fComponents[i]; + } + + return result; + } + + const char* LastComponent() const + { + return fComponentCount > 0 + ? fComponents[fComponentCount - 1] : NULL; + } + + BString ToString() const + { + if (fComponentCount == 0) + return BString(); + + size_t length = fComponentCount - 1; + for (size_t i = 0; i < fComponentCount; i++) + length += strlen(fComponents[i]); + + BString result; + char* buffer = result.LockBuffer(length + 1); + if (buffer == NULL) + return BString(); + + for (size_t i = 0; i < fComponentCount; i++) { + if (i > 0) { + *buffer = '/'; + buffer++; + } + strcpy(buffer, fComponents[i]); + buffer += strlen(buffer); + } + + return result.UnlockBuffer(); + } + + private: + static const size_t kMaxComponentCount = 3; + + const char* fComponents[kMaxComponentCount]; + size_t fComponentCount; + }; + + // throwing std::bad_alloc() + class Path { + public: + Path(const char* path) + : + fPath(path) + { + if (fPath.IsEmpty()) { + if (path[0] != '\0') + throw std::bad_alloc(); + } else { + // remove duplicate '/'s + char* buffer = fPath.LockBuffer(fPath.Length()); + int32 k = 0; + for (int32 i = 0; buffer[i] != '\0'; i++) { + if (buffer[i] == '/' && k > 0 && buffer[k - 1] == '/') + continue; + buffer[k++] = buffer[i]; + } + + // remove trailing '/' + if (k > 1 && buffer[k - 1] == '/') + k--; + + fPath.LockBuffer(k); + } + } + + Path& AppendComponent(const char* component) + { + if (fPath.IsEmpty()) { + fPath = component; + if (fPath.IsEmpty() && component[0] != '\0') + throw std::bad_alloc(); + } else { + int32 length = fPath.Length(); + if (fPath[length - 1] != '/') { + fPath += '/'; + if (++length != fPath.Length()) + throw std::bad_alloc(); + } + + fPath += component; + if (fPath.Length() <= length) + throw std::bad_alloc(); + } + + return *this; + } + + Path& RemoveLastComponent() + { + int32 index = fPath.FindLast('/'); + if (index < 0 || (index == 0 && fPath.Length() == 1)) + fPath.Truncate(0); + else if (index == 0) + fPath.Truncate(1); + else + fPath.Truncate(index - 1); + return *this; + } + + const char* Leaf() const + { + int32 index = fPath.FindLast('/'); + if (index < 0 || (index == 0 && fPath.Length() == 1)) + return fPath.String(); + return fPath.String() + index + 1; + } + + const BString ToString() const + { + return fPath; + } + + const char* ToCString() const + { + return fPath.String(); + } + + operator const BString&() const + { + return fPath; + } + + operator const char*() const + { + return fPath; + } + + private: + BString fPath; + }; + +public: + static BString ShellEscapeString(const BString& string); + // throw std::bad_alloc + + static status_t OpenSubDirectory(BDirectory& baseDirectory, + const RelativePath& path, bool create, + BDirectory& _directory); + + static status_t CompareFileContent(const Entry& entry1, + const Entry& entry2, bool& _equal); + static status_t CompareFileContent(BPositionIO& content1, + BPositionIO& content2, bool& _equal); + + static status_t CompareSymLinks(const Entry& entry1, + const Entry& entry2, bool& _equal); + static status_t CompareSymLinks(BSymLink& symLink1, + BSymLink& symLink2, bool& _equal); + + static status_t ExtractPackageContent(const Entry& packageEntry, + const char* contentPath, + const Entry& targetDirectoryEntry); + static status_t ExtractPackageContent(const char* packagePath, + const char* contentPath, + const char* targetDirectoryPath); + +private: + static status_t _OpenFile(const Entry& entry, BFile& file); + static status_t _OpenSymLink(const Entry& entry, + BSymLink& symLink); +}; + + + +#endif // FS_UTILS_H diff --git a/src/servers/package/Jamfile b/src/servers/package/Jamfile index ee980f9acb..c3837a77bc 100644 --- a/src/servers/package/Jamfile +++ b/src/servers/package/Jamfile @@ -1,11 +1,14 @@ SubDir HAIKU_TOP src servers package ; UsePrivateSystemHeaders ; -UsePrivateHeaders app interface kernel shared ; +UsePrivateHeaders app interface kernel shared storage ; Server package_daemon : DebugSupport.cpp + Exception.cpp + FSTransaction.cpp + FSUtils.cpp Job.cpp JobQueue.cpp Package.cpp diff --git a/src/servers/package/Root.cpp b/src/servers/package/Root.cpp index 8c3c3c0928..71efbf2dc0 100644 --- a/src/servers/package/Root.cpp +++ b/src/servers/package/Root.cpp @@ -257,6 +257,19 @@ Root::VolumeNodeMonitorEventOccurred(Volume* volume) } +status_t +Root::GetRootDirectoryRef(PackageFSMountType mountType, node_ref& _ref) +{ + AutoLocker locker(fLock); + Volume** volume = _GetVolume(mountType); + if (volume == NULL) + return B_ENTRY_NOT_FOUND; + + _ref = (*volume)->RootDirectoryRef(); + return B_OK; +} + + void Root::LastReferenceReleased() { diff --git a/src/servers/package/Root.h b/src/servers/package/Root.h index de4bde38b2..ed8441be24 100644 --- a/src/servers/package/Root.h +++ b/src/servers/package/Root.h @@ -50,6 +50,9 @@ public: private: // Volume::Listener virtual void VolumeNodeMonitorEventOccurred(Volume* volume); + virtual status_t GetRootDirectoryRef( + PackageFSMountType mountType, + node_ref& _ref) ; protected: virtual void LastReferenceReleased(); diff --git a/src/servers/package/Volume.cpp b/src/servers/package/Volume.cpp index 0e732f8a8c..4d1862982a 100644 --- a/src/servers/package/Volume.cpp +++ b/src/servers/package/Volume.cpp @@ -36,10 +36,15 @@ #include #include +#include +#include #include #include +#include #include "DebugSupport.h" +#include "Exception.h" +#include "FSTransaction.h" using namespace BPackageKit::BPrivate; @@ -54,14 +59,14 @@ static const char* const kActivationFileName = PACKAGES_DIRECTORY_ACTIVATION_FILE; static const char* const kTemporaryActivationFileName = PACKAGES_DIRECTORY_ACTIVATION_FILE ".tmp"; +static const char* const kWritableFilesDirectoryName = "writable-files"; +static const char* const kPackageFileAttribute = "SYS:PACKAGE"; static const bigtime_t kHandleNodeMonitorEvents = 'nmon'; static const bigtime_t kNodeMonitorEventHandlingDelay = 500000; static const bigtime_t kCommunicationTimeout = 1000000; -const char* const kShellEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!"; - // #pragma mark - Listener @@ -100,159 +105,6 @@ private: }; -// #pragma mark - RelativePath - - -struct Volume::RelativePath { - RelativePath(const char* component1 = NULL, const char* component2 = NULL, - const char* component3 = NULL) - : - fComponentCount(kMaxComponentCount) - { - fComponents[0] = component1; - fComponents[1] = component2; - fComponents[2] = component3; - - for (size_t i = 0; i < kMaxComponentCount; i++) { - if (fComponents[i] == NULL) { - fComponentCount = i; - break; - } - } - } - - bool IsEmpty() const - { - return fComponentCount == 0; - } - - RelativePath HeadPath(size_t componentsToDropCount = 1) - { - RelativePath result; - if (componentsToDropCount < fComponentCount) { - result.fComponentCount = fComponentCount - componentsToDropCount; - for (size_t i = 0; i < result.fComponentCount; i++) - result.fComponents[i] = fComponents[i]; - } - - return result; - } - - const char* LastComponent() const - { - return fComponentCount > 0 ? fComponents[fComponentCount - 1] : NULL; - } - - BString ToString() const - { - if (fComponentCount == 0) - return BString(); - - size_t length = fComponentCount - 1; - for (size_t i = 0; i < fComponentCount; i++) - length += strlen(fComponents[i]); - - BString result; - char* buffer = result.LockBuffer(length + 1); - if (buffer == NULL) - return BString(); - - for (size_t i = 0; i < fComponentCount; i++) { - if (i > 0) { - *buffer = '/'; - buffer++; - } - strcpy(buffer, fComponents[i]); - buffer += strlen(buffer); - } - - return result.UnlockBuffer(); - } - -private: - static const size_t kMaxComponentCount = 3; - - const char* fComponents[kMaxComponentCount]; - size_t fComponentCount; -}; - - -// #pragma mark - Exception - - -struct Volume::Exception { - Exception(int32 error, const char* errorMessage = NULL, - const char* packageName = NULL) - : - fError(error), - fErrorMessage(errorMessage), - fPackageName(packageName) - { - } - - int32 Error() const - { - return fError; - } - - const BString& ErrorMessage() const - { - return fErrorMessage; - } - - const BString& PackageName() const - { - return fPackageName; - } - - BString ToString() const - { - const char* error; - if (fError >= 0) { - switch (fError) { - case B_DAEMON_OK: - error = "no error"; - break; - case B_DAEMON_CHANGE_COUNT_MISMATCH: - error = "transaction out of date"; - break; - case B_DAEMON_BAD_REQUEST: - error = "invalid transaction"; - break; - case B_DAEMON_NO_SUCH_PACKAGE: - error = "no such package"; - break; - case B_DAEMON_PACKAGE_ALREADY_EXISTS: - error = "package already exists"; - break; - default: - error = "unknown error"; - break; - } - } else - error = strerror(fError); - - BString string; - if (!fErrorMessage.IsEmpty()) { - string = fErrorMessage; - string << ": "; - } - - string << error; - - if (!fPackageName.IsEmpty()) - string << ", package: \"" << fPackageName << '"'; - - return string; - } - -private: - int32 fError; - BString fErrorMessage; - BString fPackageName; -}; - - // #pragma mark - CommitTransactionHandler @@ -269,7 +121,8 @@ struct Volume::CommitTransactionHandler { fPackagesAlreadyAdded(packagesAlreadyAdded), fPackagesAlreadyRemoved(packagesAlreadyRemoved), fAddedGroups(), - fAddedUsers() + fAddedUsers(), + fFSTransaction() { } @@ -339,8 +192,9 @@ struct Volume::CommitTransactionHandler { // revert user and group changes _RevertUserGroupChanges(); - // remove old state directory - _RemoveOldStateDirectory(); + // Revert all other FS operations, i.e. the writable files changes as + // well as the creation of the old state directory. + fFSTransaction.RollBack(); } const BString& OldStateDirectoryName() const @@ -510,11 +364,16 @@ private: } // create the directory + FSTransaction::CreateOperation createOldStateDirectoryOperation( + &fFSTransaction, FSUtils::Entry(adminDirectory, directoryName)); + error = adminDirectory.CreateDirectory(directoryName, &fOldStateDirectory); if (error != B_OK) throw Exception(error, "failed to create old state directory"); + createOldStateDirectoryOperation.Finished(); + fOldStateDirectoryName = directoryName; // write the old activation file @@ -548,11 +407,8 @@ private: } // get a BEntry for the package - entry_ref entryRef; - entryRef.device = fVolume->fPackagesDirectoryRef.device; - entryRef.directory = fVolume->fPackagesDirectoryRef.node; - if (entryRef.set_name(package->FileName()) != B_OK) - throw Exception(B_NO_MEMORY); + NotOwningEntryRef entryRef(fVolume->fPackagesDirectoryRef, + package->FileName()); BEntry entry; status_t error = entry.SetTo(&entryRef); @@ -665,7 +521,7 @@ private: fAddedGroups.insert(groupName.String()); std::string commandLine("groupadd "); - commandLine += _ShellEscapeString(groupName).String(); + commandLine += FSUtils::ShellEscapeString(groupName).String(); if (system(commandLine.c_str()) != 0) { fAddedGroups.erase(groupName.String()); @@ -694,25 +550,26 @@ private: if (!user.RealName().IsEmpty()) { commandLine += std::string("-n ") - + _ShellEscapeString(user.RealName()).String() + " "; + + FSUtils::ShellEscapeString(user.RealName()).String() + " "; } if (!user.Home().IsEmpty()) { commandLine += std::string("-d ") - + _ShellEscapeString(user.Home()).String() + " "; + + FSUtils::ShellEscapeString(user.Home()).String() + " "; } if (!user.Shell().IsEmpty()) { commandLine += std::string("-s ") - + _ShellEscapeString(user.Shell()).String() + " "; + + FSUtils::ShellEscapeString(user.Shell()).String() + " "; } if (!user.Groups().IsEmpty()) { commandLine += std::string("-g ") - + _ShellEscapeString(user.Groups().First()).String() + " "; + + FSUtils::ShellEscapeString(user.Groups().First()).String() + + " "; } - commandLine += _ShellEscapeString(user.Name()).String(); + commandLine += FSUtils::ShellEscapeString(user.Name()).String(); if (system(commandLine.c_str()) != 0) { fAddedUsers.erase(user.Name().String()); @@ -726,9 +583,10 @@ private: int32 groupCount = user.Groups().CountStrings(); for (int32 i = 1; i < groupCount; i++) { commandLine = std::string("groupmod -A ") - + _ShellEscapeString(user.Name()).String() + + FSUtils::ShellEscapeString(user.Name()).String() + " " - + _ShellEscapeString(user.Groups().StringAt(i)).String(); + + FSUtils::ShellEscapeString(user.Groups().StringAt(i)) + .String(); if (system(commandLine.c_str()) != 0) { fAddedUsers.erase(user.Name().String()); throw Exception(error, @@ -743,7 +601,346 @@ private: void _AddGlobalWritableFile(Package* package, const BGlobalWritableFileInfo& file) { -// TODO:... + if (!file.IsIncluded()) + return; + + // Open the root directory of the installation location where we will + // extract the files -- that's the volume's root directory save for + // "system" where it is "common". + BDirectory rootDirectory; + status_t error = fVolume->_OpenSettingsRootDirectory(rootDirectory); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to get the root directory " + "for writable files"), + package->FileName()); + } + + // Open writable-files directory in the administrative directory. + if (fWritableFilesDirectory.InitCheck() != B_OK) { + error = fVolume->_OpenPackagesSubDirectory( + RelativePath(kAdminDirectoryName, kWritableFilesDirectoryName), + true, fWritableFilesDirectory); + + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to get the backup directory " + "for writable files"), + package->FileName()); + } + } + + // extract files into a subdir of the writable-files directory + BDirectory extractedFilesDirectory; + _ExtractPackageContent(package, file.Path(), + fWritableFilesDirectory, extractedFilesDirectory); + + // Map the path name to the actual target location. Currently this only + // concerns "settings/", which is mapped to "settings/global/". + BString targetPath(file.Path()); + if (fVolume->fMountType == PACKAGE_FS_MOUNT_TYPE_HOME) { + if (targetPath == "settings" + || targetPath.StartsWith("settings/")) { + targetPath.Insert("/global", 8); + if (targetPath.Length() == file.Path().Length()) + throw std::bad_alloc(); + } + } + + // open parent directory of the source entry + const char* lastSlash = strrchr(file.Path(), '/'); + 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(); + + error = stackSourceDirectory.SetTo(&extractedFilesDirectory, + sourceParentPath); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open directory \"%s\"", + _GetPath( + FSUtils::Entry(extractedFilesDirectory, + sourceParentPath), + sourceParentPath).String()), + package->FileName()); + } + } else { + sourceDirectory = &extractedFilesDirectory; + } + + // open parent directory of the target entry -- create, if necessary + FSUtils::Path relativeSourcePath(file.Path()); + lastSlash = strrchr(targetPath, '/'); + if (lastSlash != NULL) { + BString targetParentPath(targetPath, + lastSlash - targetPath.String()); + if (targetParentPath.Length() == 0) + throw std::bad_alloc(); + + BDirectory targetDirectory; + error = FSUtils::OpenSubDirectory(rootDirectory, + RelativePath(targetParentPath), true, targetDirectory); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open/create directory " + "\"%s\"", + _GetPath( + FSUtils::Entry(rootDirectory,targetParentPath), + targetParentPath).String()), + package->FileName()); + } + _AddGlobalWritableFileRecurse(package, *sourceDirectory, + relativeSourcePath, targetDirectory, lastSlash + 1, + file.UpdateType()); + } else { + _AddGlobalWritableFileRecurse(package, *sourceDirectory, + relativeSourcePath, rootDirectory, targetPath, + file.UpdateType()); + } + } + + void _AddGlobalWritableFileRecurse(Package* package, + BDirectory& sourceDirectory, FSUtils::Path& relativeSourcePath, + BDirectory& targetDirectory, const char* targetName, + BWritableFileUpdateType updateType) + { + // * If the file doesn't exist, just copy the extracted one. + // * If the file does exist, compare with the previous original version: + // * If unchanged, just overwrite it. + // * If changed, leave it to the user for now. When we support merging + // first back the file up, then try the merge. + + // Check whether the target location exists and what type the entry at + // both locations are. + struct stat targetStat; + if (targetDirectory.GetStatFor(targetName, &targetStat) != B_OK) { + // target doesn't exist -- just copy + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "couldn't get stat for writable file, copying...\n"); + FSTransaction::CreateOperation copyOperation(&fFSTransaction, + FSUtils::Entry(targetDirectory, targetName)); + status_t error = BCopyEngine(BCopyEngine::COPY_RECURSIVELY) + .CopyEntry( + FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), + FSUtils::Entry(targetDirectory, targetName)); + if (error != B_OK) { + if (targetDirectory.GetStatFor(targetName, &targetStat) == B_OK) + copyOperation.Finished(); + + throw Exception(error, + BString().SetToFormat("failed to copy entry \"%s\"", + _GetPath( + FSUtils::Entry(sourceDirectory, + relativeSourcePath.Leaf()), + relativeSourcePath).String()), + package->FileName()); + } + copyOperation.Finished(); + return; + } + + struct stat sourceStat; + status_t error = sourceDirectory.GetStatFor(relativeSourcePath.Leaf(), + &sourceStat); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to get stat data for entry " + "\"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, targetName), + targetName).String()), + package->FileName()); + } + + if ((sourceStat.st_mode & S_IFMT) != (targetStat.st_mode & S_IFMT) + || (!S_ISDIR(sourceStat.st_mode) && !S_ISREG(sourceStat.st_mode) + && !S_ISLNK(sourceStat.st_mode))) { + // Source and target entry types don't match or this is an entry + // we cannot handle. The user must handle this manually. + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "writable file exists, but type doesn't match previous type\n"); +// TODO: Notify user! + return; + } + + if (S_ISDIR(sourceStat.st_mode)) { + // entry is a directory -- recurse + BDirectory sourceSubDirectory; + error = sourceSubDirectory.SetTo(&sourceDirectory, + relativeSourcePath.Leaf()); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open directory \"%s\"", + _GetPath( + FSUtils::Entry(sourceDirectory, + relativeSourcePath.Leaf()), + relativeSourcePath).String()), + package->FileName()); + } + + BDirectory targetSubDirectory; + error = targetSubDirectory.SetTo(&targetDirectory, targetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open directory \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, targetName), + targetName).String()), + package->FileName()); + } + + entry_ref entry; + while (sourceSubDirectory.GetNextRef(&entry) == B_OK) { + relativeSourcePath.AppendComponent(entry.name); + _AddGlobalWritableFileRecurse(package, sourceSubDirectory, + relativeSourcePath, targetSubDirectory, entry.name, + updateType); + relativeSourcePath.RemoveLastComponent(); + } + + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "writable directory, recursion done\n"); + return; + } + + // get the package the target file originated from + BString originalPackage; + if (BNode(&targetDirectory, targetName).ReadAttrString( + kPackageFileAttribute, &originalPackage) != B_OK) { + // Can't determine the original package. The user must handle this + // manually. +// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "failed to get SYS:PACKAGE attribute\n"); + return; + } + + // If that's our package, we're happy. + if (originalPackage == package->RevisionedNameThrows()) { + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "file tagged with same package version we're activating\n"); + return; + } + + // Check, whether the writable-files directory for the original package + // exists. + BString originalRelativeSourcePath = BString().SetToFormat("%s/%s", + originalPackage.String(), relativeSourcePath.ToCString()); + if (originalRelativeSourcePath.IsEmpty()) + throw std::bad_alloc(); + + struct stat originalPackageStat; + if (fWritableFilesDirectory.GetStatFor(originalRelativeSourcePath, + &originalPackageStat) != B_OK + || (sourceStat.st_mode & S_IFMT) + != (originalPackageStat.st_mode & S_IFMT)) { + // Original entry doesn't exist (either we don't have the data from + // the original package or the entry really didn't exist) or its + // type differs from the expected one. The user must handle this + // manually. + PRINT("Volume::CommitTransactionHandler::_AddGlobalWritableFile(): " + "original \"%s\" doesn't exist or has other type\n", + _GetPath(FSUtils::Entry(fWritableFilesDirectory, + originalRelativeSourcePath), + originalRelativeSourcePath).String()); + return; +// TODO: Notify user! + } + + if (S_ISREG(sourceStat.st_mode)) { + // compare file content + bool equal; + error = FSUtils::CompareFileContent( + FSUtils::Entry(fWritableFilesDirectory, + originalRelativeSourcePath), + FSUtils::Entry(targetDirectory, targetName), + equal); + // TODO: Merge support! + if (error != B_OK || !equal) { + // The comparison failed or the files differ. The user must + // handle this manually. + PRINT("Volume::CommitTransactionHandler::" + "_AddGlobalWritableFile(): " + "file comparison failed (%s) or files aren't equal\n", + strerror(error)); + return; +// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! + } + } else { + // compare symlinks + bool equal; + error = FSUtils::CompareSymLinks( + FSUtils::Entry(fWritableFilesDirectory, + originalRelativeSourcePath), + FSUtils::Entry(targetDirectory, targetName), + equal); + if (error != B_OK || !equal) { + // The comparison failed or the symlinks differ. The user must + // handle this manually. + PRINT("Volume::CommitTransactionHandler::" + "_AddGlobalWritableFile(): " + "symlink comparison failed (%s) or symlinks aren't equal\n", + strerror(error)); + return; +// TODO: Notify user, if not B_WRITABLE_FILE_UPDATE_TYPE_KEEP_OLD! + } + } + + // Replace the existing file/symlink. We do that in two steps: First + // copy the new file to a neighoring location, then move-replace the + // old file. + BString tempTargetName; + tempTargetName.SetToFormat("%s.%s", targetName, + package->RevisionedNameThrows().String()); + if (tempTargetName.IsEmpty()) + throw std::bad_alloc(); + + // copy + FSTransaction::CreateOperation copyOperation(&fFSTransaction, + FSUtils::Entry(targetDirectory, tempTargetName)); + + error = BCopyEngine(BCopyEngine::UNLINK_DESTINATION).CopyEntry( + FSUtils::Entry(sourceDirectory, relativeSourcePath.Leaf()), + FSUtils::Entry(targetDirectory, tempTargetName)); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to copy entry \"%s\"", + _GetPath( + FSUtils::Entry(sourceDirectory, + relativeSourcePath.Leaf()), + relativeSourcePath).String()), + package->FileName()); + } + + copyOperation.Finished(); + + // rename + FSTransaction::RemoveOperation renameOperation(&fFSTransaction, + FSUtils::Entry(targetDirectory, targetName), + FSUtils::Entry(fWritableFilesDirectory, + originalRelativeSourcePath)); + + BEntry targetEntry; + error = targetEntry.SetTo(&targetDirectory, tempTargetName); + if (error == B_OK) + error = targetEntry.Rename(targetName, true); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to rename entry \"%s\" to \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, tempTargetName), + tempTargetName).String(), + targetName), + package->FileName()); + } + + renameOperation.Finished(); + copyOperation.Unregister(); } void _RevertAddPackagesToActivate() @@ -774,13 +971,8 @@ private: continue; // get BEntry for the package - entry_ref entryRef; - entryRef.device = fVolume->fPackagesDirectoryRef.device; - entryRef.directory = fVolume->fPackagesDirectoryRef.node; - if (entryRef.set_name(package->FileName()) != B_OK) { - ERROR("out of memory\n"); - continue; - } + NotOwningEntryRef entryRef(fVolume->fPackagesDirectoryRef, + package->FileName()); BEntry entry; error = entry.SetTo(&entryRef); @@ -852,7 +1044,7 @@ private: for (StringSet::const_iterator it = fAddedUsers.begin(); it != fAddedUsers.end(); ++it) { std::string commandLine("userdel "); - commandLine += _ShellEscapeString(it->c_str()).String(); + commandLine += FSUtils::ShellEscapeString(it->c_str()).String(); if (system(commandLine.c_str()) != 0) ERROR("failed to remove user \"%s\"\n", it->c_str()); } @@ -861,39 +1053,12 @@ private: for (StringSet::const_iterator it = fAddedGroups.begin(); it != fAddedGroups.end(); ++it) { std::string commandLine("groupdel "); - commandLine += _ShellEscapeString(it->c_str()).String(); + commandLine += FSUtils::ShellEscapeString(it->c_str()).String(); if (system(commandLine.c_str()) != 0) ERROR("failed to remove group \"%s\"\n", it->c_str()); } } - void _RemoveOldStateDirectory() - { - if (fOldStateDirectory.InitCheck() != B_OK) - return; - - // remove the old activation file (it won't exist, if creating it - // failed) - BEntry(&fOldStateDirectory, kActivationFileName).Remove(); - - // Now the directory should be empty. If it isn't, it still contains - // some old package file, which we failed to move back. - BEntry entry; - status_t error = fOldStateDirectory.GetEntry(&entry); - if (error != B_OK) { - ERROR("failed to get entry for old state directory: %s\n", - strerror(error)); - return; - } - - error = entry.Remove(); - if (error != B_OK) { - ERROR("failed to remove old state directory: %s\n", - strerror(error)); - return; - } - } - void _RunPostInstallScripts() { for (PackageSet::iterator it = fAddedPackages.begin(); @@ -929,13 +1094,184 @@ private: } } - BString _ShellEscapeString(const BString& string) + static BString _GetPath(const FSUtils::Entry& entry, + const BString& fallback) { - BString result(string); - result.CharacterEscape(kShellEscapeCharacters, '\\'); - if (result.IsEmpty()) + BString path = entry.Path(); + return path.IsEmpty() ? fallback : path; + } + + void _ExtractPackageContent(Package* package, const char* contentPath, + BDirectory& targetDirectory, BDirectory& _extractedFilesDirectory) + { + // check whether the subdirectory already exists + BString targetName(package->RevisionedNameThrows()); + + BEntry targetEntry; + status_t error = targetEntry.SetTo(&targetDirectory, targetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to init entry \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, targetName), + targetName).String()), + package->FileName()); + } + if (targetEntry.Exists()) { + // nothing to do -- the very same version of the package has already + // been extracted + error = _extractedFilesDirectory.SetTo(&targetDirectory, + targetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to open directory \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, targetName), + targetName).String()), + package->FileName()); + } + return; + } + + // create the subdirectory with a temporary name (remove, if it already + // exists) + BString temporaryTargetName = BString().SetToFormat("%s.tmp", + targetName.String()); + if (temporaryTargetName.IsEmpty()) throw std::bad_alloc(); - return result; + + error = targetEntry.SetTo(&targetDirectory, temporaryTargetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to init entry \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, temporaryTargetName), + temporaryTargetName).String()), + package->FileName()); + } + + if (targetEntry.Exists()) { + // remove pre-existing + error = BRemoveEngine().RemoveEntry(FSUtils::Entry(targetEntry)); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to remove directory \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, + temporaryTargetName), + temporaryTargetName).String()), + package->FileName()); + } + } + + BDirectory& subDirectory = _extractedFilesDirectory; + FSTransaction::CreateOperation createSubDirectoryOperation( + &fFSTransaction, + FSUtils::Entry(targetDirectory, temporaryTargetName)); + error = targetDirectory.CreateDirectory(temporaryTargetName, + &subDirectory); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to create directory \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, temporaryTargetName), + temporaryTargetName).String()), + package->FileName()); + } + + createSubDirectoryOperation.Finished(); + + // extract + NotOwningEntryRef packageRef(fVolume->fPackagesDirectoryRef, + package->FileName()); + + error = FSUtils::ExtractPackageContent(FSUtils::Entry(packageRef), + contentPath, FSUtils::Entry(subDirectory)); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to extracted \"%s\" from package", + contentPath), + package->FileName()); + } + + // tag all entries with the package attribute + error = _TagPackageEntriesRecursively(subDirectory, targetName, true); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to tag extract files in \"%s\" " + "with package attribute", + _GetPath( + FSUtils::Entry(targetDirectory, temporaryTargetName), + temporaryTargetName).String()), + package->FileName()); + } + + // rename the subdirectory + error = targetEntry.Rename(targetName); + if (error != B_OK) { + throw Exception(error, + BString().SetToFormat("failed to rename entry \"%s\" to \"%s\"", + _GetPath( + FSUtils::Entry(targetDirectory, temporaryTargetName), + temporaryTargetName).String(), + targetName.String()), + package->FileName()); + } + + // keep the directory, regardless of whether the transaction is rolled + // back + createSubDirectoryOperation.Unregister(); + } + + static status_t _TagPackageEntriesRecursively(BDirectory& directory, + const BString& value, bool nonDirectoriesOnly) + { + char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH]; + dirent *entry = (dirent*)buffer; + while (directory.GetNextDirents(entry, sizeof(buffer), 1) == 1) { + if (strcmp(entry->d_name, ".") == 0 + || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // determine type + struct stat st; + status_t error = directory.GetStatFor(entry->d_name, &st); + if (error != B_OK) + return error; + bool isDirectory = S_ISDIR(st.st_mode); + + // open the node and set the attribute + BNode stackNode; + BDirectory stackDirectory; + BNode* node; + if (isDirectory) { + node = &stackDirectory; + error = stackDirectory.SetTo(&directory, entry->d_name); + } else { + node = &stackNode; + error = stackNode.SetTo(&directory, entry->d_name); + } + + if (error != B_OK) + return error; + + if (!isDirectory || !nonDirectoriesOnly) { + error = node->WriteAttrString(kPackageFileAttribute, &value); + if (error != B_OK) + return error; + } + + // recurse + if (isDirectory) { + error = _TagPackageEntriesRecursively(stackDirectory, value, + nonDirectoriesOnly); + if (error != B_OK) + return error; + } + } + + return B_OK; } private: @@ -949,8 +1285,10 @@ private: BDirectory fOldStateDirectory; BString fOldStateDirectoryName; node_ref fTransactionDirectoryRef; + BDirectory fWritableFilesDirectory; StringSet fAddedGroups; StringSet fAddedUsers; + FSTransaction fFSTransaction; }; @@ -1893,40 +2231,47 @@ Volume::_OpenPackagesSubDirectory(const RelativePath& path, bool create, BDirectory directory; status_t error = directory.SetTo(&fPackagesDirectoryRef); if (error != B_OK) { - ERROR("Volume::_OpenConfigSubDirectory(): failed to open packages " + ERROR("Volume::_OpenPackagesSubDirectory(): failed to open packages " "directory: %s\n", strerror(error)); RETURN_ERROR(error); } - // get a string for the path - BString pathString = path.ToString(); - if (pathString.IsEmpty()) - RETURN_ERROR(B_NO_MEMORY); + return FSUtils::OpenSubDirectory(directory, path, create, _directory); +} - // If creating is not allowed, just try to open it. - if (!create) - RETURN_ERROR(_directory.SetTo(&directory, pathString)); - // get an absolute path and create the subdirectory - BPath absolutePath; - error = absolutePath.SetTo(&directory, pathString); - if (error != B_OK) { - ERROR("Volume::_OpenConfigSubDirectory(): failed to get absolute path " - "for subdirectory \"%s\": %s\n", pathString.String(), - strerror(error)); - RETURN_ERROR(error); +status_t +Volume::_OpenSettingsRootDirectory(BDirectory& _directory) +{ + switch (fMountType) { + case PACKAGE_FS_MOUNT_TYPE_SYSTEM: + { + // try our sibling volume + if (fListener != NULL) { + node_ref ref; + status_t error = fListener->GetRootDirectoryRef( + PACKAGE_FS_MOUNT_TYPE_COMMON, ref); + if (error != B_ENTRY_NOT_FOUND) + return error; + return _directory.SetTo(&ref); + } + + // try a path relative to our root directory + BDirectory rootDirectory; + status_t error = rootDirectory.SetTo(&fRootDirectoryRef); + if (error != B_OK) + return error; + return _directory.SetTo(&rootDirectory, "../common"); + } + + case PACKAGE_FS_MOUNT_TYPE_COMMON: + case PACKAGE_FS_MOUNT_TYPE_HOME: + return _directory.SetTo(&fRootDirectoryRef); + + case PACKAGE_FS_MOUNT_TYPE_CUSTOM: + default: + return B_BAD_VALUE; } - - error = create_directory(absolutePath.Path(), - S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); - if (error != B_OK) { - ERROR("Volume::_OpenConfigSubDirectory(): failed to create packages " - "subdirectory \"%s\": %s\n", pathString.String(), - strerror(error)); - RETURN_ERROR(error); - } - - RETURN_ERROR(_directory.SetTo(&directory, pathString)); } diff --git a/src/servers/package/Volume.h b/src/servers/package/Volume.h index 2508060c91..f6790c6233 100644 --- a/src/servers/package/Volume.h +++ b/src/servers/package/Volume.h @@ -19,6 +19,7 @@ #include #include +#include "FSUtils.h" #include "Package.h" @@ -119,12 +120,11 @@ public: private: struct NodeMonitorEvent; - struct RelativePath; - struct Exception; struct CommitTransactionHandler; friend struct CommitTransactionHandler; + typedef FSUtils::RelativePath RelativePath; typedef DoublyLinkedList NodeMonitorEventList; private: @@ -160,6 +160,9 @@ private: const RelativePath& path, bool create, BDirectory& _directory); + status_t _OpenSettingsRootDirectory( + BDirectory& _directory); + status_t _CreateActivationFileContent( const PackageSet& toActivate, const PackageSet& toDeactivate, @@ -181,6 +184,11 @@ private: const PackageSet& packagesToDeactivate); // throws Exception + status_t _ExtractPackageContent(Package* package, + const char* contentPath, + BDirectory& targetDirectory, + BDirectory& _extractedFilesDirectory); + private: BString fPath; PackageFSMountType fMountType; @@ -206,6 +214,9 @@ public: virtual void VolumeNodeMonitorEventOccurred(Volume* volume) = 0; + virtual status_t GetRootDirectoryRef( + PackageFSMountType mountType, + node_ref& _ref) = 0; };