#include "fs/VFS.h" #include "Log.h" #include "fs/Mount.h" #include "thread/Thread.h" #include #include #include namespace VFS { SharedPtr g_root_inode = {}; Inode& root_inode() { return *g_root_inode; } 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 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 = g_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 = g_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, mode_t mode, Credentials auth, SharedPtr working_directory) { auto parent_path = TRY(PathParser::dirname(path)); auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory)); if (!can_write(parent_inode, auth)) return err(EACCES); auto child_name = TRY(PathParser::basename(path)); TRY(validate_filename(child_name.view())); return parent_inode->create_subdirectory(child_name.chars(), mode); } Result> create_file(const char* path, mode_t mode, Credentials auth, SharedPtr working_directory) { auto parent_path = TRY(PathParser::dirname(path)); auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory)); if (!can_write(parent_inode, auth)) return err(EACCES); auto child_name = TRY(PathParser::basename(path)); TRY(validate_filename(child_name.view())); return parent_inode->create_file(child_name.chars(), mode); } Result validate_filename(StringView name) { #ifdef MOON_DISABLE_FILENAME_RESTRICTIONS (void)name; return {}; #endif // Forbid problematic characters that could cause trouble in shell scripts and the like. if (strpbrk(name.chars(), "*?:[]\"<>\\")) return err(EINVAL); // Forbid filenames with leading spaces. if (!name.is_empty() && name[0] == ' ') return err(EINVAL); // Forbid filenames with trailing spaces. if (!name.is_empty() && name[name.length() - 1] == ' ') return err(EINVAL); for (const auto& c : name) { // Forbid filenames with control characters. if (c < 32 || c == 127) return err(EINVAL); } // Forbid filenames that are not valid UTF-8. Utf8StringDecoder decoder(name.chars()); // This will fail if the filename is invalid UTF-8. TRY(decoder.code_points()); return {}; } bool can_execute(SharedPtr inode, Credentials auth) { if (auth.euid == 0) return true; const auto& metadata = inode->metadata(); if (metadata.uid == auth.euid) { return metadata.mode & S_IXUSR; } if (metadata.gid == auth.egid) { return metadata.mode & S_IXGRP; } return metadata.mode & S_IXOTH; } bool can_write(SharedPtr inode, Credentials auth) { if (auth.euid == 0) return true; const auto& metadata = inode->metadata(); if (metadata.uid == auth.euid) { return metadata.mode & S_IWUSR; } if (metadata.gid == auth.egid) { return metadata.mode & S_IWGRP; } return metadata.mode & S_IWOTH; } bool can_read(SharedPtr inode, Credentials auth) { if (auth.euid == 0) return true; const auto& metadata = inode->metadata(); if (metadata.uid == auth.euid) { return metadata.mode & S_IRUSR; } if (metadata.gid == auth.egid) { return metadata.mode & S_IRGRP; } return metadata.mode & S_IROTH; } bool is_setuid(SharedPtr inode) { return inode->metadata().mode & S_ISUID; } bool is_setgid(SharedPtr inode) { return inode->metadata().mode & S_ISGID; } bool is_sticky(SharedPtr inode) { return inode->metadata().mode & S_ISVTX; } bool is_seekable(SharedPtr inode) { return inode->type() != InodeType::FIFO && inode->type() != InodeType::CharacterDevice; } Result mount_root(SharedPtr fs) { check(!g_root_inode); g_root_inode = TRY(MountInode::create({}, fs)); return {}; } Result pivot_root(const char* new_root, const char* put_old, SharedPtr working_directory) { auto new_root_parent = TRY(PathParser::dirname(new_root)); auto new_root_path = TRY(PathParser::basename(new_root)); auto new_root_parent_inode = TRY(VFS::resolve_path(new_root_parent.chars(), Credentials {}, working_directory)); auto new_root_inode = TRY(new_root_parent_inode->find(new_root_path.chars())); if (new_root_inode->type() != VFS::InodeType::Directory) return err(ENOTDIR); if (!new_root_inode->is_mountpoint()) return err(EINVAL); if (new_root_inode->fs() == g_root_inode->fs()) return err(EBUSY); auto parent_path = TRY(PathParser::dirname(put_old)); auto child = TRY(PathParser::basename(put_old)); kdbgln("vfs: Pivoting root from / to %s, using %s as new root", put_old, new_root); auto parent_inode = TRY(resolve_path(parent_path.chars(), Credentials {}, working_directory)); auto inode = TRY(parent_inode->find(child.chars())); if (inode->type() != VFS::InodeType::Directory) return err(ENOTDIR); if (inode->is_mountpoint()) return err(EBUSY); if (inode->fs() != new_root_inode->fs()) return err(EINVAL); auto mount = g_root_inode; TRY(parent_inode->replace_entry(mount, child.chars())); ((MountInode*)mount.ptr())->set_source(inode); g_root_inode = new_root_inode; TRY(new_root_parent_inode->replace_entry(((MountInode*)g_root_inode.ptr())->source(), new_root_path.chars())); ((MountInode*)g_root_inode.ptr())->set_source({}); g_root_inode->fs()->reset_mount_dir(); return {}; } Result mount(const char* path, SharedPtr fs, Credentials auth, SharedPtr working_directory) { auto parent_path = TRY(PathParser::dirname(path)); auto child = TRY(PathParser::basename(path)); #ifdef MOUNT_DEBUG kdbgln("vfs: Mounting filesystem on target %s", path); #endif auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory)); auto inode = TRY(parent_inode->find(child.chars())); if (inode->type() != VFS::InodeType::Directory) return err(ENOTDIR); if (inode->is_mountpoint()) return err(EBUSY); auto mount = TRY(MountInode::create(inode, fs)); TRY(parent_inode->replace_entry(mount, child.chars())); kinfoln("vfs: Successfully mounted filesystem on target %s", path); return {}; } Result umount(const char* path, Credentials auth, SharedPtr working_directory) { auto parent_path = TRY(PathParser::dirname(path)); auto child = TRY(PathParser::basename(path)); if (child.view() == "/") return err(EBUSY); kinfoln("vfs: Unmounting filesystem on target %s", path); auto parent_inode = TRY(resolve_path(parent_path.chars(), auth, working_directory)); auto inode = TRY(parent_inode->find(child.chars())); if (!inode->is_mountpoint()) return err(EINVAL); // There are still open file descriptors referencing files within this file system. if (inode->fs()->handles() != 0) return err(EBUSY); auto mount = (MountInode*)inode.ptr(); TRY(parent_inode->replace_entry(mount->source(), child.chars())); g_mounts.remove(mount); return {}; } }