From f9efbebf31291544e01dfd05e0eed74581855292 Mon Sep 17 00:00:00 2001 From: Yuta Imazu Date: Sun, 23 Jun 2024 22:59:04 +0900 Subject: [PATCH] fs+syscall+userland: implement symlink --- kernel/api/fcntl.h | 1 + kernel/api/sys/limits.h | 3 ++ kernel/api/sys/syscall.h | 3 ++ kernel/fs/fs.c | 1 + kernel/fs/fs.h | 3 -- kernel/fs/vfs.c | 97 ++++++++++++++++++++++++++++++++++++---- kernel/process.c | 1 + kernel/syscall/fs.c | 86 ++++++++++++++++++++++++++++++++++- kernel/syscall/syscall.c | 1 + kernel/syscall/syscall.h | 3 ++ userland/Makefile | 1 + userland/lib/sys/stat.h | 1 + userland/lib/syscall.c | 16 +++++++ userland/lib/unistd.h | 2 + userland/ln.c | 33 ++++++++++++-- userland/ls.c | 20 +++++++-- userland/readlink.c | 22 +++++++++ userland/stat.c | 22 +++++++-- 18 files changed, 293 insertions(+), 23 deletions(-) create mode 100644 userland/readlink.c diff --git a/kernel/api/fcntl.h b/kernel/api/fcntl.h index f77181a3..9ddd5c63 100644 --- a/kernel/api/fcntl.h +++ b/kernel/api/fcntl.h @@ -10,3 +10,4 @@ #define O_CREAT 0x8 #define O_EXCL 0x10 #define O_NONBLOCK 0x100 +#define O_NOFOLLOW 0x400 diff --git a/kernel/api/sys/limits.h b/kernel/api/sys/limits.h index 8e022e9d..dd37bd0f 100644 --- a/kernel/api/sys/limits.h +++ b/kernel/api/sys/limits.h @@ -2,3 +2,6 @@ #define ARG_MAX 131072 #define PATH_MAX 4096 +#define OPEN_MAX 1024 +#define SYMLINK_MAX 255 +#define SYMLOOP_MAX 40 diff --git a/kernel/api/sys/syscall.h b/kernel/api/sys/syscall.h index 2e6e7d99..bff8d3c2 100644 --- a/kernel/api/sys/syscall.h +++ b/kernel/api/sys/syscall.h @@ -29,6 +29,7 @@ F(link) \ F(listen) \ F(lseek) \ + F(lstat) \ F(mkdir) \ F(mknod) \ F(mmap) \ @@ -38,6 +39,7 @@ F(pipe) \ F(poll) \ F(read) \ + F(readlink) \ F(reboot) \ F(rename) \ F(rmdir) \ @@ -46,6 +48,7 @@ F(shutdown) \ F(socket) \ F(stat) \ + F(symlink) \ F(sysconf) \ F(times) \ F(uname) \ diff --git a/kernel/fs/fs.c b/kernel/fs/fs.c index c7fe9799..0d8cae96 100644 --- a/kernel/fs/fs.c +++ b/kernel/fs/fs.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include diff --git a/kernel/fs/fs.h b/kernel/fs/fs.h index b8fc0e29..9a13a4fb 100644 --- a/kernel/fs/fs.h +++ b/kernel/fs/fs.h @@ -6,14 +6,11 @@ #include #include #include -#include #define PATH_SEPARATOR '/' #define PATH_SEPARATOR_STR "/" #define ROOT_DIR PATH_SEPARATOR_STR -#define OPEN_MAX 1024 - typedef struct unix_socket unix_socket; typedef struct multiboot_info multiboot_info_t; typedef struct multiboot_mod_list multiboot_module_t; diff --git a/kernel/fs/vfs.c b/kernel/fs/vfs.c index 086e6e40..cec23222 100644 --- a/kernel/fs/vfs.c +++ b/kernel/fs/vfs.c @@ -2,6 +2,7 @@ #include "path.h" #include #include +#include #include #include #include @@ -157,12 +158,48 @@ static bool is_absolute_path(const char* path) { return path[0] == PATH_SEPARATOR; } -struct path* vfs_resolve_path(const char* pathname, int flags) { - return vfs_resolve_path_at(current->cwd, pathname, flags); +static struct path* resolve_path_at(const struct path* base, + const char* pathname, int flags, + unsigned symlink_depth); + +static struct path* follow_symlink(const struct path* parent, + struct inode* inode, + const char* rest_pathname, int flags, + unsigned depth) { + file_description* desc = inode_open(inode, O_RDONLY, 0); + if (IS_ERR(desc)) + return ERR_CAST(desc); + + char target[SYMLINK_MAX]; + size_t target_len = 0; + while (target_len < SYMLINK_MAX) { + ssize_t nread = file_description_read(desc, target + target_len, + SYMLINK_MAX - target_len); + if (IS_ERR(nread)) { + file_description_close(desc); + return ERR_PTR(nread); + } + if (nread == 0) + break; + target_len += nread; + } + file_description_close(desc); + + char* pathname = kmalloc(target_len + 1 + strlen(rest_pathname) + 1); + if (!pathname) + return ERR_PTR(-ENOMEM); + memcpy(pathname, target, target_len); + pathname[target_len] = '/'; + strcpy(pathname + target_len + 1, rest_pathname); + + struct path* path = resolve_path_at(parent, pathname, flags, depth + 1); + kfree(pathname); + return path; } -struct path* vfs_resolve_path_at(const struct path* base, const char* pathname, - int flags) { +static struct path* resolve_path_at(const struct path* base, + const char* pathname, int flags, + unsigned symlink_depth) { struct path* path = is_absolute_path(pathname) ? vfs_get_root() : path_dup(base); if (IS_ERR(path)) @@ -197,11 +234,16 @@ struct path* vfs_resolve_path_at(const struct path* base, const char* pathname, inode_ref(path->inode); struct inode* inode = inode_lookup_child(path->inode, component); + bool has_more_components = false; + for (char* p = saved_ptr; p && *p; ++p) { + if (*p != PATH_SEPARATOR) { + has_more_components = true; + break; + } + } + if ((flags & O_ALLOW_NOENT) && PTR_ERR(inode) == -ENOENT) { - const char* next_component = - strtok_r(NULL, PATH_SEPARATOR_STR, &saved_ptr); - if (next_component) { - // This is not the last component. + if (has_more_components) { path_destroy_recursive(path); kfree(dup_pathname); return ERR_PTR(-ENOENT); @@ -221,6 +263,36 @@ struct path* vfs_resolve_path_at(const struct path* base, const char* pathname, inode = resolve_mounts(inode); + if (S_ISLNK(inode->mode)) { + if (symlink_depth > SYMLOOP_MAX) { + inode_unref(inode); + path_destroy_recursive(path); + kfree(dup_pathname); + return ERR_PTR(-ELOOP); + } + + if (!(flags & O_NOFOLLOW)) { + const char* rest_pathname = strtok_r(NULL, "", &saved_ptr); + if (!rest_pathname) + rest_pathname = "."; + struct path* dest = follow_symlink(path, inode, rest_pathname, + flags, symlink_depth); + path_destroy_recursive(path); + if (IS_ERR(dest)) { + kfree(dup_pathname); + return dest; + } + return dest; + } + + if (has_more_components) { + inode_unref(inode); + path_destroy_recursive(path); + kfree(dup_pathname); + return ERR_PTR(-ELOOP); + } + } + struct path* joined = path_join(path, inode, component); if (IS_ERR(joined)) { path_destroy_recursive(path); @@ -234,6 +306,15 @@ struct path* vfs_resolve_path_at(const struct path* base, const char* pathname, return path; } +struct path* vfs_resolve_path(const char* pathname, int flags) { + return vfs_resolve_path_at(current->cwd, pathname, flags); +} + +struct path* vfs_resolve_path_at(const struct path* base, const char* pathname, + int flags) { + return resolve_path_at(base, pathname, flags, 0); +} + static struct inode* resolve_special_file(struct inode* inode) { if (S_ISBLK(inode->mode) || S_ISCHR(inode->mode)) { struct inode* device = vfs_get_device(inode->rdev); diff --git a/kernel/process.c b/kernel/process.c index a8328a13..2f657ed0 100644 --- a/kernel/process.c +++ b/kernel/process.c @@ -1,5 +1,6 @@ #include "process.h" #include "api/signum.h" +#include "api/sys/limits.h" #include "boot_defs.h" #include "fs/path.h" #include "interrupts.h" diff --git a/kernel/syscall/fs.c b/kernel/syscall/fs.c index 5d3b99aa..089b5b65 100644 --- a/kernel/syscall/fs.c +++ b/kernel/syscall/fs.c @@ -1,4 +1,5 @@ #include "syscall.h" +#include #include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include static int copy_pathname_from_user(char* dest, const char* user_src) { ssize_t pathname_len = strncpy_from_user(dest, user_src, PATH_MAX); @@ -55,6 +55,46 @@ ssize_t sys_read(int fd, void* user_buf, size_t count) { return file_description_read(desc, user_buf, count); } +ssize_t sys_readlink(const char* user_pathname, char* user_buf, size_t bufsiz) { + char pathname[PATH_MAX]; + int rc = copy_pathname_from_user(pathname, user_pathname); + if (IS_ERR(rc)) + return rc; + + struct path* path = vfs_resolve_path(pathname, O_NOFOLLOW); + if (IS_ERR(path)) + return PTR_ERR(path); + + struct inode* inode = path_into_inode(path); + if (!S_ISLNK(inode->mode)) { + return -EINVAL; + } + + file_description* desc = inode_open(inode, O_RDONLY, 0); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + bufsiz = MIN(bufsiz, SYMLINK_MAX); + + char buf[SYMLINK_MAX]; + size_t buf_len = 0; + while (buf_len < bufsiz) { + ssize_t nread = file_description_read(desc, buf + buf_len, bufsiz); + if (IS_ERR(nread)) { + file_description_close(desc); + return nread; + } + if (nread == 0) + break; + buf_len += nread; + } + file_description_close(desc); + + if (!copy_to_user(user_buf, buf, buf_len)) + return -EFAULT; + return buf_len; +} + ssize_t sys_write(int fd, const void* user_buf, size_t count) { if (!user_buf || !is_user_range(user_buf, count)) return -EFAULT; @@ -78,6 +118,20 @@ off_t sys_lseek(int fd, off_t offset, int whence) { return file_description_seek(desc, offset, whence); } +int sys_lstat(const char* user_pathname, struct stat* user_buf) { + char pathname[PATH_MAX]; + int rc = copy_pathname_from_user(pathname, user_pathname); + if (IS_ERR(rc)) + return rc; + struct stat buf; + rc = vfs_stat(pathname, &buf, O_NOFOLLOW); + if (IS_ERR(rc)) + return rc; + if (!copy_to_user(user_buf, &buf, sizeof(struct stat))) + return -EFAULT; + return 0; +} + int sys_stat(const char* user_pathname, struct stat* user_buf) { char pathname[PATH_MAX]; int rc = copy_pathname_from_user(pathname, user_pathname); @@ -92,6 +146,36 @@ int sys_stat(const char* user_pathname, struct stat* user_buf) { return 0; } +int sys_symlink(const char* user_target, const char* user_linkpath) { + char target[PATH_MAX]; + int rc = copy_pathname_from_user(target, user_target); + if (IS_ERR(rc)) + return rc; + size_t target_len = strlen(target); + if (target_len > SYMLINK_MAX) + return -ENAMETOOLONG; + + char linkpath[PATH_MAX]; + rc = copy_pathname_from_user(linkpath, user_linkpath); + if (IS_ERR(rc)) + return rc; + + struct inode* inode = vfs_create(linkpath, S_IFLNK); + if (IS_ERR(inode)) + return PTR_ERR(inode); + + file_description* desc = inode_open(inode, O_WRONLY, 0); + if (IS_ERR(desc)) { + inode_unref(inode); + return PTR_ERR(desc); + } + rc = file_description_write(desc, target, target_len); + + file_description_close(desc); + inode_unref(inode); + return rc; +} + int sys_ioctl(int fd, int request, void* user_argp) { if (!is_user_address(user_argp)) return -EFAULT; diff --git a/kernel/syscall/syscall.c b/kernel/syscall/syscall.c index 869dc181..090d24d5 100644 --- a/kernel/syscall/syscall.c +++ b/kernel/syscall/syscall.c @@ -1,4 +1,5 @@ #include "syscall.h" +#include #include #include #include diff --git a/kernel/syscall/syscall.h b/kernel/syscall/syscall.h index 4b54aef5..9b3bce8f 100644 --- a/kernel/syscall/syscall.h +++ b/kernel/syscall/syscall.h @@ -35,6 +35,7 @@ int sys_kill(pid_t pid, int sig); int sys_link(const char* oldpath, const char* newpath); int sys_listen(int sockfd, int backlog); off_t sys_lseek(int fd, off_t offset, int whence); +int sys_lstat(const char* pathname, struct stat* buf); int sys_mkdir(const char* pathname, mode_t mode); int sys_mknod(const char* pathname, mode_t mode, dev_t dev); void* sys_mmap(const mmap_params* params); @@ -44,6 +45,7 @@ int sys_open(const char* pathname, int flags, unsigned mode); int sys_pipe(int pipefd[2]); int sys_poll(struct pollfd* fds, nfds_t nfds, int timeout); ssize_t sys_read(int fd, void* buf, size_t count); +ssize_t sys_readlink(const char* pathname, char* buf, size_t bufsiz); int sys_reboot(int howto); int sys_rename(const char* oldpath, const char* newpath); int sys_rmdir(const char* pathname); @@ -52,6 +54,7 @@ int sys_setpgid(pid_t pid, pid_t pgid); int sys_shutdown(int sockfd, int how); int sys_socket(int domain, int type, int protocol); int sys_stat(const char* pathname, struct stat* buf); +int sys_symlink(const char* target, const char* linkpath); long sys_sysconf(int name); clock_t sys_times(struct tms* buf); int sys_uname(struct utsname* buf); diff --git a/userland/Makefile b/userland/Makefile index fa658143..36337d95 100644 --- a/userland/Makefile +++ b/userland/Makefile @@ -38,6 +38,7 @@ BIN_TARGET_NAMES := \ poweroff \ ps \ pwd \ + readlink \ reboot \ rm \ rmdir \ diff --git a/userland/lib/sys/stat.h b/userland/lib/sys/stat.h index 8dd43914..cc0cdbd0 100644 --- a/userland/lib/sys/stat.h +++ b/userland/lib/sys/stat.h @@ -3,5 +3,6 @@ #include int stat(const char* pathname, struct stat* buf); +int lstat(const char* pathname, struct stat* buf); int mkdir(const char* pathname, mode_t mode); int mkfifo(const char* pathname, mode_t mode); diff --git a/userland/lib/syscall.c b/userland/lib/syscall.c index f5f1273d..b1e1842e 100644 --- a/userland/lib/syscall.c +++ b/userland/lib/syscall.c @@ -162,6 +162,11 @@ off_t lseek(int fd, off_t offset, int whence) { RETURN_WITH_ERRNO(rc, off_t) } +int lstat(const char* pathname, struct stat* buf) { + int rc = syscall(SYS_lstat, (uintptr_t)pathname, (uintptr_t)buf, 0, 0); + RETURN_WITH_ERRNO(rc, int) +} + int mkdir(const char* pathname, mode_t mode) { int rc = syscall(SYS_mkdir, (uintptr_t)pathname, mode, 0, 0); RETURN_WITH_ERRNO(rc, int) @@ -233,6 +238,12 @@ ssize_t read(int fd, void* buf, size_t count) { RETURN_WITH_ERRNO(rc, ssize_t) } +ssize_t readlink(const char* pathname, char* buf, size_t bufsiz) { + int rc = + syscall(SYS_readlink, (uintptr_t)pathname, (uintptr_t)buf, bufsiz, 0); + RETURN_WITH_ERRNO(rc, ssize_t) +} + int reboot(int howto) { int rc = syscall(SYS_reboot, howto, 0, 0, 0); RETURN_WITH_ERRNO(rc, int) @@ -273,6 +284,11 @@ int stat(const char* pathname, struct stat* buf) { RETURN_WITH_ERRNO(rc, int) } +int symlink(const char* target, const char* linkpath) { + int rc = syscall(SYS_symlink, (uintptr_t)target, (uintptr_t)linkpath, 0, 0); + RETURN_WITH_ERRNO(rc, int) +} + long sysconf(int name) { int rc = syscall(SYS_sysconf, name, 0, 0, 0); RETURN_WITH_ERRNO(rc, long) diff --git a/userland/lib/unistd.h b/userland/lib/unistd.h index 2a31d05a..32c08a5b 100644 --- a/userland/lib/unistd.h +++ b/userland/lib/unistd.h @@ -21,7 +21,9 @@ int ftruncate(int fd, off_t length); off_t lseek(int fd, off_t offset, int whence); int mknod(const char* pathname, mode_t mode, dev_t dev); int link(const char* oldpath, const char* newpath); +int symlink(const char* target, const char* linkpath); int unlink(const char* pathname); +ssize_t readlink(const char* pathname, char* buf, size_t bufsiz); int rename(const char* oldpath, const char* newpath); int rmdir(const char* pathname); diff --git a/userland/ln.c b/userland/ln.c index a7251e20..2704148e 100644 --- a/userland/ln.c +++ b/userland/ln.c @@ -1,13 +1,38 @@ #include #include +#include #include +static void usage(void) { + dprintf(STDERR_FILENO, "Usage: ln TARGET LINK_NAME\n"); + exit(EXIT_FAILURE); +} + int main(int argc, char* argv[]) { - if (argc != 3) { - dprintf(STDERR_FILENO, "Usage: ln TARGET LINK_NAME\n"); - return EXIT_FAILURE; + if (argc < 3) + usage(); + + const char* target = NULL; + const char* link_name = NULL; + bool symbolic = false; + for (int i = 1; i < argc; i++) { + const char* arg = argv[i]; + if (!strcmp(arg, "-s")) + symbolic = true; + else if (!target) + target = arg; + else if (!link_name) + link_name = arg; + else + usage(); } - if (link(argv[1], argv[2]) < 0) { + + if (symbolic) { + if (symlink(target, link_name) < 0) { + perror("symlink"); + return EXIT_FAILURE; + } + } else if (link(target, link_name) < 0) { perror("link"); return EXIT_FAILURE; } diff --git a/userland/ls.c b/userland/ls.c index 97455f81..24f1121f 100644 --- a/userland/ls.c +++ b/userland/ls.c @@ -64,6 +64,7 @@ static char get_file_type_char(mode_t mode) { static int list_dir(const char* path, size_t terminal_width, bool long_format) { int ret = -1; DIR* dirp = NULL; + char* full_path = NULL; dirp = opendir(path); if (!dirp) { @@ -84,7 +85,7 @@ static int list_dir(const char* path, size_t terminal_width, bool long_format) { } size_t name_len = strlen(dent->d_name); - char* full_path = malloc(path_len + 1 + name_len + 1); + full_path = malloc(path_len + 1 + name_len + 1); if (!full_path) { perror("malloc"); goto fail; @@ -94,8 +95,7 @@ static int list_dir(const char* path, size_t terminal_width, bool long_format) { strcpy(full_path + path_len + 1, dent->d_name); struct stat st; - ret = stat(full_path, &st); - free(full_path); + ret = lstat(full_path, &st); if (ret < 0) { perror("stat"); goto fail; @@ -109,7 +109,17 @@ static int list_dir(const char* path, size_t terminal_width, bool long_format) { printf(" %4u,%4u ", major(st.st_rdev), minor(st.st_rdev)); else printf("%10u ", st.st_size); + printf(format, dent->d_name); + if (S_ISLNK(st.st_mode)) { + char target[SYMLINK_MAX + 1] = {0}; + if (readlink(full_path, target, SYMLINK_MAX) < 0) { + perror("readlink"); + goto fail; + } + printf(" -> %s", target); + } + putchar('\n'); } else { size_t next_pos = round_up(x_pos + len + 1, TAB_STOP); @@ -122,6 +132,9 @@ static int list_dir(const char* path, size_t terminal_width, bool long_format) { printf(format, dent->d_name); putchar('\t'); } + + free(full_path); + full_path = NULL; } if (x_pos > 0) putchar('\n'); @@ -129,6 +142,7 @@ static int list_dir(const char* path, size_t terminal_width, bool long_format) { ret = 0; fail: putchar('\n'); + free(full_path); if (dirp) closedir(dirp); return ret; diff --git a/userland/readlink.c b/userland/readlink.c new file mode 100644 index 00000000..09f265f3 --- /dev/null +++ b/userland/readlink.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + if (argc < 2) { + dprintf(STDERR_FILENO, "Usage: FILE...\n"); + return EXIT_FAILURE; + } + + char target[SYMLINK_MAX + 1] = {0}; + for (int i = 1; i < argc; i++) { + if (readlink(argv[i], target, SYMLINK_MAX) < 0) { + perror("readlink"); + return EXIT_FAILURE; + } + puts(target); + } + + return EXIT_SUCCESS; +} diff --git a/userland/stat.c b/userland/stat.c index ad24e16f..25221205 100644 --- a/userland/stat.c +++ b/userland/stat.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -30,14 +31,27 @@ int main(int argc, char* argv[]) { for (int i = 1; i < argc; ++i) { const char* filename = argv[i]; struct stat buf; - if (stat(filename, &buf) < 0) { + if (lstat(filename, &buf) < 0) { perror("stat"); return EXIT_FAILURE; } - printf(" File: %s\n" - " Size: %-10d\t%s\n" + + printf(" File: %s", filename); + + if (S_ISLNK(buf.st_mode)) { + char target[SYMLINK_MAX + 1] = {0}; + if (readlink(filename, target, SYMLINK_MAX) < 0) { + perror("readlink"); + return EXIT_FAILURE; + } + printf(" -> %s\n", target); + } else { + putchar('\n'); + } + + printf(" Size: %-10d\t%s\n" "Device: %d,%d\tLinks: %-5d", - filename, buf.st_size, file_type(&buf), major(buf.st_dev), + buf.st_size, file_type(&buf), major(buf.st_dev), minor(buf.st_dev), buf.st_nlink); if (S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode)) { printf("\tDevice type: %d,%d", major(buf.st_rdev),