2020-01-18 08:38:21 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
|
|
*
|
2021-04-22 08:24:48 +00:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 08:38:21 +00:00
|
|
|
*/
|
|
|
|
|
2022-08-10 15:50:23 +00:00
|
|
|
#include <AK/AnyOf.h>
|
2021-08-18 11:22:52 +00:00
|
|
|
#include <AK/GenericLexer.h>
|
2022-08-20 23:04:35 +00:00
|
|
|
#include <AK/RefPtr.h>
|
2020-08-25 01:35:19 +00:00
|
|
|
#include <AK/Singleton.h>
|
2018-10-28 11:20:25 +00:00
|
|
|
#include <AK/StringBuilder.h>
|
2021-09-12 11:29:28 +00:00
|
|
|
#include <Kernel/API/POSIX/errno.h>
|
2021-01-25 15:07:10 +00:00
|
|
|
#include <Kernel/Debug.h>
|
2020-02-16 00:50:16 +00:00
|
|
|
#include <Kernel/Devices/BlockDevice.h>
|
2021-09-11 06:19:20 +00:00
|
|
|
#include <Kernel/Devices/DeviceManagement.h>
|
2019-05-30 15:46:08 +00:00
|
|
|
#include <Kernel/FileSystem/Custody.h>
|
2020-04-06 08:54:21 +00:00
|
|
|
#include <Kernel/FileSystem/FileBackedFileSystem.h>
|
2019-06-07 17:29:34 +00:00
|
|
|
#include <Kernel/FileSystem/FileSystem.h>
|
2021-09-07 11:39:11 +00:00
|
|
|
#include <Kernel/FileSystem/OpenFileDescription.h>
|
2019-06-07 17:29:34 +00:00
|
|
|
#include <Kernel/FileSystem/VirtualFileSystem.h>
|
2021-07-06 09:21:52 +00:00
|
|
|
#include <Kernel/KLexicalPath.h>
|
2020-02-16 00:27:42 +00:00
|
|
|
#include <Kernel/KSyms.h>
|
2019-06-07 09:43:58 +00:00
|
|
|
#include <Kernel/Process.h>
|
2021-06-22 15:40:16 +00:00
|
|
|
#include <Kernel/Sections.h>
|
2018-10-10 09:53:07 +00:00
|
|
|
|
2020-02-16 00:27:42 +00:00
|
|
|
namespace Kernel {
|
|
|
|
|
2021-08-07 19:34:11 +00:00
|
|
|
static Singleton<VirtualFileSystem> s_the;
|
2023-02-11 08:34:57 +00:00
|
|
|
static constexpr int root_mount_flags = 0;
|
2018-10-18 08:27:07 +00:00
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
UNMAP_AFTER_INIT void VirtualFileSystem::initialize()
|
2020-08-25 01:35:19 +00:00
|
|
|
{
|
|
|
|
s_the.ensure_instance();
|
|
|
|
}
|
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
VirtualFileSystem& VirtualFileSystem::the()
|
2018-10-18 08:27:07 +00:00
|
|
|
{
|
|
|
|
return *s_the;
|
|
|
|
}
|
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
UNMAP_AFTER_INIT VirtualFileSystem::VirtualFileSystem()
|
2018-10-10 09:53:07 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-03-16 19:15:15 +00:00
|
|
|
UNMAP_AFTER_INIT VirtualFileSystem::~VirtualFileSystem() = default;
|
2018-10-30 14:33:37 +00:00
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
InodeIdentifier VirtualFileSystem::root_inode_id() const
|
2018-11-18 22:28:43 +00:00
|
|
|
{
|
2021-02-23 19:42:32 +00:00
|
|
|
VERIFY(m_root_inode);
|
2019-01-16 11:57:07 +00:00
|
|
|
return m_root_inode->identifier();
|
2018-11-18 22:28:43 +00:00
|
|
|
}
|
|
|
|
|
2022-08-10 15:50:23 +00:00
|
|
|
bool VirtualFileSystem::mount_point_exists_at_inode(InodeIdentifier inode_identifier)
|
|
|
|
{
|
|
|
|
return m_mounts.with([&](auto& mounts) -> bool {
|
2022-08-20 23:04:35 +00:00
|
|
|
return any_of(mounts, [&inode_identifier](auto const& existing_mount) {
|
2022-11-21 20:10:56 +00:00
|
|
|
return existing_mount.host() && existing_mount.host()->identifier() == inode_identifier;
|
2022-08-10 15:50:23 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-11-07 23:51:39 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::mount(FileSystem& fs, Custody& mount_point, int flags)
|
2019-08-02 17:03:50 +00:00
|
|
|
{
|
2022-08-20 23:04:35 +00:00
|
|
|
auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(fs, &mount_point, flags)));
|
2022-02-03 00:37:46 +00:00
|
|
|
return m_mounts.with([&](auto& mounts) -> ErrorOr<void> {
|
2021-08-15 23:40:19 +00:00
|
|
|
auto& inode = mount_point.inode();
|
2022-08-19 21:03:24 +00:00
|
|
|
dbgln("VirtualFileSystem: FileSystemID {}, Mounting {} at inode {} with flags {}",
|
|
|
|
fs.fsid(),
|
2021-08-15 23:40:19 +00:00
|
|
|
fs.class_name(),
|
|
|
|
inode.identifier(),
|
|
|
|
flags);
|
2022-08-10 15:50:23 +00:00
|
|
|
if (mount_point_exists_at_inode(inode.identifier())) {
|
|
|
|
dbgln("VirtualFileSystem: Mounting unsuccessful - inode {} is already a mount-point.", inode.identifier());
|
|
|
|
return EBUSY;
|
|
|
|
}
|
2022-08-19 21:03:24 +00:00
|
|
|
// 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++;
|
2023-01-08 00:19:43 +00:00
|
|
|
|
|
|
|
// 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<FileBackedFileSystem&>(fs);
|
|
|
|
m_file_backed_file_systems_list.with([&](auto& fs_list) {
|
|
|
|
fs_list.append(file_backed_fs);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-08-19 21:03:24 +00:00
|
|
|
});
|
2022-11-21 20:10:56 +00:00
|
|
|
|
|
|
|
// 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());
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2021-08-15 23:40:19 +00:00
|
|
|
});
|
2019-08-02 17:03:50 +00:00
|
|
|
}
|
|
|
|
|
2021-11-07 23:51:39 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::bind_mount(Custody& source, Custody& mount_point, int flags)
|
2020-01-11 16:08:35 +00:00
|
|
|
{
|
2022-08-20 23:04:35 +00:00
|
|
|
auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(source.inode(), mount_point, flags)));
|
2022-02-03 00:37:46 +00:00
|
|
|
return m_mounts.with([&](auto& mounts) -> ErrorOr<void> {
|
2022-08-10 15:50:23 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-11-21 20:10:56 +00:00
|
|
|
|
|
|
|
// 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());
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2021-08-15 23:40:19 +00:00
|
|
|
});
|
2020-01-11 16:08:35 +00:00
|
|
|
}
|
|
|
|
|
2021-11-07 23:51:39 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::remount(Custody& mount_point, int new_flags)
|
2020-05-28 18:12:13 +00:00
|
|
|
{
|
2021-09-06 10:24:36 +00:00
|
|
|
dbgln("VirtualFileSystem: Remounting inode {}", mount_point.inode().identifier());
|
2020-05-28 18:12:13 +00:00
|
|
|
|
2021-08-15 23:40:19 +00:00
|
|
|
auto* mount = find_mount_for_guest(mount_point.inode().identifier());
|
2020-05-28 18:12:13 +00:00
|
|
|
if (!mount)
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENODEV;
|
2020-05-28 18:12:13 +00:00
|
|
|
|
|
|
|
mount->set_flags(new_flags);
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2020-05-28 18:12:13 +00:00
|
|
|
}
|
|
|
|
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
void VirtualFileSystem::sync_filesystems()
|
|
|
|
{
|
2023-04-02 15:25:09 +00:00
|
|
|
Vector<NonnullRefPtr<FileSystem>, 32> file_systems;
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
m_file_systems_list.with([&](auto const& list) {
|
|
|
|
for (auto& fs : list)
|
|
|
|
file_systems.append(fs);
|
|
|
|
});
|
|
|
|
|
|
|
|
for (auto& fs : file_systems)
|
2023-03-06 16:56:28 +00:00
|
|
|
fs->flush_writes();
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VirtualFileSystem::lock_all_filesystems()
|
|
|
|
{
|
2023-04-02 15:25:09 +00:00
|
|
|
Vector<NonnullRefPtr<FileSystem>, 32> file_systems;
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
m_file_systems_list.with([&](auto const& list) {
|
|
|
|
for (auto& fs : list)
|
|
|
|
file_systems.append(fs);
|
|
|
|
});
|
|
|
|
|
|
|
|
for (auto& fs : file_systems)
|
2023-03-06 16:56:28 +00:00
|
|
|
fs->m_lock.lock();
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 21:03:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::unmount(Custody& mountpoint_custody)
|
2019-08-11 13:56:39 +00:00
|
|
|
{
|
2022-08-19 21:03:24 +00:00
|
|
|
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());
|
2019-08-17 12:24:50 +00:00
|
|
|
|
2022-02-03 00:37:46 +00:00
|
|
|
return m_mounts.with([&](auto& mounts) -> ErrorOr<void> {
|
2022-11-21 20:10:56 +00:00
|
|
|
for (auto& mount : mounts) {
|
|
|
|
if (&mount.guest() != &guest_inode)
|
2021-08-15 23:40:19 +00:00
|
|
|
continue;
|
2022-11-21 20:10:56 +00:00
|
|
|
auto mountpoint_path = TRY(mount.absolute_path());
|
2022-08-19 21:03:24 +00:00
|
|
|
if (custody_path->view() != mountpoint_path->view())
|
|
|
|
continue;
|
2022-11-21 20:10:56 +00:00
|
|
|
NonnullRefPtr<FileSystem> fs = mount.guest_fs();
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
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<FileBackedFileSystem&>(*fs);
|
|
|
|
m_file_backed_file_systems_list.with([&](auto& list) {
|
|
|
|
list.remove(file_backed_fs);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mounted_count--;
|
|
|
|
}
|
2022-08-19 21:03:24 +00:00
|
|
|
});
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
dbgln("VirtualFileSystem: Unmounting file system {}...", fs->fsid());
|
2022-11-21 20:10:56 +00:00
|
|
|
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;
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2019-08-11 13:56:39 +00:00
|
|
|
}
|
2021-08-15 23:40:19 +00:00
|
|
|
dbgln("VirtualFileSystem: Nothing mounted on inode {}", guest_inode.identifier());
|
|
|
|
return ENODEV;
|
|
|
|
});
|
2019-08-11 13:56:39 +00:00
|
|
|
}
|
|
|
|
|
2021-11-07 23:51:39 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::mount_root(FileSystem& fs)
|
2018-10-10 09:53:07 +00:00
|
|
|
{
|
2019-01-16 11:57:07 +00:00
|
|
|
if (m_root_inode) {
|
2021-07-10 22:25:24 +00:00
|
|
|
dmesgln("VirtualFileSystem: mount_root can't mount another root");
|
2021-09-05 12:46:44 +00:00
|
|
|
return EEXIST;
|
2018-10-10 09:53:07 +00:00
|
|
|
}
|
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
auto new_mount = TRY(adopt_nonnull_own_or_enomem(new (nothrow) Mount(fs, nullptr, root_mount_flags)));
|
2021-07-17 23:50:47 +00:00
|
|
|
auto& root_inode = fs.root_inode();
|
|
|
|
if (!root_inode.is_directory()) {
|
|
|
|
dmesgln("VirtualFileSystem: root inode ({}) for / is not a directory :(", root_inode.identifier());
|
2021-09-05 12:46:44 +00:00
|
|
|
return ENOTDIR;
|
2018-10-10 09:53:07 +00:00
|
|
|
}
|
|
|
|
|
2021-07-17 23:50:47 +00:00
|
|
|
m_root_inode = root_inode;
|
2022-08-19 21:11:59 +00:00
|
|
|
if (fs.is_file_backed()) {
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
auto pseudo_path = TRY(static_cast<FileBackedFileSystem&>(fs).file_description().pseudo_path());
|
|
|
|
dmesgln("VirtualFileSystem: mounted root({}) from {} ({})", fs.fsid(), fs.class_name(), pseudo_path);
|
2022-08-19 21:11:59 +00:00
|
|
|
m_file_backed_file_systems_list.with([&](auto& list) {
|
|
|
|
list.append(static_cast<FileBackedFileSystem&>(fs));
|
|
|
|
});
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
} else {
|
|
|
|
dmesgln("VirtualFileSystem: mounted root({}) from {}", fs.fsid(), fs.class_name());
|
2022-08-19 21:11:59 +00:00
|
|
|
}
|
|
|
|
|
Kernel/FileSystem: Discard safely filesystems when unmounted last time
This commit reached that goal of "safely discarding" a filesystem by
doing the following:
1. Stop using the s_file_system_map HashMap as it was an unsafe measure
to access pointers of FileSystems. Instead, make sure to register all
FileSystems at the VFS layer, with an IntrusiveList, to avoid problems
related to OOM conditions.
2. Make sure to cleanly remove the DiskCache object from a BlockBased
filesystem, so the destructor of such object will not need to do that in
the destruction point.
3. For ext2 filesystems, don't cache the root inode at m_inode_cache
HashMap. The reason for this is that when unmounting an ext2 filesystem,
we lookup at the cache to see if there's a reference to a cached inode
and if that's the case, we fail with EBUSY. If we keep the m_root_inode
also being referenced at the m_inode_cache map, we have 2 references to
that object, which will lead to fail with EBUSY. Also, it's much simpler
to always ask for a root inode and get it immediately from m_root_inode,
instead of looking up the cache for that inode.
2022-08-20 06:28:02 +00:00
|
|
|
m_file_systems_list.with([&](auto& fs_list) {
|
|
|
|
fs_list.append(fs);
|
|
|
|
});
|
|
|
|
|
2022-11-21 20:10:56 +00:00
|
|
|
fs.mounted_count({}).with([&](auto& mounted_count) {
|
|
|
|
mounted_count++;
|
|
|
|
});
|
|
|
|
|
2022-08-19 21:03:24 +00:00
|
|
|
// Note: Actually add a mount for the filesystem and increment the filesystem mounted count
|
2022-02-03 00:37:46 +00:00
|
|
|
m_mounts.with([&](auto& mounts) {
|
2022-11-21 20:10:56 +00:00
|
|
|
// 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());
|
2021-08-15 23:40:19 +00:00
|
|
|
});
|
2021-05-10 07:28:23 +00:00
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> 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);
|
|
|
|
});
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2018-10-10 09:53:07 +00:00
|
|
|
}
|
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
auto VirtualFileSystem::find_mount_for_host(InodeIdentifier id) -> Mount*
|
2018-10-10 09:53:07 +00:00
|
|
|
{
|
2022-02-03 00:37:46 +00:00
|
|
|
return m_mounts.with([&](auto& mounts) -> Mount* {
|
2021-08-15 23:40:19 +00:00
|
|
|
for (auto& mount : mounts) {
|
2022-11-21 20:10:56 +00:00
|
|
|
if (mount.host() && mount.host()->identifier() == id)
|
|
|
|
return &mount;
|
2021-08-15 23:40:19 +00:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
});
|
2020-06-24 21:16:24 +00:00
|
|
|
}
|
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
auto VirtualFileSystem::find_mount_for_guest(InodeIdentifier id) -> Mount*
|
2020-06-24 21:16:24 +00:00
|
|
|
{
|
2022-02-03 00:37:46 +00:00
|
|
|
return m_mounts.with([&](auto& mounts) -> Mount* {
|
2021-08-15 23:40:19 +00:00
|
|
|
for (auto& mount : mounts) {
|
2022-11-21 20:10:56 +00:00
|
|
|
if (mount.guest().identifier() == id)
|
|
|
|
return &mount;
|
2021-08-15 23:40:19 +00:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
});
|
2018-10-10 09:53:07 +00:00
|
|
|
}
|
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
bool VirtualFileSystem::is_vfs_root(InodeIdentifier inode) const
|
2018-10-10 09:53:07 +00:00
|
|
|
{
|
2019-01-16 11:57:07 +00:00
|
|
|
return inode == root_inode_id();
|
2018-10-10 09:53:07 +00:00
|
|
|
}
|
|
|
|
|
2021-11-10 14:42:39 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::traverse_directory_inode(Inode& dir_inode, Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback)
|
2018-10-10 09:53:07 +00:00
|
|
|
{
|
2021-11-10 14:42:39 +00:00
|
|
|
return dir_inode.traverse_as_directory([&](auto& entry) -> ErrorOr<void> {
|
2019-01-31 16:31:23 +00:00
|
|
|
InodeIdentifier resolved_inode;
|
2018-11-15 14:10:12 +00:00
|
|
|
if (auto mount = find_mount_for_host(entry.inode))
|
2020-06-24 21:16:24 +00:00
|
|
|
resolved_inode = mount->guest().identifier();
|
2018-10-10 09:53:07 +00:00
|
|
|
else
|
2019-01-31 16:31:23 +00:00
|
|
|
resolved_inode = entry.inode;
|
2018-10-10 09:53:07 +00:00
|
|
|
|
2020-01-15 11:06:48 +00:00
|
|
|
// FIXME: This is now broken considering chroot and bind mounts.
|
2021-07-17 23:50:47 +00:00
|
|
|
bool is_root_inode = dir_inode.identifier() == dir_inode.fs().root_inode().identifier();
|
2020-08-18 10:41:27 +00:00
|
|
|
if (is_root_inode && !is_vfs_root(dir_inode.identifier()) && entry.name == "..") {
|
2021-07-10 22:50:08 +00:00
|
|
|
auto mount = find_mount_for_guest(dir_inode.identifier());
|
2021-02-23 19:42:32 +00:00
|
|
|
VERIFY(mount);
|
|
|
|
VERIFY(mount->host());
|
2020-06-24 21:16:24 +00:00
|
|
|
resolved_inode = mount->host()->identifier();
|
2018-10-10 09:53:07 +00:00
|
|
|
}
|
2021-11-10 14:42:39 +00:00
|
|
|
TRY(callback({ entry.name, resolved_inode, entry.file_type }));
|
|
|
|
return {};
|
2018-10-10 09:53:07 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::utime(Credentials const& credentials, StringView path, Custody& base, time_t atime, time_t mtime)
|
2019-02-21 15:37:41 +00:00
|
|
|
{
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path(credentials, path, base));
|
2021-09-05 12:00:18 +00:00
|
|
|
auto& inode = custody->inode();
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!credentials.is_superuser() && inode.metadata().uid != credentials.euid())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2021-09-05 12:00:18 +00:00
|
|
|
if (custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2019-03-06 21:14:31 +00:00
|
|
|
|
2023-03-13 21:11:13 +00:00
|
|
|
TRY(inode.update_timestamps(UnixDateTime::from_seconds_since_epoch(atime), {}, UnixDateTime::from_seconds_since_epoch(mtime)));
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2019-02-21 15:37:41 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::utimensat(Credentials const& credentials, StringView path, Custody& base, timespec const& atime, timespec const& mtime, int options)
|
2022-05-02 20:26:10 +00:00
|
|
|
{
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path(credentials, path, base, nullptr, options));
|
2023-04-08 08:37:15 +00:00
|
|
|
return do_utimens(credentials, custody, atime, mtime);
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorOr<void> VirtualFileSystem::do_utimens(Credentials const& credentials, Custody& custody, timespec const& atime, timespec const& mtime)
|
|
|
|
{
|
|
|
|
auto& inode = custody.inode();
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!credentials.is_superuser() && inode.metadata().uid != credentials.euid())
|
2022-05-02 20:26:10 +00:00
|
|
|
return EACCES;
|
2023-04-08 08:37:15 +00:00
|
|
|
if (custody.is_readonly())
|
2022-05-02 20:26:10 +00:00
|
|
|
return EROFS;
|
|
|
|
|
|
|
|
// NOTE: A standard ext2 inode cannot store nanosecond timestamps.
|
2022-08-22 11:34:22 +00:00
|
|
|
TRY(inode.update_timestamps(
|
2023-03-13 21:11:13 +00:00
|
|
|
(atime.tv_nsec != UTIME_OMIT) ? UnixDateTime::from_unix_timespec(atime) : Optional<UnixDateTime> {},
|
2022-08-22 11:34:22 +00:00
|
|
|
{},
|
2023-03-13 21:11:13 +00:00
|
|
|
(mtime.tv_nsec != UTIME_OMIT) ? UnixDateTime::from_unix_timespec(mtime) : Optional<UnixDateTime> {}));
|
2022-05-02 20:26:10 +00:00
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<InodeMetadata> VirtualFileSystem::lookup_metadata(Credentials const& credentials, StringView path, Custody& base, int options)
|
2019-02-21 15:09:12 +00:00
|
|
|
{
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path(credentials, path, base, nullptr, options));
|
2021-09-05 12:00:18 +00:00
|
|
|
return custody->inode().metadata();
|
2019-02-21 15:09:12 +00:00
|
|
|
}
|
|
|
|
|
2023-04-02 15:25:09 +00:00
|
|
|
ErrorOr<NonnullRefPtr<FileBackedFileSystem>> VirtualFileSystem::find_already_existing_or_create_file_backed_file_system(OpenFileDescription& description, Function<ErrorOr<NonnullRefPtr<FileSystem>>(OpenFileDescription&)> callback)
|
2022-08-19 21:03:24 +00:00
|
|
|
{
|
2023-04-02 15:25:09 +00:00
|
|
|
return TRY(m_file_backed_file_systems_list.with([&](auto& list) -> ErrorOr<NonnullRefPtr<FileBackedFileSystem>> {
|
2022-08-19 21:03:24 +00:00
|
|
|
for (auto& node : list) {
|
|
|
|
if (&node.file_description() == &description) {
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
if (&node.file() == &description.file()) {
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto fs = TRY(callback(description));
|
2023-01-08 00:19:43 +00:00
|
|
|
|
|
|
|
// The created FileSystem is only added to the file_systems_lists
|
|
|
|
// when the FS has been successfully initialized and mounted
|
|
|
|
// (in VirtualFileSystem::mount()). This prevents file systems which
|
|
|
|
// fail to initialize or mount from existing in the list when the
|
|
|
|
// FileSystem is destroyed after failure.
|
2022-08-19 21:03:24 +00:00
|
|
|
return static_ptr_cast<FileBackedFileSystem>(fs);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2023-03-06 18:29:25 +00:00
|
|
|
ErrorOr<NonnullRefPtr<OpenFileDescription>> VirtualFileSystem::open(Credentials const& credentials, StringView path, int options, mode_t mode, Custody& base, Optional<UidAndGid> owner)
|
2023-03-04 18:01:54 +00:00
|
|
|
{
|
|
|
|
return open(Process::current(), credentials, path, options, mode, base, owner);
|
|
|
|
}
|
|
|
|
|
2023-03-06 18:29:25 +00:00
|
|
|
ErrorOr<NonnullRefPtr<OpenFileDescription>> VirtualFileSystem::open(Process const& process, Credentials const& credentials, StringView path, int options, mode_t mode, Custody& base, Optional<UidAndGid> owner)
|
2018-10-10 09:53:07 +00:00
|
|
|
{
|
2020-01-03 01:23:50 +00:00
|
|
|
if ((options & O_CREAT) && (options & O_DIRECTORY))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EINVAL;
|
2020-01-03 01:23:50 +00:00
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> parent_custody;
|
2023-03-04 18:01:54 +00:00
|
|
|
auto custody_or_error = resolve_path(process, credentials, path, base, &parent_custody, options);
|
2021-05-19 09:33:23 +00:00
|
|
|
if (custody_or_error.is_error()) {
|
|
|
|
// NOTE: ENOENT with a non-null parent custody signals us that the immediate parent
|
|
|
|
// of the file exists, but the file itself does not.
|
2021-11-07 23:51:39 +00:00
|
|
|
if ((options & O_CREAT) && custody_or_error.error().code() == ENOENT && parent_custody)
|
2023-03-04 18:01:54 +00:00
|
|
|
return create(process, credentials, path, options, mode, *parent_custody, move(owner));
|
2021-11-07 23:51:39 +00:00
|
|
|
return custody_or_error.release_error();
|
2021-05-19 09:33:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((options & O_CREAT) && (options & O_EXCL))
|
|
|
|
return EEXIST;
|
2019-02-21 15:09:12 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
auto& custody = *custody_or_error.value();
|
|
|
|
auto& inode = custody.inode();
|
|
|
|
auto metadata = inode.metadata();
|
2019-03-27 15:42:30 +00:00
|
|
|
|
2022-10-21 16:29:50 +00:00
|
|
|
if (metadata.is_regular_file() && (custody.mount_flags() & MS_NOREGULAR))
|
|
|
|
return EACCES;
|
|
|
|
|
2020-01-03 01:23:11 +00:00
|
|
|
if ((options & O_DIRECTORY) && !metadata.is_directory())
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOTDIR;
|
2020-01-03 01:23:11 +00:00
|
|
|
|
2019-03-27 15:42:30 +00:00
|
|
|
bool should_truncate_file = false;
|
2019-02-21 15:09:12 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if ((options & O_RDONLY) && !metadata.may_read(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2020-01-21 12:14:26 +00:00
|
|
|
|
|
|
|
if (options & O_WRONLY) {
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!metadata.may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-03-06 21:14:31 +00:00
|
|
|
if (metadata.is_directory())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EISDIR;
|
2019-03-27 15:42:30 +00:00
|
|
|
should_truncate_file = options & O_TRUNC;
|
2019-02-21 14:45:31 +00:00
|
|
|
}
|
2020-01-11 15:33:35 +00:00
|
|
|
if (options & O_EXEC) {
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!metadata.may_execute(credentials) || (custody.mount_flags() & MS_NOEXEC))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2020-01-11 15:33:35 +00:00
|
|
|
}
|
2019-02-21 15:09:12 +00:00
|
|
|
|
2020-07-16 21:23:03 +00:00
|
|
|
if (metadata.is_fifo()) {
|
2021-09-07 11:56:10 +00:00
|
|
|
auto fifo = TRY(inode.fifo());
|
2020-07-16 21:23:03 +00:00
|
|
|
if (options & O_WRONLY) {
|
2021-09-05 12:55:25 +00:00
|
|
|
auto description = TRY(fifo->open_direction_blocking(FIFO::Direction::Writer));
|
2020-07-16 21:23:03 +00:00
|
|
|
description->set_rw_mode(options);
|
|
|
|
description->set_file_flags(options);
|
|
|
|
description->set_original_inode({}, inode);
|
|
|
|
return description;
|
|
|
|
} else if (options & O_RDONLY) {
|
2021-09-05 12:55:25 +00:00
|
|
|
auto description = TRY(fifo->open_direction_blocking(FIFO::Direction::Reader));
|
2020-07-16 21:23:03 +00:00
|
|
|
description->set_rw_mode(options);
|
|
|
|
description->set_file_flags(options);
|
|
|
|
description->set_original_inode({}, inode);
|
|
|
|
return description;
|
|
|
|
}
|
2021-01-20 22:11:17 +00:00
|
|
|
return EINVAL;
|
2020-07-16 21:23:03 +00:00
|
|
|
}
|
|
|
|
|
2019-02-21 15:09:12 +00:00
|
|
|
if (metadata.is_device()) {
|
2020-01-11 15:45:38 +00:00
|
|
|
if (custody.mount_flags() & MS_NODEV)
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2021-09-11 06:19:20 +00:00
|
|
|
auto device = DeviceManagement::the().get_device(metadata.major_device, metadata.minor_device);
|
2019-08-18 11:48:15 +00:00
|
|
|
if (device == nullptr) {
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENODEV;
|
2019-01-16 11:57:07 +00:00
|
|
|
}
|
2021-09-05 12:00:18 +00:00
|
|
|
auto description = TRY(device->open(options));
|
|
|
|
description->set_original_inode({}, inode);
|
2021-08-14 02:04:56 +00:00
|
|
|
description->set_original_custody({}, custody);
|
2021-09-05 12:00:18 +00:00
|
|
|
return description;
|
2019-01-16 11:57:07 +00:00
|
|
|
}
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2021-12-05 09:49:21 +00:00
|
|
|
// Check for read-only FS. Do this after handling devices, but before modifying the inode in any way.
|
2020-05-28 14:56:25 +00:00
|
|
|
if ((options & O_WRONLY) && custody.is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2020-01-08 12:57:22 +00:00
|
|
|
if (should_truncate_file) {
|
2021-09-05 12:55:25 +00:00
|
|
|
TRY(inode.truncate(0));
|
2022-11-22 20:01:45 +00:00
|
|
|
TRY(inode.update_timestamps({}, {}, kgettimeofday()));
|
2020-09-17 19:51:09 +00:00
|
|
|
}
|
2021-09-07 11:39:11 +00:00
|
|
|
auto description = TRY(OpenFileDescription::try_create(custody));
|
2021-09-05 12:55:25 +00:00
|
|
|
description->set_rw_mode(options);
|
|
|
|
description->set_file_flags(options);
|
2020-01-18 22:15:52 +00:00
|
|
|
return description;
|
2018-10-10 09:53:07 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::mknod(Credentials const& credentials, StringView path, mode_t mode, dev_t dev, Custody& base)
|
2019-05-03 20:59:58 +00:00
|
|
|
{
|
|
|
|
if (!is_regular_file(mode) && !is_block_device(mode) && !is_character_device(mode) && !is_fifo(mode) && !is_socket(mode))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EINVAL;
|
2019-05-03 20:59:58 +00:00
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> parent_custody;
|
2022-08-21 14:02:24 +00:00
|
|
|
auto existing_file_or_error = resolve_path(credentials, path, base, &parent_custody);
|
2019-05-03 20:59:58 +00:00
|
|
|
if (!existing_file_or_error.is_error())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EEXIST;
|
2019-05-30 16:58:59 +00:00
|
|
|
if (!parent_custody)
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOENT;
|
2021-11-07 23:51:39 +00:00
|
|
|
if (existing_file_or_error.error().code() != ENOENT)
|
|
|
|
return existing_file_or_error.release_error();
|
2019-05-30 16:58:59 +00:00
|
|
|
auto& parent_inode = parent_custody->inode();
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!parent_inode.metadata().may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2020-05-28 14:56:25 +00:00
|
|
|
if (parent_custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2019-05-03 20:59:58 +00:00
|
|
|
|
2021-07-06 09:21:52 +00:00
|
|
|
auto basename = KLexicalPath::basename(path);
|
2021-08-14 04:11:30 +00:00
|
|
|
dbgln_if(VFS_DEBUG, "VirtualFileSystem::mknod: '{}' mode={} dev={} in {}", basename, mode, dev, parent_inode.identifier());
|
2022-08-21 14:02:24 +00:00
|
|
|
(void)TRY(parent_inode.create_child(basename, mode, dev, credentials.euid(), credentials.egid()));
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2019-05-03 20:59:58 +00:00
|
|
|
}
|
|
|
|
|
2023-03-06 18:29:25 +00:00
|
|
|
ErrorOr<NonnullRefPtr<OpenFileDescription>> VirtualFileSystem::create(Credentials const& credentials, StringView path, int options, mode_t mode, Custody& parent_custody, Optional<UidAndGid> owner)
|
2023-03-04 18:01:54 +00:00
|
|
|
{
|
|
|
|
return create(Process::current(), credentials, path, options, mode, parent_custody, owner);
|
|
|
|
}
|
|
|
|
|
2023-03-06 18:29:25 +00:00
|
|
|
ErrorOr<NonnullRefPtr<OpenFileDescription>> VirtualFileSystem::create(Process const& process, Credentials const& credentials, StringView path, int options, mode_t mode, Custody& parent_custody, Optional<UidAndGid> owner)
|
2018-10-10 09:53:07 +00:00
|
|
|
{
|
2021-07-06 09:21:52 +00:00
|
|
|
auto basename = KLexicalPath::basename(path);
|
2021-09-06 10:24:36 +00:00
|
|
|
auto parent_path = TRY(parent_custody.try_serialize_absolute_path());
|
2021-09-06 17:24:54 +00:00
|
|
|
auto full_path = TRY(KLexicalPath::try_join(parent_path->view(), basename));
|
2023-03-04 18:01:54 +00:00
|
|
|
TRY(validate_path_against_process_veil(process, full_path->view(), options));
|
2020-04-04 14:40:36 +00:00
|
|
|
|
2021-01-24 07:31:18 +00:00
|
|
|
if (!is_socket(mode) && !is_fifo(mode) && !is_block_device(mode) && !is_character_device(mode)) {
|
|
|
|
// Turn it into a regular file. (This feels rather hackish.)
|
|
|
|
mode |= 0100000;
|
|
|
|
}
|
2019-01-23 03:29:56 +00:00
|
|
|
|
2019-06-09 17:52:03 +00:00
|
|
|
auto& parent_inode = parent_custody.inode();
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!parent_inode.metadata().may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2020-05-28 14:56:25 +00:00
|
|
|
if (parent_custody.is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2022-10-21 16:29:50 +00:00
|
|
|
if (is_regular_file(mode) && (parent_custody.mount_flags() & MS_NOREGULAR))
|
|
|
|
return EACCES;
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
dbgln_if(VFS_DEBUG, "VirtualFileSystem::create: '{}' in {}", basename, parent_inode.identifier());
|
2022-08-21 14:02:24 +00:00
|
|
|
auto uid = owner.has_value() ? owner.value().uid : credentials.euid();
|
|
|
|
auto gid = owner.has_value() ? owner.value().gid : credentials.egid();
|
2021-09-05 12:00:18 +00:00
|
|
|
|
|
|
|
auto inode = TRY(parent_inode.create_child(basename, mode, 0, uid, gid));
|
|
|
|
auto custody = TRY(Custody::try_create(&parent_custody, basename, inode, parent_custody.mount_flags()));
|
|
|
|
|
2021-09-07 11:39:11 +00:00
|
|
|
auto description = TRY(OpenFileDescription::try_create(move(custody)));
|
2021-09-05 12:55:25 +00:00
|
|
|
description->set_rw_mode(options);
|
|
|
|
description->set_file_flags(options);
|
2020-01-18 22:15:52 +00:00
|
|
|
return description;
|
2018-10-15 22:35:03 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::mkdir(Credentials const& credentials, StringView path, mode_t mode, Custody& base)
|
2018-10-15 22:35:03 +00:00
|
|
|
{
|
2020-02-20 14:28:36 +00:00
|
|
|
// Unlike in basically every other case, where it's only the last
|
|
|
|
// path component (the one being created) that is allowed not to
|
|
|
|
// exist, POSIX allows mkdir'ed path to have trailing slashes.
|
|
|
|
// Let's handle that case by trimming any trailing slashes.
|
2021-07-11 11:46:05 +00:00
|
|
|
path = path.trim("/"sv, TrimMode::Right);
|
|
|
|
if (path.is_empty()) {
|
|
|
|
// NOTE: This means the path was a series of slashes, which resolves to "/".
|
2022-07-11 17:32:29 +00:00
|
|
|
path = "/"sv;
|
2021-07-11 11:46:05 +00:00
|
|
|
}
|
2020-02-20 14:28:36 +00:00
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> parent_custody;
|
2022-02-13 16:31:33 +00:00
|
|
|
// FIXME: The errors returned by resolve_path_without_veil can leak information about paths that are not unveiled,
|
|
|
|
// e.g. when the error is EACCESS or similar.
|
2022-08-21 14:02:24 +00:00
|
|
|
auto result = resolve_path_without_veil(credentials, path, base, &parent_custody);
|
2021-07-11 11:46:05 +00:00
|
|
|
if (!result.is_error())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EEXIST;
|
2021-04-10 22:40:38 +00:00
|
|
|
else if (!parent_custody)
|
2021-11-07 23:51:39 +00:00
|
|
|
return result.release_error();
|
2021-07-11 11:46:05 +00:00
|
|
|
// NOTE: If resolve_path fails with a non-null parent custody, the error should be ENOENT.
|
2021-11-07 23:51:39 +00:00
|
|
|
VERIFY(result.error().code() == ENOENT);
|
2019-02-25 19:47:56 +00:00
|
|
|
|
2021-07-11 12:50:15 +00:00
|
|
|
TRY(validate_path_against_process_veil(*parent_custody, O_CREAT));
|
2019-05-30 16:58:59 +00:00
|
|
|
auto& parent_inode = parent_custody->inode();
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!parent_inode.metadata().may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2020-05-28 14:56:25 +00:00
|
|
|
if (parent_custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2019-02-21 14:45:31 +00:00
|
|
|
|
2021-07-06 09:21:52 +00:00
|
|
|
auto basename = KLexicalPath::basename(path);
|
2021-07-10 22:25:24 +00:00
|
|
|
dbgln_if(VFS_DEBUG, "VirtualFileSystem::mkdir: '{}' in {}", basename, parent_inode.identifier());
|
2022-08-21 14:02:24 +00:00
|
|
|
(void)TRY(parent_inode.create_child(basename, S_IFDIR | mode, 0, credentials.euid(), credentials.egid()));
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2018-10-10 09:53:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-01 12:24:56 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::access(Credentials const& credentials, StringView path, int mode, Custody& base, AccessFlags access_flags)
|
2019-02-26 14:57:59 +00:00
|
|
|
{
|
2022-10-01 12:24:56 +00:00
|
|
|
auto should_follow_symlinks = !has_flag(access_flags, AccessFlags::DoNotFollowSymlinks);
|
|
|
|
auto custody = TRY(resolve_path(credentials, path, base, nullptr, should_follow_symlinks ? 0 : O_NOFOLLOW_NOERROR));
|
2021-09-05 12:00:18 +00:00
|
|
|
|
|
|
|
auto& inode = custody->inode();
|
2019-05-30 16:58:59 +00:00
|
|
|
auto metadata = inode.metadata();
|
2022-10-01 12:24:56 +00:00
|
|
|
auto use_effective_ids = has_flag(access_flags, AccessFlags::EffectiveAccess) ? UseEffectiveIDs::Yes : UseEffectiveIDs::No;
|
2019-02-26 14:57:59 +00:00
|
|
|
if (mode & R_OK) {
|
2022-10-01 12:24:56 +00:00
|
|
|
if (!metadata.may_read(credentials, use_effective_ids))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-02-26 14:57:59 +00:00
|
|
|
}
|
|
|
|
if (mode & W_OK) {
|
2022-10-01 12:24:56 +00:00
|
|
|
if (!metadata.may_write(credentials, use_effective_ids))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2021-09-05 12:00:18 +00:00
|
|
|
if (custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2019-02-26 14:57:59 +00:00
|
|
|
}
|
|
|
|
if (mode & X_OK) {
|
2022-10-01 12:24:56 +00:00
|
|
|
if (!metadata.may_execute(credentials, use_effective_ids))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-02-26 14:57:59 +00:00
|
|
|
}
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2019-02-26 14:57:59 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<NonnullRefPtr<Custody>> VirtualFileSystem::open_directory(Credentials const& credentials, StringView path, Custody& base)
|
2019-03-01 22:54:07 +00:00
|
|
|
{
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path(credentials, path, base));
|
2021-09-05 12:00:18 +00:00
|
|
|
auto& inode = custody->inode();
|
2019-05-30 16:58:59 +00:00
|
|
|
if (!inode.is_directory())
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOTDIR;
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!inode.metadata().may_execute(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-05-30 16:58:59 +00:00
|
|
|
return custody;
|
2019-03-01 22:54:07 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::chmod(Credentials const& credentials, Custody& custody, mode_t mode)
|
2019-01-29 03:55:08 +00:00
|
|
|
{
|
2020-05-28 14:41:04 +00:00
|
|
|
auto& inode = custody.inode();
|
2019-02-21 14:45:31 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if (credentials.euid() != inode.metadata().uid && !credentials.is_superuser())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EPERM;
|
2020-05-28 14:56:25 +00:00
|
|
|
if (custody.is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2019-01-29 03:55:08 +00:00
|
|
|
|
|
|
|
// Only change the permission bits.
|
2021-01-19 17:21:43 +00:00
|
|
|
mode = (inode.mode() & ~07777u) | (mode & 07777u);
|
2019-03-01 09:39:19 +00:00
|
|
|
return inode.chmod(mode);
|
|
|
|
}
|
2019-01-29 03:55:08 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::chmod(Credentials const& credentials, StringView path, mode_t mode, Custody& base, int options)
|
2019-03-01 09:39:19 +00:00
|
|
|
{
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path(credentials, path, base, nullptr, options));
|
|
|
|
return chmod(credentials, custody, mode);
|
2019-02-25 19:47:56 +00:00
|
|
|
}
|
|
|
|
|
2022-10-01 11:42:25 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::rename(Credentials const& credentials, Custody& old_base, StringView old_path, Custody& new_base, StringView new_path)
|
2019-04-07 21:35:26 +00:00
|
|
|
{
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> old_parent_custody;
|
2022-10-01 11:42:25 +00:00
|
|
|
auto old_custody = TRY(resolve_path(credentials, old_path, old_base, &old_parent_custody, O_NOFOLLOW_NOERROR));
|
2021-09-05 12:00:18 +00:00
|
|
|
auto& old_inode = old_custody->inode();
|
2019-04-07 21:35:26 +00:00
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> new_parent_custody;
|
2022-10-01 11:42:25 +00:00
|
|
|
auto new_custody_or_error = resolve_path(credentials, new_path, new_base, &new_parent_custody);
|
2019-05-30 16:58:59 +00:00
|
|
|
if (new_custody_or_error.is_error()) {
|
2021-11-07 23:51:39 +00:00
|
|
|
if (new_custody_or_error.error().code() != ENOENT || !new_parent_custody)
|
|
|
|
return new_custody_or_error.release_error();
|
2019-04-07 21:35:26 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 17:35:34 +00:00
|
|
|
if (!old_parent_custody || !new_parent_custody) {
|
|
|
|
return EPERM;
|
|
|
|
}
|
|
|
|
|
2021-08-01 20:58:50 +00:00
|
|
|
if (!new_custody_or_error.is_error()) {
|
|
|
|
auto& new_inode = new_custody_or_error.value()->inode();
|
|
|
|
|
|
|
|
if (old_inode.index() != new_inode.index() && old_inode.is_directory() && new_inode.is_directory()) {
|
|
|
|
size_t child_count = 0;
|
2021-11-10 14:42:39 +00:00
|
|
|
TRY(new_inode.traverse_as_directory([&child_count](auto&) -> ErrorOr<void> {
|
2021-08-01 20:58:50 +00:00
|
|
|
++child_count;
|
2021-11-10 14:42:39 +00:00
|
|
|
return {};
|
2021-09-06 18:30:18 +00:00
|
|
|
}));
|
2021-08-01 20:58:50 +00:00
|
|
|
if (child_count > 2)
|
|
|
|
return ENOTEMPTY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
auto& old_parent_inode = old_parent_custody->inode();
|
|
|
|
auto& new_parent_inode = new_parent_custody->inode();
|
|
|
|
|
2020-01-03 03:10:05 +00:00
|
|
|
if (&old_parent_inode.fs() != &new_parent_inode.fs())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EXDEV;
|
2020-01-03 03:10:05 +00:00
|
|
|
|
2020-11-01 16:17:23 +00:00
|
|
|
for (auto* new_ancestor = new_parent_custody.ptr(); new_ancestor; new_ancestor = new_ancestor->parent()) {
|
|
|
|
if (&old_inode == &new_ancestor->inode())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EDIRINTOSELF;
|
2020-11-01 16:17:23 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!new_parent_inode.metadata().may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-04-07 21:35:26 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!old_parent_inode.metadata().may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-04-07 21:35:26 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
if (old_parent_inode.metadata().is_sticky()) {
|
2022-09-01 12:16:32 +00:00
|
|
|
if (!credentials.is_superuser() && old_parent_inode.metadata().uid != credentials.euid() && old_inode.metadata().uid != credentials.euid())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-04-28 20:54:30 +00:00
|
|
|
}
|
|
|
|
|
2020-05-28 14:56:25 +00:00
|
|
|
if (old_parent_custody->is_readonly() || new_parent_custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2021-07-16 19:12:07 +00:00
|
|
|
auto old_basename = KLexicalPath::basename(old_path);
|
|
|
|
if (old_basename.is_empty() || old_basename == "."sv || old_basename == ".."sv)
|
|
|
|
return EINVAL;
|
|
|
|
|
2021-07-06 09:21:52 +00:00
|
|
|
auto new_basename = KLexicalPath::basename(new_path);
|
2021-07-16 19:12:07 +00:00
|
|
|
if (new_basename.is_empty() || new_basename == "."sv || new_basename == ".."sv)
|
|
|
|
return EINVAL;
|
2019-05-31 13:22:52 +00:00
|
|
|
|
2021-08-10 11:39:44 +00:00
|
|
|
if (old_basename == new_basename && old_parent_inode.index() == new_parent_inode.index())
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2021-08-10 11:39:44 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
if (!new_custody_or_error.is_error()) {
|
|
|
|
auto& new_custody = *new_custody_or_error.value();
|
|
|
|
auto& new_inode = new_custody.inode();
|
2022-12-31 06:44:45 +00:00
|
|
|
// When the source/dest inodes are the same (in other words,
|
|
|
|
// when `old_path` and `new_path` are the same), perform a no-op
|
|
|
|
// and return success.
|
|
|
|
// Linux (`vfs_rename()`) and OpenBSD (`dorenameat()`) appear to have
|
|
|
|
// this same no-op behavior.
|
2019-05-30 16:58:59 +00:00
|
|
|
if (&new_inode == &old_inode)
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2019-05-30 16:58:59 +00:00
|
|
|
if (new_parent_inode.metadata().is_sticky()) {
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!credentials.is_superuser() && new_inode.metadata().uid != credentials.euid())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-04-28 21:34:33 +00:00
|
|
|
}
|
2019-05-30 16:58:59 +00:00
|
|
|
if (new_inode.is_directory() && !old_inode.is_directory())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EISDIR;
|
2021-09-05 12:55:25 +00:00
|
|
|
TRY(new_parent_inode.remove_child(new_basename));
|
2019-04-07 21:35:26 +00:00
|
|
|
}
|
|
|
|
|
2021-09-05 12:55:25 +00:00
|
|
|
TRY(new_parent_inode.add_child(old_inode, new_basename, old_inode.mode()));
|
|
|
|
TRY(old_parent_inode.remove_child(old_basename));
|
2022-10-08 09:22:12 +00:00
|
|
|
|
|
|
|
// If the inode that we moved is a directory and we changed parent
|
|
|
|
// directories, then we also have to make .. point to the new parent inode,
|
|
|
|
// because .. is its own inode.
|
|
|
|
if (old_inode.is_directory() && old_parent_inode.index() != new_parent_inode.index()) {
|
|
|
|
TRY(old_inode.replace_child(".."sv, new_parent_inode));
|
|
|
|
}
|
|
|
|
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2019-04-07 21:35:26 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::chown(Credentials const& credentials, Custody& custody, UserID a_uid, GroupID a_gid)
|
2019-02-27 11:32:53 +00:00
|
|
|
{
|
2020-05-28 14:41:04 +00:00
|
|
|
auto& inode = custody.inode();
|
2019-06-02 08:31:25 +00:00
|
|
|
auto metadata = inode.metadata();
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if (credentials.euid() != metadata.uid && !credentials.is_superuser())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EPERM;
|
2019-02-27 11:32:53 +00:00
|
|
|
|
2021-08-28 20:11:16 +00:00
|
|
|
UserID new_uid = metadata.uid;
|
|
|
|
GroupID new_gid = metadata.gid;
|
2019-02-27 11:32:53 +00:00
|
|
|
|
|
|
|
if (a_uid != (uid_t)-1) {
|
2022-08-21 14:02:24 +00:00
|
|
|
if (credentials.euid() != a_uid && !credentials.is_superuser())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EPERM;
|
2019-02-27 11:32:53 +00:00
|
|
|
new_uid = a_uid;
|
|
|
|
}
|
|
|
|
if (a_gid != (gid_t)-1) {
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!credentials.in_group(a_gid) && !credentials.is_superuser())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EPERM;
|
2019-02-27 11:32:53 +00:00
|
|
|
new_gid = a_gid;
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:56:25 +00:00
|
|
|
if (custody.is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
dbgln_if(VFS_DEBUG, "VirtualFileSystem::chown(): inode {} <- uid={} gid={}", inode.identifier(), new_uid, new_gid);
|
2020-04-04 17:46:55 +00:00
|
|
|
|
|
|
|
if (metadata.is_setuid() || metadata.is_setgid()) {
|
2021-07-10 22:25:24 +00:00
|
|
|
dbgln_if(VFS_DEBUG, "VirtualFileSystem::chown(): Stripping SUID/SGID bits from {}", inode.identifier());
|
2021-09-05 12:55:25 +00:00
|
|
|
TRY(inode.chmod(metadata.mode & ~(04000 | 02000)));
|
2020-04-04 17:46:55 +00:00
|
|
|
}
|
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
return inode.chown(new_uid, new_gid);
|
2019-02-27 11:32:53 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::chown(Credentials const& credentials, StringView path, UserID a_uid, GroupID a_gid, Custody& base, int options)
|
2019-06-02 10:30:24 +00:00
|
|
|
{
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path(credentials, path, base, nullptr, options));
|
|
|
|
return chown(credentials, custody, a_uid, a_gid);
|
2019-06-02 10:30:24 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
static bool hard_link_allowed(Credentials const& credentials, Inode const& inode)
|
2021-01-19 16:59:32 +00:00
|
|
|
{
|
|
|
|
auto metadata = inode.metadata();
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if (credentials.euid() == metadata.uid)
|
2021-01-19 16:59:32 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
if (metadata.is_regular_file()
|
|
|
|
&& !metadata.is_setuid()
|
|
|
|
&& !(metadata.is_setgid() && metadata.mode & S_IXGRP)
|
2022-08-21 14:02:24 +00:00
|
|
|
&& metadata.may_write(credentials)) {
|
2021-01-19 16:59:32 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::link(Credentials const& credentials, StringView old_path, StringView new_path, Custody& base)
|
2019-02-21 12:26:40 +00:00
|
|
|
{
|
2022-11-26 09:48:02 +00:00
|
|
|
// NOTE: To prevent unveil bypass by creating an hardlink after unveiling a path as read-only,
|
|
|
|
// check that if write permission is allowed by the veil info on the old_path.
|
|
|
|
auto old_custody = TRY(resolve_path(credentials, old_path, base, nullptr, O_RDWR));
|
2021-09-05 12:00:18 +00:00
|
|
|
auto& old_inode = old_custody->inode();
|
2019-02-21 12:26:40 +00:00
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> parent_custody;
|
2022-08-21 14:02:24 +00:00
|
|
|
auto new_custody_or_error = resolve_path(credentials, new_path, base, &parent_custody);
|
2019-05-30 16:58:59 +00:00
|
|
|
if (!new_custody_or_error.is_error())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EEXIST;
|
2019-01-22 06:03:44 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
if (!parent_custody)
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOENT;
|
2019-02-27 14:31:26 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
auto& parent_inode = parent_custody->inode();
|
|
|
|
|
|
|
|
if (parent_inode.fsid() != old_inode.fsid())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EXDEV;
|
2019-02-27 14:31:26 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!parent_inode.metadata().may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-02-27 14:31:26 +00:00
|
|
|
|
2020-01-15 21:10:38 +00:00
|
|
|
if (old_inode.is_directory())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EPERM;
|
2020-01-15 21:10:38 +00:00
|
|
|
|
2020-05-28 14:56:25 +00:00
|
|
|
if (parent_custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!hard_link_allowed(credentials, old_inode))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EPERM;
|
2021-01-19 16:59:32 +00:00
|
|
|
|
2021-07-06 09:21:52 +00:00
|
|
|
return parent_inode.add_child(old_inode, KLexicalPath::basename(new_path), old_inode.mode());
|
2019-02-21 12:26:40 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::unlink(Credentials const& credentials, StringView path, Custody& base)
|
2019-02-21 12:26:40 +00:00
|
|
|
{
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> parent_custody;
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path(credentials, path, base, &parent_custody, O_NOFOLLOW_NOERROR | O_UNLINK_INTERNAL));
|
2021-09-05 12:00:18 +00:00
|
|
|
auto& inode = custody->inode();
|
2019-01-23 04:35:42 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
if (inode.is_directory())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EISDIR;
|
2019-02-21 14:45:31 +00:00
|
|
|
|
2020-05-28 14:56:25 +00:00
|
|
|
// We have just checked that the inode is not a directory, and thus it's not
|
|
|
|
// the root. So it should have a parent. Note that this would be invalidated
|
|
|
|
// if we were to support bind-mounting regular files on top of the root.
|
2021-02-23 19:42:32 +00:00
|
|
|
VERIFY(parent_custody);
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
auto& parent_inode = parent_custody->inode();
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!parent_inode.metadata().may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-01-22 06:03:44 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
if (parent_inode.metadata().is_sticky()) {
|
2022-09-01 12:16:32 +00:00
|
|
|
if (!credentials.is_superuser() && parent_inode.metadata().uid != credentials.euid() && inode.metadata().uid != credentials.euid())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-04-28 20:54:30 +00:00
|
|
|
}
|
|
|
|
|
2020-05-28 14:56:25 +00:00
|
|
|
if (parent_custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2021-09-05 12:55:25 +00:00
|
|
|
return parent_inode.remove_child(KLexicalPath::basename(path));
|
2019-01-22 06:03:44 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::symlink(Credentials const& credentials, StringView target, StringView linkpath, Custody& base)
|
2019-03-02 00:50:34 +00:00
|
|
|
{
|
2022-12-17 05:55:38 +00:00
|
|
|
// NOTE: Check that the actual target (if it exists right now) is unveiled and prevent creating symlinks on veiled paths!
|
|
|
|
if (auto target_custody_or_error = resolve_path_without_veil(credentials, target, base, nullptr, O_RDWR, 0); !target_custody_or_error.is_error()) {
|
|
|
|
auto target_custody = target_custody_or_error.release_value();
|
|
|
|
TRY(validate_path_against_process_veil(*target_custody, O_RDWR));
|
|
|
|
}
|
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> parent_custody;
|
2022-12-17 05:55:38 +00:00
|
|
|
auto existing_custody_or_error = resolve_path(credentials, linkpath, base, &parent_custody, O_RDWR);
|
2019-05-30 16:58:59 +00:00
|
|
|
if (!existing_custody_or_error.is_error())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EEXIST;
|
2019-05-30 16:58:59 +00:00
|
|
|
if (!parent_custody)
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOENT;
|
2022-12-17 05:55:38 +00:00
|
|
|
|
|
|
|
// NOTE: VERY IMPORTANT! We prevent creating symlinks in case the program didn't unveil the parent_custody
|
|
|
|
// path! For example, say the program wanted to create a symlink in /tmp/symlink to /tmp/test/pointee_symlink
|
|
|
|
// and unveiled the /tmp/test/ directory path beforehand, but not the /tmp directory path - the symlink syscall will
|
|
|
|
// fail here because we can't create the symlink in a parent directory path we didn't unveil beforehand.
|
|
|
|
TRY(validate_path_against_process_veil(*parent_custody, O_RDWR));
|
|
|
|
|
2021-11-07 23:51:39 +00:00
|
|
|
if (existing_custody_or_error.is_error() && existing_custody_or_error.error().code() != ENOENT)
|
|
|
|
return existing_custody_or_error.release_error();
|
2019-05-30 16:58:59 +00:00
|
|
|
auto& parent_inode = parent_custody->inode();
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!parent_inode.metadata().may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2020-05-28 14:56:25 +00:00
|
|
|
if (parent_custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2019-03-02 00:50:34 +00:00
|
|
|
|
2021-07-06 09:21:52 +00:00
|
|
|
auto basename = KLexicalPath::basename(linkpath);
|
2021-07-10 22:25:24 +00:00
|
|
|
dbgln_if(VFS_DEBUG, "VirtualFileSystem::symlink: '{}' (-> '{}') in {}", basename, target, parent_inode.identifier());
|
2021-09-05 12:00:18 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
auto inode = TRY(parent_inode.create_child(basename, S_IFLNK | 0644, 0, credentials.euid(), credentials.egid()));
|
2021-09-05 12:00:18 +00:00
|
|
|
|
2022-04-01 17:58:27 +00:00
|
|
|
auto target_buffer = UserOrKernelBuffer::for_kernel_buffer(const_cast<u8*>((u8 const*)target.characters_without_null_termination()));
|
2021-09-05 12:00:18 +00:00
|
|
|
TRY(inode->write_bytes(0, target.length(), target_buffer, nullptr));
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2019-03-02 00:50:34 +00:00
|
|
|
}
|
|
|
|
|
2022-12-19 18:05:44 +00:00
|
|
|
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::rmdir(Credentials const& credentials, StringView path, Custody& base)
|
2019-01-28 03:16:01 +00:00
|
|
|
{
|
2022-08-20 23:04:35 +00:00
|
|
|
RefPtr<Custody> parent_custody;
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path(credentials, path, base, &parent_custody));
|
2021-09-05 12:00:18 +00:00
|
|
|
auto& inode = custody->inode();
|
2019-01-28 03:16:01 +00:00
|
|
|
|
2022-12-19 17:47:35 +00:00
|
|
|
auto last_component = KLexicalPath::basename(path);
|
|
|
|
|
|
|
|
// [EINVAL] The path argument contains a last component that is dot.
|
|
|
|
if (last_component == "."sv)
|
|
|
|
return EINVAL;
|
|
|
|
|
2022-12-19 18:05:44 +00:00
|
|
|
// [ENOTDIR] A component of path names an existing file that is neither a directory
|
|
|
|
// nor a symbolic link to a directory.
|
2019-05-30 16:58:59 +00:00
|
|
|
if (!inode.is_directory())
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOTDIR;
|
2019-02-21 14:45:31 +00:00
|
|
|
|
2022-12-19 18:05:44 +00:00
|
|
|
// [EBUSY] The directory to be removed is currently in use by the system or some process
|
|
|
|
// and the implementation considers this to be an error.
|
|
|
|
// NOTE: If there is no parent, that means we're trying to rmdir the root directory!
|
2020-04-19 16:07:16 +00:00
|
|
|
if (!parent_custody)
|
2021-01-20 22:11:17 +00:00
|
|
|
return EBUSY;
|
2020-04-19 16:07:16 +00:00
|
|
|
|
2019-05-30 16:58:59 +00:00
|
|
|
auto& parent_inode = parent_custody->inode();
|
2021-01-10 09:12:15 +00:00
|
|
|
auto parent_metadata = parent_inode.metadata();
|
2019-05-30 16:58:59 +00:00
|
|
|
|
2022-12-19 18:05:44 +00:00
|
|
|
// [EACCES] Search permission is denied on a component of the path prefix,
|
|
|
|
// or write permission is denied on the parent directory of the directory to be removed.
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!parent_metadata.may_write(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-01-28 03:16:01 +00:00
|
|
|
|
2021-01-10 09:12:15 +00:00
|
|
|
if (parent_metadata.is_sticky()) {
|
2022-12-19 18:32:31 +00:00
|
|
|
// [EACCES] The S_ISVTX flag is set on the directory containing the file referred to by the path argument
|
|
|
|
// and the process does not satisfy the criteria specified in XBD Directory Protection.
|
|
|
|
if (!credentials.is_superuser()
|
|
|
|
&& inode.metadata().uid != credentials.euid()
|
|
|
|
&& parent_metadata.uid != credentials.euid()) {
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2022-12-19 18:32:31 +00:00
|
|
|
}
|
2021-01-10 09:12:15 +00:00
|
|
|
}
|
|
|
|
|
2021-07-17 20:34:43 +00:00
|
|
|
size_t child_count = 0;
|
2021-11-10 14:42:39 +00:00
|
|
|
TRY(inode.traverse_as_directory([&child_count](auto&) -> ErrorOr<void> {
|
2021-07-17 20:34:43 +00:00
|
|
|
++child_count;
|
2021-11-10 14:42:39 +00:00
|
|
|
return {};
|
2021-09-06 18:30:18 +00:00
|
|
|
}));
|
2020-08-05 08:00:18 +00:00
|
|
|
|
2022-12-19 18:05:44 +00:00
|
|
|
// [ENOTEMPTY] The path argument names a directory that is not an empty directory,
|
|
|
|
// or there are hard links to the directory other than dot or a single entry in dot-dot.
|
2021-07-17 20:34:43 +00:00
|
|
|
if (child_count != 2)
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOTEMPTY;
|
2019-01-28 03:16:01 +00:00
|
|
|
|
2022-12-19 18:05:44 +00:00
|
|
|
// [EROFS] The directory entry to be removed resides on a read-only file system.
|
2021-09-05 12:00:18 +00:00
|
|
|
if (custody->is_readonly())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EROFS;
|
2020-05-28 14:56:25 +00:00
|
|
|
|
2022-07-11 17:32:29 +00:00
|
|
|
TRY(inode.remove_child("."sv));
|
|
|
|
TRY(inode.remove_child(".."sv));
|
2019-01-28 03:16:01 +00:00
|
|
|
|
2021-07-06 09:21:52 +00:00
|
|
|
return parent_inode.remove_child(KLexicalPath::basename(path));
|
2019-01-28 03:16:01 +00:00
|
|
|
}
|
|
|
|
|
2022-02-24 18:08:48 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::for_each_mount(Function<ErrorOr<void>(Mount const&)> callback) const
|
2018-10-26 16:43:25 +00:00
|
|
|
{
|
2022-02-24 18:08:48 +00:00
|
|
|
return m_mounts.with([&](auto& mounts) -> ErrorOr<void> {
|
|
|
|
for (auto& mount : mounts)
|
2022-11-21 20:10:56 +00:00
|
|
|
TRY(callback(mount));
|
2022-02-24 18:08:48 +00:00
|
|
|
return {};
|
2021-08-15 23:40:19 +00:00
|
|
|
});
|
2018-10-26 16:43:25 +00:00
|
|
|
}
|
2018-12-19 23:39:29 +00:00
|
|
|
|
2021-07-10 22:25:24 +00:00
|
|
|
void VirtualFileSystem::sync()
|
2018-12-19 23:39:29 +00:00
|
|
|
{
|
2021-07-10 22:20:38 +00:00
|
|
|
FileSystem::sync();
|
2018-12-19 23:39:29 +00:00
|
|
|
}
|
2019-05-30 15:46:08 +00:00
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
NonnullRefPtr<Custody> VirtualFileSystem::root_custody()
|
2019-05-30 15:46:08 +00:00
|
|
|
{
|
2022-08-20 23:04:35 +00:00
|
|
|
return m_root_custody.with([](auto& root_custody) -> NonnullRefPtr<Custody> { return *root_custody; });
|
2019-05-30 15:46:08 +00:00
|
|
|
}
|
|
|
|
|
2023-03-04 18:01:54 +00:00
|
|
|
UnveilNode const& VirtualFileSystem::find_matching_unveiled_path(Process const& process, StringView path)
|
2020-12-26 10:24:34 +00:00
|
|
|
{
|
2023-03-04 18:01:54 +00:00
|
|
|
VERIFY(process.veil_state() != VeilState::None);
|
|
|
|
return process.unveil_data().with([&](auto const& unveil_data) -> UnveilNode const& {
|
2022-03-07 20:23:08 +00:00
|
|
|
auto path_parts = KLexicalPath::parts(path);
|
|
|
|
return unveil_data.paths.traverse_until_last_accessible_node(path_parts.begin(), path_parts.end());
|
|
|
|
});
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
}
|
|
|
|
|
2021-11-07 23:51:39 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::validate_path_against_process_veil(Custody const& custody, int options)
|
2021-07-05 15:15:07 +00:00
|
|
|
{
|
2023-03-04 18:01:54 +00:00
|
|
|
return validate_path_against_process_veil(Process::current(), custody, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorOr<void> VirtualFileSystem::validate_path_against_process_veil(Process const& process, Custody const& custody, int options)
|
|
|
|
{
|
|
|
|
if (process.veil_state() == VeilState::None)
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
2021-09-06 10:24:36 +00:00
|
|
|
auto absolute_path = TRY(custody.try_serialize_absolute_path());
|
2023-03-04 18:01:54 +00:00
|
|
|
return validate_path_against_process_veil(process, absolute_path->view(), options);
|
2021-07-05 15:15:07 +00:00
|
|
|
}
|
|
|
|
|
2023-03-04 18:01:54 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::validate_path_against_process_veil(Process const& process, StringView path, int options)
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
{
|
2023-03-04 18:01:54 +00:00
|
|
|
if (process.veil_state() == VeilState::None)
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
|
2021-07-05 16:03:54 +00:00
|
|
|
VERIFY(path.starts_with('/'));
|
|
|
|
VERIFY(!path.contains("/../"sv) && !path.ends_with("/.."sv));
|
|
|
|
VERIFY(!path.contains("/./"sv) && !path.ends_with("/."sv));
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
|
2022-03-05 01:05:24 +00:00
|
|
|
#ifdef SKIP_PATH_VALIDATION_FOR_COVERAGE_INSTRUMENTATION
|
|
|
|
// Skip veil validation against profile data when coverage is enabled for userspace
|
|
|
|
// so that all processes can write out coverage data even with veils in place
|
|
|
|
if (KLexicalPath::basename(path).ends_with(".profraw"sv))
|
|
|
|
return {};
|
|
|
|
#endif
|
|
|
|
|
2023-04-25 14:29:10 +00:00
|
|
|
auto log_veiled_path = [&](Optional<StringView> const& with_permissions = {}) {
|
|
|
|
if (with_permissions.has_value())
|
|
|
|
dbgln("\033[31;1mRejecting path '{}' because it hasn't been unveiled with {} permissions\033[0m", path, *with_permissions);
|
|
|
|
else
|
|
|
|
dbgln("\033[31;1mRejecting path '{}' because it hasn't been unveiled\033[0m", path);
|
|
|
|
|
|
|
|
dump_backtrace();
|
|
|
|
};
|
|
|
|
|
2023-03-04 18:01:54 +00:00
|
|
|
auto& unveiled_path = find_matching_unveiled_path(process, path);
|
2021-06-06 21:13:26 +00:00
|
|
|
if (unveiled_path.permissions() == UnveilAccess::None) {
|
2023-04-25 14:29:10 +00:00
|
|
|
log_veiled_path();
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOENT;
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options & O_CREAT) {
|
2021-06-06 21:13:26 +00:00
|
|
|
if (!(unveiled_path.permissions() & UnveilAccess::CreateOrRemove)) {
|
2023-04-25 14:29:10 +00:00
|
|
|
log_veiled_path("'c'"sv);
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (options & O_UNLINK_INTERNAL) {
|
2021-06-06 21:13:26 +00:00
|
|
|
if (!(unveiled_path.permissions() & UnveilAccess::CreateOrRemove)) {
|
2023-04-25 14:29:10 +00:00
|
|
|
log_veiled_path("'c'"sv);
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
}
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
}
|
2020-01-21 12:14:26 +00:00
|
|
|
if (options & O_RDONLY) {
|
2020-11-21 19:55:20 +00:00
|
|
|
if (options & O_DIRECTORY) {
|
2021-06-06 21:13:26 +00:00
|
|
|
if (!(unveiled_path.permissions() & (UnveilAccess::Read | UnveilAccess::Browse))) {
|
2023-04-25 14:29:10 +00:00
|
|
|
log_veiled_path("'r' or 'b'"sv);
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2020-11-21 19:55:20 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-06-06 21:13:26 +00:00
|
|
|
if (!(unveiled_path.permissions() & UnveilAccess::Read)) {
|
2023-04-25 14:29:10 +00:00
|
|
|
log_veiled_path("'r'"sv);
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2020-11-21 19:55:20 +00:00
|
|
|
}
|
2020-01-21 12:14:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (options & O_WRONLY) {
|
2021-06-06 21:13:26 +00:00
|
|
|
if (!(unveiled_path.permissions() & UnveilAccess::Write)) {
|
2023-04-25 14:29:10 +00:00
|
|
|
log_veiled_path("'w'"sv);
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
}
|
2020-01-21 12:14:26 +00:00
|
|
|
}
|
|
|
|
if (options & O_EXEC) {
|
2021-06-06 21:13:26 +00:00
|
|
|
if (!(unveiled_path.permissions() & UnveilAccess::Execute)) {
|
2023-04-25 14:29:10 +00:00
|
|
|
log_veiled_path("'x'"sv);
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
}
|
|
|
|
}
|
2021-11-07 23:51:39 +00:00
|
|
|
return {};
|
Kernel: Add a basic implementation of unveil()
This syscall is a complement to pledge() and adds the same sort of
incremental relinquishing of capabilities for filesystem access.
The first call to unveil() will "drop a veil" on the process, and from
now on, only unveiled parts of the filesystem are visible to it.
Each call to unveil() specifies a path to either a directory or a file
along with permissions for that path. The permissions are a combination
of the following:
- r: Read access (like the "rpath" promise)
- w: Write access (like the "wpath" promise)
- x: Execute access
- c: Create/remove access (like the "cpath" promise)
Attempts to open a path that has not been unveiled with fail with
ENOENT. If the unveiled path lacks sufficient permissions, it will fail
with EACCES.
Like pledge(), subsequent calls to unveil() with the same path can only
remove permissions, not add them.
Once you call unveil(nullptr, nullptr), the veil is locked, and it's no
longer possible to unveil any more paths for the process, ever.
This concept comes from OpenBSD, and their implementation does various
things differently, I'm sure. This is just a first implementation for
SerenityOS, and we'll keep improving on it as we go. :^)
2020-01-20 21:12:04 +00:00
|
|
|
}
|
|
|
|
|
2023-03-04 18:01:54 +00:00
|
|
|
ErrorOr<void> VirtualFileSystem::validate_path_against_process_veil(StringView path, int options)
|
|
|
|
{
|
|
|
|
return validate_path_against_process_veil(Process::current(), path, options);
|
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<NonnullRefPtr<Custody>> VirtualFileSystem::resolve_path(Credentials const& credentials, StringView path, NonnullRefPtr<Custody> base, RefPtr<Custody>* out_parent, int options, int symlink_recursion_level)
|
2023-03-04 18:01:54 +00:00
|
|
|
{
|
|
|
|
return resolve_path(Process::current(), credentials, path, base, out_parent, options, symlink_recursion_level);
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorOr<NonnullRefPtr<Custody>> VirtualFileSystem::resolve_path(Process const& process, Credentials const& credentials, StringView path, NonnullRefPtr<Custody> base, RefPtr<Custody>* out_parent, int options, int symlink_recursion_level)
|
2019-05-30 15:46:08 +00:00
|
|
|
{
|
2022-02-13 16:31:33 +00:00
|
|
|
// FIXME: The errors returned by resolve_path_without_veil can leak information about paths that are not unveiled,
|
|
|
|
// e.g. when the error is EACCESS or similar.
|
2022-08-21 14:02:24 +00:00
|
|
|
auto custody = TRY(resolve_path_without_veil(credentials, path, base, out_parent, options, symlink_recursion_level));
|
2023-03-04 18:01:54 +00:00
|
|
|
if (auto result = validate_path_against_process_veil(process, *custody, options); result.is_error()) {
|
2021-07-11 12:46:15 +00:00
|
|
|
if (out_parent)
|
|
|
|
out_parent->clear();
|
|
|
|
return result.release_error();
|
|
|
|
}
|
2020-03-19 08:57:34 +00:00
|
|
|
return custody;
|
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
static bool safe_to_follow_symlink(Credentials const& credentials, Inode const& inode, InodeMetadata const& parent_metadata)
|
2021-01-19 17:12:09 +00:00
|
|
|
{
|
|
|
|
auto metadata = inode.metadata();
|
2022-08-21 14:02:24 +00:00
|
|
|
if (credentials.euid() == metadata.uid)
|
2021-01-19 17:12:09 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
if (!(parent_metadata.is_sticky() && parent_metadata.mode & S_IWOTH))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (metadata.uid == parent_metadata.uid)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
ErrorOr<NonnullRefPtr<Custody>> VirtualFileSystem::resolve_path_without_veil(Credentials const& credentials, StringView path, NonnullRefPtr<Custody> base, RefPtr<Custody>* out_parent, int options, int symlink_recursion_level)
|
2020-03-19 08:57:34 +00:00
|
|
|
{
|
2019-12-24 09:39:21 +00:00
|
|
|
if (symlink_recursion_level >= symlink_recursion_limit)
|
2021-01-20 22:11:17 +00:00
|
|
|
return ELOOP;
|
2019-08-25 16:18:51 +00:00
|
|
|
|
2019-05-30 15:46:08 +00:00
|
|
|
if (path.is_empty())
|
2021-01-20 22:11:17 +00:00
|
|
|
return EINVAL;
|
2019-05-30 15:46:08 +00:00
|
|
|
|
2021-05-21 22:12:32 +00:00
|
|
|
GenericLexer path_lexer(path);
|
2020-01-10 22:14:04 +00:00
|
|
|
|
2022-08-20 23:04:35 +00:00
|
|
|
NonnullRefPtr<Custody> custody = path[0] == '/' ? root_custody() : base;
|
2021-05-21 22:12:32 +00:00
|
|
|
bool extra_iteration = path[path.length() - 1] == '/';
|
|
|
|
|
|
|
|
while (!path_lexer.is_eof() || extra_iteration) {
|
|
|
|
if (path_lexer.is_eof())
|
|
|
|
extra_iteration = false;
|
|
|
|
auto part = path_lexer.consume_until('/');
|
2022-01-24 21:47:22 +00:00
|
|
|
path_lexer.ignore();
|
2019-05-30 15:46:08 +00:00
|
|
|
|
2020-01-15 07:52:33 +00:00
|
|
|
Custody& parent = custody;
|
|
|
|
auto parent_metadata = parent.inode().metadata();
|
2020-01-14 10:30:15 +00:00
|
|
|
if (!parent_metadata.is_directory())
|
2021-01-20 22:11:17 +00:00
|
|
|
return ENOTDIR;
|
2020-01-14 10:30:15 +00:00
|
|
|
// Ensure the current user is allowed to resolve paths inside this directory.
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!parent_metadata.may_execute(credentials))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2019-06-13 13:33:01 +00:00
|
|
|
|
2021-05-21 22:12:32 +00:00
|
|
|
bool have_more_parts = !path_lexer.is_eof() || extra_iteration;
|
2020-01-14 10:30:15 +00:00
|
|
|
|
|
|
|
if (part == "..") {
|
|
|
|
// If we encounter a "..", take a step back, but don't go beyond the root.
|
2020-01-15 07:52:33 +00:00
|
|
|
if (custody->parent())
|
|
|
|
custody = *custody->parent();
|
2019-09-20 21:45:16 +00:00
|
|
|
continue;
|
2020-01-14 10:30:15 +00:00
|
|
|
} else if (part == "." || part.is_empty()) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-06-13 13:33:01 +00:00
|
|
|
|
2020-01-14 10:30:15 +00:00
|
|
|
// Okay, let's look up this part.
|
2021-08-14 11:32:35 +00:00
|
|
|
auto child_or_error = parent.inode().lookup(part);
|
|
|
|
if (child_or_error.is_error()) {
|
2020-01-15 07:52:33 +00:00
|
|
|
if (out_parent) {
|
2020-01-14 10:30:15 +00:00
|
|
|
// ENOENT with a non-null parent custody signals to caller that
|
2020-01-03 02:53:06 +00:00
|
|
|
// we found the immediate parent of the file, but the file itself
|
|
|
|
// does not exist yet.
|
2020-01-15 07:52:33 +00:00
|
|
|
*out_parent = have_more_parts ? nullptr : &parent;
|
2020-01-03 02:53:06 +00:00
|
|
|
}
|
2021-11-07 23:51:39 +00:00
|
|
|
return child_or_error.release_error();
|
2020-01-03 02:53:06 +00:00
|
|
|
}
|
2021-08-14 11:32:35 +00:00
|
|
|
auto child_inode = child_or_error.release_value();
|
2020-01-14 10:30:15 +00:00
|
|
|
|
2020-01-15 07:52:33 +00:00
|
|
|
int mount_flags_for_child = parent.mount_flags();
|
2020-02-01 08:23:46 +00:00
|
|
|
|
2020-01-14 10:30:15 +00:00
|
|
|
// See if there's something mounted on the child; in that case
|
|
|
|
// we would need to return the guest inode, not the host inode.
|
2021-07-10 22:50:08 +00:00
|
|
|
if (auto mount = find_mount_for_host(child_inode->identifier())) {
|
2020-06-24 21:16:24 +00:00
|
|
|
child_inode = mount->guest();
|
2020-01-14 10:30:15 +00:00
|
|
|
mount_flags_for_child = mount->flags();
|
2019-05-30 15:46:08 +00:00
|
|
|
}
|
2019-05-31 04:42:49 +00:00
|
|
|
|
2021-09-05 12:00:18 +00:00
|
|
|
custody = TRY(Custody::try_create(&parent, part, *child_inode, mount_flags_for_child));
|
2019-05-31 04:42:49 +00:00
|
|
|
|
2020-01-14 10:30:15 +00:00
|
|
|
if (child_inode->metadata().is_symlink()) {
|
|
|
|
if (!have_more_parts) {
|
2019-05-30 15:46:08 +00:00
|
|
|
if (options & O_NOFOLLOW)
|
2021-01-20 22:11:17 +00:00
|
|
|
return ELOOP;
|
2019-05-30 15:46:08 +00:00
|
|
|
if (options & O_NOFOLLOW_NOERROR)
|
2020-01-14 10:30:15 +00:00
|
|
|
break;
|
2019-05-30 15:46:08 +00:00
|
|
|
}
|
2021-01-19 17:12:09 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
if (!safe_to_follow_symlink(credentials, *child_inode, parent_metadata))
|
2021-01-20 22:11:17 +00:00
|
|
|
return EACCES;
|
2021-01-19 17:12:09 +00:00
|
|
|
|
2021-09-05 12:55:25 +00:00
|
|
|
TRY(validate_path_against_process_veil(*custody, options));
|
2021-02-06 18:11:44 +00:00
|
|
|
|
2022-08-21 14:17:13 +00:00
|
|
|
auto symlink_target = TRY(child_inode->resolve_as_link(credentials, parent, out_parent, options, symlink_recursion_level + 1));
|
2021-09-05 12:55:25 +00:00
|
|
|
if (!have_more_parts)
|
2019-06-12 13:36:05 +00:00
|
|
|
return symlink_target;
|
|
|
|
|
2020-01-14 10:30:15 +00:00
|
|
|
// Now, resolve the remaining path relative to the symlink target.
|
|
|
|
// We prepend a "." to it to ensure that it's not empty and that
|
|
|
|
// any initial slashes it might have get interpreted properly.
|
|
|
|
StringBuilder remaining_path;
|
2021-12-29 19:13:29 +00:00
|
|
|
TRY(remaining_path.try_append('.'));
|
|
|
|
TRY(remaining_path.try_append(path.substring_view_starting_after_substring(part)));
|
2019-06-12 13:36:05 +00:00
|
|
|
|
2022-08-21 14:02:24 +00:00
|
|
|
return resolve_path_without_veil(credentials, remaining_path.string_view(), symlink_target, out_parent, options, symlink_recursion_level + 1);
|
2019-05-30 15:46:08 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-14 10:30:15 +00:00
|
|
|
|
2020-01-15 07:52:33 +00:00
|
|
|
if (out_parent)
|
|
|
|
*out_parent = custody->parent();
|
|
|
|
return custody;
|
2019-05-30 15:46:08 +00:00
|
|
|
}
|
2020-02-16 00:27:42 +00:00
|
|
|
}
|