* 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 <SymLink.h>
#include <TypeConstants.h>
namespace BPrivate {
static const size_t kDefaultBufferSize = 1024 * 1024;
static const size_t kSmallBufferSize = 64 * 1024;
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 Entry& sourceEntry, const Entry& destEntry)
{
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;
}
BPath sourcePathBuffer;
const char* sourcePath;
status_t error = sourceEntry.GetPath(sourcePathBuffer, sourcePath);
if (error != B_OK)
return error;
BPath destPathBuffer;
const char* destPath;
error = destEntry.GetPath(destPathBuffer, destPath);
if (error != B_OK)
return error;
return _CopyEntry(sourcePath, destPath);
}
status_t
BCopyEngine::_CopyEntry(const char* sourcePath, const char* destPath)
{
if (fController != NULL && !fController->EntryStarted(sourcePath))
return B_OK;
struct stat sourceStat;
if (lstat(sourcePath, &sourceStat) < 0) {
return _HandleEntryError(sourcePath, errno,
"Couldn't access \"%s\": %s\n", sourcePath, strerror(errno));
}
struct stat destStat;
bool destExists = lstat(destPath, &destStat) == 0;
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)) {
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);
}
}
if (unlinkDest) {
if (unlink(destPath) < 0) {
return _HandleEntryError(sourcePath, errno,
"Failed to unlink \"%s\": %s\n", destPath, strerror(errno));
}
}
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));
}
BNode _destNode;
BDirectory destDir;
BFile destFile;
BSymLink destSymLink;
BNode* destNode = NULL;
if (createDest) {
if (S_ISDIR(sourceStat.st_mode)) {
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)) {
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;
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)) {
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));
}
linkTo[bytesRead] = '\0';
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);
}
error = _CopyAttributes(sourcePath, *sourceNode, destPath, *destNode);
if (error != B_OK) {
if (fController != NULL
&& fController->EntryFinished(sourcePath, error)) {
return B_OK;
}
return error;
}
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);
}
destNode->Unset();
if ((fFlags & COPY_RECURSIVELY) != 0 && S_ISDIR(sourceStat.st_mode)) {
char buffer[offsetof(struct dirent, d_name) + 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;
}
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));
}
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) {
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;
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) {
attr_info attrInfo;
status_t error = source.GetAttrInfo(attrName, &attrInfo);
if (error != B_OK) {
attrInfo.type = B_ANY_TYPE;
}
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;
}
off_t offset = 0;
off_t bytesLeft = attrInfo.size;
do {
size_t toRead = fBufferSize;
if ((off_t)toRead > bytesLeft)
toRead = bytesLeft;
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;
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;
}
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)
{
}
}