/* * Copyright (c) 2018-2020, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Kernel { static Singleton s_the; static constexpr int root_mount_flags = 0; UNMAP_AFTER_INIT void VirtualFileSystem::initialize() { s_the.ensure_instance(); } VirtualFileSystem& VirtualFileSystem::the() { return *s_the; } UNMAP_AFTER_INIT VirtualFileSystem::VirtualFileSystem() { } UNMAP_AFTER_INIT VirtualFileSystem::~VirtualFileSystem() = default; InodeIdentifier VirtualFileSystem::root_inode_id() const { VERIFY(m_root_inode); return m_root_inode->identifier(); } bool VirtualFileSystem::mount_point_exists_at_inode(InodeIdentifier inode_identifier) { return m_mounts.with([&](auto& mounts) -> bool { return any_of(mounts, [&inode_identifier](auto const& existing_mount) { return existing_mount.host() && existing_mount.host()->identifier() == inode_identifier; }); }); } ErrorOr VirtualFileSystem::mount(FileSystem& fs, Custody& mount_point, int flags) { auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(fs, &mount_point, flags))); return m_mounts.with([&](auto& mounts) -> ErrorOr { auto& inode = mount_point.inode(); dbgln("VirtualFileSystem: FileSystemID {}, Mounting {} at inode {} with flags {}", fs.fsid(), fs.class_name(), inode.identifier(), flags); if (mount_point_exists_at_inode(inode.identifier())) { dbgln("VirtualFileSystem: Mounting unsuccessful - inode {} is already a mount-point.", inode.identifier()); return EBUSY; } // Note: Actually add a mount for the filesystem and increment the filesystem mounted count new_mount->guest_fs().mounted_count({}).with([&](auto& mounted_count) { mounted_count++; // When this is the first time this FileSystem is mounted, // begin managing the FileSystem by adding it to the list of // managed file systems. This is symmetric with // VirtualFileSystem::unmount()'s `remove()` calls (which remove // the FileSystem once it is no longer mounted). if (mounted_count == 1) { m_file_systems_list.with([&](auto& fs_list) { fs_list.append(fs); }); if (fs.is_file_backed()) { auto& file_backed_fs = static_cast(fs); m_file_backed_file_systems_list.with([&](auto& fs_list) { fs_list.append(file_backed_fs); }); } } }); // NOTE: Leak the mount pointer so it can be added to the mount list, but it won't be // deleted after being added. mounts.append(*new_mount.leak_ptr()); return {}; }); } ErrorOr VirtualFileSystem::bind_mount(Custody& source, Custody& mount_point, int flags) { auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(source.inode(), mount_point, flags))); return m_mounts.with([&](auto& mounts) -> ErrorOr { auto& inode = mount_point.inode(); dbgln("VirtualFileSystem: Bind-mounting inode {} at inode {}", source.inode().identifier(), inode.identifier()); if (mount_point_exists_at_inode(inode.identifier())) { dbgln("VirtualFileSystem: Bind-mounting unsuccessful - inode {} is already a mount-point.", mount_point.inode().identifier()); return EBUSY; } // NOTE: Leak the mount pointer so it can be added to the mount list, but it won't be // deleted after being added. mounts.append(*new_mount.leak_ptr()); return {}; }); } ErrorOr VirtualFileSystem::remount(Custody& mount_point, int new_flags) { dbgln("VirtualFileSystem: Remounting inode {}", mount_point.inode().identifier()); auto* mount = find_mount_for_guest(mount_point.inode().identifier()); if (!mount) return ENODEV; mount->set_flags(new_flags); return {}; } void VirtualFileSystem::sync_filesystems() { Vector, 32> file_systems; m_file_systems_list.with([&](auto const& list) { for (auto& fs : list) file_systems.append(fs); }); for (auto& fs : file_systems) fs->flush_writes(); } void VirtualFileSystem::lock_all_filesystems() { Vector, 32> file_systems; m_file_systems_list.with([&](auto const& list) { for (auto& fs : list) file_systems.append(fs); }); for (auto& fs : file_systems) fs->m_lock.lock(); } ErrorOr VirtualFileSystem::unmount(Custody& mountpoint_custody) { auto& guest_inode = mountpoint_custody.inode(); auto custody_path = TRY(mountpoint_custody.try_serialize_absolute_path()); dbgln("VirtualFileSystem: unmount called with inode {} on mountpoint {}", guest_inode.identifier(), custody_path->view()); return m_mounts.with([&](auto& mounts) -> ErrorOr { for (auto& mount : mounts) { if (&mount.guest() != &guest_inode) continue; auto mountpoint_path = TRY(mount.absolute_path()); if (custody_path->view() != mountpoint_path->view()) continue; NonnullRefPtr fs = mount.guest_fs(); TRY(fs->prepare_to_unmount()); fs->mounted_count({}).with([&](auto& mounted_count) { VERIFY(mounted_count > 0); if (mounted_count == 1) { dbgln("VirtualFileSystem: Unmounting file system {} for the last time...", fs->fsid()); m_file_systems_list.with([&](auto& list) { list.remove(*fs); }); if (fs->is_file_backed()) { dbgln("VirtualFileSystem: Unmounting file backed file system {} for the last time...", fs->fsid()); auto& file_backed_fs = static_cast(*fs); m_file_backed_file_systems_list.with([&](auto& list) { list.remove(file_backed_fs); }); } } else { mounted_count--; } }); dbgln("VirtualFileSystem: Unmounting file system {}...", fs->fsid()); mount.m_vfs_list_node.remove(); // Note: This is balanced by a `new` statement that is happening in various places before inserting the Mount object to the list. delete &mount; return {}; } dbgln("VirtualFileSystem: Nothing mounted on inode {}", guest_inode.identifier()); return ENODEV; }); } ErrorOr VirtualFileSystem::mount_root(FileSystem& fs) { if (m_root_inode) { dmesgln("VirtualFileSystem: mount_root can't mount another root"); return EEXIST; } auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(fs, nullptr, root_mount_flags))); auto& root_inode = fs.root_inode(); if (!root_inode.is_directory()) { dmesgln("VirtualFileSystem: root inode ({}) for / is not a directory :(", root_inode.identifier()); return ENOTDIR; } m_root_inode = root_inode; if (fs.is_file_backed()) { auto pseudo_path = TRY(static_cast(fs).file_description().pseudo_path()); dmesgln("VirtualFileSystem: mounted root({}) from {} ({})", fs.fsid(), fs.class_name(), pseudo_path); m_file_backed_file_systems_list.with([&](auto& list) { list.append(static_cast(fs)); }); } else { dmesgln("VirtualFileSystem: mounted root({}) from {}", fs.fsid(), fs.class_name()); } m_file_systems_list.with([&](auto& fs_list) { fs_list.append(fs); }); fs.mounted_count({}).with([&](auto& mounted_count) { mounted_count++; }); // Note: Actually add a mount for the filesystem and increment the filesystem mounted count m_mounts.with([&](auto& mounts) { // NOTE: Leak the mount pointer so it can be added to the mount list, but it won't be // deleted after being added. mounts.append(*new_mount.leak_ptr()); }); RefPtr new_root_custody = TRY(Custody::try_create(nullptr, ""sv, *m_root_inode, root_mount_flags)); m_root_custody.with([&](auto& root_custody) { swap(root_custody, new_root_custody); }); return {}; } auto VirtualFileSystem::find_mount_for_host(InodeIdentifier id) -> Mount* { return m_mounts.with([&](auto& mounts) -> Mount* { for (auto& mount : mounts) { if (mount.host() && mount.host()->identifier() == id) return &mount; } return nullptr; }); } auto VirtualFileSystem::find_mount_for_guest(InodeIdentifier id) -> Mount* { return m_mounts.with([&](auto& mounts) -> Mount* { for (auto& mount : mounts) { if (mount.guest().identifier() == id) return &mount; } return nullptr; }); } bool VirtualFileSystem::is_vfs_root(InodeIdentifier inode) const { return inode == root_inode_id(); } ErrorOr VirtualFileSystem::traverse_directory_inode(Inode& dir_inode, Function(FileSystem::DirectoryEntryView const&)> callback) { return dir_inode.traverse_as_directory([&](auto& entry) -> ErrorOr { InodeIdentifier resolved_inode; if (auto mount = find_mount_for_host(entry.inode)) resolved_inode = mount->guest().identifier(); else resolved_inode = entry.inode; // FIXME: This is now broken considering chroot and bind mounts. bool is_root_inode = dir_inode.identifier() == dir_inode.fs().root_inode().identifier(); if (is_root_inode && !is_vfs_root(dir_inode.identifier()) && entry.name == "..") { auto mount = find_mount_for_guest(dir_inode.identifier()); VERIFY(mount); VERIFY(mount->host()); resolved_inode = mount->host()->identifier(); } TRY(callback({ entry.name, resolved_inode, entry.file_type })); return {}; }); } ErrorOr VirtualFileSystem::utime(Credentials const& credentials, StringView path, Custody& base, time_t atime, time_t mtime) { auto custody = TRY(resolve_path(credentials, path, base)); auto& inode = custody->inode(); if (!credentials.is_superuser() && inode.metadata().uid != credentials.euid()) return EACCES; if (custody->is_readonly()) return EROFS; TRY(inode.update_timestamps(Time::from_timespec({ atime, 0 }), {}, Time::from_timespec({ mtime, 0 }))); return {}; } ErrorOr VirtualFileSystem::utimensat(Credentials const& credentials, StringView path, Custody& base, timespec const& atime, timespec const& mtime, int options) { auto custody = TRY(resolve_path(credentials, path, base, nullptr, options)); return do_utimens(credentials, custody, atime, mtime); } ErrorOr VirtualFileSystem::do_utimens(Credentials const& credentials, Custody& custody, timespec const& atime, timespec const& mtime) { auto& inode = custody.inode(); if (!credentials.is_superuser() && inode.metadata().uid != credentials.euid()) return EACCES; if (custody.is_readonly()) return EROFS; // NOTE: A standard ext2 inode cannot store nanosecond timestamps. TRY(inode.update_timestamps( (atime.tv_nsec != UTIME_OMIT) ? Time::from_timespec(atime) : Optional