Add private storage kit class BCopyEngine

It provides the functionality to copy file system entries (also
recursively). The code originates from the copyattr sources. Some
copyattr specific functionality has been removed and the code has been
adjusted for library use (i.e. no exit()s or fprintf()s). An optional
controller object can be set to customize the behavior.
This commit is contained in:
Ingo Weinhold 2013-08-28 00:20:44 +02:00
parent bc0491ae52
commit 617be97d8e
3 changed files with 664 additions and 0 deletions

View File

@ -0,0 +1,103 @@
/*
* Copyright 2013, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold <ingo_weinhold@gmx.de>
*/
#ifndef _COPY_ENGINE_H
#define _COPY_ENGINE_H
#include <stdarg.h>
#include <SupportDefs.h>
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

View File

@ -0,0 +1,560 @@
/*
* Copyright 2013, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold <ingo_weinhold@gmx.de>
*/
#include <CopyEngine.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <fs_attr.h>
#include <Path.h>
#include <String.h>
#include <SymLink.h>
#include <TypeConstants.h>
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

View File

@ -21,6 +21,7 @@ for architectureObject in [ MultiArchSubDirSetup ] {
MergeObject <libbe!$(architecture)>storage_kit.o :
AppFileInfo.cpp
CopyEngine.cpp
Directory.cpp
DriverSettings.cpp
Entry.cpp