From 67a9d130e288668b2bea4a0e291fdb09259ab840 Mon Sep 17 00:00:00 2001 From: apio Date: Sat, 20 May 2023 21:46:31 +0200 Subject: [PATCH] kernel: Add initial support for symbolic links :D --- kernel/src/fs/VFS.cpp | 44 ++++++++++-- kernel/src/fs/VFS.h | 14 +++- kernel/src/fs/tmpfs/FileSystem.cpp | 9 +++ kernel/src/fs/tmpfs/FileSystem.h | 111 +++++++++++++++++++++++++++++ kernel/src/sys/id.cpp | 4 +- kernel/src/sys/link.cpp | 30 +++++++- kernel/src/sys/open.cpp | 6 +- kernel/src/sys/stat.cpp | 3 +- kernel/src/thread/Thread.cpp | 10 +-- kernel/src/thread/Thread.h | 1 + libc/include/bits/atfile.h | 1 + libc/include/bits/modes.h | 2 + libc/include/bits/open-flags.h | 1 + libluna/include/luna/Syscall.h | 2 +- 14 files changed, 220 insertions(+), 18 deletions(-) diff --git a/kernel/src/fs/VFS.cpp b/kernel/src/fs/VFS.cpp index d51cec6d..2ee8225f 100644 --- a/kernel/src/fs/VFS.cpp +++ b/kernel/src/fs/VFS.cpp @@ -15,28 +15,60 @@ namespace VFS return *root_fs->root_inode(); } - Result> resolve_path(const char* path, Credentials auth, SharedPtr working_directory) + static constexpr int MAX_SYMLINKS = 8; + + Result> resolve_path_impl(const char* path, Credentials auth, SharedPtr current_inode, + bool follow_last_symlink, int& symlinks_followed) { + if (symlinks_followed >= MAX_SYMLINKS) return err(ELOOP); + if (*path == '\0') return err(ENOENT); auto parser = TRY(PathParser::create(path)); - SharedPtr current_inode; - - if (parser.is_absolute() || !working_directory) current_inode = root_fs->root_inode(); - else - current_inode = working_directory; + SharedPtr parent_inode = current_inode; const char* section; while (parser.next().try_set_value(section)) { if (!can_execute(current_inode, auth)) return err(EACCES); current_inode = TRY(current_inode->find(section)); + + if (current_inode->type() == VFS::InodeType::Symlink && (follow_last_symlink || parser.has_next())) + { + auto link = TRY(current_inode->readlink()); + + SharedPtr symlink_root; + + if (PathParser::is_absolute(link.chars())) symlink_root = root_fs->root_inode(); + else + symlink_root = parent_inode; + + symlinks_followed++; + current_inode = TRY(resolve_path_impl(link.chars(), auth, symlink_root, true, symlinks_followed)); + symlinks_followed--; + } + + parent_inode = current_inode; } return current_inode; } + Result> resolve_path(const char* path, Credentials auth, SharedPtr working_directory, + bool follow_last_symlink) + { + SharedPtr current_inode; + + if (PathParser::is_absolute(path) || !working_directory) current_inode = root_fs->root_inode(); + else + current_inode = working_directory; + + int symlinks_followed = 0; + + return resolve_path_impl(path, auth, current_inode, follow_last_symlink, symlinks_followed); + } + Result> create_directory(const char* path, Credentials auth, SharedPtr working_directory) { auto parser = TRY(PathParser::create(path)); diff --git a/kernel/src/fs/VFS.h b/kernel/src/fs/VFS.h index ea0188ce..02d8813c 100644 --- a/kernel/src/fs/VFS.h +++ b/kernel/src/fs/VFS.h @@ -12,7 +12,8 @@ namespace VFS { RegularFile, Directory, - Device + Device, + Symlink }; class Inode; @@ -28,6 +29,8 @@ namespace VFS virtual Result> create_device_inode(u32 major, u32 minor) = 0; + virtual Result> create_symlink_inode(StringView link) = 0; + virtual Result set_mount_dir(SharedPtr parent) = 0; virtual u64 handles() const @@ -92,6 +95,12 @@ namespace VFS virtual bool blocking() const = 0; + // Symlink-specific methods + virtual Result readlink() + { + return StringView {}; + } + // Metadata accessors virtual usize size() const = 0; @@ -250,7 +259,8 @@ namespace VFS }; Result> resolve_path(const char* path, Credentials auth, - SharedPtr working_directory = {}); + SharedPtr working_directory = {}, + bool follow_last_symlink = true); Result> create_directory(const char* path, Credentials auth, SharedPtr working_directory = {}); diff --git a/kernel/src/fs/tmpfs/FileSystem.cpp b/kernel/src/fs/tmpfs/FileSystem.cpp index 1024876d..b2ef9b7d 100644 --- a/kernel/src/fs/tmpfs/FileSystem.cpp +++ b/kernel/src/fs/tmpfs/FileSystem.cpp @@ -24,6 +24,15 @@ namespace TmpFS return (SharedPtr)inode; } + Result> FileSystem::create_symlink_inode(StringView link) + { + SharedPtr inode = TRY(make_shared()); + inode->set_fs(*this, {}); + TRY(inode->set_link(link, {})); + inode->set_inode_number(m_next_inode_number++, {}); + return (SharedPtr)inode; + } + Result> FileSystem::create_dir_inode(SharedPtr parent) { SharedPtr inode = TRY(make_shared()); diff --git a/kernel/src/fs/tmpfs/FileSystem.h b/kernel/src/fs/tmpfs/FileSystem.h index 8691552c..bec93b30 100644 --- a/kernel/src/fs/tmpfs/FileSystem.h +++ b/kernel/src/fs/tmpfs/FileSystem.h @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace TmpFS @@ -20,6 +21,7 @@ namespace TmpFS Result> create_file_inode() override; Result> create_dir_inode(SharedPtr parent) override; Result> create_device_inode(u32 major, u32 minor) override; + Result> create_symlink_inode(StringView link) override; Result set_mount_dir(SharedPtr parent) override; @@ -120,6 +122,115 @@ namespace TmpFS u32 m_nlinks { 0 }; }; + class SymlinkInode : public VFS::FileInode + { + public: + SymlinkInode() = default; + + VFS::InodeType type() const override + { + return VFS::InodeType::Symlink; + } + + void set_fs(FileSystem& fs, Badge) + { + m_fs = &fs; + } + + void set_inode_number(usize inum, Badge) + { + m_inode_number = inum; + } + + Result set_link(StringView link, Badge) + { + m_link = TRY(String::from_string_view(link)); + return {}; + } + + VFS::FileSystem* fs() const override + { + return m_fs; + } + + usize inode_number() const override + { + return m_inode_number; + } + + Result read(u8*, usize, usize) const override + { + return err(ENOTSUP); + } + + Result write(const u8*, usize, usize) override + { + return err(ENOTSUP); + } + + Result truncate(usize) override + { + return err(ENOTSUP); + } + + usize size() const override + { + return m_link.length(); + } + + mode_t mode() const override + { + return 0777; + } + + u32 uid() const override + { + return m_uid; + } + + u32 gid() const override + { + return m_gid; + } + + Result chmod(mode_t) override + { + return {}; + } + + Result chown(u32 uid, u32 gid) override + { + m_uid = uid; + m_gid = gid; + return {}; + } + + void did_link() override + { + m_nlinks++; + } + + void did_unlink() override + { + m_nlinks--; + } + + Result readlink() override + { + return m_link.view(); + } + + virtual ~SymlinkInode() = default; + + private: + VFS::FileSystem* m_fs; + String m_link; + usize m_inode_number; + u32 m_uid { 0 }; + u32 m_gid { 0 }; + u32 m_nlinks { 0 }; + }; + class DeviceInode : public VFS::DeviceInode { public: diff --git a/kernel/src/sys/id.cpp b/kernel/src/sys/id.cpp index f40c92a1..4a82bbd8 100644 --- a/kernel/src/sys/id.cpp +++ b/kernel/src/sys/id.cpp @@ -104,7 +104,7 @@ Result sys_fchmodat(Registers*, SyscallArgs args) auto* current = Scheduler::current(); - auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH)); + auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH, !(flags & AT_SYMLINK_NOFOLLOW))); if (current->auth.euid != 0 && current->auth.euid != inode->uid()) return err(EPERM); @@ -123,7 +123,7 @@ Result sys_fchownat(Registers*, SyscallArgs args) auto* current = Scheduler::current(); - auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH)); + auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH, !(flags & AT_SYMLINK_NOFOLLOW))); if (current->auth.euid != 0) return err(EPERM); diff --git a/kernel/src/sys/link.cpp b/kernel/src/sys/link.cpp index 7dc3fc78..ecefdd23 100644 --- a/kernel/src/sys/link.cpp +++ b/kernel/src/sys/link.cpp @@ -21,7 +21,7 @@ Result sys_unlinkat(Registers*, SyscallArgs args) kinfoln("unlinkat: remove %s from directory %s, dirfd is %d", basename.chars(), dirname.chars(), dirfd); - auto inode = TRY(current->resolve_atfile(dirfd, dirname, false)); + auto inode = TRY(current->resolve_atfile(dirfd, dirname, false, false)); if (!VFS::can_write(inode, current->auth)) return err(EACCES); if (flags > 0) @@ -34,3 +34,31 @@ Result sys_unlinkat(Registers*, SyscallArgs args) return 0; } + +Result sys_symlinkat(Registers*, SyscallArgs args) +{ + auto target = TRY(MemoryManager::strdup_from_user(args[0])); + int dirfd = (int)args[1]; + auto linkpath = TRY(MemoryManager::strdup_from_user(args[2])); + + if (target.is_empty()) return err(ENOENT); + + auto* current = Scheduler::current(); + + auto parser = TRY(PathParser::create(linkpath.chars())); + auto parent = TRY(parser.dirname()); + + auto parent_inode = TRY(current->resolve_atfile(dirfd, parent, false, true)); + + if (!VFS::can_write(parent_inode, current->auth)) return err(EACCES); + + auto child_name = TRY(parser.basename()); + + TRY(VFS::validate_filename(child_name.view())); + + auto inode = TRY(parent_inode->fs()->create_symlink_inode(target.view())); + TRY(inode->chown(current->auth.euid, current->auth.egid)); + TRY(parent_inode->add_entry(inode, child_name.chars())); + + return 0; +} diff --git a/kernel/src/sys/open.cpp b/kernel/src/sys/open.cpp index 9d3a2d9a..fb239854 100644 --- a/kernel/src/sys/open.cpp +++ b/kernel/src/sys/open.cpp @@ -31,7 +31,8 @@ Result sys_openat(Registers*, SyscallArgs args) int error; SharedPtr parent_inode; - bool ok = current->resolve_atfile(dirfd, path, false, &parent_inode).try_set_value_or_error(inode, error); + bool ok = current->resolve_atfile(dirfd, path, false, !(flags & O_NOFOLLOW), &parent_inode) + .try_set_value_or_error(inode, error); if (!ok) { if (error == ENOENT && (flags & O_CREAT) && !path.is_empty()) @@ -51,6 +52,9 @@ Result sys_openat(Registers*, SyscallArgs args) if ((flags & O_WRONLY) && !VFS::can_write(inode, current->auth)) return err(EACCES); } + // This should only be possible if O_NOFOLLOW was in flags. + if (inode->type() == VFS::InodeType::Symlink) return err(ELOOP); + if (flags & O_TMPFILE) { if (inode->type() != VFS::InodeType::Directory) return err(EINVAL); diff --git a/kernel/src/sys/stat.cpp b/kernel/src/sys/stat.cpp index c5a525e8..c40e7017 100644 --- a/kernel/src/sys/stat.cpp +++ b/kernel/src/sys/stat.cpp @@ -14,6 +14,7 @@ static mode_t make_mode(mode_t mode, VFS::InodeType type) case VFS::InodeType::RegularFile: result |= S_IFREG; break; case VFS::InodeType::Directory: result |= S_IFDIR; break; case VFS::InodeType::Device: result |= S_IFCHR; break; + case VFS::InodeType::Symlink: result |= S_IFLNK; break; default: break; } @@ -29,7 +30,7 @@ Result sys_fstatat(Registers*, SyscallArgs args) Thread* current = Scheduler::current(); - auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH)); + auto inode = TRY(current->resolve_atfile(dirfd, path, flags & AT_EMPTY_PATH, !(flags & AT_SYMLINK_NOFOLLOW))); stat kstat; diff --git a/kernel/src/thread/Thread.cpp b/kernel/src/thread/Thread.cpp index 4a45ebde..df5e5cd8 100644 --- a/kernel/src/thread/Thread.cpp +++ b/kernel/src/thread/Thread.cpp @@ -49,13 +49,15 @@ Result Thread::resolve_fd(int fd) } Result> Thread::resolve_atfile(int dirfd, const String& path, bool allow_empty_path, - SharedPtr* parent_inode) + bool follow_last_symlink, SharedPtr* parent_inode) { if (parent_inode) *parent_inode = this->current_directory; - if (PathParser::is_absolute(path.view())) return VFS::resolve_path(path.chars(), this->auth); + if (PathParser::is_absolute(path.view())) + return VFS::resolve_path(path.chars(), this->auth, {}, follow_last_symlink); - if (dirfd == AT_FDCWD) return VFS::resolve_path(path.chars(), this->auth, this->current_directory); + if (dirfd == AT_FDCWD) + return VFS::resolve_path(path.chars(), this->auth, this->current_directory, follow_last_symlink); auto descriptor = TRY(resolve_fd(dirfd)); @@ -63,7 +65,7 @@ Result> Thread::resolve_atfile(int dirfd, const String& pa if (path.is_empty() && allow_empty_path) return descriptor->inode; - return VFS::resolve_path(path.chars(), this->auth, descriptor->inode); + return VFS::resolve_path(path.chars(), this->auth, descriptor->inode, follow_last_symlink); } bool FileDescriptor::should_append() diff --git a/kernel/src/thread/Thread.h b/kernel/src/thread/Thread.h index 3bef0015..0aebd05f 100644 --- a/kernel/src/thread/Thread.h +++ b/kernel/src/thread/Thread.h @@ -76,6 +76,7 @@ struct Thread : public LinkedListNode Result allocate_fd(int min); Result resolve_fd(int fd); Result> resolve_atfile(int dirfd, const String& path, bool allow_empty_path, + bool follow_last_symlink, SharedPtr* parent_inode = nullptr); FPData fp_data; diff --git a/libc/include/bits/atfile.h b/libc/include/bits/atfile.h index a7f9ce7e..4511299e 100644 --- a/libc/include/bits/atfile.h +++ b/libc/include/bits/atfile.h @@ -6,6 +6,7 @@ #define AT_FDCWD -100 #define AT_EMPTY_PATH 1 +#define AT_SYMLINK_NOFOLLOW 2 #define AT_REMOVEDIR 1 diff --git a/libc/include/bits/modes.h b/libc/include/bits/modes.h index 4dacc697..ba43766c 100644 --- a/libc/include/bits/modes.h +++ b/libc/include/bits/modes.h @@ -5,6 +5,7 @@ #define S_IFMT 070000 #define S_IFREG 000000 +#define S_IFLNK 010000 #define S_IFDIR 040000 #define S_IFCHR 050000 @@ -13,6 +14,7 @@ #define S_ISREG(mode) __CHECK_TYPE(mode, S_IFREG) #define S_ISDIR(mode) __CHECK_TYPE(mode, S_IFDIR) #define S_ISCHR(mode) __CHECK_TYPE(mode, S_IFCHR) +#define S_ISLNK(mode) __CHECK_TYPE(mode, S_IFLNK) #define S_IRWXU 0700 #define S_IRUSR 0400 diff --git a/libc/include/bits/open-flags.h b/libc/include/bits/open-flags.h index e2d8b2ac..799ee300 100644 --- a/libc/include/bits/open-flags.h +++ b/libc/include/bits/open-flags.h @@ -14,6 +14,7 @@ #define O_CLOEXEC 128 #define O_DIRECTORY 256 #define O_TMPFILE 512 +#define O_NOFOLLOW 1024 #define O_ACCMODE O_RDWR diff --git a/libluna/include/luna/Syscall.h b/libluna/include/luna/Syscall.h index 28ad8710..3280d8a8 100644 --- a/libluna/include/luna/Syscall.h +++ b/libluna/include/luna/Syscall.h @@ -5,7 +5,7 @@ _e(lseek) _e(mkdir) _e(execve) _e(fork) _e(waitpid) _e(getppid) _e(fcntl) _e(getdents) _e(getuid) _e(geteuid) \ _e(getgid) _e(getegid) _e(setuid) _e(setgid) _e(seteuid) _e(setegid) _e(fchmodat) _e(fchownat) _e(ioctl) \ _e(fstatat) _e(chdir) _e(getcwd) _e(unlinkat) _e(uname) _e(sethostname) _e(dup2) _e(pipe) _e(mount) \ - _e(umount) _e(pstat) _e(getrusage) + _e(umount) _e(pstat) _e(getrusage) _e(symlinkat) enum Syscalls {