kernel: Add initial support for symbolic links :D

This commit is contained in:
apio 2023-05-20 21:46:31 +02:00
parent b75bd4cd14
commit 67a9d130e2
Signed by: apio
GPG Key ID: B8A7D06E42258954
14 changed files with 220 additions and 18 deletions

View File

@ -15,28 +15,60 @@ namespace VFS
return *root_fs->root_inode();
}
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth, SharedPtr<Inode> working_directory)
static constexpr int MAX_SYMLINKS = 8;
Result<SharedPtr<Inode>> resolve_path_impl(const char* path, Credentials auth, SharedPtr<Inode> 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<Inode> current_inode;
if (parser.is_absolute() || !working_directory) current_inode = root_fs->root_inode();
else
current_inode = working_directory;
SharedPtr<Inode> 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<VFS::Inode> 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<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth, SharedPtr<VFS::Inode> working_directory,
bool follow_last_symlink)
{
SharedPtr<Inode> 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<SharedPtr<Inode>> create_directory(const char* path, Credentials auth, SharedPtr<Inode> working_directory)
{
auto parser = TRY(PathParser::create(path));

View File

@ -12,7 +12,8 @@ namespace VFS
{
RegularFile,
Directory,
Device
Device,
Symlink
};
class Inode;
@ -28,6 +29,8 @@ namespace VFS
virtual Result<SharedPtr<Inode>> create_device_inode(u32 major, u32 minor) = 0;
virtual Result<SharedPtr<Inode>> create_symlink_inode(StringView link) = 0;
virtual Result<void> set_mount_dir(SharedPtr<Inode> parent) = 0;
virtual u64 handles() const
@ -92,6 +95,12 @@ namespace VFS
virtual bool blocking() const = 0;
// Symlink-specific methods
virtual Result<StringView> readlink()
{
return StringView {};
}
// Metadata accessors
virtual usize size() const = 0;
@ -250,7 +259,8 @@ namespace VFS
};
Result<SharedPtr<Inode>> resolve_path(const char* path, Credentials auth,
SharedPtr<VFS::Inode> working_directory = {});
SharedPtr<VFS::Inode> working_directory = {},
bool follow_last_symlink = true);
Result<SharedPtr<Inode>> create_directory(const char* path, Credentials auth,
SharedPtr<VFS::Inode> working_directory = {});

View File

@ -24,6 +24,15 @@ namespace TmpFS
return (SharedPtr<VFS::Inode>)inode;
}
Result<SharedPtr<VFS::Inode>> FileSystem::create_symlink_inode(StringView link)
{
SharedPtr<SymlinkInode> inode = TRY(make_shared<SymlinkInode>());
inode->set_fs(*this, {});
TRY(inode->set_link(link, {}));
inode->set_inode_number(m_next_inode_number++, {});
return (SharedPtr<VFS::Inode>)inode;
}
Result<SharedPtr<VFS::Inode>> FileSystem::create_dir_inode(SharedPtr<VFS::Inode> parent)
{
SharedPtr<DirInode> inode = TRY(make_shared<DirInode>());

View File

@ -5,6 +5,7 @@
#include <luna/Badge.h>
#include <luna/Buffer.h>
#include <luna/StaticString.h>
#include <luna/String.h>
#include <luna/Vector.h>
namespace TmpFS
@ -20,6 +21,7 @@ namespace TmpFS
Result<SharedPtr<VFS::Inode>> create_file_inode() override;
Result<SharedPtr<VFS::Inode>> create_dir_inode(SharedPtr<VFS::Inode> parent) override;
Result<SharedPtr<VFS::Inode>> create_device_inode(u32 major, u32 minor) override;
Result<SharedPtr<VFS::Inode>> create_symlink_inode(StringView link) override;
Result<void> set_mount_dir(SharedPtr<VFS::Inode> 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<FileSystem>)
{
m_fs = &fs;
}
void set_inode_number(usize inum, Badge<FileSystem>)
{
m_inode_number = inum;
}
Result<void> set_link(StringView link, Badge<FileSystem>)
{
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<usize> read(u8*, usize, usize) const override
{
return err(ENOTSUP);
}
Result<usize> write(const u8*, usize, usize) override
{
return err(ENOTSUP);
}
Result<void> 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<void> chmod(mode_t) override
{
return {};
}
Result<void> 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<StringView> 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:

View File

@ -104,7 +104,7 @@ Result<u64> 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<u64> 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);

View File

@ -21,7 +21,7 @@ Result<u64> 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<u64> sys_unlinkat(Registers*, SyscallArgs args)
return 0;
}
Result<u64> 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;
}

View File

@ -31,7 +31,8 @@ Result<u64> sys_openat(Registers*, SyscallArgs args)
int error;
SharedPtr<VFS::Inode> 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<u64> 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);

View File

@ -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<u64> 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;

View File

@ -49,13 +49,15 @@ Result<FileDescriptor*> Thread::resolve_fd(int fd)
}
Result<SharedPtr<VFS::Inode>> Thread::resolve_atfile(int dirfd, const String& path, bool allow_empty_path,
SharedPtr<VFS::Inode>* parent_inode)
bool follow_last_symlink, SharedPtr<VFS::Inode>* 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<SharedPtr<VFS::Inode>> 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()

View File

@ -76,6 +76,7 @@ struct Thread : public LinkedListNode<Thread>
Result<int> allocate_fd(int min);
Result<FileDescriptor*> resolve_fd(int fd);
Result<SharedPtr<VFS::Inode>> resolve_atfile(int dirfd, const String& path, bool allow_empty_path,
bool follow_last_symlink,
SharedPtr<VFS::Inode>* parent_inode = nullptr);
FPData fp_data;

View File

@ -6,6 +6,7 @@
#define AT_FDCWD -100
#define AT_EMPTY_PATH 1
#define AT_SYMLINK_NOFOLLOW 2
#define AT_REMOVEDIR 1

View File

@ -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

View File

@ -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

View File

@ -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
{