diff --git a/src/tools/fs_shell/command_cp.cpp b/src/tools/fs_shell/command_cp.cpp new file mode 100644 index 0000000000..9a9d620ce7 --- /dev/null +++ b/src/tools/fs_shell/command_cp.cpp @@ -0,0 +1,1361 @@ +/* + * Copyright 2005-2007, Ingo Weinhold, bonefish@cs.tu-berlin.de. + * Distributed under the terms of the MIT License. + */ + +#include + +#include "command_cp.h" + +#include +#include +#include + +#include +#include +#include + +#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 "path_util.h" +#include "stat_util.h" +#include "syscalls.h" + + +namespace FSShell { + + +static void *sCopyBuffer = NULL; +static const int sCopyBufferSize = 64 * 1024; // 64 KB + +struct Options { + Options() + : dereference(true), + force(false), + recursive(false) + { + } + + bool dereference; + bool force; + bool recursive; +}; + +class Directory; +class File; +class SymLink; + +// Node +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; // To be initialized by implementing classes. +}; + +// Directory +class Directory : public virtual Node { +public: + virtual Directory *ToDirectory() { return this; } + + virtual fssh_ssize_t GetNextEntry(struct fssh_dirent *entry, int size) = 0; +}; + +// File +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; +}; + +// SymLink +class SymLink : public virtual Node { +public: + virtual SymLink *ToSymLink() { return this; } + + virtual fssh_ssize_t ReadLink(char *buffer, int bufferSize) = 0; +}; + +// FSDomain +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; +}; + + +// #pragma mark - + +// HostNode +class HostNode : public virtual Node { +public: + HostNode() + : Node(), + fFD(-1), + fAttrDir(NULL) + { + } + + virtual ~HostNode() + { + if (fFD >= 0) + 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; + + // open the attribute directory + 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; + + errno = 0; + 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; +}; + +// HostDirectory +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) + { + errno = 0; + 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; + + #ifdef __BEOS__ + 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; +}; + +// HostFile +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()); + } +}; + +// HostSymLink +class HostSymLink : public SymLink, public HostNode { +public: + HostSymLink() + : SymLink(), + HostNode() + { + } + + 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; +}; + +// HostFSDomain +class HostFSDomain : public FSDomain { +public: + HostFSDomain() {} + virtual ~HostFSDomain() {} + + virtual fssh_status_t Open(const char *path, int openMode, Node *&_node) + { + // open the node + int fd = open(path, to_platform_open_mode(openMode)); + if (fd < 0) + return fssh_get_errno(); + + // stat the node + struct stat st; + if (fstat(fd, &st) < 0) { + close(fd); + return fssh_get_errno(); + } + + // check the node type and create the node + HostNode *node = NULL; + switch (st.st_mode & S_IFMT) { + case S_IFLNK: + node = new HostSymLink; + break; + case S_IFREG: + node = new HostFile; + break; + case S_IFDIR: + node = new HostDirectory; + break; + default: + close(fd); + return FSSH_EINVAL; + } + + // convert the stat + struct fssh_stat myst; + from_platform_stat(&st, &myst); + + // init the node + fssh_status_t error = node->Init(path, fd, myst); + // the node receives ownership of the FD + 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 &myst, File *&_file) + { + struct stat st; + to_platform_stat(&myst, &st); + + // create the file + int fd = creat(path, st.st_mode & S_IUMSK); + if (fd < 0) + return fssh_get_errno(); + + // apply the other stat fields + fssh_status_t error = _ApplyStat(fd, st); + if (error != FSSH_B_OK) { + close(fd); + return error; + } + + // create the object + HostFile *file = new HostFile; + error = file->Init(path, fd, myst); + 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 &myst, Directory *&_dir) + { + struct stat st; + to_platform_stat(&myst, &st); + + // create the dir + if (mkdir(path, st.st_mode & S_IUMSK) < 0) + return fssh_get_errno(); + + // open the dir node + int fd = open(path, O_RDONLY | O_NOTRAVERSE); + if (fd < 0) + return fssh_get_errno(); + + // apply the other stat fields + fssh_status_t error = _ApplyStat(fd, st); + if (error != FSSH_B_OK) { + close(fd); + return error; + } + + // create the object + HostDirectory *dir = new HostDirectory; + error = dir->Init(path, fd, myst); + 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 &myst, SymLink *&_link) + { + struct stat st; + to_platform_stat(&myst, &st); + + // create the link + if (symlink(linkTo, path) < 0) + return fssh_get_errno(); + + // open the symlink node + int fd = open(path, O_RDONLY | O_NOTRAVERSE); + if (fd < 0) + return fssh_get_errno(); + + // apply the other stat fields + fssh_status_t error = _ApplyStat(fd, st); + if (error != FSSH_B_OK) { + close(fd); + return error; + } + + // create the object + HostSymLink *link = new HostSymLink; + error = link->Init(path, fd, myst); + if (error != FSSH_B_OK) { + delete link; + return error; + } + + _link = link; + return FSSH_B_OK; + } + + + virtual fssh_status_t Unlink(const char *path) + { + if (unlink(path) < 0) + return fssh_get_errno(); + return FSSH_B_OK; + } + +private: + fssh_status_t _ApplyStat(int fd, const struct stat &st) + { + // TODO: Set times... + return FSSH_B_OK; + } +}; + + +// #pragma mark - + +// GuestNode +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; + + // open the attribute directory + fAttrDir = _kern_open_attr_dir(fd, NULL); + if (fAttrDir < 0) + return fAttrDir; + + 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) + { + // open attr + int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY); + if (attrFD < 0) + return attrFD; + + // stat attr + struct fssh_stat st; + fssh_status_t error = _kern_read_stat(attrFD, NULL, false, &st, + sizeof(st)); + + // close attr + _kern_close(attrFD); + + if (error != FSSH_B_OK) + return error; + + // convert stat to attr info + 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) + { + // open attr + int attrFD = _kern_open_attr(fFD, name, FSSH_O_RDONLY); + if (attrFD < 0) + return attrFD; + + // stat attr + fssh_ssize_t bytesRead = _kern_read(attrFD, pos, buffer, size); + + // close attr + _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) + { + // open attr + int attrFD = _kern_create_attr(fFD, name, type, FSSH_O_WRONLY); + if (attrFD < 0) + return attrFD; + + // stat attr + fssh_ssize_t bytesWritten = _kern_write(attrFD, pos, buffer, size); + + // close attr + _kern_close(attrFD); + + return bytesWritten; + } + + virtual fssh_status_t RemoveAttr(const char *name) + { +printf("GuestNode::RemoveAttr(\"%s\")\n", name); + return _kern_remove_attr(fFD, name); + } + +protected: + int fFD; + int fAttrDir; +}; + +// GuestDirectory +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; +}; + +// GuestFile +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); + } +}; + +// GuestSymLink +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); + } +}; + +// GuestFSDomain +class GuestFSDomain : public FSDomain { +public: + GuestFSDomain() {} + virtual ~GuestFSDomain() {} + + virtual fssh_status_t Open(const char *path, int openMode, Node *&_node) + { + // open the node + int fd = _kern_open(-1, path, openMode, 0); + if (fd < 0) + return fd; + + // stat the node + 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; + } + + // check the node type and create the node + 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; + } + + // init the node + error = node->Init(path, fd, st); + // the node receives ownership of the FD + 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) + { + // create the 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; + + // apply the other stat fields + fssh_status_t error = _ApplyStat(fd, st); + if (error != FSSH_B_OK) { + _kern_close(fd); + return error; + } + + // create the object + 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) + { + // create the dir + fssh_status_t error = _kern_create_dir(-1, path, + st.fssh_st_mode & FSSH_S_IUMSK); + if (error < 0) + return error; + + // open the dir node + int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0); + if (fd < 0) + return fd; + + // apply the other stat fields + error = _ApplyStat(fd, st); + if (error != FSSH_B_OK) { + _kern_close(fd); + return error; + } + + // create the object + 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) + { + // create the link + fssh_status_t error = _kern_create_symlink(-1, path, linkTo, + st.fssh_st_mode & FSSH_S_IUMSK); + if (error < 0) + return error; + + // open the symlink node + int fd = _kern_open(-1, path, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, 0); + if (fd < 0) + return fd; + + // apply the other stat fields + error = _ApplyStat(fd, st); + if (error != FSSH_B_OK) { + _kern_close(fd); + return error; + } + + // create the object + 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) + { + // TODO: Set times... + return FSSH_B_OK; + } +}; + + +// #pragma mark - + +static fssh_status_t copy_entry(FSDomain *sourceDomain, const char *source, + FSDomain *targetDomain, const char *target, const Options &options); + +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 NodeDeleter; +typedef ObjectDeleter DomainDeleter; +typedef MemoryDeleter PathDeleter; + + +static fssh_status_t +copy_file_contents(const char *source, File *sourceFile, const char *target, + File *targetFile) +{ + fssh_ssize_t bytesRead; + while ((bytesRead = sourceFile->Read(sCopyBuffer, sCopyBufferSize)) > 0) { + fssh_ssize_t bytesWritten = targetFile->Write(sCopyBuffer, bytesRead); + if (bytesWritten < 0) { + fprintf(stderr, "Error while writing to file `%s': %s\n", + target, fssh_strerror(bytesWritten)); + return bytesWritten; + } + } + + if (bytesRead < 0) { + fprintf(stderr, "Error while reading from file `%s': %s\n", + source, fssh_strerror(bytesRead)); + return bytesRead; + } + + 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) { + // skip "." and ".." + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + // compose a new source path name + 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); + + // compose a new target path name + 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); + 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) +{ + // remove the attribute first + targetNode->RemoveAttr(name); + + // special case: empty attribute + 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; + } + + // non-empty attribute + fssh_off_t pos = 0; + int toCopy = info.size; + while (toCopy > 0) { + // read data from source + 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; + } + + // write data to target + 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; + // get attribute 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; + } + + // copy the attribute + 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) +{ + // open the source node + Node *sourceNode; + fssh_status_t 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 source path `%s': %s\n", source, + fssh_strerror(error)); + return error; + } + NodeDeleter sourceDeleter(sourceNode); + + // check, if target exists + Node *targetNode = NULL; + // try opening with resolving symlinks first + error = targetDomain->Open(target, FSSH_O_RDONLY | FSSH_O_NOTRAVERSE, + targetNode); + NodeDeleter targetDeleter; + if (error == FSSH_B_OK) { + // 1. target exists: + // check, if it is a dir and, if so, whether source is a dir too + targetDeleter.SetTo(targetNode); + + // if the target is a symlink, try resolving it + 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()) { + // 1.1. target and source are dirs: + // -> just copy their contents + // ... + } else { + // 1.2. source and/or target are no dirs + + if (options.force) { + // 1.2.1. /force/ + // -> remove the target and continue with 2. + 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()) { + // 1.2.1.1. !/force/, but both source and target are files + // -> truncate the target file and continue + 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 { + // 1.2.1.2. !/force/, source or target isn't a file + // -> fail + fprintf(stderr, "Error: File `%s' does exist.\n", target); + return FSSH_B_FILE_EXISTS; + } + } + } // else: 2. target doesn't exist: -> just create it + + // create the target node + 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()) { + // check /recursive/ + if (!options.recursive) { + fprintf(stderr, "Error: Entry `%s' is a directory.\n", source); + return FSSH_EISDIR; + } + + // create the target only, if it doesn't already exist + if (!targetNode) { + Directory *dir = NULL; + error = targetDomain->CreateDirectory(target, sourceNode->Stat(), + dir); + if (error == 0) + targetNode = dir; + } + } else if (sourceNode->IsSymLink()) { + // read the source link + 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'; // always NULL-terminate + + // create the target link + 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); + + // copy attributes + error = copy_attributes(source, sourceNode, target, targetNode); + if (error != FSSH_B_OK) + return error; + + // copy contents + 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 _(sources); + + // parse parameters + 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; + } + + for (int i = 1; arg[i]; i++) { + switch (arg[i]) { + case 'd': + options.dereference = false; + break; + case 'f': + options.force = true; + break; + case 'r': + options.recursive = true; + break; + default: + fprintf(stderr, "Error: Unknown option `-%c'\n", + arg[i]); + return FSSH_EINVAL; + } + } + } else { + sources[sourceCount++] = arg; + } + } + + // check params + if (sourceCount < 2) { + fprintf(stderr, "Error: Must specify at least 2 files!\n"); + return FSSH_EINVAL; + } + + // check the target + 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 (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 (sourceCount > 1) { + fprintf(stderr, "Error: Failed to open destination directory `%s':" + " `%s'\n", target, fssh_strerror(error)); + return error; + } + } + + // allocate a copy buffer + sCopyBuffer = malloc(sCopyBufferSize); + if (!sCopyBuffer) { + fprintf(stderr, "Error: Failed to allocate copy buffer.\n"); + return FSSH_ENOMEM; + } + MemoryDeleter copyBufferDeleter(sCopyBuffer); + + // the copy loop + for (int i = 0; i < sourceCount; i++) { + const char *source = sources[i]; + FSDomain *sourceDomain = get_file_domain(source, source); + DomainDeleter sourceDomainDeleter(sourceDomain); + if (targetExists && targetIsDir) { + // 1. target exists: + // 1.1. target is a dir: + // get the source leaf name + 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) { + // 1.1.1. source name is `.' or `..' + // -> copy the contents only + // (copy_dir_contents()) + // open the source dir + 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); + + // check, if it is a dir + 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 { + // 1.1.2. source has normal name + // -> we copy into the dir + // (copy_entry(, /)) + // compose a new target path name + 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); + } + } else { + // 1.2. target is no dir: + // -> if /force/ is given, we replace the target, otherwise + // we fail + // (copy_entry(, )) + // or + // 2. target doesn't exist: + // -> we create the target as a clone of the source + // (copy_entry(, )) + error = copy_entry(sourceDomain, source, targetDomain, target, + options); + } + + if (error != 0) + return error; + } + + return FSSH_B_OK; +} + + +} // namespace FSShell diff --git a/src/tools/fs_shell/command_cp.h b/src/tools/fs_shell/command_cp.h new file mode 100644 index 0000000000..ac45bf20f3 --- /dev/null +++ b/src/tools/fs_shell/command_cp.h @@ -0,0 +1,22 @@ +/* + * Copyright 2005-2007, Ingo Weinhold, bonefish@cs.tu-berlin.de. + * Distributed under the terms of the MIT License. + */ + +#ifndef _FSSH_COMMAND_CP_H +#define _FSSH_COMMAND_CP_H + + +#include "fssh_defs.h" + + +namespace FSShell { + + +fssh_status_t command_cp(int argc, const char* const* argv); + + +} // namespace FSShell + + +#endif // _FSSH_COMMAND_CP_H diff --git a/src/tools/fs_shell/fssh.cpp b/src/tools/fs_shell/fssh.cpp index e23539cc58..449ca45ea3 100644 --- a/src/tools/fs_shell/fssh.cpp +++ b/src/tools/fs_shell/fssh.cpp @@ -14,6 +14,7 @@ #include +#include "command_cp.h" #include "fd.h" #include "fssh_dirent.h" #include "fssh_errors.h" @@ -833,6 +834,7 @@ register_commands() { CommandManager::Default()->AddCommands( command_cd, "cd", "change current directory", + command_cp, "cp", "copy files and directories", command_help, "help", "list supported commands", command_ln, "ln", "create a hard or symbolic link", command_ls, "ls", "list files or directories",