diff --git a/src/tests/add-ons/kernel/file_systems/fs_shell/fsh.cpp b/src/tests/add-ons/kernel/file_systems/fs_shell/fsh.cpp index 25ba5b914c..e794e9700d 100644 --- a/src/tests/add-ons/kernel/file_systems/fs_shell/fsh.cpp +++ b/src/tests/add-ons/kernel/file_systems/fs_shell/fsh.cpp @@ -27,7 +27,9 @@ #include "argv.h" #include "external_commands.h" #include "kprotos.h" +#include "path_util.h" #include "tracker.h" +#include "xcp.h" #include #include @@ -37,6 +39,7 @@ static bool sInteractiveMode = true; static int do_lat_fs(int argc, char **argv); static void do_fsh(void); +static int remove_entry(int dir, const char *entry, bool recursive, bool force); static void print_usage(const char *program) @@ -95,6 +98,7 @@ main(int argc, char **argv) do_fsh(); + sys_chdir(1, -1, "/"); if (sys_unmount(1, -1, "/myfs") != 0) { printf("could not un-mount /myfs\n"); return 5; @@ -163,7 +167,7 @@ do_open(int argc, char **argv) else sprintf(name, "/myfs/%s", &argv[1][0]); - cur_fd = sys_open(1, -1, name, O_RDWR, MY_S_IFREG, 0); + cur_fd = sys_open(1, -1, name, MY_O_RDWR, MY_S_IFREG, 0); if (cur_fd < 0) { printf("error opening %s : %s (%d)\n", name, fs_strerror(cur_fd), cur_fd); return cur_fd; @@ -188,7 +192,7 @@ do_make(int argc, char **argv) else strcpy(name, &argv[1][0]); - cur_fd = sys_open(1, -1, name, O_RDWR|O_CREAT, 0666, 0); + cur_fd = sys_open(1, -1, name, MY_O_RDWR | MY_O_CREAT, 0666, 0); if (cur_fd < 0) { printf("error creating: %s: %s\n", name, fs_strerror(cur_fd)); return cur_fd; @@ -437,6 +441,56 @@ do_remove_attr(int argc, char **argv) } +static int +do_list_attr(int argc, char **argv) +{ + if (argc < 2 && cur_fd < 0) { + fprintf(stderr, "Must specify a file!\n"); + return FS_EINVAL; + } + + // open attr dir + int attrDir = -1; + if (argc >= 2) { + attrDir = sys_open_attr_dir(true, -1, argv[1]); + } else if (cur_fd >= 0) { + attrDir = sys_open_attr_dir(true, cur_fd, NULL); + } else { + fprintf(stderr, "Must specify a file!\n"); + return FS_EINVAL; + } + if (attrDir < 0) { + fprintf(stderr, "Failed to open attribute directory: %s\n", + fs_strerror(attrDir)); + return attrDir; + } + + printf(" Type Size Name\n"); + printf("---------- -------- --------------------------------\n"); + + // iterate through the attributes + char entryBuffer[sizeof(my_dirent) + B_ATTR_NAME_LENGTH]; + my_dirent *entry = (my_dirent *)entryBuffer; + while (sys_readdir(true, attrDir, entry, sizeof(entryBuffer), 1) > 0) { + // stat the attribute + my_attr_info info; + int error = sys_stat_attr(true, attrDir, NULL, entry->d_name, &info); + if (error == FS_OK) { + printf("0x%08lx %8lld %32s\n", info.type, (long long)info.size, + entry->d_name); + } else { + fprintf(stderr, "Failed to stat attribute `%s': %s\n", + entry->d_name, fs_strerror(error)); + } + } + + // close the attr dir + sys_closedir(true, attrDir); + + return FS_OK; +} + + static void mode_bits_to_str(int mode, char *str) { @@ -464,6 +518,35 @@ mode_bits_to_str(int mode, char *str) } +static int +do_chdir(int argc, char **argv) +{ + if (argc <= 1) { + fprintf(stderr, "No directory specified!\n"); + return FS_EINVAL; + } + + // change dir + const char *dir = argv[1]; + int error = 0; + if (dir[0] == ':') { + // host environment + if (chdir(dir + 1) < 0) + error = from_platform_error(errno); + } else { + // emulated environment + error = sys_chdir(1, -1, dir); + } + + if (error != 0) { + fprintf(stderr, "Failed to cd into `%s': %s\n", argv[1], + fs_strerror(error)); + } + + return error; +} + + static int do_dir(int argc, char **argv) { @@ -476,9 +559,10 @@ do_dir(int argc, char **argv) char mode_str[16]; dent = (struct my_dirent *)buff; - strcpy(dirname, "/myfs/"); if (argc > 1) - strcat(dirname, &argv[1][0]); + strcpy(dirname, &argv[1][0]); + else + strcpy(dirname, "."); if ((dirfd = sys_opendir(1, -1, dirname, 0)) < 0) { printf("dir: error opening: %s\n", dirname); @@ -503,7 +587,7 @@ do_dir(int argc, char **argv) if (err == 0) break; - err = sys_rstat(1, dirfd, dent->d_name, &st, 1); + err = sys_rstat(1, dirfd, dent->d_name, &st, false); if (err != 0) { // may happen for unresolvable links printf("stat failed for: %s (%Ld)\n", dent->d_name, dent->d_ino); @@ -515,8 +599,24 @@ do_dir(int argc, char **argv) mode_bits_to_str(st.mode, mode_str); - printf("%12Ld %s %6d %6d %12Ld %s %s\n", st.ino, mode_str, + printf("%12Ld %s %6d %6d %12Ld %s %s", st.ino, mode_str, st.uid, st.gid, st.size, time_buf, dent->d_name); + + // if the entry is a symlink, print its target + if (MY_S_ISLNK(st.mode)) { + char linkTo[B_PATH_NAME_LENGTH]; + ssize_t bytesRead = sys_readlink(true, dirfd, dent->d_name, linkTo, + sizeof(linkTo) - 1); + if (bytesRead >= 0) { + linkTo[bytesRead] = '\0'; + printf(" -> %s", linkTo); + } else { + printf(" -> (%s)", fs_strerror(bytesRead)); + } + } + + printf("\n"); + count++; } @@ -544,7 +644,7 @@ do_ioctl(int argc, char **argv) return FS_EINVAL; } - if ((fd = sys_open(1, -1, "/myfs/.", O_RDONLY, S_IFREG, 0)) < 0) { + if ((fd = sys_open(1, -1, "/myfs/.", MY_O_RDONLY, S_IFREG, 0)) < 0) { printf("ioctl: error opening '.'\n"); return fd; } @@ -682,7 +782,7 @@ do_seek(int argc, char **argv) } pos = strtoul(&argv[1][0], NULL, 0); - err = sys_lseek(1, cur_fd, pos, SEEK_SET); + err = sys_lseek(1, cur_fd, pos, MY_SEEK_SET); if (err != pos) { printf("seek to %Ld failed (%Ld)\n", pos, err); } @@ -691,28 +791,144 @@ do_seek(int argc, char **argv) } +int +remove_dir_contents(int parentDir, const char *name, bool force) +{ + // open the dir + int dir = sys_opendir(true, parentDir, name, true); + if (dir < 0) { + fprintf(stderr, "Failed to open dir `%s': %s\n", name, + fs_strerror(dir)); + return dir; + } + + status_t error = FS_OK; + + // iterate through the entries + ssize_t numRead; + char buffer[sizeof(my_dirent) + B_FILE_NAME_LENGTH]; + my_dirent *entry = (my_dirent*)buffer; + while ((numRead = sys_readdir(true, dir, entry, sizeof(buffer), 1)) > 0) { + // skip "." and ".." + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + error = remove_entry(dir, entry->d_name, true, force); + if (error != FS_OK) + break; + } + + if (numRead < 0) { + fprintf(stderr, "Error while reading directory `%s': %s\n", name, + fs_strerror(numRead)); + return numRead; + } + + // close + sys_closedir(true, dir); + + return error; +} + + +static int +remove_entry(int dir, const char *entry, bool recursive, bool force) +{ + // stat the file + struct my_stat st; + status_t error = sys_rstat(true, dir, entry, &st, false); + if (error != FS_OK) { + if (force && error == FS_ENTRY_NOT_FOUND) + return FS_OK; + + fprintf(stderr, "Failed to remove `%s': %s\n", entry, + fs_strerror(error)); + return error; + } + + if (MY_S_ISDIR(st.mode)) { + if (!recursive) { + fprintf(stderr, "`%s' is a directory.\n", entry); + // TODO: get the full path + return FS_EISDIR; + } + + // remove the contents + error = remove_dir_contents(dir, entry, force); + if (error != FS_OK) + return error; + + // remove the directory + error = sys_rmdir(true, dir, entry); + if (error != FS_OK) { + fprintf(stderr, "Failed to remove directory `%s': %s\n", entry, + fs_strerror(error)); + return error; + } + } else { + // remove the entry + error = sys_unlink(true, dir, entry); + if (error != FS_OK) { + fprintf(stderr, "Failed to remove entry `%s': %s\n", entry, + fs_strerror(error)); + return error; + } + } + + return FS_OK; +} + + static int do_rm(int argc, char **argv) { - int err; - char name[256]; - if (cur_fd >= 0) do_close(0, NULL); - if (argc < 2) { - printf("rm: need a file name to remove\n"); - return FS_EINVAL; - } + bool recursive = false; + bool force = false; - sprintf(name, "/myfs/%s", &argv[1][0]); - - err = sys_unlink(1, -1, name); - if (err != 0) { - printf("error removing: %s: %s\n", name, fs_strerror(err)); - } + // parse parameters + int argi = 1; + for (argi = 1; argi < argc; argi++) { + const char *arg = argv[argi]; + if (arg[0] != '-') + break; - return err; + if (arg[1] == '\0') { + fprintf(stderr, "Invalid option `-'\n"); + return FS_EINVAL; + } + + for (int i = 1; arg[i]; i++) { + switch (arg[i]) { + case 'f': + force = true; + break; + case 'r': + recursive = true; + break; + default: + fprintf(stderr, "Unknown option `-%c'\n", arg[i]); + return FS_EINVAL; + } + } + } + + // check params + if (argi >= argc) { + fprintf(stderr, "Usage: %s [ -r ] ...\n", argv[0]); + return FS_EINVAL; + } + + // remove loop + for (; argi < argc; argi++) { + status_t error = remove_entry(-1, argv[argi], recursive, force); + if (error != FS_OK) + return error; + } + + return FS_OK; } @@ -758,7 +974,7 @@ do_copy_to_myfs(char *host_file, char *bfile) return from_platform_error(errno); } - if ((bfd = sys_open(1, -1, myfs_name, O_RDWR|O_CREAT, + if ((bfd = sys_open(1, -1, myfs_name, MY_O_RDWR|MY_O_CREAT, MY_S_IFREG|MY_S_IRWXU, 0)) < 0) { fclose(fp); printf("error opening: %s\n", myfs_name); @@ -804,7 +1020,7 @@ do_copy_from_myfs(char *bfile, char *host_file) return from_platform_error(errno); } - if ((bfd = sys_open(1, -1, myfs_name, O_RDONLY, MY_S_IFREG, 0)) < 0) { + if ((bfd = sys_open(1, -1, myfs_name, MY_O_RDONLY, MY_S_IFREG, 0)) < 0) { fclose(fp); printf("error opening: %s\n", myfs_name); return bfd; @@ -917,7 +1133,7 @@ copydir(char *fromPath,char *toPath) } sprintf(myfs_name, "%s/%s", toPath, dirent->d_name); - if ((bfd = sys_open(1, -1, myfs_name, O_RDWR|O_CREAT, + if ((bfd = sys_open(1, -1, myfs_name, MY_O_RDWR|MY_O_CREAT, MY_S_IFREG|MY_S_IRWXU, 0)) < 0) { close(fd); printf("error opening: %s\n", myfs_name); @@ -1110,8 +1326,10 @@ create_remove_thread(void *data) while (sRunStatTest) { int fd; - if ((fd = sys_open(1, -1, sStatTestFile, O_RDWR | O_CREAT, S_IFREG | S_IRWXU, 0)) >= 0) + if ((fd = sys_open(1, -1, sStatTestFile, MY_O_RDWR | MY_O_CREAT, + S_IFREG | S_IRWXU, 0)) >= 0) { sys_close(1, fd); + } snooze(10000); sys_unlink(1, -1, sStatTestFile); @@ -1226,27 +1444,101 @@ do_rename(int argc, char **argv) static int -do_symlink(int argc, char **argv) +do_link(int argc, char **argv) { - char target[B_PATH_NAME_LENGTH]; - char *source; - status_t status; + bool force = false; + bool symbolic = false; - if (argc < 3) { - printf("usage: symlink \n"); + // parse parameters + int argi = 1; + for (argi = 1; argi < argc; argi++) { + const char *arg = argv[argi]; + if (arg[0] != '-') + break; + + if (arg[1] == '\0') { + fprintf(stderr, "Invalid option `-'\n"); + return FS_EINVAL; + } + + for (int i = 1; arg[i]; i++) { + switch (arg[i]) { + case 'f': + force = true; + break; + case 's': + symbolic = true; + break; + default: + fprintf(stderr, "Unknown option `-%c'\n", arg[i]); + return FS_EINVAL; + } + } + } + + if (argc - argi != 2) { + printf("usage: %s [Options] \n", argv[0]); return FS_EINVAL; } - source = argv[1]; + const char *source = argv[argi]; + const char *target = argv[argi + 1]; - strcpy(target, "/myfs/"); - strcpy(target + 6, argv[2]); + // check, if the the target is an existing directory + struct my_stat st; + char targetBuffer[B_PATH_NAME_LENGTH]; + status_t error = sys_rstat(true, -1, target, &st, true); + if (error == FS_OK) { + if (MY_S_ISDIR(st.mode)) { + // get source leaf + char leaf[B_FILE_NAME_LENGTH]; + error = get_last_path_component(source, leaf, sizeof(leaf)); + if (error != B_OK) { + fprintf(stderr, "Failed to get leaf name of source path: %s\n", + fs_strerror(error)); + return error; + } - status = sys_symlink(1, source, -1, target); - if (status != FS_OK) - printf("symlink failed with error: %s\n", fs_strerror(status)); + // compose a new path + int len = strlen(target) + 1 + strlen(leaf); + if (len > (int)sizeof(targetBuffer)) { + fprintf(stderr, "Resulting target path is too long.\n"); + return FS_EINVAL; + } + + strcpy(targetBuffer, target); + strcat(targetBuffer, "/"); + strcat(targetBuffer, leaf); + target = targetBuffer; + } + } - return status; + // check, if the target exists + error = sys_rstat(true, -1, target, &st, true); + if (error == FS_OK) { + if (!force) { + fprintf(stderr, "Can't create link. `%s' is in the way.", target); + return FS_FILE_EXISTS; + } + + // unlink the entry + error = sys_unlink(true, -1, target); + if (error != FS_OK) { + fprintf(stderr, "Failed to remove `%s' to make way for link: " + "%s\n", target, fs_strerror(error)); + return error; + } + } + + // finally create the link + if (symbolic) + error = sys_symlink(1, source, -1, target); + else + error = sys_link(1, -1, source, -1, target); + if (error != FS_OK) + printf("Failed to create link: %s\n", fs_strerror(error)); + + return error; } @@ -1434,7 +1726,7 @@ do_cio(int argc, char **argv) else strcat(fname, &argv[1][0]); - fd = sys_open(1, -1, fname, O_RDONLY, MY_S_IFREG, 0); + fd = sys_open(1, -1, fname, MY_O_RDONLY, MY_S_IFREG, 0); if (fd < 0) { printf("can't open %s\n", fname); return fd; @@ -1452,7 +1744,7 @@ do_cio(int argc, char **argv) } pos = 0; - if (sys_lseek(1, fd, pos, SEEK_SET) != pos) { + if (sys_lseek(1, fd, pos, MY_SEEK_SET) != pos) { perror("cio lseek"); break; } @@ -1480,7 +1772,7 @@ mkfile(char *s, int sz) int err = 0; char *buffer; - if ((fd = sys_open(1, -1, s, O_RDWR|O_CREAT, + if ((fd = sys_open(1, -1, s, MY_O_RDWR|MY_O_CREAT, MY_S_IFREG|MY_S_IRWXU, 0)) < 0) { printf("error creating: %s\n", s); return fd; @@ -1593,6 +1885,7 @@ static int do_help(int argc, char **argv); static cmd_entry builtin_commands[] = { + { "cd", do_chdir, "change current directory" }, { "ls", do_dir, "print a directory listing" }, { "dir", do_dir, "print a directory listing (same as ls)" }, { "open", do_open, "open an existing file for read/write access" }, @@ -1614,6 +1907,7 @@ static cmd_entry builtin_commands[] = { "wrattr", do_write_attr, "write attribute \"name\" to the current file (N bytes [256])." }, { "rdattr", do_read_attr, "read attribute \"name\" from the current file (N bytes [256])." }, { "rmattr", do_remove_attr, "remove attribute \"name\" from the current file." }, + { "listattr", do_list_attr, "lists all attributes of the given or current file." }, { "attrs", do_attrtest, "writes random attributes [a-z] up to 1023 bytes, N [10240] iterations." }, { "lat_fs", do_lat_fs, "simulate what the lmbench test lat_fs does" }, { "create", do_create, "create N files. default is 100" }, @@ -1630,7 +1924,9 @@ static cmd_entry builtin_commands[] = { "tracker", do_tracker, "starts a Tracker like background thread that will listen to fs updates" }, { "cio", do_cio, "does a I/O speed test" }, { "stattest", do_stattest, "does an \"early\"/\"late\" stat test for files that are created or deleted" }, - { "symlink", do_symlink, "creates the specified symlink on the device" }, + { "link", do_link, "creates the specified [sym]link on the device" }, + { "ln", do_link, "creates the specified [sym]link on the device" }, + { "xcp", do_xcp, "similar to shell cp, can copy in, out, within, and outside the FS, supports attributes." }, { NULL, NULL } }; @@ -1746,6 +2042,3 @@ do_fsh(void) do_close(0, NULL); } - - - diff --git a/src/tests/add-ons/kernel/file_systems/fs_shell/xcp.cpp b/src/tests/add-ons/kernel/file_systems/fs_shell/xcp.cpp new file mode 100644 index 0000000000..3f78f76435 --- /dev/null +++ b/src/tests/add-ons/kernel/file_systems/fs_shell/xcp.cpp @@ -0,0 +1,1245 @@ +/* + * Copyright 2005, Ingo Weinhold, bonefish@users.sf.net. + * Distributed under the terms of the MIT License. + */ + +#include "compat.h" + +#include +#include +#include + +#include +#include +#include + +#include "kprotos.h" +#include "path_util.h" +#include "stat_util.h" +#include "xcp.h" + +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 my_stat &Stat() const { return fStat; } + bool IsFile() const { return MY_S_ISREG(fStat.mode); } + bool IsDirectory() const { return MY_S_ISDIR(fStat.mode); } + bool IsSymLink() const { return MY_S_ISLNK(fStat.mode); } + + virtual File *ToFile() { return NULL; } + virtual Directory *ToDirectory() { return NULL; } + virtual SymLink *ToSymLink() { return NULL; } + + virtual ssize_t GetNextAttr(char *name, int size) = 0; + virtual status_t GetAttrInfo(const char *name, my_attr_info &info) = 0; + virtual ssize_t ReadAttr(const char *name, uint32 type, fs_off_t pos, + void *buffer, int size) = 0; + virtual ssize_t WriteAttr(const char *name, uint32 type, fs_off_t pos, + const void *buffer, int size) = 0; + +protected: + struct my_stat fStat; // To be initialized by implementing classes. +}; + +// Directory +class Directory : public virtual Node { +public: + virtual Directory *ToDirectory() { return this; } + + virtual ssize_t GetNextEntry(struct my_dirent *entry, int size) = 0; +}; + +// File +class File : public virtual Node { +public: + virtual File *ToFile() { return this; } + + virtual ssize_t Read(void *buffer, int size) = 0; + virtual ssize_t Write(const void *buffer, int size) = 0; +}; + +// SymLink +class SymLink : public virtual Node { +public: + virtual SymLink *ToSymLink() { return this; } + + virtual ssize_t ReadLink(char *buffer, int bufferSize) = 0; +}; + +// FSDomain +class FSDomain { +public: + virtual status_t Open(const char *path, bool followLink, Node *&node) = 0; + + virtual status_t CreateFile(const char *path, const struct my_stat &st, + File *&file) = 0; + virtual status_t CreateDirectory(const char *path, const struct my_stat &st, + Directory *&dir) = 0; + virtual status_t CreateSymLink(const char *path, const char *linkTo, + const struct my_stat &st, SymLink *&link) = 0; + + virtual 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 status_t Init(const char *path, int fd, const my_stat &st) + { + fFD = fd; + fStat = st; + + // open the attribute directory + fAttrDir = fs_fopen_attr_dir(fd); + if (!fAttrDir) + return from_platform_error(errno); + + return FS_OK; + } + + virtual ssize_t GetNextAttr(char *name, int size) + { + if (!fAttrDir) + return 0; + + errno = 0; + struct dirent *entry = fs_read_attr_dir(fAttrDir); + if (!entry) + return from_platform_error(errno); + + int len = strlen(entry->d_name); + if (len >= size) + return FS_NAME_TOO_LONG; + + strcpy(name, entry->d_name); + return 1; + } + + virtual status_t GetAttrInfo(const char *name, my_attr_info &info) + { + attr_info hostInfo; + if (fs_stat_attr(fFD, name, &hostInfo) < 0) + return from_platform_error(errno); + + info.type = hostInfo.type; + info.size = hostInfo.size; + return FS_OK; + } + + virtual ssize_t ReadAttr(const char *name, uint32 type, fs_off_t pos, + void *buffer, int size) + { + ssize_t bytesRead = fs_read_attr(fFD, name, type, pos, buffer, size); + return (bytesRead >= 0 ? bytesRead : from_platform_error(errno)); + } + + virtual ssize_t WriteAttr(const char *name, uint32 type, fs_off_t pos, + const void *buffer, int size) + { + ssize_t bytesWritten = fs_write_attr(fFD, name, type, pos, buffer, + size); + return (bytesWritten >= 0 ? bytesWritten : from_platform_error(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 status_t Init(const char *path, int fd, const my_stat &st) + { + status_t error = HostNode::Init(path, fd, st); + if (error != FS_OK) + return error; + + fDir = opendir(path); + if (!fDir) + return from_platform_error(errno); + + return FS_OK; + } + + virtual ssize_t GetNextEntry(struct my_dirent *entry, int size) + { + errno = 0; + struct dirent *hostEntry = readdir(fDir); + if (!hostEntry) + return from_platform_error(errno); + + int nameLen = strlen(hostEntry->d_name); + int recLen = entry->d_name + nameLen + 1 - (char*)entry; + if (recLen > size) + return FS_NAME_TOO_LONG; + + entry->d_dev = hostEntry->d_dev; + 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 ssize_t Read(void *buffer, int size) + { + ssize_t bytesRead = read(fFD, buffer, size); + return (bytesRead >= 0 ? bytesRead : from_platform_error(errno)); + } + + virtual ssize_t Write(const void *buffer, int size) + { + ssize_t bytesWritten = write(fFD, buffer, size); + return (bytesWritten >= 0 ? bytesWritten : from_platform_error(errno)); + } +}; + +// HostSymLink +class HostSymLink : public SymLink, public HostNode { +public: + HostSymLink() + : SymLink(), + HostNode() + { + } + + virtual ~HostSymLink() + { + if (fPath) + free(fPath); + } + + virtual status_t Init(const char *path, int fd, const my_stat &st) + { + status_t error = HostNode::Init(path, fd, st); + if (error != FS_OK) + return error; + + fPath = strdup(path); + if (!fPath) + return FS_NO_MEMORY; + + return FS_OK; + } + + virtual ssize_t ReadLink(char *buffer, int bufferSize) + { + ssize_t bytesRead = readlink(fPath, buffer, bufferSize); + return (bytesRead >= 0 ? bytesRead : from_platform_error(errno)); + } + +private: + char *fPath; +}; + +// HostFSDomain +class HostFSDomain : public FSDomain { +public: + HostFSDomain() {} + virtual ~HostFSDomain() {} + + virtual status_t Open(const char *path, bool followLink, Node *&_node) + { + // open the node + int fd = open(path, O_RDONLY + | (followLink ? 0 : O_NOTRAVERSE)); + if (fd < 0) + return from_platform_error(errno); + + // stat the node + struct stat st; + if (fstat(fd, &st) < 0) { + close(fd); + return from_platform_error(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 FS_EINVAL; + } + + // convert the stat + struct my_stat myst; + from_platform_stat(&st, &myst); + + // init the node + status_t error = node->Init(path, fd, myst); + // the node receives ownership of the FD + if (error != FS_OK) { + delete node; + return error; + } + + _node = node; + return FS_OK; + } + + virtual status_t CreateFile(const char *path, const struct my_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 from_platform_error(errno); + + // apply the other stat fields + status_t error = _ApplyStat(fd, st); + if (error != FS_OK) { + close(fd); + return error; + } + + // create the object + HostFile *file = new HostFile; + error = file->Init(path, fd, myst); + if (error != FS_OK) { + delete file; + return error; + } + + _file = file; + return FS_OK; + } + + virtual status_t CreateDirectory(const char *path, + const struct my_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 from_platform_error(errno); + + // open the dir node + int fd = open(path, O_RDONLY | O_NOTRAVERSE); + if (fd < 0) + return from_platform_error(errno); + + // apply the other stat fields + status_t error = _ApplyStat(fd, st); + if (error != FS_OK) { + close(fd); + return error; + } + + // create the object + HostDirectory *dir = new HostDirectory; + error = dir->Init(path, fd, myst); + if (error != FS_OK) { + delete dir; + return error; + } + + _dir = dir; + return FS_OK; + } + + virtual status_t CreateSymLink(const char *path, const char *linkTo, + const struct my_stat &myst, SymLink *&_link) + { + struct stat st; + to_platform_stat(&myst, &st); + + // create the link + if (symlink(linkTo, path) < 0) + return from_platform_error(errno); + + // open the symlink node + int fd = open(path, O_RDONLY | O_NOTRAVERSE); + if (fd < 0) + return from_platform_error(errno); + + // apply the other stat fields + status_t error = _ApplyStat(fd, st); + if (error != FS_OK) { + close(fd); + return error; + } + + // create the object + HostSymLink *link = new HostSymLink; + error = link->Init(path, fd, myst); + if (error != FS_OK) { + delete link; + return error; + } + + _link = link; + return FS_OK; + } + + + virtual status_t Unlink(const char *path) + { + if (unlink(path) < 0) + return from_platform_error(errno); + return FS_OK; + } + +private: + status_t _ApplyStat(int fd, const struct stat &st) + { + // TODO: Set times... + return FS_OK; + } +}; + + +// #pragma mark - + +// GuestNode +class GuestNode : public virtual Node { +public: + GuestNode() + : Node(), + fFD(-1), + fAttrDir(-1) + { + } + + virtual ~GuestNode() + { + if (fFD >= 0) + sys_close(true, fFD); + if (fAttrDir) + sys_closedir(true, fAttrDir); + } + + virtual status_t Init(const char *path, int fd, const my_stat &st) + { + fFD = fd; + fStat = st; + + // open the attribute directory + fAttrDir = sys_open_attr_dir(true, fd, NULL); + if (fAttrDir < 0) + return fAttrDir; + + return FS_OK; + } + + virtual status_t GetNextAttr(char *name, int size) + { + if (fAttrDir < 0) + return 0; + + char buffer[sizeof(my_dirent) + B_ATTR_NAME_LENGTH]; + struct my_dirent *entry = (my_dirent *)buffer; + int numRead = sys_readdir(true, 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 FS_NAME_TOO_LONG; + + strcpy(name, entry->d_name); + return 1; + } + + virtual status_t GetAttrInfo(const char *name, my_attr_info &info) + { + return sys_stat_attr(true, fFD, NULL, name, &info); + } + + virtual ssize_t ReadAttr(const char *name, uint32 type, fs_off_t pos, + void *buffer, int size) + { + return sys_read_attr(true, fFD, name, type, buffer, size, pos); + } + + virtual ssize_t WriteAttr(const char *name, uint32 type, fs_off_t pos, + const void *buffer, int size) + { + return sys_write_attr(true, fFD, name, type, buffer, size, pos); + } + +protected: + int fFD; + int fAttrDir; +}; + +// GuestDirectory +class GuestDirectory : public Directory, public GuestNode { +public: + GuestDirectory() + : Directory(), + GuestNode(), + fDir(-1) + { + } + + virtual ~GuestDirectory() + { + if (fDir) + sys_closedir(true, fDir); + } + + virtual status_t Init(const char *path, int fd, const my_stat &st) + { + status_t error = GuestNode::Init(path, fd, st); + if (error != FS_OK) + return error; + + fDir = sys_opendir(true, fd, NULL, true); + if (fDir < 0) + return fDir; + + return FS_OK; + } + + virtual status_t GetNextEntry(struct my_dirent *entry, int size) + { + return sys_readdir(true, fDir, entry, size, 1); + } + +private: + int fDir; +}; + +// GuestFile +class GuestFile : public File, public GuestNode { +public: + GuestFile() + : File(), + GuestNode() + { + } + + virtual ~GuestFile() + { + } + + virtual ssize_t Read(void *buffer, int size) + { + return sys_read(true, fFD, buffer, size); + } + + virtual ssize_t Write(const void *buffer, int size) + { + return sys_write(true, fFD, buffer, size); + } +}; + +// GuestSymLink +class GuestSymLink : public SymLink, public GuestNode { +public: + GuestSymLink() + : SymLink(), + GuestNode() + { + } + + virtual ~GuestSymLink() + { + } + + virtual ssize_t ReadLink(char *buffer, int bufferSize) + { + return sys_readlink(true, fFD, NULL, buffer, bufferSize); + } +}; + +// GuestFSDomain +class GuestFSDomain : public FSDomain { +public: + GuestFSDomain() {} + virtual ~GuestFSDomain() {} + + virtual status_t Open(const char *path, bool followLink, Node *&_node) + { + // open the node + int fd = sys_open(true, -1, path, + MY_O_RDONLY | (followLink ? 0 : MY_O_NOTRAVERSE), + 0, true); + if (fd < 0) + return fd; + + // stat the node + struct my_stat st; + status_t error = sys_rstat(true, fd, NULL, &st, false); + if (error < 0) { + sys_close(true, fd); + return error; + } + + // check the node type and create the node + GuestNode *node = NULL; + switch (st.mode & MY_S_IFMT) { + case MY_S_IFLNK: + node = new GuestSymLink; + break; + case MY_S_IFREG: + node = new GuestFile; + break; + case MY_S_IFDIR: + node = new GuestDirectory; + break; + default: + sys_close(true, fd); + return FS_EINVAL; + } + + // init the node + error = node->Init(path, fd, st); + // the node receives ownership of the FD + if (error != FS_OK) { + delete node; + return error; + } + + _node = node; + return FS_OK; + } + + virtual status_t CreateFile(const char *path, const struct my_stat &st, + File *&_file) + { + // create the file + int fd = sys_open(true, -1, path, MY_O_RDWR | MY_O_EXCL | MY_O_CREAT, + st.mode & MY_S_IUMSK, true); + if (fd < 0) + return fd; + + // apply the other stat fields + status_t error = _ApplyStat(fd, st); + if (error != FS_OK) { + sys_close(true, fd); + return error; + } + + // create the object + GuestFile *file = new GuestFile; + error = file->Init(path, fd, st); + if (error != FS_OK) { + delete file; + return error; + } + + _file = file; + return FS_OK; + } + + virtual status_t CreateDirectory(const char *path, const struct my_stat &st, + Directory *&_dir) + { + // create the dir + status_t error = sys_mkdir(true, -1, path, st.mode & MY_S_IUMSK); + if (error < 0) + return error; + + // open the dir node + int fd = sys_open(true, -1, path, MY_O_RDONLY | MY_O_NOTRAVERSE, + 0, true); + if (fd < 0) + return fd; + + // apply the other stat fields + error = _ApplyStat(fd, st); + if (error != FS_OK) { + sys_close(true, fd); + return error; + } + + // create the object + GuestDirectory *dir = new GuestDirectory; + error = dir->Init(path, fd, st); + if (error != FS_OK) { + delete dir; + return error; + } + + _dir = dir; + return FS_OK; + } + + virtual status_t CreateSymLink(const char *path, const char *linkTo, + const struct my_stat &st, SymLink *&_link) + { + // create the link + status_t error = sys_symlink(true, linkTo, -1, path); + if (error < 0) + return error; + + // open the symlink node + int fd = sys_open(true, -1, path, MY_O_RDONLY | MY_O_NOTRAVERSE, + 0, true); + if (fd < 0) + return fd; + + // apply the other stat fields + error = _ApplyStat(fd, st); + if (error != FS_OK) { + sys_close(true, fd); + return error; + } + + // create the object + GuestSymLink *link = new GuestSymLink; + error = link->Init(path, fd, st); + if (error != FS_OK) { + delete link; + return error; + } + + _link = link; + return FS_OK; + } + + virtual status_t Unlink(const char *path) + { + return sys_unlink(true, -1, path); + } + +private: + status_t _ApplyStat(int fd, const struct my_stat &st) + { + // TODO: Set times... + return FS_OK; + } +}; + + +// #pragma mark - + +static 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 status_t +copy_file_contents(const char *source, File *sourceFile, const char *target, + File *targetFile) +{ + ssize_t bytesRead; + while ((bytesRead = sourceFile->Read(sCopyBuffer, sCopyBufferSize)) > 0) { + ssize_t bytesWritten = targetFile->Write(sCopyBuffer, bytesRead); + if (bytesWritten < 0) { + fprintf(stderr, "Error while writing to file `%s': %s\n", + target, fs_strerror(bytesWritten)); + return bytesWritten; + } + } + + if (bytesRead < 0) { + fprintf(stderr, "Error while reading from file `%s': %s\n", + source, fs_strerror(bytesRead)); + return bytesRead; + } + + return FS_OK; +} + + +static status_t +copy_dir_contents(FSDomain *sourceDomain, const char *source, + Directory *sourceDir, FSDomain *targetDomain, const char *target, + const Options &options) +{ + char buffer[sizeof(my_dirent) + B_FILE_NAME_LENGTH]; + struct my_dirent *entry = (struct my_dirent *)buffer; + 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, "Failed to allocate source path!\n"); + return FS_ENOMEM; + } + PathDeleter sourceDeleter(sourceEntry); + + // compose a new target path name + char *targetEntry = make_path(target, entry->d_name); + if (!targetEntry) { + fprintf(stderr, "Failed to allocate target path!\n"); + return FS_ENOMEM; + } + PathDeleter targetDeleter(targetEntry); + + status_t error = copy_entry(sourceDomain, sourceEntry, targetDomain, + targetEntry, options); + if (error != FS_OK) + return error; + } + + if (numRead < 0) { + fprintf(stderr, "Error reading directory `%s': %s\n", source, + fs_strerror(numRead)); + return numRead; + } + + return FS_OK; +} + + +static status_t +copy_attribute(const char *source, Node *sourceNode, const char *target, + Node *targetNode, const char *name, const my_attr_info &info) +{ + // special case: empty attribute + if (info.size <= 0) { + 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, fs_strerror(bytesWritten)); + return bytesWritten; + } + + return FS_OK; + } + + // non-empty attribute + fs_off_t pos = 0; + int toCopy = info.size; + while (toCopy > 0) { + // read data from source + int toRead = (toCopy < sCopyBufferSize ? toCopy : sCopyBufferSize); + 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, fs_strerror(bytesRead)); + return bytesRead; + } + + // write data to target + 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, fs_strerror(bytesWritten)); + return bytesWritten; + } + + pos += bytesRead; + toCopy -= bytesRead; + } + + return FS_OK; +} + + +static status_t +copy_attributes(const char *source, Node *sourceNode, const char *target, + Node *targetNode) +{ + char name[B_ATTR_NAME_LENGTH]; + ssize_t numRead; + while ((numRead = sourceNode->GetNextAttr(name, sizeof(name))) > 0) { + my_attr_info info; + // get attribute info + status_t error = sourceNode->GetAttrInfo(name, info); + if (error != FS_OK) { + fprintf(stderr, "Error getting info for attribute `%s' of file " + "`%s': %s\n", name, source, fs_strerror(error)); + return error; + } + + // copy the attribute + error = copy_attribute(source, sourceNode, target, targetNode, name, + info); + if (error != FS_OK) + return error; + } + + if (numRead < 0) { + fprintf(stderr, "Error reading attribute directory of `%s': %s\n", + source, fs_strerror(numRead)); + return numRead; + } + + return FS_OK; +} + + +static status_t +copy_entry(FSDomain *sourceDomain, const char *source, + FSDomain *targetDomain, const char *target, const Options &options) +{ + // open the source node + Node *sourceNode; + status_t error = sourceDomain->Open(source, options.dereference, + sourceNode); + if (error != FS_OK) { + fprintf(stderr, "Failed to open source path `%s': %s\n", source, + fs_strerror(error)); + return error; + } + NodeDeleter sourceDeleter(sourceNode); + + // check, if target exists + Node *targetNode = NULL; + error = targetDomain->Open(target, false, targetNode); + NodeDeleter targetDeleter; + if (error == FS_OK) { + // 1. target exists: + // check, if it is a dir and, if so, whether source is a dir too + 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 /force/, remove the target and continue with 2., + // otherwise fail + if (!options.force) { + fprintf(stderr, "File `%s' does exist.\n", target); + return FS_FILE_EXISTS; + } + + targetDeleter.SetTo(NULL); + targetNode = NULL; + error = targetDomain->Unlink(target); + if (error != FS_OK) { + fprintf(stderr, "Failed to remove `%s'\n", target); + return error; + } + } + } // else: 2. target doesn't exist: -> just create it + + // create the target node + error = FS_OK; + if (sourceNode->IsFile()) { + 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, "Entry `%s' is a directory.\n", source); + return FS_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[B_PATH_NAME_LENGTH]; + ssize_t bytesRead = sourceLink->ReadLink(linkTo, sizeof(linkTo) - 1); + if (bytesRead < 0) { + fprintf(stderr, "Failed to read symlink `%s': %s\n", source, + fs_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, "Unknown node type. We shouldn't be here!\n"); + return FS_EINVAL; + } + + if (error != FS_OK) { + fprintf(stderr, "Failed to create `%s': %s\n", target, + fs_strerror(error)); + return error; + } + targetDeleter.Detach(); + targetDeleter.SetTo(targetNode); + + // copy attributes + error = copy_attributes(source, sourceNode, target, targetNode); + if (error != FS_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; +} + + +int +do_xcp(int argc, char **argv) +{ + enum { MAX_SOURCE_FILES = 128 }; + const char *sources[MAX_SOURCE_FILES]; + int sourceCount = 0; + Options options; + + if (argc > MAX_SOURCE_FILES) { + fprintf(stderr, "Too many args!\n"); + return FS_EINVAL; + } + + // parse parameters + for (int argi = 1; argi < argc; argi++) { + const char *arg = argv[argi]; + if (arg[0] == '-') { + if (arg[1] == '\0') { + fprintf(stderr, "Invalid option `-'\n"); + return FS_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, "Unknown option `-%c'\n", arg[i]); + return FS_EINVAL; + } + } + } else { + sources[sourceCount++] = arg; + } + } + + // check params + if (sourceCount < 2) { + fprintf(stderr, "Must specify at least 2 files!\n"); + return FS_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; + status_t error = targetDomain->Open(target, true, targetNode); + if (error == 0) { + NodeDeleter targetDeleter(targetNode); + targetExists = true; + + if (targetNode->IsDirectory()) { + targetIsDir = true; + } else { + if (sourceCount > 1) { + fprintf(stderr, "Destination `%s' is not a directory!", + target); + return FS_NOT_A_DIRECTORY; + } + } + } else { + if (sourceCount > 1) { + fprintf(stderr, "Failed to open destination directory `%s': `%s'\n", + target, fs_strerror(error)); + return error; + } + } + + // allocate a copy buffer + sCopyBuffer = malloc(sCopyBufferSize); + if (!sCopyBuffer) { + fprintf(stderr, "Failed to allocate copy buffer.\n"); + return FS_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[B_FILE_NAME_LENGTH]; + error = get_last_path_component(source, leafName, sizeof(leafName)); + if (error != FS_OK) { + fprintf(stderr, "Failed to get last path component of `%s': " + "%s\n", source, fs_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, options.dereference, + sourceNode); + if (error != FS_OK) { + fprintf(stderr, "Failed to open `%s': %s.\n", source, + fs_strerror(error)); + return error; + } + NodeDeleter sourceDeleter(sourceNode); + + // check, if it is a dir + Directory *sourceDir = sourceNode->ToDirectory(); + if (!sourceDir) { + fprintf(stderr, "Source `%s' is not a directory although" + "it's last path component is `%s'\n", source, leafName); + return FS_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, "Failed to allocate target path!\n"); + return FS_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 0; +} + diff --git a/src/tests/add-ons/kernel/file_systems/fs_shell/xcp.h b/src/tests/add-ons/kernel/file_systems/fs_shell/xcp.h new file mode 100644 index 0000000000..c189d79bb7 --- /dev/null +++ b/src/tests/add-ons/kernel/file_systems/fs_shell/xcp.h @@ -0,0 +1,19 @@ +/* + * Copyright 2005, Ingo Weinhold, bonefish@users.sf.net. + * Distributed under the terms of the MIT License. + */ +#ifndef FS_SHELL_XCP_H +#define FS_SHELL_XCP_H + +#ifdef __cplusplus +extern "C" { +#endif + +int do_xcp(int argc, char **argv); + +#ifdef __cplusplus +} +#endif + + +#endif FS_SHELL_XCP_H