Kernel+Userland: Add immutable mounts

Immutable mounts are mounts that can't be changed in any aspect, if the
VFSRootContext that hold them is used by a process. This includes two
operations on a mount:
1. Trying to remove the mount from the mount table.
2. Trying to change the flags of the mount.

The condition of a VFSRootContext being held by a process or not is
crucial, as the intention is to allow removal of mounts that marked as
immutable if the VFSRootContext is not being used anymore (for example,
if the container that was created with such context stopped).

Marking mounts as immutable on the first VFS root context essentially
ensures they will never be modified because there will be a process
using that context (which is the "main" VFS root context in the system
runtime).

It should be noted that setting a mount as immutable can be done in
creation time of the mount by passing the MS_IMMUTABLE flag, or by doing
a remount with MS_IMMUTABLE flag.
This commit is contained in:
Liav A.
2024-08-09 01:44:22 +03:00
committed by Sönke Holz
parent d6bdb9a3ed
commit 1dfc9e2df3
12 changed files with 316 additions and 199 deletions

View File

@@ -67,6 +67,13 @@ in mount flags of the underlying file system. To "refresh" the working directory
to use the new mount flags after remounting a filesystem, a process can call
`chdir()` with the path to the same directory.
## Immutable mounts
When passing the `MS_IMMUTABLE` flag, it will set a mount as immutable.
An immutable mount cannot change (e.g. changing flags), nor be removed, if the associated VFS root context with mount is being used by a process.
Be extremely careful on setting this flag for mounts on the main VFS root context (i.e. the VFS root context the system starts with) - naturally, you will not be able to remove or change such mount until a complete reboot.
## Errors
- `EINVAL`: The `flags` value contains deprecated flags such as `MS_REMOUNT` or `MS_BIND`.

View File

