diff --git a/headers/private/storage/CopyEngine.h b/headers/private/storage/CopyEngine.h new file mode 100644 index 0000000000..f2529c944c --- /dev/null +++ b/headers/private/storage/CopyEngine.h @@ -0,0 +1,103 @@ +/* + * Copyright 2013, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ +#ifndef _COPY_ENGINE_H +#define _COPY_ENGINE_H + + +#include + +#include + + +class BFile; +class BNode; + + +namespace BPrivate { + + +class BCopyEngine { +public: + class BController; + + enum { + COPY_RECURSIVELY = 0x01, + MERGE_EXISTING_DIRECTORIES = 0x02, + UNLINK_DESTINATION = 0x04, + }; + +public: + BCopyEngine(uint32 flags = 0); + ~BCopyEngine(); + + BController* Controller() const; + void SetController(BController* controller); + + uint32 Flags() const; + BCopyEngine& SetFlags(uint32 flags); + BCopyEngine& AddFlags(uint32 flags); + BCopyEngine& RemoveFlags(uint32 flags); + + status_t CopyEntry(const char* sourcePath, + const char* destPath); + +private: + status_t _CopyEntry(const char* sourcePath, + const char* destPath); + status_t _CopyFileData(const char* sourcePath, + BFile& source, const char* destPath, + BFile& destination); + status_t _CopyAttributes(const char* sourcePath, + BNode& source, const char* destPath, + BNode& destination); + + void _NotifyError(status_t error, const char* format, + ...); + void _NotifyErrorVarArgs(status_t error, + const char* format, va_list args); + status_t _HandleEntryError(const char* path, + status_t error, const char* format, ...); + status_t _HandleAttributeError(const char* path, + const char* attribute, uint32 attributeType, + status_t error, const char* format, ...); + +private: + BController* fController; + uint32 fFlags; + char* fBuffer; + size_t fBufferSize; +}; + + +class BCopyEngine::BController { +public: + BController(); + virtual ~BController(); + + virtual bool EntryStarted(const char* path); + virtual bool EntryFinished(const char* path, status_t error); + + virtual bool AttributeStarted(const char* path, + const char* attribute, + uint32 attributeType); + virtual bool AttributeFinished(const char* path, + const char* attribute, + uint32 attributeType, status_t error); + + virtual void ErrorOccurred(const char* message, + status_t error); +}; + + +} // namespace BPrivate + + +using ::BPrivate::BCopyEngine; + + +#endif // _COPY_ENGINE_H diff --git a/src/kits/storage/CopyEngine.cpp b/src/kits/storage/CopyEngine.cpp new file mode 100644 index 0000000000..7848297e5a --- /dev/null +++ b/src/kits/storage/CopyEngine.cpp @@ -0,0 +1,560 @@ +/* + * Copyright 2013, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold + */ + + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace BPrivate { + + +static const size_t kDefaultBufferSize = 1024 * 1024; +static const size_t kSmallBufferSize = 64 * 1024; + + +// #pragma mark - BCopyEngine + + +BCopyEngine::BCopyEngine(uint32 flags) + : + fController(NULL), + fFlags(flags), + fBuffer(NULL), + fBufferSize(0) +{ +} + + +BCopyEngine::~BCopyEngine() +{ + delete[] fBuffer; +} + + +BCopyEngine::BController* +BCopyEngine::Controller() const +{ + return fController; +} + + +void +BCopyEngine::SetController(BController* controller) +{ + fController = controller; +} + + +uint32 +BCopyEngine::Flags() const +{ + return fFlags; +} + + +BCopyEngine& +BCopyEngine::SetFlags(uint32 flags) +{ + fFlags = flags; + return *this; +} + + +BCopyEngine& +BCopyEngine::AddFlags(uint32 flags) +{ + fFlags |= flags; + return *this; +} + + +BCopyEngine& +BCopyEngine::RemoveFlags(uint32 flags) +{ + fFlags &= ~flags; + return *this; +} + + +status_t +BCopyEngine::CopyEntry(const char* sourcePath, const char* destPath) +{ + if (fBuffer == NULL) { + fBuffer = new(std::nothrow) char[kDefaultBufferSize]; + if (fBuffer == NULL) { + fBuffer = new(std::nothrow) char[kSmallBufferSize]; + if (fBuffer == NULL) { + _NotifyError(B_NO_MEMORY, "Failed to allocate buffer"); + return B_NO_MEMORY; + } + fBufferSize = kSmallBufferSize; + } else + fBufferSize = kDefaultBufferSize; + } + + return _CopyEntry(sourcePath, destPath); +} + + +status_t +BCopyEngine::_CopyEntry(const char* sourcePath, const char* destPath) +{ + // apply entry filter + if (fController != NULL && !fController->EntryStarted(sourcePath)) + return B_OK; + + // stat source + struct stat sourceStat; + if (lstat(sourcePath, &sourceStat) < 0) { + return _HandleEntryError(sourcePath, errno, + "Couldn't access \"%s\": %s\n", sourcePath, strerror(errno)); + } + + // stat destination + struct stat destStat; + bool destExists = lstat(destPath, &destStat) == 0; + + // check whether to delete/create the destination + bool unlinkDest = destExists; + bool createDest = true; + if (destExists) { + if (S_ISDIR(destStat.st_mode)) { + if (!S_ISDIR(sourceStat.st_mode) + || (fFlags & MERGE_EXISTING_DIRECTORIES) == 0) { + return _HandleEntryError(sourcePath, B_FILE_EXISTS, + "Can't copy \"%s\", since directory \"%s\" is in the " + "way.\n", sourcePath, destPath); + } + + if (S_ISDIR(sourceStat.st_mode)) { + // both are dirs; nothing to do + unlinkDest = false; + destExists = false; + } + } else if ((fFlags & UNLINK_DESTINATION) == 0) { + return _HandleEntryError(sourcePath, B_FILE_EXISTS, + "Can't copy \"%s\", since entry \"%s\" is in the way.\n", + sourcePath, destPath); + } + } + + // unlink the destination + if (unlinkDest) { + if (unlink(destPath) < 0) { + return _HandleEntryError(sourcePath, errno, + "Failed to unlink \"%s\": %s\n", destPath, strerror(errno)); + } + } + + // open source node + BNode _sourceNode; + BFile sourceFile; + BDirectory sourceDir; + BNode* sourceNode = NULL; + status_t error; + + if (S_ISDIR(sourceStat.st_mode)) { + error = sourceDir.SetTo(sourcePath); + sourceNode = &sourceDir; + } else if (S_ISREG(sourceStat.st_mode)) { + error = sourceFile.SetTo(sourcePath, B_READ_ONLY); + sourceNode = &sourceFile; + } else { + error = _sourceNode.SetTo(sourcePath); + sourceNode = &_sourceNode; + } + + if (error != B_OK) { + return _HandleEntryError(sourcePath, error, + "Failed to open \"%s\": %s\n", sourcePath, strerror(error)); + } + + // create the destination + BNode _destNode; + BDirectory destDir; + BFile destFile; + BSymLink destSymLink; + BNode* destNode = NULL; + + if (createDest) { + if (S_ISDIR(sourceStat.st_mode)) { + // create dir + error = BDirectory().CreateDirectory(destPath, &destDir); + if (error != B_OK) { + return _HandleEntryError(sourcePath, error, + "Failed to make directory \"%s\": %s\n", destPath, + strerror(error)); + } + + destNode = &destDir; + } else if (S_ISREG(sourceStat.st_mode)) { + // create file + error = BDirectory().CreateFile(destPath, &destFile); + if (error != B_OK) { + return _HandleEntryError(sourcePath, error, + "Failed to create file \"%s\": %s\n", destPath, + strerror(error)); + } + + destNode = &destFile; + + // copy file contents + error = _CopyFileData(sourcePath, sourceFile, destPath, destFile); + if (error != B_OK) { + if (fController != NULL + && fController->EntryFinished(sourcePath, error)) { + return B_OK; + } + return error; + } + } else if (S_ISLNK(sourceStat.st_mode)) { + // read symlink + char* linkTo = fBuffer; + ssize_t bytesRead = readlink(sourcePath, linkTo, fBufferSize - 1); + if (bytesRead < 0) { + return _HandleEntryError(sourcePath, errno, + "Failed to read symlink \"%s\": %s\n", sourcePath, + strerror(errno)); + } + + // null terminate the link contents + linkTo[bytesRead] = '\0'; + + // create symlink + error = BDirectory().CreateSymLink(destPath, linkTo, &destSymLink); + if (error != B_OK) { + return _HandleEntryError(sourcePath, error, + "Failed to create symlink \"%s\": %s\n", destPath, + strerror(error)); + } + + destNode = &destSymLink; + + } else { + return _HandleEntryError(sourcePath, B_NOT_SUPPORTED, + "Source file \"%s\" has unsupported type.\n", sourcePath); + } + + // copy attributes (before setting the permissions!) + error = _CopyAttributes(sourcePath, *sourceNode, destPath, *destNode); + if (error != B_OK) { + if (fController != NULL + && fController->EntryFinished(sourcePath, error)) { + return B_OK; + } + return error; + } + + // set file owner, group, permissions, times + destNode->SetOwner(sourceStat.st_uid); + destNode->SetGroup(sourceStat.st_gid); + destNode->SetPermissions(sourceStat.st_mode); + #ifdef HAIKU_TARGET_PLATFORM_HAIKU + destNode->SetCreationTime(sourceStat.st_crtime); + #endif + destNode->SetModificationTime(sourceStat.st_mtime); + } + + // the destination node is no longer needed + destNode->Unset(); + + // recurse + if ((fFlags & COPY_RECURSIVELY) != 0 && S_ISDIR(sourceStat.st_mode)) { + char buffer[sizeof(dirent) + B_FILE_NAME_LENGTH]; + dirent *entry = (dirent*)buffer; + while (sourceDir.GetNextDirents(entry, sizeof(buffer), 1) == 1) { + if (strcmp(entry->d_name, ".") == 0 + || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // construct new entry paths + BPath sourceEntryPath; + error = sourceEntryPath.SetTo(sourcePath, entry->d_name); + if (error != B_OK) { + return _HandleEntryError(sourcePath, error, + "Failed to construct entry path from dir \"%s\" and name " + "\"%s\": %s\n", sourcePath, entry->d_name, strerror(error)); + } + + BPath destEntryPath; + error = destEntryPath.SetTo(destPath, entry->d_name); + if (error != B_OK) { + return _HandleEntryError(sourcePath, error, + "Failed to construct entry path from dir \"%s\" and name " + "\"%s\": %s\n", destPath, entry->d_name, strerror(error)); + } + + // copy the entry + error = _CopyEntry(sourceEntryPath.Path(), destEntryPath.Path()); + if (error != B_OK) { + if (fController != NULL + && fController->EntryFinished(sourcePath, error)) { + return B_OK; + } + return error; + } + } + } + + if (fController != NULL) + fController->EntryFinished(sourcePath, B_OK); + return B_OK; +} + + +status_t +BCopyEngine::_CopyFileData(const char* sourcePath, BFile& source, + const char* destPath, BFile& destination) +{ + off_t offset = 0; + while (true) { + // read + ssize_t bytesRead = source.ReadAt(offset, fBuffer, fBufferSize); + if (bytesRead < 0) { + _NotifyError(bytesRead, "Failed to read from file \"%s\": %s\n", + sourcePath, strerror(bytesRead)); + return bytesRead; + } + + if (bytesRead == 0) + return B_OK; + + // write + ssize_t bytesWritten = destination.WriteAt(offset, fBuffer, bytesRead); + if (bytesWritten < 0) { + _NotifyError(bytesWritten, "Failed to write to file \"%s\": %s\n", + destPath, strerror(bytesWritten)); + return bytesWritten; + } + + if (bytesWritten != bytesRead) { + _NotifyError(B_ERROR, "Failed to write all data to file \"%s\"\n", + destPath); + return B_ERROR; + } + + offset += bytesRead; + } +} + + +status_t +BCopyEngine::_CopyAttributes(const char* sourcePath, BNode& source, + const char* destPath, BNode& destination) +{ + char attrName[B_ATTR_NAME_LENGTH]; + while (source.GetNextAttrName(attrName) == B_OK) { + // get attr info + attr_info attrInfo; + status_t error = source.GetAttrInfo(attrName, &attrInfo); + if (error != B_OK) { + // Delay reporting/handling the error until the controller has been + // asked whether it is interested. + attrInfo.type = B_ANY_TYPE; + } + + // filter + if (fController != NULL + && !fController->AttributeStarted(sourcePath, attrName, + attrInfo.type)) { + if (error != B_OK) { + _NotifyError(error, "Failed to get info of attribute \"%s\" " + "of file \"%s\": %s\n", attrName, sourcePath, + strerror(error)); + } + continue; + } + + if (error != B_OK) { + error = _HandleAttributeError(sourcePath, attrName, attrInfo.type, + error, "Failed to get info of attribute \"%s\" of file \"%s\": " + "%s\n", attrName, sourcePath, strerror(error)); + if (error != B_OK) + return error; + continue; + } + + // copy the attribute + off_t offset = 0; + off_t bytesLeft = attrInfo.size; + // go at least once through the loop, so that an empty attribute will be + // created as well + do { + size_t toRead = fBufferSize; + if ((off_t)toRead > bytesLeft) + toRead = bytesLeft; + + // read + ssize_t bytesRead = source.ReadAttr(attrName, attrInfo.type, + offset, fBuffer, toRead); + if (bytesRead < 0) { + error = _HandleAttributeError(sourcePath, attrName, + attrInfo.type, bytesRead, "Failed to read attribute \"%s\" " + "of file \"%s\": %s\n", attrName, sourcePath, + strerror(bytesRead)); + if (error != B_OK) + return error; + break; + } + + if (bytesRead == 0 && offset > 0) + break; + + // write + ssize_t bytesWritten = destination.WriteAttr(attrName, + attrInfo.type, offset, fBuffer, bytesRead); + if (bytesWritten < 0) { + error = _HandleAttributeError(sourcePath, attrName, + attrInfo.type, bytesWritten, "Failed to write attribute " + "\"%s\" of file \"%s\": %s\n", attrName, destPath, + strerror(bytesWritten)); + if (error != B_OK) + return error; + break; + } + + bytesLeft -= bytesRead; + offset += bytesRead; + } while (bytesLeft > 0); + + if (fController != NULL) { + fController->AttributeFinished(sourcePath, attrName, attrInfo.type, + B_OK); + } + } + + return B_OK; +} + + +void +BCopyEngine::_NotifyError(status_t error, const char* format, ...) +{ + if (fController != NULL) { + va_list args; + va_start(args, format); + _NotifyErrorVarArgs(error, format, args); + va_end(args); + } +} + + +void +BCopyEngine::_NotifyErrorVarArgs(status_t error, const char* format, + va_list args) +{ + if (fController != NULL) { + BString message; + message.SetToFormatVarArgs(format, args); + fController->ErrorOccurred(message, error); + } +} + + +status_t +BCopyEngine::_HandleEntryError(const char* path, status_t error, + const char* format, ...) +{ + if (fController == NULL) + return error; + + va_list args; + va_start(args, format); + _NotifyErrorVarArgs(error, format, args); + va_end(args); + + if (fController->EntryFinished(path, error)) + return B_OK; + return error; +} + + +status_t +BCopyEngine::_HandleAttributeError(const char* path, const char* attribute, + uint32 attributeType, status_t error, const char* format, ...) +{ + if (fController == NULL) + return error; + + va_list args; + va_start(args, format); + _NotifyErrorVarArgs(error, format, args); + va_end(args); + + if (fController->AttributeFinished(path, attribute, attributeType, error)) + return B_OK; + return error; +} + + +// #pragma mark - BController + + +BCopyEngine::BController::BController() +{ +} + + +BCopyEngine::BController::~BController() +{ +} + + +bool +BCopyEngine::BController::EntryStarted(const char* path) +{ + return true; +} + + +bool +BCopyEngine::BController::EntryFinished(const char* path, status_t error) +{ + return error == B_OK; +} + + +bool +BCopyEngine::BController::AttributeStarted(const char* path, + const char* attribute, uint32 attributeType) +{ + return true; +} + + +bool +BCopyEngine::BController::AttributeFinished(const char* path, + const char* attribute, uint32 attributeType, status_t error) +{ + return error == B_OK; +} + + +void +BCopyEngine::BController::ErrorOccurred(const char* message, status_t error) +{ +} + + +} // namespace BPrivate diff --git a/src/kits/storage/Jamfile b/src/kits/storage/Jamfile index ca0810013b..af98f93aeb 100644 --- a/src/kits/storage/Jamfile +++ b/src/kits/storage/Jamfile @@ -21,6 +21,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { MergeObject storage_kit.o : AppFileInfo.cpp + CopyEngine.cpp Directory.cpp DriverSettings.cpp Entry.cpp