diff --git a/kernel/arch/aarch64/syscall/linux_syscalls.h b/kernel/arch/aarch64/syscall/linux_syscalls.h index 179ab3ff..3beae0da 100644 --- a/kernel/arch/aarch64/syscall/linux_syscalls.h +++ b/kernel/arch/aarch64/syscall/linux_syscalls.h @@ -27,6 +27,7 @@ constexpr uint64_t WRITE = 64; constexpr uint64_t WRITEV = 66; constexpr uint64_t PSELECT6 = 72; constexpr uint64_t PPOLL = 73; +constexpr uint64_t READLINKAT = 78; constexpr uint64_t NEWFSTATAT = 79; constexpr uint64_t FSTAT = 80; constexpr uint64_t FSYNC = 82; diff --git a/kernel/arch/x86_64/syscall/linux_syscalls.h b/kernel/arch/x86_64/syscall/linux_syscalls.h index 34608a42..7b037c54 100644 --- a/kernel/arch/x86_64/syscall/linux_syscalls.h +++ b/kernel/arch/x86_64/syscall/linux_syscalls.h @@ -54,6 +54,7 @@ constexpr uint64_t RENAME = 82; constexpr uint64_t MKDIR = 83; constexpr uint64_t RMDIR = 84; constexpr uint64_t UNLINK = 87; +constexpr uint64_t READLINK = 89; constexpr uint64_t GETTIMEOFDAY = 96; constexpr uint64_t GETUID = 102; constexpr uint64_t GETGID = 104; @@ -70,6 +71,7 @@ constexpr uint64_t MKDIRAT = 258; constexpr uint64_t NEWFSTATAT = 262; constexpr uint64_t UNLINKAT = 263; constexpr uint64_t RENAMEAT = 264; +constexpr uint64_t READLINKAT = 267; constexpr uint64_t FACCESSAT = 269; constexpr uint64_t PSELECT6 = 270; constexpr uint64_t PPOLL = 271; diff --git a/kernel/syscall/handlers/sys_fd.cpp b/kernel/syscall/handlers/sys_fd.cpp index 7275c77f..f01e87d5 100644 --- a/kernel/syscall/handlers/sys_fd.cpp +++ b/kernel/syscall/handlers/sys_fd.cpp @@ -1447,6 +1447,73 @@ DEFINE_SYSCALL2(access, pathname, mode) { pathname, mode, 0, 0, 0); } +static int64_t do_readlinkat(int64_t dirfd, uint64_t pathname, + uint64_t u_buf, uint64_t bufsize) { + if (bufsize == 0) { + return syscall::EINVAL; + } + + char kpath[fs::PATH_MAX]; + int32_t copy_rc = mm::uaccess::copy_cstr_from_user( + kpath, sizeof(kpath), + reinterpret_cast(pathname)); + if (copy_rc != mm::uaccess::OK) { + if (copy_rc == mm::uaccess::ERR_NAMETOOLONG) { + return syscall::ENAMETOOLONG; + } + return syscall::EFAULT; + } + + if (kpath[0] == '\0') { + return syscall::ENOENT; + } + + sched::task* task = sched::current(); + if (!task) { + return syscall::EIO; + } + + fs::node* node = nullptr; + int64_t lookup_rc = lookup_node_for_dirfd_path(task, dirfd, kpath, &node); + if (lookup_rc != 0) { + return lookup_rc; + } + + // Only symbolic links have a target to read + if (node->type() != fs::node_type::symlink) { + release_node_ref(node); + return syscall::EINVAL; + } + + // The input path is no longer needed, so reuse kpath for the target + size_t cap = bufsize < sizeof(kpath) ? bufsize : sizeof(kpath); + size_t target_len = 0; + int32_t fs_rc = node->readlink(kpath, cap, &target_len); + release_node_ref(node); + if (fs_rc != fs::OK) { + return syscall::error_map::map_fs_error(fs_rc); + } + + // Guard against a provider reporting more than it wrote + if (target_len > cap) { + target_len = cap; + } + + if (mm::uaccess::copy_to_user(reinterpret_cast(u_buf), + kpath, target_len) != mm::uaccess::OK) { + return syscall::EFAULT; + } + return static_cast(target_len); +} + +DEFINE_SYSCALL4(readlinkat, dirfd, pathname, buf, bufsize) { + return do_readlinkat(static_cast(dirfd), pathname, buf, bufsize); +} + +DEFINE_SYSCALL3(readlink, pathname, buf, bufsize) { + return do_readlinkat(static_cast(-100), pathname, buf, bufsize); +} + DEFINE_SYSCALL4(renameat, olddirfd, oldpath, newdirfd, newpath) { (void)olddirfd; (void)oldpath; diff --git a/kernel/syscall/handlers/sys_fd.h b/kernel/syscall/handlers/sys_fd.h index daa9d62c..77008473 100644 --- a/kernel/syscall/handlers/sys_fd.h +++ b/kernel/syscall/handlers/sys_fd.h @@ -24,6 +24,8 @@ DECLARE_SYSCALL(mkdirat); DECLARE_SYSCALL(mkdir); DECLARE_SYSCALL(faccessat); DECLARE_SYSCALL(access); +DECLARE_SYSCALL(readlinkat); +DECLARE_SYSCALL(readlink); DECLARE_SYSCALL(renameat); DECLARE_SYSCALL(fsync); diff --git a/kernel/syscall/handlers/sys_io.cpp b/kernel/syscall/handlers/sys_io.cpp index 184477b2..4147f062 100644 --- a/kernel/syscall/handlers/sys_io.cpp +++ b/kernel/syscall/handlers/sys_io.cpp @@ -109,5 +109,8 @@ DEFINE_SYSCALL3(ioctl, fd, cmd, arg) { task, static_cast(fd), static_cast(cmd), arg); if (rc == resource::OK) return 0; + + // POSIX: an unsupported request on any fd is ENOTTY, not ENOSYS + if (rc == resource::ERR_UNSUP) return syscall::ENOTTY; return map_resource_error(rc); } diff --git a/kernel/syscall/syscall_table.cpp b/kernel/syscall/syscall_table.cpp index 7618dc89..61629754 100644 --- a/kernel/syscall/syscall_table.cpp +++ b/kernel/syscall/syscall_table.cpp @@ -99,12 +99,14 @@ __PRIVILEGED_CODE void init_syscall_table() { REGISTER_SYSCALL(linux_nr::UNLINKAT, unlinkat); REGISTER_SYSCALL(linux_nr::FACCESSAT, faccessat); REGISTER_SYSCALL(linux_nr::RENAMEAT, renameat); + REGISTER_SYSCALL(linux_nr::READLINKAT, readlinkat); #if defined(__x86_64__) REGISTER_SYSCALL(linux_nr::MKDIR, mkdir); REGISTER_SYSCALL(linux_nr::UNLINK, unlink); REGISTER_SYSCALL(linux_nr::RMDIR, rmdir); REGISTER_SYSCALL(linux_nr::ACCESS, access); REGISTER_SYSCALL(linux_nr::RENAME, renameat); + REGISTER_SYSCALL(linux_nr::READLINK, readlink); #endif REGISTER_SYSCALL(SYS_ELEVATE, elevate);