diff --git a/kernel/fs/vfs.c b/kernel/fs/vfs.c index cc7c01c2..bdb7fd4d 100644 --- a/kernel/fs/vfs.c +++ b/kernel/fs/vfs.c @@ -16,6 +16,9 @@ #include #include +#define MAX_SYMLINK_DEPTH 8 +#define MAX_SYMLINK_SIZE 4096 + tree_t * fs_tree = NULL; /* File system mountpoint tree */ fs_node_t * fs_root = NULL; /* Pointer to the root mount fs_node (must be some form of filesystem, even ramdisk) */ @@ -365,6 +368,54 @@ fs_node_t *clone_fs(fs_node_t *source) { return source; } +int symlink_fs(char * target, char * name) { + fs_node_t * parent; + char *cwd = (char *)(current_process->wd_name); + char *path = canonicalize_path(cwd, name); + + char * parent_path = malloc(strlen(path) + 4); + sprintf(parent_path, "%s/..", path); + + char * f_path = path + strlen(path) - 1; + while (f_path > path) { + if (*f_path == '/') { + f_path += 1; + break; + } + f_path--; + } + + debug_print(WARNING, "creating symlink %s within %s", f_path, parent_path); + + parent = kopen(parent_path, 0); + free(parent_path); + + if (!parent) { + free(path); + return -1; + } + + if (parent->symlink) { + parent->symlink(parent, target, f_path); + } + + free(path); + close_fs(parent); + + return 0; +} + +int readlink_fs(fs_node_t *node, char * buf, uint32_t size) { + if (!node) return -1; + + if (node->readlink) { + return node->readlink(node, buf, size); + } else { + return -1; + } +} + + /** * canonicalize_path: Canonicalize a path. * @@ -721,31 +772,14 @@ fs_node_t *get_mount_point(char * path, unsigned int path_depth, char **outpath, - - -/** - * kopen: Open a file by name. - * - * Explore the file system tree to find the appropriate node for - * for a given path. The path can be relative to the working directory - * and will be canonicalized by the kernel. - * - * @param filename Filename to open - * @param flags Flag bits for read/write mode. - * @returns A file system node element that the caller can free. - */ -fs_node_t *kopen(char *filename, uint32_t flags) { +fs_node_t *kopen_recur(char *filename, uint32_t flags, uint32_t symlink_depth, char *relative_to) { /* Simple sanity checks that we actually have a file system */ if (!filename) { return NULL; } - debug_print(INFO, "kopen(%s)", filename); - - /* Reference the current working directory */ - char *cwd = (char *)(current_process->wd_name); /* Canonicalize the (potentially relative) path... */ - char *path = canonicalize_path(cwd, filename); + char *path = canonicalize_path(relative_to, filename); /* And store the length once to save recalculations */ size_t path_len = strlen(path); @@ -791,6 +825,8 @@ fs_node_t *kopen(char *filename, uint32_t flags) { unsigned int depth = 0; /* Find the mountpoint for this file */ fs_node_t *node_ptr = get_mount_point(path, path_depth, &path_offset, &depth); + debug_print(CRITICAL, "path_offset: %s", path_offset); + debug_print(CRITICAL, "depth: %d", depth); if (!node_ptr) return NULL; @@ -804,13 +840,80 @@ fs_node_t *kopen(char *filename, uint32_t flags) { /* Search the active directory for the requested directory */ debug_print(INFO, "... Searching for %s", path_offset); node_next = finddir_fs(node_ptr, path_offset); - free(node_ptr); + close_fs(node_ptr); node_ptr = node_next; if (!node_ptr) { /* We failed to find the requested directory */ free((void *)path); return NULL; - } else if (depth == path_depth - 1) { + } + /* + * This test is a little complicated, but we basically always resolve symlinks in the + * of a path (like /home/symlink/file) even if O_NOFOLLOW and O_PATH are set. If we are + * on the leaf of the path then we will look at those flags and act accordingly + */ + if ((node_ptr->flags & FS_SYMLINK) && + !((flags & O_NOFOLLOW) && (flags & O_PATH) && depth == path_depth - 1)) { + /* This ensures we don't return a path when NOFOLLOW is requested but PATH + * isn't passed. + */ + debug_print(CRITICAL, "resolving symlink at %s", node_ptr->name); + if ((flags & O_NOFOLLOW) && depth == path_depth - 1) { + /* TODO(gerow): should probably be setting errno from this */ + debug_print(NOTICE, "Refusing to follow final entry for open with O_NOFOLLOW for %s.", node_ptr->name); + free((void *)path); + close_fs(node_ptr); + return NULL; + } + if (symlink_depth >= MAX_SYMLINK_DEPTH) { + /* TODO(gerow): should probably be setting errno from this */ + debug_print(WARNING, "Reached max symlink depth on %s.", node_ptr->name); + free((void *)path); + close_fs(node_ptr); + return NULL; + } + /* + * This may actually be big enough that we wouldn't want to allocate it on + * the stack, especially considering this function is called recursively + */ + char symlink_buf[MAX_SYMLINK_SIZE]; + int len = node_ptr->readlink(node_ptr, symlink_buf, sizeof(symlink_buf)); + if (len < 0) { + /* TODO(gerow): should probably be setting errno from this */ + debug_print(WARNING, "Got error %d from symlink for %s.", len, node_ptr->name); + free((void *)path); + close_fs(node_ptr); + return NULL; + } + if (symlink_buf[len - 1] != '\0') { + /* TODO(gerow): should probably be setting errno from this */ + debug_print(WARNING, "readlink for %s doesn't end in a null pointer. That's weird...", len, node_ptr->name); + free((void *)path); + close_fs(node_ptr); + return NULL; + } + fs_node_t * old_node_ptr = node_ptr; + /* Rebuild our path up to this point. This is hella hacky. */ + char * relpath = malloc(path_len + 1); + char * ptr = relpath; + memcpy(relpath, path, path_len + 1); + for (unsigned int i = 0; i < depth; i++) { + while(*ptr != '\0') { + ptr++; + } + *ptr = PATH_SEPARATOR; + } + node_ptr = kopen_recur(symlink_buf, 0, symlink_depth + 1, relpath); + free(relpath); + close_fs(old_node_ptr); + if (!node_ptr) { + /* Dangling symlink? */ + debug_print(WARNING, "Failed to open symlink path %s. Perhaps it's a dangling symlink?", symlink_buf); + free((void *)path); + return NULL; + } + } + if (depth == path_depth - 1) { /* We found the file and are done, open the node */ open_fs(node_ptr, flags); free((void *)path); @@ -825,3 +928,20 @@ fs_node_t *kopen(char *filename, uint32_t flags) { return NULL; } +/** + * kopen: Open a file by name. + * + * Explore the file system tree to find the appropriate node for + * for a given path. The path can be relative to the working directory + * and will be canonicalized by the kernel. + * + * @param filename Filename to open + * @param flags Flag bits for read/write mode. + * @returns A file system node element that the caller can free. + */ +fs_node_t *kopen(char *filename, uint32_t flags) { + debug_print(NOTICE, "kopen(%s)", filename); + + return kopen_recur(filename, flags, 0, (char *)(current_process->wd_name)); +} + diff --git a/kernel/include/fs.h b/kernel/include/fs.h index 5f10c825..c9200e9d 100644 --- a/kernel/include/fs.h +++ b/kernel/include/fs.h @@ -15,6 +15,8 @@ #define O_CREAT 0x0200 #define O_TRUNC 0x0400 #define O_EXCL 0x0800 +#define O_NOFOLLOW 0x1000 +#define O_PATH 0x2000 #define FS_FILE 0x01 #define FS_DIRECTORY 0x02 @@ -47,6 +49,8 @@ typedef void (*mkdir_type_t) (struct fs_node *, char *name, uint16_t permission) typedef int (*ioctl_type_t) (struct fs_node *, int request, void * argp); typedef int (*get_size_type_t) (struct fs_node *); typedef int (*chmod_type_t) (struct fs_node *, int mode); +typedef void (*symlink_type_t) (struct fs_node *, char * name, char * value); +typedef int (*readlink_type_t) (struct fs_node *, char * buf, size_t size); typedef struct fs_node { char name[256]; /* The filename. */ @@ -78,6 +82,8 @@ typedef struct fs_node { get_size_type_t get_size; chmod_type_t chmod; unlink_type_t unlink; + symlink_type_t symlink; + readlink_type_t readlink; struct fs_node *ptr; /* Alias pointer, for symlinks. */ uint32_t offset; /* Offset for read operations XXX move this to new "file descriptor" entry */ @@ -129,6 +135,8 @@ fs_node_t *clone_fs(fs_node_t * source); int ioctl_fs(fs_node_t *node, int request, void * argp); int chmod_fs(fs_node_t *node, int mode); int unlink_fs(char * name); +int symlink_fs(char * value, char * name); +int readlink_fs(fs_node_t * node, char * buf, size_t size); void vfs_install(void); void * vfs_mount(char * path, fs_node_t * local_root); diff --git a/kernel/sys/syscall.c b/kernel/sys/syscall.c index 1d77d2a4..ddf7c770 100644 --- a/kernel/sys/syscall.c +++ b/kernel/sys/syscall.c @@ -663,6 +663,38 @@ static int sys_mount(char * arg, char * mountpoint, char * type, unsigned long f return -EFAULT; } +static int sys_symlink(char * target, char * name) { + PTR_VALIDATE(target); + PTR_VALIDATE(name); + return symlink_fs(target, name); +} + +static int sys_readlink(const char * file, char * ptr, int len) { + PTR_VALIDATE(file); + fs_node_t * node = kopen((char *) file, O_PATH | O_NOFOLLOW); + if (!node) { + return -ENOENT; + } + int rv = readlink_fs(node, ptr, len); + close_fs(node); + return rv; +} + +static int sys_lstat(char * file, uintptr_t st) { + int result; + PTR_VALIDATE(file); + PTR_VALIDATE(st); + fs_node_t * fn = kopen(file, O_PATH | O_NOFOLLOW); + result = stat_node(fn, st); + if (fn) { + close_fs(fn); + } + return result; +} + +/* + * System Call Internals + */ static int (*syscalls[])() = { /* System Call Table */ [SYS_EXT] = sys_exit, @@ -709,6 +741,9 @@ static int (*syscalls[])() = { [SYS_WAITPID] = sys_waitpid, [SYS_PIPE] = sys_pipe, [SYS_MOUNT] = sys_mount, + [SYS_SYMLINK] = sys_symlink, + [SYS_READLINK] = sys_readlink, + [SYS_LSTAT] = sys_lstat, }; uint32_t num_syscalls = sizeof(syscalls) / sizeof(*syscalls); diff --git a/toolchain/patches/newlib/include/syscall_nums.h b/toolchain/patches/newlib/include/syscall_nums.h index a04fde36..37b82960 100644 --- a/toolchain/patches/newlib/include/syscall_nums.h +++ b/toolchain/patches/newlib/include/syscall_nums.h @@ -42,3 +42,6 @@ #define SYS_WAITPID 53 #define SYS_PIPE 54 #define SYS_MOUNT 55 +#define SYS_SYMLINK 56 +#define SYS_READLINK 57 +#define SYS_LSTAT 58 diff --git a/toolchain/patches/newlib/toaru/syscalls.c b/toolchain/patches/newlib/toaru/syscalls.c index 0508a4fd..e695866c 100644 --- a/toolchain/patches/newlib/toaru/syscalls.c +++ b/toolchain/patches/newlib/toaru/syscalls.c @@ -89,6 +89,9 @@ DEFN_SYSCALL1(unlink, 52, char *); DEFN_SYSCALL3(waitpid, 53, int, int *, int); DEFN_SYSCALL1(pipe, 54, int *); DEFN_SYSCALL5(mount, SYS_MOUNT, char *, char *, char *, unsigned long, void *); +DEFN_SYSCALL2(symlink, SYS_SYMLINK, char *, char *); +DEFN_SYSCALL3(readlink, SYS_READLINK, char *, char *, int); +DEFN_SYSCALL2(lstat, SYS_LSTAT, char *, void *); static int toaru_debug_stubs_enabled(void) { static int checked = 0; @@ -319,8 +322,15 @@ char *getwd(char *buf) { return getcwd(buf, 256); } -int lstat(const char *path, struct stat *buf) { - return stat(path, buf); +int lstat(const char *path, struct stat *st) { + int ret = syscall_lstat((char *)path, (void *)st); + if (ret >= 0) { + return ret; + } else { + errno = -ret; + memset(st, 0x00, sizeof(struct stat)); + return ret; + } } int mkdir(const char *pathname, mode_t mode) { @@ -688,3 +698,24 @@ int mount(char * source, char * target, char * type, unsigned long flags, void * return r; } +int symlink(char * target, char * name) { + int r = syscall_symlink(target, name); + + if (r < 0) { + errno = -r; + return -1; + } + + return r; +} + +int readlink(char * name, char * buf, size_t len) { + int r = syscall_readlink(name, buf, len); + + if (r < 0) { + errno = -r; + return -1; + } + + return r; +}