Kernel/Devices: Introduce the LoopDevice device

This device is a block device that allows a user to effectively treat an
Inode as a block device.

The static construction method is given an OpenFileDescription reference
but validates that:
- The description has a valid custody (so it's not some arbitrary file).
  Failing this requirement will yield EINVAL.
- The description custody points to an Inode which is a regular file, as
  we only support (seekable) regular files. Failing this requirement
  will yield ENOTSUP.

LoopDevice can be used to mount a regular file on the filesystem like
other supported types of (physical) block devices.
This commit is contained in:
Liav A 2024-02-23 17:11:48 +02:00 committed by Andrew Kaster
parent a9d240c647
commit 5dcf03ad9a
Notes: sideshowbarker 2024-07-16 19:42:24 +09:00
12 changed files with 254 additions and 4 deletions

View File

@ -143,6 +143,8 @@ enum IOCtlNumber {
VIRGL_IOCTL_TRANSFER_DATA,
KDSETMODE,
KDGETMODE,
DEVCTL_CREATE_LOOP_DEVICE,
DEVCTL_DESTROY_LOOP_DEVICE,
};
#define TIOCGPGRP TIOCGPGRP
@ -202,3 +204,5 @@ enum IOCtlNumber {
#define VIRGL_IOCTL_TRANSFER_DATA VIRGL_IOCTL_TRANSFER_DATA
#define KDSETMODE KDSETMODE
#define KDGETMODE KDGETMODE
#define DEVCTL_CREATE_LOOP_DEVICE DEVCTL_CREATE_LOOP_DEVICE
#define DEVCTL_DESTROY_LOOP_DEVICE DEVCTL_DESTROY_LOOP_DEVICE

View File

@ -113,6 +113,7 @@ set(KERNEL_SOURCES
Devices/GPU/VirtIO/Console.cpp
Devices/GPU/VirtIO/GPU3DDevice.cpp
Devices/GPU/VirtIO/GraphicsAdapter.cpp
Devices/Loop/LoopDevice.cpp
Devices/Storage/ATA/AHCI/Controller.cpp
Devices/Storage/ATA/AHCI/Port.cpp
Devices/Storage/ATA/AHCI/InterruptHandler.cpp

View File

@ -4,8 +4,11 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/API/Ioctl.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/Generic/DeviceControlDevice.h>
#include <Kernel/Devices/Loop/LoopDevice.h>
#include <Kernel/Library/StdLib.h>
namespace Kernel {
@ -51,9 +54,33 @@ ErrorOr<size_t> DeviceControlDevice::read(OpenFileDescription&, u64 offset, User
});
}
ErrorOr<void> DeviceControlDevice::ioctl(OpenFileDescription&, unsigned, Userspace<void*>)
ErrorOr<void> DeviceControlDevice::ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg)
{
return Error::from_errno(ENOTSUP);
switch (request) {
case DEVCTL_CREATE_LOOP_DEVICE: {
unsigned fd { 0 };
TRY(copy_from_user(&fd, static_ptr_cast<unsigned*>(arg)));
auto file_description = TRY(Process::current().open_file_description(fd));
auto device = TRY(LoopDevice::create_with_file_description(file_description));
unsigned index = device->index();
return copy_to_user(static_ptr_cast<unsigned*>(arg), &index);
}
case DEVCTL_DESTROY_LOOP_DEVICE: {
unsigned index { 0 };
TRY(copy_from_user(&index, static_ptr_cast<unsigned*>(arg)));
return LoopDevice::all_instances().with([index](auto& list) -> ErrorOr<void> {
for (auto& device : list) {
if (device.index() == index) {
device.remove({});
return {};
}
}
return Error::from_errno(ENODEV);
});
}
default:
return Error::from_errno(EINVAL);
};
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright (c) 2024, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Singleton.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/Loop/LoopDevice.h>
#include <Kernel/Sections.h>
#include <Kernel/Tasks/WorkQueue.h>
namespace Kernel {
static Singleton<SpinlockProtected<LoopDevice::List, LockRank::None>> s_all_instances;
static Atomic<u64> s_loop_device_id { 0 };
SpinlockProtected<LoopDevice::List, LockRank::None>& LoopDevice::all_instances()
{
return s_all_instances;
}
void LoopDevice::remove(Badge<DeviceControlDevice>)
{
LoopDevice::all_instances().with([&](auto&) {
m_list_node.remove();
});
}
bool LoopDevice::unref() const
{
bool did_hit_zero = LoopDevice::all_instances().with([&](auto&) {
if (deref_base())
return false;
const_cast<LoopDevice&>(*this).revoke_weak_ptrs();
return true;
});
if (did_hit_zero) {
const_cast<LoopDevice&>(*this).will_be_destroyed();
delete this;
}
return did_hit_zero;
}
ErrorOr<NonnullRefPtr<LoopDevice>> LoopDevice::create_with_file_description(OpenFileDescription& description)
{
auto custody = description.custody();
if (!custody)
return Error::from_errno(EINVAL);
// NOTE: We only support regular inode files, because anything else
// just doesn't make sense (could be non-seekable files or char devices)
if (!custody->inode().metadata().is_regular_file())
return Error::from_errno(ENOTSUP);
// NOTE: We could technically allow the user to create a loop device from a file
// on SysFS, ProcFS, etc, but the added value from allowing this is non-existent
// because there's simply no good reason to ever do this kind of operation.
//
// If you need more justification, some filesystems (like ProcFS, SysFS, etc) don't
// support keeping Inode objects and instead keep re-creating them - this has serious
// consequences on the integrity of loop devices, as we rely on the backing Inode to
// be consistent while the LoopDevice is alive.
if (!custody->inode().fs().supports_backing_loop_devices())
return Error::from_errno(ENOTSUP);
return TRY(LoopDevice::all_instances().with([custody](auto& all_instances_list) -> ErrorOr<NonnullRefPtr<LoopDevice>> {
NonnullRefPtr<LoopDevice> device = *TRY(DeviceManagement::try_create_device<LoopDevice>(*custody, s_loop_device_id.fetch_add(1)));
all_instances_list.append(*device);
return device;
}));
}
void LoopDevice::start_request(AsyncBlockDeviceRequest& request)
{
auto work_item_creation_result = g_io_work->try_queue([this, &request]() {
if (request.request_type() == AsyncBlockDeviceRequest::RequestType::Read) {
auto result = m_backing_custody->inode().read_bytes(request.block_index() * request.block_size(), request.buffer_size(), request.buffer(), nullptr);
if (result.is_error())
request.complete(AsyncDeviceRequest::Failure);
else
request.complete(AsyncDeviceRequest::Success);
return;
} else if (request.request_type() == AsyncBlockDeviceRequest::RequestType::Write) {
auto result = m_backing_custody->inode().write_bytes(request.block_index() * request.block_size(), request.buffer_size(), request.buffer(), nullptr);
if (result.is_error())
request.complete(AsyncDeviceRequest::Failure);
else
request.complete(AsyncDeviceRequest::Success);
return;
}
VERIFY_NOT_REACHED();
});
if (work_item_creation_result.is_error())
request.complete(AsyncDeviceRequest::OutOfMemory);
}
bool LoopDevice::can_read(OpenFileDescription const&, u64) const
{
return true;
}
bool LoopDevice::can_write(OpenFileDescription const&, u64) const
{
return true;
}
ErrorOr<size_t> LoopDevice::read(OpenFileDescription& description, u64 offset, UserOrKernelBuffer& buffer, size_t size)
{
return m_backing_custody->inode().read_bytes(offset, size, buffer, &description);
}
ErrorOr<size_t> LoopDevice::write(OpenFileDescription& description, u64 offset, UserOrKernelBuffer const& buffer, size_t size)
{
return m_backing_custody->inode().write_bytes(offset, size, buffer, &description);
}
// FIXME: Allow passing different block sizes to the constructor
LoopDevice::LoopDevice(NonnullRefPtr<Custody> backing_custody, unsigned index)
: BlockDevice(20, index, 512)
, m_backing_custody(backing_custody)
, m_index(index)
{
}
ErrorOr<void> LoopDevice::ioctl(OpenFileDescription&, unsigned, Userspace<void*>)
{
return EINVAL;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2024, Liav A. <liavalb@hotmail.co.il>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Badge.h>
#include <AK/IntrusiveList.h>
#include <Kernel/Devices/BlockDevice.h>
#include <Kernel/FileSystem/Inode.h>
#include <Kernel/Forward.h>
#include <Kernel/Library/ListedRefCounted.h>
namespace Kernel {
class LoopDevice final : public BlockDevice {
friend class DeviceManagement;
public:
virtual bool unref() const override;
virtual ~LoopDevice() = default;
void remove(Badge<DeviceControlDevice>);
static ErrorOr<NonnullRefPtr<LoopDevice>> create_with_file_description(OpenFileDescription&);
virtual StringView class_name() const override { return "LoopDevice"sv; }
virtual ErrorOr<size_t> read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override;
virtual ErrorOr<size_t> write(OpenFileDescription&, u64, UserOrKernelBuffer const&, size_t) override;
virtual bool can_read(OpenFileDescription const&, u64) const override;
virtual bool can_write(OpenFileDescription const&, u64) const override;
virtual ErrorOr<void> ioctl(OpenFileDescription&, unsigned request, Userspace<void*> arg) override;
virtual bool is_loop_device() const override { return true; }
unsigned index() const { return m_index; }
Inode const& inode() const { return m_backing_custody->inode(); }
Inode& inode() { return m_backing_custody->inode(); }
Custody const& custody() const { return *m_backing_custody; }
Custody& custody() { return *m_backing_custody; }
private:
virtual void start_request(AsyncBlockDeviceRequest&) override;
LoopDevice(NonnullRefPtr<Custody>, unsigned index);
NonnullRefPtr<Custody> const m_backing_custody;
unsigned const m_index { 0 };
mutable IntrusiveListNode<LoopDevice, NonnullRefPtr<LoopDevice>> m_list_node;
public:
using List = IntrusiveList<&LoopDevice::m_list_node>;
static SpinlockProtected<LoopDevice::List, LockRank::None>& all_instances();
};
}

View File

@ -47,6 +47,7 @@ public:
virtual unsigned free_inode_count() const override;
virtual bool supports_watchers() const override { return true; }
virtual bool supports_backing_loop_devices() const override { return true; }
virtual u8 internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const override;

View File

@ -115,6 +115,7 @@ public:
virtual bool is_socket() const { return false; }
virtual bool is_inode_watcher() const { return false; }
virtual bool is_mount_file() const { return false; }
virtual bool is_loop_device() const { return false; }
virtual bool is_regular_file() const { return false; }

View File

@ -34,6 +34,11 @@ public:
virtual Inode& root_inode() = 0;
virtual bool supports_watchers() const { return false; }
// FIXME: We should aim to provide more concise mechanism to ensure
// that backing Inodes from the FileSystem are kept intact so we can
// attach them to a loop device.
virtual bool supports_backing_loop_devices() const { return false; }
bool is_readonly() const { return m_readonly; }
virtual unsigned total_block_count() const { return 0; }

View File

@ -24,6 +24,7 @@ public:
virtual StringView class_name() const override { return "RAMFS"sv; }
virtual bool supports_watchers() const override { return true; }
virtual bool supports_backing_loop_devices() const override { return true; }
virtual Inode& root_inode() override;

View File

@ -5,6 +5,7 @@
*/
#include <AK/JsonObjectSerializer.h>
#include <Kernel/Devices/Loop/LoopDevice.h>
#include <Kernel/FileSystem/FileBackedFileSystem.h>
#include <Kernel/FileSystem/SysFS/Subsystems/Kernel/DiskUsage.h>
#include <Kernel/FileSystem/VirtualFileSystem.h>
@ -40,8 +41,15 @@ ErrorOr<void> SysFSDiskUsage::try_generate(KBufferBuilder& builder)
TRY(fs_object.add("mount_flags"sv, mount.flags()));
if (fs.is_file_backed()) {
auto pseudo_path = TRY(static_cast<const FileBackedFileSystem&>(fs).file_description().pseudo_path());
TRY(fs_object.add("source"sv, pseudo_path->view()));
auto& file = static_cast<const FileBackedFileSystem&>(fs).file();
if (file.is_loop_device()) {
auto& device = static_cast<LoopDevice const&>(file);
auto path = TRY(device.custody().try_serialize_absolute_path());
TRY(fs_object.add("source"sv, path->view()));
} else {
auto pseudo_path = TRY(static_cast<const FileBackedFileSystem&>(fs).file_description().pseudo_path());
TRY(fs_object.add("source"sv, pseudo_path->view()));
}
} else {
TRY(fs_object.add("source"sv, "none"));
}

View File

@ -14,6 +14,7 @@
#include <Kernel/Debug.h>
#include <Kernel/Devices/BlockDevice.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/Loop/LoopDevice.h>
#include <Kernel/FileSystem/Custody.h>
#include <Kernel/FileSystem/FileBackedFileSystem.h>
#include <Kernel/FileSystem/FileSystem.h>
@ -213,6 +214,12 @@ ErrorOr<void> VirtualFileSystem::mount(MountFile& mount_file, OpenFileDescriptio
}));
TRY(fs->initialize());
}
if (source_description->file().is_loop_device()) {
auto& device = static_cast<LoopDevice&>(source_description->file());
auto path = TRY(device.custody().try_serialize_absolute_path());
dbgln("VirtualFileSystem: mounting from loop device {}, originated from {}", device.index(), path->view());
}
TRY(add_file_system_to_mount_table(*fs, mount_point, flags));
list.append(static_cast<FileBackedFileSystem&>(*fs));
return {};

View File

@ -19,6 +19,7 @@ class Coredump;
class Credentials;
class Custody;
class Device;
class DeviceControlDevice;
class DiskCache;
class DoubleBuffer;
class File;
@ -36,6 +37,7 @@ class Jail;
class KBuffer;
class KString;
class LocalSocket;
class LoopDevice;
class Mutex;
class MasterPTY;
class Mount;