@@ -31,6 +31,7 @@ extern "C" {
#define MS_AXALLOWED (1 << 7)
#define MS_NOREGULAR (1 << 8)
#define MS_SRCHIDDEN (1 << 9)
#define MS_IMMUTABLE (1 << 10)
enum {
_SC_MONOTONIC_CLOCK,

View File

@@ -12,18 +12,31 @@
namespace Kernel {
Mount::Mount(NonnullRefPtr<Inode> source, int flags)
: m_guest_fs(source->fs())
, m_guest(move(source))
, m_flags(flags)
: m_details(Mount::Details(source->fs(), move(source)))
{
set_flags(flags);
}
Mount::Mount(NonnullRefPtr<Inode> source, NonnullRefPtr<Custody> host_custody, int flags)
: m_guest_fs(source->fs())
, m_guest(move(source))
: m_details(Mount::Details(source->fs(), move(source)))
, m_host_custody(move(host_custody))
, m_flags(flags)
{
set_flags(flags);
}
void Mount::set_flags(int flags)
{
// NOTE: We use a spinlock to serialize access, to protect against
// a case which the user requested to set the immutable flag, and
// there's another ongoing call to set the flags without it.
m_flags.with([this, flags](auto& current_flags) {
if (flags & MS_IMMUTABLE)
m_immutable.set();
current_flags = flags;
if (m_immutable.was_set())
current_flags |= MS_IMMUTABLE;
});
}
void Mount::delete_mount_from_list(Mount& mount)

View File

@@ -21,6 +21,11 @@ class Mount {
friend class VFSRootContext;
public:
struct Details {
NonnullRefPtr<FileSystem> guest_fs;
NonnullRefPtr<Inode> guest;
};
// NOTE: This constructor is valid for VFSRootContext root inodes (as for the "/" directory)
Mount(NonnullRefPtr<Inode> source, int flags);
@@ -32,24 +37,33 @@ public:
RefPtr<Custody const> host_custody() const;
RefPtr<Custody> host_custody();
Inode const& guest() const { return *m_guest; }
Inode& guest() { return *m_guest; }
Inode const& guest() const { return *m_details.guest; }
Inode& guest() { return *m_details.guest; }
FileSystem const& guest_fs() const { return *m_guest_fs; }
FileSystem& guest_fs() { return *m_guest_fs; }
FileSystem const& guest_fs() const { return *m_details.guest_fs; }
FileSystem& guest_fs() { return *m_details.guest_fs; }
ErrorOr<NonnullOwnPtr<KString>> absolute_path() const;
int flags() const { return m_flags; }
void set_flags(int flags) { m_flags = flags; }
int flags() const
{
return m_flags.with([](auto const& current_flags) -> int { return current_flags; });
}
void set_flags(int flags);
static void delete_mount_from_list(Mount&);
bool is_immutable() const { return m_immutable.was_set(); }
Details const& details() const { return m_details; }
private:
NonnullRefPtr<FileSystem> const m_guest_fs;
NonnullRefPtr<Inode> const m_guest;
Details const m_details;
RefPtr<Custody> const m_host_custody;
int m_flags { 0 };
SpinlockProtected<int, LockRank::None> m_flags { 0 };
SetOnce m_immutable;
IntrusiveListNode<Mount> m_vfs_list_node;
};

View File

@@ -50,8 +50,8 @@ Custody const& VFSRootContext::empty_context_custody_for_kernel_processes()
ErrorOr<void> VFSRootContext::for_each_mount(Function<ErrorOr<void>(Mount const&)> callback) const
{
return mounts().with([&](auto& mounts) -> ErrorOr<void> {
for (auto& mount : mounts)
return m_details.with([&](auto& details) -> ErrorOr<void> {
for (auto& mount : details.mounts)
TRY(callback(mount));
return {};
});
@@ -98,14 +98,14 @@ ErrorOr<NonnullRefPtr<VFSRootContext>> VFSRootContext::create_with_empty_ramfs()
auto root_custody = TRY(Custody::try_create(nullptr, ""sv, fs->root_inode(), 0));
auto context = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) VFSRootContext(root_custody)));
auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(fs->root_inode(), 0)));
TRY(context->mounts().with([&](auto& mounts) -> ErrorOr<void> {
TRY(context->m_details.with([&](auto& details) -> ErrorOr<void> {
dbgln("VFSRootContext({}): Root (\"/\") FileSystemID {}, Mounting {} at inode {} with flags {}",
context->id(),
fs->fsid(),
fs->class_name(),
root_custody->inode().identifier(),
0);
add_to_mounts_list_and_increment_fs_mounted_count(DoBindMount::No, mounts, move(new_mount));
add_to_mounts_list_and_increment_fs_mounted_count(DoBindMount::No, details.mounts, move(new_mount));
return {};
}));
@@ -116,32 +116,225 @@ ErrorOr<NonnullRefPtr<VFSRootContext>> VFSRootContext::create_with_empty_ramfs()
return context;
}
void VFSRootContext::set_attached(Badge<Process>)
ErrorOr<void> VFSRootContext::pivot_root(FileBackedFileSystem::List& file_backed_file_systems_list, FileSystem& fs, NonnullOwnPtr<Mount> new_mount, NonnullRefPtr<Custody> root_mount_point, int root_mount_flags)
{
m_attributes.with([](auto& attributes) {
attributes.attached_by_process.set();
return m_details.with([&](auto& details) -> ErrorOr<void> {
return fs.mounted_count().with([&](auto& mounted_count) -> ErrorOr<void> {
// NOTE: If the mounted count is 0, then this filesystem is about to be
// deleted, so this must be a kernel bug as we don't include such filesystem
// in the VirtualFileSystem s_details->file_backed_file_systems_list list anymore.
VERIFY(mounted_count > 0);
// NOTE: The mounts table should not be empty as it always need
// to have at least one mount!
VERIFY(!details.mounts.is_empty());
// NOTE: If we have many mounts in the table, then simply don't allow
// userspace to override them but instead require to unmount everything except
// the root mount first.
if (details.mounts.size_slow() != 1)
return EPERM;
auto& mount = *details.mounts.first();
TRY(VirtualFileSystem::remove_mount(mount, file_backed_file_systems_list));
VERIFY(details.mounts.is_empty());
dbgln("VFSRootContext({}): Root mount set to FileSystemID {}, Mounting {} at inode {} with flags {}",
id(),
new_mount->guest_fs().fsid(),
new_mount->guest_fs().class_name(),
root_mount_point->inode().identifier(),
root_mount_flags);
// NOTE: Leak the mount pointer so it can be added to the mount list, but it won't be
// deleted after being added.
details.mounts.append(*new_mount.leak_ptr());
// NOTE: We essentially do the same thing like VFSRootContext::add_to_mounts_list_and_increment_fs_mounted_count function
// but because we already locked the spinlock of the attach count, we can't call that function here.
mounted_count++;
// NOTE: Now fill the root custody with a valid custody for the new root mount.
m_root_custody.with([&root_mount_point](auto& custody) {
custody = move(root_mount_point);
});
return {};
});
});
}
ErrorOr<void> VFSRootContext::do_full_teardown(Badge<PowerStateSwitchTask>)
{
// NOTE: We are going to tear down the entire VFS root context from its mounts.
// To do this properly, we swap out the original root custody with the empty
// root custody for vfs root context of kernel processes.
m_root_custody.with([](auto& custody) {
custody = VFSRootContext::empty_context_custody_for_kernel_processes();
});
auto unmount_was_successful = true;
while (unmount_was_successful) {
unmount_was_successful = false;
Vector<Mount&, 16> mounts;
TRY(m_details.with([&mounts](auto& details) -> ErrorOr<void> {
for (auto& mount : details.mounts) {
TRY(mounts.try_append(const_cast<Mount&>(mount)));
}
return {};
}));
if (mounts.is_empty())
break;
auto const remaining_mounts = mounts.size();
while (!mounts.is_empty()) {
auto& mount = mounts.take_last();
TRY(mount.guest_fs().flush_writes());
auto mount_path = TRY(mount.absolute_path());
auto& mount_inode = mount.guest();
auto const result = VirtualFileSystem::unmount(*this, mount_inode, mount_path->view());
if (result.is_error()) {
dbgln("Error during unmount of {}: {}", mount_path, result.error());
// FIXME: For unknown reasons the root FS stays busy even after everything else has shut down and was unmounted.
// Until we find the underlying issue, allow an unclean shutdown here.
if (remaining_mounts <= 1)
dbgln("BUG! One mount remaining; the root file system may not be unmountable at all. Shutting down anyways.");
} else {
unmount_was_successful = true;
}
}
}
return {};
}
ErrorOr<void> VFSRootContext::unmount(FileBackedFileSystem::List& file_backed_file_systems_list, Inode& guest_inode, StringView custody_path)
{
return m_details.with([&](auto& details) -> ErrorOr<void> {
bool did_unmount = false;
for (auto& mount : details.mounts) {
if (&mount.guest() != &guest_inode)
continue;
auto mountpoint_path = TRY(mount.absolute_path());
if (custody_path != mountpoint_path->view())
continue;
TRY(VFSRootContext::validate_mount_not_immutable_while_being_used(details, mount));
dbgln("VFSRootContext({}): Unmounting {}...", id(), custody_path);
TRY(VirtualFileSystem::remove_mount(mount, file_backed_file_systems_list));
did_unmount = true;
break;
}
if (!did_unmount) {
dbgln("VFSRootContext: Nothing mounted on inode {}", guest_inode.identifier());
return ENODEV;
}
// NOTE: The VFSRootContext mount table is not empty and we
// successfully deleted the desired mount from it, so return
// a success now.
if (!details.mounts.is_empty())
return {};
// NOTE: If the mount table is empty, then the VFSRootContext
// is no longer in valid state (each VFSRootContext at least should
// have a root mount), so remove it now.
VFSRootContext::all_root_contexts_list().with([this](auto& list) {
dbgln("VFSRootContext: Nothing mounted in VFSRootContext({}), removing it", id());
list.remove(*this);
});
return {};
});
}
void VFSRootContext::detach(Badge<Process>)
{
m_details.with([](auto& details) {
VERIFY(details.attached_by_process.was_set());
VERIFY(details.attach_count > 0);
details.attach_count--;
});
}
void VFSRootContext::attach(Badge<Process>)
{
m_details.with([](auto& details) {
details.attached_by_process.set();
details.attach_count++;
});
}
bool VFSRootContext::mount_point_exists_at_custody(Custody& mount_point)
{
return m_mounts.with([&](auto& mounts) -> bool {
return any_of(mounts, [&mount_point](auto const& existing_mount) {
return m_details.with([&](auto& details) -> bool {
return any_of(details.mounts, [&mount_point](auto const& existing_mount) {
return existing_mount.host_custody() && VirtualFileSystem::check_matching_absolute_path_hierarchy(*existing_mount.host_custody(), mount_point);
});
});
}
ErrorOr<void> VFSRootContext::do_on_mount_for_host_custody(ValidateImmutableFlag validate_immutable_flag, Custody const& current_custody, Function<void(Mount&)> callback) const
{
VERIFY(validate_immutable_flag == ValidateImmutableFlag::Yes || validate_immutable_flag == ValidateImmutableFlag::No);
return m_details.with([&](auto& details) -> ErrorOr<void> {
// NOTE: We either search for the root mount or for a mount that has a parent custody!
if (!current_custody.parent()) {
for (auto& mount : details.mounts) {
if (!mount.host_custody()) {
if (validate_immutable_flag == ValidateImmutableFlag::Yes)
TRY(VFSRootContext::validate_mount_not_immutable_while_being_used(details, mount));
callback(mount);
return {};
}
}
// NOTE: There must be a root mount entry, so fail if we don't find it.
VERIFY_NOT_REACHED();
} else {
for (auto& mount : details.mounts) {
if (mount.host_custody() && VirtualFileSystem::check_matching_absolute_path_hierarchy(*mount.host_custody(), current_custody)) {
if (validate_immutable_flag == ValidateImmutableFlag::Yes)
TRY(VFSRootContext::validate_mount_not_immutable_while_being_used(details, mount));
callback(mount);
return {};
}
}
}
return Error::from_errno(ENODEV);
});
}
ErrorOr<void> VFSRootContext::apply_to_mount_for_host_custody(Custody const& current_custody, Function<void(Mount&)> callback)
{
return do_on_mount_for_host_custody(ValidateImmutableFlag::Yes, current_custody, move(callback));
}
ErrorOr<VFSRootContext::CurrentMountState> VFSRootContext::current_mount_state_for_host_custody(Custody const& current_custody) const
{
RefPtr<FileSystem> guest_fs;
RefPtr<Inode> guest;
int mount_flags_for_child { 0 };
TRY(do_on_mount_for_host_custody(ValidateImmutableFlag::No, current_custody, [&guest, &guest_fs, &mount_flags_for_child](auto const& mount) {
guest = mount.guest();
guest_fs = mount.guest_fs();
mount_flags_for_child = mount.flags();
}));
return VFSRootContext::CurrentMountState {
Mount::Details { guest_fs.release_nonnull(), guest.release_nonnull() },
mount_flags_for_child
};
}
ErrorOr<void> VFSRootContext::add_new_mount(DoBindMount do_bind_mount, Inode& source, Custody& mount_point, int flags)
{
auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(source, mount_point, flags)));
return m_mounts.with([&](auto& mounts) -> ErrorOr<void> {
return m_details.with([&](auto& details) -> ErrorOr<void> {
// NOTE: The VFSRootContext should be attached to the list if there's
// at least one mount in the mount table.
// We also should have at least one mount in the table because
// this method shouldn't be called for new contexts when adding
// their root mounts.
VERIFY(!mounts.is_empty());
VERIFY(!details.mounts.is_empty());
VFSRootContext::all_root_contexts_list().with([&](auto& list) {
VERIFY(list.contains(*this));
});
@@ -163,7 +356,7 @@ ErrorOr<void> VFSRootContext::add_new_mount(DoBindMount do_bind_mount, Inode& so
dbgln("VFSRootContext({}): Mounting unsuccessful - inode {} is already a mount-point.", id(), mount_point.inode().identifier());
return EBUSY;
}
add_to_mounts_list_and_increment_fs_mounted_count(do_bind_mount, mounts, move(new_mount));
add_to_mounts_list_and_increment_fs_mounted_count(do_bind_mount, details.mounts, move(new_mount));
return {};
});
}

View File

@@ -14,6 +14,7 @@
#include <Kernel/FileSystem/FileBackedFileSystem.h>
#include <Kernel/FileSystem/FileSystem.h>
#include <Kernel/FileSystem/Mount.h>
#include <Kernel/Forward.h>
#include <Kernel/Locking/SpinlockProtected.h>
namespace Kernel {
@@ -35,15 +36,6 @@ public:
SpinlockProtected<NonnullRefPtr<Custody>, LockRank::None>& root_custody() { return m_root_custody; }
SpinlockProtected<NonnullRefPtr<Custody>, LockRank::None> const& root_custody() const { return m_root_custody; }
SpinlockProtected<IntrusiveList<&Mount::m_vfs_list_node>, LockRank::None>& mounts() { return m_mounts; }
SpinlockProtected<IntrusiveList<&Mount::m_vfs_list_node>, LockRank::None> const& mounts() const { return m_mounts; }
struct Attributes {
SetOnce attached_by_process;
SetOnce immutable;
};
SpinlockProtected<Attributes, LockRank::None>& attributes() { return m_attributes; }
bool mount_point_exists_at_custody(Custody& mount_point);
enum class DoBindMount {
@@ -52,20 +44,53 @@ public:
};
ErrorOr<void> add_new_mount(DoBindMount, Inode& source, Custody& mount_point, int flags);
ErrorOr<void> do_full_teardown(Badge<PowerStateSwitchTask>);
ErrorOr<void> unmount(FileBackedFileSystem::List& file_backed_file_systems_list, Inode& guest_inode, StringView custody_path);
ErrorOr<void> pivot_root(FileBackedFileSystem::List& file_backed_file_systems_list, FileSystem& fs, NonnullOwnPtr<Mount> new_mount, NonnullRefPtr<Custody> root_mount_point, int root_mount_flags);
ErrorOr<void> apply_to_mount_for_host_custody(Custody const& current_custody, Function<void(Mount&)>);
struct CurrentMountState {
Mount::Details details;
int flags { 0 };
};
ErrorOr<CurrentMountState> current_mount_state_for_host_custody(Custody const& current_custody) const;
IndexID id() const { return m_id; }
void set_attached(Badge<Process>);
void attach(Badge<Process>);
void detach(Badge<Process>);
ErrorOr<void> for_each_mount(Function<ErrorOr<void>(Mount const&)>) const;
private:
VFSRootContext(NonnullRefPtr<Custody> custody);
enum class ValidateImmutableFlag {
Yes,
No,
};
ErrorOr<void> do_on_mount_for_host_custody(ValidateImmutableFlag validate_immutable_flag, Custody const& current_custody, Function<void(Mount&)> callback) const;
static void add_to_mounts_list_and_increment_fs_mounted_count(DoBindMount do_bind_mount, IntrusiveList<&Mount::m_vfs_list_node>&, NonnullOwnPtr<Mount>);
SpinlockProtected<Attributes, LockRank::None> m_attributes {};
struct Details {
SetOnce attached_by_process;
size_t attach_count { 0 };
IntrusiveList<&Mount::m_vfs_list_node> mounts;
};
static inline ErrorOr<void> validate_mount_not_immutable_while_being_used(Details& details, Mount& mount)
{
if (mount.is_immutable() && details.attach_count > 0)
return Error::from_errno(EPERM);
return {};
}
mutable SpinlockProtected<Details, LockRank::None> m_details {};
SpinlockProtected<NonnullRefPtr<Custody>, LockRank::None> m_root_custody;
SpinlockProtected<IntrusiveList<&Mount::m_vfs_list_node>, LockRank::None> m_mounts {};
IntrusiveListNode<VFSRootContext, NonnullRefPtr<VFSRootContext>> m_list_node;

View File

@@ -47,10 +47,6 @@ static ErrorOr<NonnullRefPtr<FileSystem>> create_and_initialize_filesystem_from_
static ErrorOr<NonnullRefPtr<FileSystem>> create_and_initialize_filesystem_from_mount_file_and_description(FileBackedFileSystem::List& file_backed_fs_list, MountFile& mount_file, OpenFileDescription& source_description);
static ErrorOr<void> verify_mount_file_and_description_requirements(MountFile& mount_file, OpenFileDescription& source_description);
static ErrorOr<void> remove_mount(Mount& mount, FileBackedFileSystem::List& file_backed_fs_list);
ErrorOr<void> apply_to_mount_for_host_custody(VFSRootContext&, Custody const& current_custody, Function<void(Mount&)>);
struct VirtualFileSystemDetails {
// NOTE: The FileBackedFileSystem list is protected by a mutex because we need to scan it
// to search for existing filesystems for already used block devices and therefore when doing
@@ -266,7 +262,7 @@ ErrorOr<void> VirtualFileSystem::remount(VFSRootContext& context, Custody& mount
{
dbgln("VirtualFileSystem: Remounting inode {}", mount_point.inode().identifier());
TRY(apply_to_mount_for_host_custody(context, mount_point, [new_flags](auto& mount) {
TRY(context.apply_to_mount_for_host_custody(mount_point, [new_flags](auto& mount) {
mount.set_flags(new_flags);
}));
return {};
@@ -295,7 +291,7 @@ ErrorOr<void> VirtualFileSystem::unmount(VFSRootContext& context, Custody& mount
return unmount(context, guest_inode, custody_path->view());
}
ErrorOr<void> remove_mount(Mount& mount, FileBackedFileSystem::List& file_backed_fs_list)
ErrorOr<void> VirtualFileSystem::remove_mount(Mount& mount, FileBackedFileSystem::List& file_backed_fs_list)
{
NonnullRefPtr<FileSystem> fs = mount.guest_fs();
TRY(fs->prepare_to_unmount(mount.guest()));
@@ -325,113 +321,14 @@ ErrorOr<void> VirtualFileSystem::pivot_root_by_copying_mounted_fs_instance(VFSRo
auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(fs.root_inode(), root_mount_flags)));
return s_details->file_backed_file_systems_list.with_exclusive([&](auto& file_backed_file_systems_list) -> ErrorOr<void> {
return context.mounts().with([&](auto& mounts) -> ErrorOr<void> {
return fs.mounted_count().with([&](auto& mounted_count) -> ErrorOr<void> {
// NOTE: If the mounted count is 0, then this filesystem is about to be
// deleted, so this must be a kernel bug as we don't include such filesystem
// in the s_details->file_backed_file_systems_list list anymore.
VERIFY(mounted_count > 0);
// NOTE: The mounts table should not be empty as it always need
// to have at least one mount!
VERIFY(!mounts.is_empty());
// NOTE: If we have many mounts in the table, then simply don't allow
// userspace to override them but instead require to unmount everything except
// the root mount first.
if (mounts.size_slow() != 1)
return EPERM;
auto& mount = *mounts.first();
TRY(remove_mount(mount, file_backed_file_systems_list));
VERIFY(mounts.is_empty());
dbgln("VFSRootContext({}): Root mount set to FileSystemID {}, Mounting {} at inode {} with flags {}",
context.id(),
new_mount->guest_fs().fsid(),
new_mount->guest_fs().class_name(),
root_mount_point->inode().identifier(),
root_mount_flags);
// 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());
// NOTE: We essentially do the same thing like VFSRootContext::add_to_mounts_list_and_increment_fs_mounted_count function
// but because we already locked the spinlock of the attach count, we can't call that function here.
mounted_count++;
// NOTE: Now fill the root custody with a valid custody for the new root mount.
context.root_custody().with([&root_mount_point](auto& custody) {
custody = root_mount_point;
});
return {};
});
});
return context.pivot_root(file_backed_file_systems_list, fs, move(new_mount), move(root_mount_point), root_mount_flags);
});
}
ErrorOr<void> VirtualFileSystem::unmount(VFSRootContext& context, Inode& guest_inode, StringView custody_path)
{
return s_details->file_backed_file_systems_list.with_exclusive([&](auto& file_backed_fs_list) -> ErrorOr<void> {
TRY(context.mounts().with([&](auto& mounts) -> ErrorOr<void> {
bool did_unmount = false;
for (auto& mount : mounts) {
if (&mount.guest() != &guest_inode)
continue;
auto mountpoint_path = TRY(mount.absolute_path());
if (custody_path != mountpoint_path->view())
continue;
dbgln("VFSRootContext({}): Unmounting {}...", context.id(), custody_path);
TRY(remove_mount(mount, file_backed_fs_list));
did_unmount = true;
break;
}
if (!did_unmount) {
dbgln("VirtualFileSystem: Nothing mounted on inode {}", guest_inode.identifier());
return ENODEV;
}
// NOTE: The VFSRootContext mount table is not empty and we
// successfully deleted the desired mount from it, so return
// a success now.
if (!mounts.is_empty())
return {};
// NOTE: If the mount table is empty, then the VFSRootContext
// is no longer in valid state (each VFSRootContext at least should
// have a root mount), so remove it now.
s_details->root_contexts.with([&context](auto& list) {
dbgln("VirtualFileSystem: Nothing mounted in VFSRootContext({}), removing it", context.id());
list.remove(context);
});
return {};
}));
return {};
});
}
ErrorOr<void> apply_to_mount_for_host_custody(VFSRootContext& context, Custody const& current_custody, Function<void(Mount&)> callback)
{
return context.mounts().with([&](auto& mounts) -> ErrorOr<void> {
// NOTE: We either search for the root mount or for a mount that has a parent custody!
if (!current_custody.parent()) {
for (auto& mount : mounts) {
if (!mount.host_custody()) {
callback(mount);
return {};
}
}
// NOTE: There must be a root mount entry, so fail if we don't find it.
VERIFY_NOT_REACHED();
} else {
for (auto& mount : mounts) {
if (mount.host_custody() && VirtualFileSystem::check_matching_absolute_path_hierarchy(*mount.host_custody(), current_custody)) {
callback(mount);
return {};
}
}
}
return Error::from_errno(ENODEV);
return s_details->file_backed_file_systems_list.with_exclusive([&](auto& file_backed_file_systems_list) -> ErrorOr<void> {
return context.unmount(file_backed_file_systems_list, guest_inode, custody_path);
});
}
@@ -1230,11 +1127,11 @@ ErrorOr<NonnullRefPtr<Custody>> VirtualFileSystem::resolve_path_without_veil(VFS
// See if there's something mounted on the child; in that case
// we would need to return the guest inode, not the host inode.
auto found_mount_or_error = apply_to_mount_for_host_custody(const_cast<VFSRootContext&>(vfs_root_context), current_custody, [&child_inode, &mount_flags_for_child](auto& mount) {
child_inode = mount.guest();
mount_flags_for_child = mount.flags();
});
if (!found_mount_or_error.is_error()) {
auto found_mount_state_or_error = vfs_root_context.current_mount_state_for_host_custody(current_custody);
if (!found_mount_state_or_error.is_error()) {
auto found_mount_state = found_mount_state_or_error.release_value();
child_inode = found_mount_state.details.guest;
mount_flags_for_child = found_mount_state.flags;
custody = TRY(Custody::try_create(&parent, part, *child_inode, mount_flags_for_child));
} else {
custody = current_custody;

View File

@@ -56,6 +56,8 @@ bool check_matching_absolute_path_hierarchy(Custody const& first_custody, Custod
ErrorOr<FileSystemInitializer const*> find_filesystem_type_initializer(StringView fs_type);
ErrorOr<void> remove_mount(Mount& mount, FileBackedFileSystem::List& file_backed_fs_list);
ErrorOr<void> mount(VFSRootContext&, MountFile&, OpenFileDescription*, Custody& mount_point, int flags);
ErrorOr<void> pivot_root_by_copying_mounted_fs_instance(VFSRootContext&, FileSystem& fs, int root_mount_flags);

View File

@@ -82,7 +82,7 @@ ErrorOr<FlatPtr> Process::sys$unshare_attach(Userspace<Syscall::SC_unshare_attac
m_attached_vfs_root_context.with([vfs_root_context](auto& context) {
context = vfs_root_context;
});
vfs_root_context->set_attached({});
vfs_root_context->attach({});
return 0;
}
case UnshareType::HostnameContext: {

View File

@@ -57,50 +57,6 @@ void PowerStateSwitchTask::spawn(PowerStateCommand command)
g_power_state_switch_task = move(power_state_switch_task_thread);
}
static ErrorOr<void> unmount_mounts_on_vfs_root_context(VFSRootContext& vfs_root_context)
{
// NOTE: We are going to tear down the entire VFS root context from its mounts.
// To do this properly, we swap out the original root custody with the empty
// root custody for vfs root context of kernel processes.
vfs_root_context.root_custody().with([](auto& custody) {
custody = VFSRootContext::empty_context_custody_for_kernel_processes();
});
auto unmount_was_successful = true;
while (unmount_was_successful) {
unmount_was_successful = false;
Vector<Mount&, 16> mounts;
TRY(vfs_root_context.mounts().with([&mounts](auto& list) -> ErrorOr<void> {
for (auto& mount : list) {
TRY(mounts.try_append(const_cast<Mount&>(mount)));
}
return {};
}));
if (mounts.is_empty())
break;
auto const remaining_mounts = mounts.size();
while (!mounts.is_empty()) {
auto& mount = mounts.take_last();
TRY(mount.guest_fs().flush_writes());
auto mount_path = TRY(mount.absolute_path());
auto& mount_inode = mount.guest();
auto const result = VirtualFileSystem::unmount(vfs_root_context, mount_inode, mount_path->view());
if (result.is_error()) {
dbgln("Error during unmount of {}: {}", mount_path, result.error());
// FIXME: For unknown reasons the root FS stays busy even after everything else has shut down and was unmounted.
// Until we find the underlying issue, allow an unclean shutdown here.
if (remaining_mounts <= 1)
dbgln("BUG! One mount remaining; the root file system may not be unmountable at all. Shutting down anyways.");
} else {
unmount_was_successful = true;
}
}
}
return {};
}
ErrorOr<void> PowerStateSwitchTask::perform_shutdown(PowerStateSwitchTask::DoReboot do_reboot)
{
// We assume that by this point userland has tried as much as possible to shut down everything in an orderly fashion.
@@ -146,7 +102,7 @@ ErrorOr<void> PowerStateSwitchTask::perform_shutdown(PowerStateSwitchTask::DoReb
});
for (size_t index = 0; index < collected_contexts_count; index++) {
VERIFY(contexts[index]);
TRY(unmount_mounts_on_vfs_root_context(*contexts[index]));
TRY(contexts[index]->do_full_teardown({}));
}
} while (collected_contexts_count > 0);

View File

@@ -352,7 +352,7 @@ Process::Process(StringView name, NonnullRefPtr<Credentials> credentials, Proces
}
m_attached_vfs_root_context.with([](auto& context) {
context->set_attached({});
context->attach({});
});
m_attached_hostname_context.with([](auto& context) {
@@ -892,6 +892,11 @@ void Process::finalize()
context = nullptr;
});
m_attached_vfs_root_context.with([](auto& context) {
context->detach({});
context = nullptr;
});
m_state.store(State::Dead, AK::MemoryOrder::memory_order_release);
{

View File

@@ -45,6 +45,8 @@ static int parse_options(StringView options)
flags |= MS_NOREGULAR;
else if (part == "srchidden")
flags |= MS_SRCHIDDEN;
else if (part == "immutable")
flags |= MS_IMMUTABLE;
else
warnln("Ignoring invalid option: {}", part);
}
@@ -179,6 +181,8 @@ static ErrorOr<void> print_mounts()
else
out("rw");
if (mount_flags & MS_IMMUTABLE)
out(",immutable");
if (mount_flags & MS_NODEV)
out(",nodev");
if (mount_flags & MS_NOREGULAR)