* Copyright 2005-2008, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "compatibility.h"
#include "command_cp.h"
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <AutoDeleter.h>
#include <EntryFilter.h>
#include <fs_attr.h>
#include <StorageDefs.h>
#include "fssh_dirent.h"
#include "fssh_errno.h"
#include "fssh_errors.h"
#include "fssh_fcntl.h"
#include "fssh_fs_attr.h"
#include "fssh_stat.h"
#include "fssh_string.h"
#include "fssh_unistd.h"
#include "path_util.h"
#include "stat_util.h"
#include "syscalls.h"
using BPrivate::EntryFilter;
namespace FSShell {
static void *sCopyBuffer = NULL;
static const int sCopyBufferSize = 64 * 1024;
struct Options {
Options()
: entryFilter(),
attributesOnly(false),
ignoreAttributes(false),
dereference(true),
alwaysDereference(false),
force(false),
recursive(false)
{
}
EntryFilter entryFilter;
bool attributesOnly;
bool ignoreAttributes;
bool dereference;
bool alwaysDereference;
bool force;
bool recursive;
};
class Directory;
class File;
class SymLink;
class Node {
public:
Node() {}
virtual ~Node() {}
const struct fssh_stat &Stat() const { return fStat; }
bool IsFile() const { return FSSH_S_ISREG(fStat.fssh_st_mode); }
bool IsDirectory() const { return FSSH_S_ISDIR(fStat.fssh_st_mode); }
bool IsSymLink() const { return FSSH_S_ISLNK(fStat.fssh_st_mode); }
virtual File *ToFile() { return NULL; }
virtual Directory *ToDirectory() { return NULL; }
virtual SymLink *ToSymLink() { return NULL; }
virtual fssh_ssize_t GetNextAttr(char *name, int size) = 0;
virtual fssh_status_t GetAttrInfo(const char *name,
fssh_attr_info &info) = 0;
virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type,
fssh_off_t pos, void *buffer, int size) = 0;
virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type,
fssh_off_t pos, const void *buffer, int size) = 0;
virtual fssh_status_t RemoveAttr(const char *name) = 0;
protected:
struct fssh_stat fStat;
};
class Directory : public virtual Node {
public:
virtual Directory *ToDirectory() { return this; }
virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size) = 0;
};
class File : public virtual Node {
public:
virtual File *ToFile() { return this; }
virtual fssh_ssize_t Read(void *buffer, int size) = 0;
virtual fssh_ssize_t Write(const void *buffer, int size) = 0;
};
class SymLink : public virtual Node {
public:
virtual SymLink *ToSymLink() { return this; }
virtual fssh_ssize_t ReadLink(char *buffer, int bufferSize) = 0;
};
class FSDomain {
public:
virtual ~FSDomain() {}
virtual fssh_status_t Open(const char *path, int openMode, Node *&node) = 0;
virtual fssh_status_t CreateFile(const char *path,
const struct fssh_stat &st, File *&file) = 0;
virtual fssh_status_t CreateDirectory(const char *path,
const struct fssh_stat &st, Directory *&dir) = 0;
virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo,
const struct fssh_stat &st, SymLink *&link) = 0;
virtual fssh_status_t Unlink(const char *path) = 0;
};
class HostNode : public virtual Node {
public:
HostNode()
: Node(),
fFD(-1),
fAttrDir(NULL)
{
}
virtual ~HostNode()
{
if (fFD >= 0)
fssh_close(fFD);
if (fAttrDir)
fs_close_attr_dir(fAttrDir);
}
virtual fssh_status_t Init(const char *path, int fd,
const struct fssh_stat &st)
{
fFD = fd;
fStat = st;
fAttrDir = fs_fopen_attr_dir(fd);
if (!fAttrDir)
return fssh_get_errno();
return FSSH_B_OK;
}
virtual fssh_ssize_t GetNextAttr(char *name, int size)
{
if (!fAttrDir)
return 0;
fssh_set_errno(FSSH_B_OK);
struct dirent *entry = fs_read_attr_dir(fAttrDir);
if (!entry)
return fssh_get_errno();
int len = strlen(entry->d_name);
if (len >= size)
return FSSH_B_NAME_TOO_LONG;
strcpy(name, entry->d_name);
return 1;
}
virtual fssh_status_t GetAttrInfo(const char *name, fssh_attr_info &info)
{
attr_info hostInfo;
if (fs_stat_attr(fFD, name, &hostInfo) < 0)
return fssh_get_errno();
info.type = hostInfo.type;
info.size = hostInfo.size;
return FSSH_B_OK;
}
virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type,
fssh_off_t pos, void *buffer, int size)
{
fssh_ssize_t bytesRead = fs_read_attr(fFD, name, type, pos, buffer,
size);
return (bytesRead >= 0 ? bytesRead : fssh_get_errno());
}
virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type,
fssh_off_t pos, const void *buffer, int size)
{
fssh_ssize_t bytesWritten = fs_write_attr(fFD, name, type, pos, buffer,
size);
return (bytesWritten >= 0 ? bytesWritten : fssh_get_errno());
}
virtual fssh_status_t RemoveAttr(const char *name)
{
return (fs_remove_attr(fFD, name) == 0 ? 0 : fssh_get_errno());
}
protected:
int fFD;
DIR *fAttrDir;
};
class HostDirectory : public Directory, public HostNode {
public:
HostDirectory()
: Directory(),
HostNode(),
fDir(NULL)
{
}
virtual ~HostDirectory()
{
if (fDir)
closedir(fDir);
}
virtual fssh_status_t Init(const char *path, int fd,
const struct fssh_stat &st)
{
fssh_status_t error = HostNode::Init(path, fd, st);
if (error != FSSH_B_OK)
return error;
fDir = opendir(path);
if (!fDir)
return fssh_get_errno();
return FSSH_B_OK;
}
virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size)
{
fssh_set_errno(FSSH_B_OK);
struct dirent *hostEntry = readdir(fDir);
if (!hostEntry)
return fssh_get_errno();
int nameLen = strlen(hostEntry->d_name);
int recLen = entry->d_name + nameLen + 1 - (char*)entry;
if (recLen > size)
return FSSH_B_NAME_TOO_LONG;
#if (defined(__BEOS__) || defined(__HAIKU__))
entry->d_dev = hostEntry->d_dev;
#endif
entry->d_ino = hostEntry->d_ino;
strcpy(entry->d_name, hostEntry->d_name);
entry->d_reclen = recLen;
return 1;
}
private:
DIR *fDir;
};
class HostFile : public File, public HostNode {
public:
HostFile()
: File(),
HostNode()
{
}
virtual ~HostFile()
{
}
virtual fssh_ssize_t Read(void *buffer, int size)
{
fssh_ssize_t bytesRead = read(fFD, buffer, size);
return (bytesRead >= 0 ? bytesRead : fssh_get_errno());
}
virtual fssh_ssize_t Write(const void *buffer, int size)
{
fssh_ssize_t bytesWritten = write(fFD, buffer, size);
return (bytesWritten >= 0 ? bytesWritten : fssh_get_errno());
}
};
class HostSymLink : public SymLink, public HostNode {
public:
HostSymLink()
: SymLink(),
HostNode(),
fPath(NULL)
{
}
virtual ~HostSymLink()
{
if (fPath)
free(fPath);
}
virtual fssh_status_t Init(const char *path, int fd,
const struct fssh_stat &st)
{
fssh_status_t error = HostNode::Init(path, fd, st);
if (error != FSSH_B_OK)
return error;
fPath = strdup(path);
if (!fPath)
return FSSH_B_NO_MEMORY;
return FSSH_B_OK;
}
virtual fssh_ssize_t ReadLink(char *buffer, int bufferSize)
{
fssh_ssize_t bytesRead = readlink(fPath, buffer, bufferSize);
return (bytesRead >= 0 ? bytesRead : fssh_get_errno());
}
private:
char *fPath;
};
class HostFSDomain : public FSDomain {
public:
HostFSDomain() {}
virtual ~HostFSDomain() {}
virtual fssh_status_t Open(const char *path, int openMode, Node *&_node)
{
int fd = fssh_open(path, openMode);
if (fd < 0)
return fssh_get_errno();
struct fssh_stat st;
if (fssh_fstat(fd, &st) < 0) {
fssh_close(fd);
return fssh_get_errno();
}
HostNode *node = NULL;
switch (st.fssh_st_mode & FSSH_S_IFMT) {
case FSSH_S_IFLNK:
node = new HostSymLink;
break;
case FSSH_S_IFREG:
node = new HostFile;
break;
case FSSH_S_IFDIR:
node = new HostDirectory;
break;
default:
fssh_close(fd);
return FSSH_EINVAL;
}
fssh_status_t error = node->Init(path, fd, st);
if (error != FSSH_B_OK) {
delete node;
return error;
}
_node = node;
return FSSH_B_OK;
}
virtual fssh_status_t CreateFile(const char *path,
const struct fssh_stat &st, File *&_file)
{
int fd = fssh_creat(path, st.fssh_st_mode & FSSH_S_IUMSK);
if (fd < 0)
return fssh_get_errno();
fssh_status_t error = _ApplyStat(fd, st);
if (error != FSSH_B_OK) {
fssh_close(fd);
return error;
}
HostFile *file = new HostFile;
error = file->Init(path, fd, st);
if (error != FSSH_B_OK) {
delete file;
return error;
}
_file = file;
return FSSH_B_OK;
}
virtual fssh_status_t CreateDirectory(const char *path,
const struct fssh_stat &st, Directory *&_dir)
{
if (fssh_mkdir(path, st.fssh_st_mode & FSSH_S_IUMSK) < 0)
return fssh_get_errno();
int fd = fssh_open(path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE);
if (fd < 0)
return fssh_get_errno();
fssh_status_t error = _ApplyStat(fd, st);
if (error != FSSH_B_OK) {
fssh_close(fd);
return error;
}
HostDirectory *dir = new HostDirectory;
error = dir->Init(path, fd, st);
if (error != FSSH_B_OK) {
delete dir;
return error;
}
_dir = dir;
return FSSH_B_OK;
}
virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo,
const struct fssh_stat &st, SymLink *&_link)
{
if (symlink(linkTo, path) < 0)
return fssh_get_errno();
int fd = fssh_open(path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE);
if (fd < 0)
return fssh_get_errno();
fssh_status_t error = _ApplyStat(fd, st);
if (error != FSSH_B_OK) {
fssh_close(fd);
return error;
}
HostSymLink *link = new HostSymLink;
error = link->Init(path, fd, st);
if (error != FSSH_B_OK) {
delete link;
return error;
}
_link = link;
return FSSH_B_OK;
}
virtual fssh_status_t Unlink(const char *path)
{
if (fssh_unlink(path) < 0)
return fssh_get_errno();
return FSSH_B_OK;
}
private:
fssh_status_t _ApplyStat(int fd, const struct fssh_stat &st)
{
return FSSH_B_OK;
}
};
class GuestNode : public virtual Node {
public:
GuestNode()
: Node(),
fFD(-1),
fAttrDir(-1)
{
}
virtual ~GuestNode()
{
if (fFD >= 0)
_kern_close(fFD);
if (fAttrDir)
_kern_close(fAttrDir);
}
virtual fssh_status_t Init(const char *path, int fd,
const struct fssh_stat &st)
{
fFD = fd;
fStat = st;
fAttrDir = _kern_open_attr_dir(fd, NULL);
if (fAttrDir < 0) {
}
return FSSH_B_OK;
}
virtual fssh_ssize_t GetNextAttr(char *name, int size)
{
if (fAttrDir < 0)
return 0;
char buffer[sizeof(fssh_dirent) + B_ATTR_NAME_LENGTH];
struct fssh_dirent *entry = (fssh_dirent *)buffer;
int numRead = _kern_read_dir(fAttrDir, entry, sizeof(buffer), 1);
if (numRead < 0)
return numRead;
if (numRead == 0)
return 0;
int len = strlen(entry->d_name);
if (len >= size)
return FSSH_B_NAME_TOO_LONG;
strcpy(name, entry->d_name);
return 1;
}
virtual fssh_status_t GetAttrInfo(const char *name, fssh_attr_info &info)
{
int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY);
if (attrFD < 0)
return attrFD;
struct fssh_stat st;
fssh_status_t error = _kern_read_stat(attrFD, NULL, false, &st,
sizeof(st));
_kern_close(attrFD);
if (error != FSSH_B_OK)
return error;
info.type = st.fssh_st_type;
info.size = st.fssh_st_size;
return FSSH_B_OK;
}
virtual fssh_ssize_t ReadAttr(const char *name, uint32_t type,
fssh_off_t pos, void *buffer, int size)
{
int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY);
if (attrFD < 0)
return attrFD;
fssh_ssize_t bytesRead = _kern_read(attrFD, pos, buffer, size);
_kern_close(attrFD);
return bytesRead;
}
virtual fssh_ssize_t WriteAttr(const char *name, uint32_t type,
fssh_off_t pos, const void *buffer, int size)
{
int attrFD = _kern_create_attr(fFD, name, type, FSSH_O_WRONLY);
if (attrFD < 0)
return attrFD;
fssh_ssize_t bytesWritten = _kern_write(attrFD, pos, buffer, size);
_kern_close(attrFD);
return bytesWritten;
}
virtual fssh_status_t RemoveAttr(const char *name)
{
return _kern_remove_attr(fFD, name);
}
protected:
int fFD;
int fAttrDir;
};
class GuestDirectory : public Directory, public GuestNode {
public:
GuestDirectory()
: Directory(),
GuestNode(),
fDir(-1)
{
}
virtual ~GuestDirectory()
{
if (fDir)
_kern_close(fDir);
}
virtual fssh_status_t Init(const char *path, int fd,
const struct fssh_stat &st)
{
fssh_status_t error = GuestNode::Init(path, fd, st);
if (error != FSSH_B_OK)
return error;
fDir = _kern_open_dir(fd, NULL);
if (fDir < 0)
return fDir;
return FSSH_B_OK;
}
virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size)
{
return _kern_read_dir(fDir, entry, size, 1);
}
private:
int fDir;
};
class GuestFile : public File, public GuestNode {
public:
GuestFile()
: File(),
GuestNode()
{
}
virtual ~GuestFile()
{
}
virtual fssh_ssize_t Read(void *buffer, int size)
{
return _kern_read(fFD, -1, buffer, size);
}
virtual fssh_ssize_t Write(const void *buffer, int size)
{
return _kern_write(fFD, -1, buffer, size);
}
};
class GuestSymLink : public SymLink, public GuestNode {
public:
GuestSymLink()
: SymLink(),
GuestNode()
{
}
virtual ~GuestSymLink()
{
}
virtual fssh_ssize_t ReadLink(char *buffer, int _bufferSize)
{
fssh_size_t bufferSize = _bufferSize;
fssh_status_t error = _kern_read_link(fFD, NULL, buffer, &bufferSize);
return (error == FSSH_B_OK ? bufferSize : error);
}
};
class GuestFSDomain : public FSDomain {
public:
GuestFSDomain() {}
virtual ~GuestFSDomain() {}
virtual fssh_status_t Open(const char *path, int openMode, Node *&_node)
{
int fd = _kern_open(-1, path, openMode, 0);
if (fd < 0)
return fd;
struct fssh_stat st;
fssh_status_t error = _kern_read_stat(fd, NULL, false, &st, sizeof(st));
if (error < 0) {
_kern_close(fd);
return error;
}
GuestNode *node = NULL;
switch (st.fssh_st_mode & FSSH_S_IFMT) {
case FSSH_S_IFLNK:
node = new GuestSymLink;
break;
case FSSH_S_IFREG:
node = new GuestFile;
break;
case FSSH_S_IFDIR:
node = new GuestDirectory;
break;
default:
_kern_close(fd);
return FSSH_EINVAL;
}
error = node->Init(path, fd, st);
if (error != FSSH_B_OK) {
delete node;
return error;
}
_node = node;
return FSSH_B_OK;
}
virtual fssh_status_t CreateFile(const char *path,
const struct fssh_stat &st, File *&_file)
{
int fd = _kern_open(-1, path, FSSH_O_RDWR | FSSH_O_EXCL | FSSH_O_CREAT,
st.fssh_st_mode & FSSH_S_IUMSK);
if (fd < 0)
return fd;
fssh_status_t error = _ApplyStat(fd, st);
if (error != FSSH_B_OK) {
_kern_close(fd);
return error;
}
GuestFile *file = new GuestFile;
error = file->Init(path, fd, st);
if (error != FSSH_B_OK) {
delete file;
return error;
}
_file = file;
return FSSH_B_OK;
}
virtual fssh_status_t CreateDirectory(const char *path,
const struct fssh_stat &st, Directory *&_dir)
{
fssh_status_t error = _kern_create_dir(-1, path,
st.fssh_st_mode & FSSH_S_IUMSK);
if (error < 0)
return error;
int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0);
if (fd < 0)
return fd;
error = _ApplyStat(fd, st);
if (error != FSSH_B_OK) {
_kern_close(fd);
return error;
}
GuestDirectory *dir = new GuestDirectory;
error = dir->Init(path, fd, st);
if (error != FSSH_B_OK) {
delete dir;
return error;
}
_dir = dir;
return FSSH_B_OK;
}
virtual fssh_status_t CreateSymLink(const char *path, const char *linkTo,
const struct fssh_stat &st, SymLink *&_link)
{
fssh_status_t error = _kern_create_symlink(-1, path, linkTo,
st.fssh_st_mode & FSSH_S_IUMSK);
if (error < 0)
return error;
int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0);
if (fd < 0)
return fd;
error = _ApplyStat(fd, st);
if (error != FSSH_B_OK) {
_kern_close(fd);
return error;
}
GuestSymLink *link = new GuestSymLink;
error = link->Init(path, fd, st);
if (error != FSSH_B_OK) {
delete link;
return error;
}
_link = link;
return FSSH_B_OK;
}
virtual fssh_status_t Unlink(const char *path)
{
return _kern_unlink(-1, path);
}
private:
fssh_status_t _ApplyStat(int fd, const struct fssh_stat &st)
{
return FSSH_B_OK;
}
};
static fssh_status_t copy_entry(FSDomain *sourceDomain, const char *source,
FSDomain *targetDomain, const char *target, const Options &options,
bool dereference);
static FSDomain *
get_file_domain(const char *target, const char *&fsTarget)
{
if (target[0] == ':') {
fsTarget = target + 1;
return new HostFSDomain;
} else {
fsTarget = target;
return new GuestFSDomain;
}
}
typedef ObjectDeleter<Node> NodeDeleter;
typedef ObjectDeleter<FSDomain> DomainDeleter;
typedef MemoryDeleter PathDeleter;
static fssh_status_t
copy_file_contents(const char *source, File *sourceFile, const char *target,
File *targetFile)
{
fssh_off_t chunkSize = (sourceFile->Stat().fssh_st_size / 20) / sCopyBufferSize * sCopyBufferSize;
if (chunkSize == 0)
chunkSize = 1;
bool progress = sourceFile->Stat().fssh_st_size > 1024 * 1024;
if (progress) {
printf("%s ", strrchr(target, '/') ? strrchr(target, '/') + 1 : target);
fflush(stdout);
}
fssh_off_t total = 0;
fssh_ssize_t bytesRead;
while ((bytesRead = sourceFile->Read(sCopyBuffer, sCopyBufferSize)) > 0) {
fssh_ssize_t bytesWritten = targetFile->Write(sCopyBuffer, bytesRead);
if (progress && (total % chunkSize) == 0) {
putchar('.');
fflush(stdout);
}
if (bytesWritten < 0) {
fprintf(stderr, "Error while writing to file `%s': %s\n",
target, fssh_strerror(bytesWritten));
return bytesWritten;
}
if (bytesWritten != bytesRead) {
fprintf(stderr, "Could not write all data to file \"%s\".\n",
target);
return FSSH_B_IO_ERROR;
}
total += bytesWritten;
}
if (bytesRead < 0) {
fprintf(stderr, "Error while reading from file `%s': %s\n",
source, fssh_strerror(bytesRead));
return bytesRead;
}
if (progress)
putchar('\n');
return FSSH_B_OK;
}
static fssh_status_t
copy_dir_contents(FSDomain *sourceDomain, const char *source,
Directory *sourceDir, FSDomain *targetDomain, const char *target,
const Options &options)
{
char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
struct fssh_dirent *entry = (struct fssh_dirent *)buffer;
fssh_ssize_t numRead;
while ((numRead = sourceDir->GetNextEntry(entry, sizeof(buffer))) > 0) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char *sourceEntry = make_path(source, entry->d_name);
if (!sourceEntry) {
fprintf(stderr, "Error: Failed to allocate source path!\n");
return FSSH_ENOMEM;
}
PathDeleter sourceDeleter(sourceEntry);
char *targetEntry = make_path(target, entry->d_name);
if (!targetEntry) {
fprintf(stderr, "Error: Failed to allocate target path!\n");
return FSSH_ENOMEM;
}
PathDeleter targetDeleter(targetEntry);
fssh_status_t error = copy_entry(sourceDomain, sourceEntry,
targetDomain, targetEntry, options, options.alwaysDereference);
if (error != FSSH_B_OK)
return error;
}
if (numRead < 0) {
fprintf(stderr, "Error reading directory `%s': %s\n", source,
fssh_strerror(numRead));
return numRead;
}
return FSSH_B_OK;
}
static fssh_status_t
copy_attribute(const char *source, Node *sourceNode, const char *target,
Node *targetNode, const char *name, const fssh_attr_info &info)
{
targetNode->RemoveAttr(name);
if (info.size <= 0) {
fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, 0,
sCopyBuffer, 0);
if (bytesWritten) {
fprintf(stderr, "Error while writing to attribute `%s' of file "
"`%s': %s\n", name, target, fssh_strerror(bytesWritten));
return bytesWritten;
}
return FSSH_B_OK;
}
fssh_off_t pos = 0;
int toCopy = info.size;
while (toCopy > 0) {
int toRead = (toCopy < sCopyBufferSize ? toCopy : sCopyBufferSize);
fssh_ssize_t bytesRead = sourceNode->ReadAttr(name, info.type, pos,
sCopyBuffer, toRead);
if (bytesRead < 0) {
fprintf(stderr, "Error while reading from attribute `%s' of file "
"`%s': %s\n", name, source, fssh_strerror(bytesRead));
return bytesRead;
}
fssh_ssize_t bytesWritten = targetNode->WriteAttr(name, info.type, pos,
sCopyBuffer, bytesRead);
if (bytesWritten < 0) {
fprintf(stderr, "Error while writing to attribute `%s' of file "
"`%s': %s\n", name, target, fssh_strerror(bytesWritten));
return bytesWritten;
}
pos += bytesRead;
toCopy -= bytesRead;
}
return FSSH_B_OK;
}
static fssh_status_t
copy_attributes(const char *source, Node *sourceNode, const char *target,
Node *targetNode)
{
char name[B_ATTR_NAME_LENGTH];
fssh_ssize_t numRead;
while ((numRead = sourceNode->GetNextAttr(name, sizeof(name))) > 0) {
fssh_attr_info info;
fssh_status_t error = sourceNode->GetAttrInfo(name, info);
if (error != FSSH_B_OK) {
fprintf(stderr, "Error getting info for attribute `%s' of file "
"`%s': %s\n", name, source, fssh_strerror(error));
return error;
}
error = copy_attribute(source, sourceNode, target, targetNode, name,
info);
if (error != FSSH_B_OK)
return error;
}
if (numRead < 0) {
fprintf(stderr, "Error reading attribute directory of `%s': %s\n",
source, fssh_strerror(numRead));
return numRead;
}
return FSSH_B_OK;
}
static fssh_status_t
copy_entry(FSDomain *sourceDomain, const char *source,
FSDomain *targetDomain, const char *target, const Options &options,
bool dereference)
{
if (!options.entryFilter.Filter(source))
return FSSH_B_OK;
Node *sourceNode;
fssh_status_t error = sourceDomain->Open(source,
FSSH_O_RDONLY | (dereference ? 0 : FSSH_O_NOTRAVERSE),
sourceNode);
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Failed to open source path `%s': %s\n", source,
fssh_strerror(error));
return error;
}
NodeDeleter sourceDeleter(sourceNode);
Node *targetNode = NULL;
error = targetDomain->Open(target, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE,
targetNode);
NodeDeleter targetDeleter;
if (error == FSSH_B_OK) {
targetDeleter.SetTo(targetNode);
if (targetNode->IsSymLink()) {
Node *resolvedTargetNode;
error = targetDomain->Open(target, FSSH_O_RDONLY,
resolvedTargetNode);
if (error == FSSH_B_OK) {
targetNode = resolvedTargetNode;
targetDeleter.SetTo(targetNode);
}
}
if (sourceNode->IsDirectory() && targetNode->IsDirectory()) {
} else {
if (options.force) {
targetDeleter.Delete();
targetNode = NULL;
error = targetDomain->Unlink(target);
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Failed to remove `%s'\n", target);
return error;
}
} else if (sourceNode->IsFile() && targetNode->IsFile()) {
targetDeleter.Delete();
targetNode = NULL;
error = targetDomain->Open(target, FSSH_O_RDWR | FSSH_O_TRUNC,
targetNode);
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Failed to open `%s' for writing\n",
target);
return error;
}
} else {
fprintf(stderr, "Error: File `%s' does exist.\n", target);
return FSSH_B_FILE_EXISTS;
}
}
}
error = FSSH_B_OK;
if (sourceNode->IsFile()) {
if (!targetNode) {
File *file = NULL;
error = targetDomain->CreateFile(target, sourceNode->Stat(), file);
if (error == 0)
targetNode = file;
}
} else if (sourceNode->IsDirectory()) {
if (!options.recursive) {
fprintf(stderr, "Error: Entry `%s' is a directory.\n", source);
return FSSH_EISDIR;
}
if (!targetNode) {
Directory *dir = NULL;
error = targetDomain->CreateDirectory(target, sourceNode->Stat(),
dir);
if (error == 0)
targetNode = dir;
}
} else if (sourceNode->IsSymLink()) {
SymLink *sourceLink = sourceNode->ToSymLink();
char linkTo[FSSH_B_PATH_NAME_LENGTH];
fssh_ssize_t bytesRead = sourceLink->ReadLink(linkTo,
sizeof(linkTo) - 1);
if (bytesRead < 0) {
fprintf(stderr, "Error: Failed to read symlink `%s': %s\n", source,
fssh_strerror(bytesRead));
}
linkTo[bytesRead] = '\0';
SymLink *link;
error = targetDomain->CreateSymLink(target, linkTo,
sourceNode->Stat(), link);
if (error == 0)
targetNode = link;
} else {
fprintf(stderr, "Error: Unknown node type. We shouldn't be here!\n");
return FSSH_EINVAL;
}
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Failed to create `%s': %s\n", target,
fssh_strerror(error));
return error;
}
targetDeleter.SetTo(targetNode);
if (!options.ignoreAttributes) {
error = copy_attributes(source, sourceNode, target, targetNode);
if (error != FSSH_B_OK)
return error;
}
if (sourceNode->IsFile()) {
error = copy_file_contents(source, sourceNode->ToFile(), target,
targetNode->ToFile());
} else if (sourceNode->IsDirectory()) {
error = copy_dir_contents(sourceDomain, source,
sourceNode->ToDirectory(), targetDomain, target, options);
}
return error;
}
fssh_status_t
command_cp(int argc, const char* const* argv)
{
int sourceCount = 0;
Options options;
const char **sources = new const char*[argc];
if (!sources) {
fprintf(stderr, "Error: No memory!\n");
return FSSH_EINVAL;
}
ArrayDeleter<const char*> _(sources);
for (int argi = 1; argi < argc; argi++) {
const char *arg = argv[argi];
if (arg[0] == '-') {
if (arg[1] == '\0') {
fprintf(stderr, "Error: Invalid option '-'\n");
return FSSH_EINVAL;
}
if (arg[1] == '-') {
if (strcmp(arg, "--ignore-attributes") == 0) {
options.ignoreAttributes = true;
} else {
fprintf(stderr, "Error: Unknown option '%s'\n", arg);
return FSSH_EINVAL;
}
} else {
for (int i = 1; arg[i]; i++) {
switch (arg[i]) {
case 'a':
options.attributesOnly = true;
break;
case 'd':
options.dereference = false;
break;
case 'f':
options.force = true;
break;
case 'L':
options.dereference = true;
options.alwaysDereference = true;
break;
case 'r':
options.recursive = true;
break;
case 'x':
case 'X':
{
const char* pattern;
if (arg[i + 1] == '\0') {
if (++argi >= argc) {
fprintf(stderr, "Error: Option '-%c' need "
"a pattern as parameter\n", arg[i]);
return FSSH_EINVAL;
}
pattern = argv[argi];
} else
pattern = arg + i + 1;
options.entryFilter.AddExcludeFilter(pattern,
arg[i] == 'x');
break;
}
default:
fprintf(stderr, "Error: Unknown option '-%c'\n",
arg[i]);
return FSSH_EINVAL;
}
}
}
} else {
sources[sourceCount++] = arg;
}
}
if (sourceCount < 2) {
fprintf(stderr, "Error: Must specify at least 2 files!\n");
return FSSH_EINVAL;
}
const char *target = sources[--sourceCount];
bool targetIsDir = false;
bool targetExists = false;
FSDomain *targetDomain = get_file_domain(target, target);
DomainDeleter targetDomainDeleter(targetDomain);
Node *targetNode;
fssh_status_t error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode);
if (error == 0) {
NodeDeleter targetDeleter(targetNode);
targetExists = true;
if (options.attributesOnly) {
} else if (targetNode->IsDirectory()) {
targetIsDir = true;
} else {
if (sourceCount > 1) {
fprintf(stderr, "Error: Destination `%s' is not a directory!",
target);
return FSSH_B_NOT_A_DIRECTORY;
}
}
} else {
if (options.attributesOnly) {
fprintf(stderr, "Error: Failed to open target `%s' (it must exist "
"in attributes only mode): `%s'\n", target,
fssh_strerror(error));
return error;
} else if (sourceCount > 1) {
fprintf(stderr, "Error: Failed to open destination directory `%s':"
" `%s'\n", target, fssh_strerror(error));
return error;
}
}
sCopyBuffer = malloc(sCopyBufferSize);
if (!sCopyBuffer) {
fprintf(stderr, "Error: Failed to allocate copy buffer.\n");
return FSSH_ENOMEM;
}
MemoryDeleter copyBufferDeleter(sCopyBuffer);
NodeDeleter targetDeleter;
if (options.attributesOnly) {
error = targetDomain->Open(target, FSSH_O_RDONLY, targetNode);
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Failed to open target `%s' for writing: "
"`%s'\n", target, fssh_strerror(error));
return error;
}
targetDeleter.SetTo(targetNode);
}
for (int i = 0; i < sourceCount; i++) {
const char *source = sources[i];
FSDomain *sourceDomain = get_file_domain(source, source);
DomainDeleter sourceDomainDeleter(sourceDomain);
if (options.attributesOnly) {
Node *sourceNode;
error = sourceDomain->Open(source,
FSSH_O_RDONLY | (options.dereference ? 0 : FSSH_O_NOTRAVERSE),
sourceNode);
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Failed to open `%s': %s.\n", source,
fssh_strerror(error));
return error;
}
NodeDeleter sourceDeleter(sourceNode);
error = copy_attributes(source, sourceNode, target, targetNode);
} else if (targetExists && targetIsDir) {
char leafName[FSSH_B_FILE_NAME_LENGTH];
error = get_last_path_component(source, leafName, sizeof(leafName));
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Failed to get last path component of "
"`%s': %s\n", source, fssh_strerror(error));
return error;
}
if (strcmp(leafName, ".") == 0 || strcmp(leafName, "..") == 0) {
Node *sourceNode;
error = sourceDomain->Open(source,
FSSH_O_RDONLY
| (options.dereference ? 0 : FSSH_O_NOTRAVERSE),
sourceNode);
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Failed to open `%s': %s.\n", source,
fssh_strerror(error));
return error;
}
NodeDeleter sourceDeleter(sourceNode);
Directory *sourceDir = sourceNode->ToDirectory();
if (!sourceDir) {
fprintf(stderr, "Error: Source `%s' is not a directory "
"although it's last path component is `%s'\n", source,
leafName);
return FSSH_EINVAL;
}
error = copy_dir_contents(sourceDomain, source, sourceDir,
targetDomain, target, options);
} else {
char *targetEntry = make_path(target, leafName);
if (!targetEntry) {
fprintf(stderr, "Error: Failed to allocate target path!\n");
return FSSH_ENOMEM;
}
PathDeleter targetDeleter(targetEntry);
error = copy_entry(sourceDomain, source, targetDomain,
targetEntry, options, options.dereference);
}
} else {
error = copy_entry(sourceDomain, source, targetDomain, target,
options, options.dereference);
}
if (error != 0)
return error;
}
return FSSH_B_OK;
}
}