diff --git a/Kernel/API/Ioctl.h b/Kernel/API/Ioctl.h index 0785a320967..608e9c84c7f 100644 --- a/Kernel/API/Ioctl.h +++ b/Kernel/API/Ioctl.h @@ -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 diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index be6b25ff878..ca67fdb7cdf 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -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 diff --git a/Kernel/Devices/Generic/DeviceControlDevice.cpp b/Kernel/Devices/Generic/DeviceControlDevice.cpp index d06ce6b9294..fdf3a8b7a6c 100644 --- a/Kernel/Devices/Generic/DeviceControlDevice.cpp +++ b/Kernel/Devices/Generic/DeviceControlDevice.cpp @@ -4,8 +4,11 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include +#include +#include namespace Kernel { @@ -51,9 +54,33 @@ ErrorOr DeviceControlDevice::read(OpenFileDescription&, u64 offset, User }); } -ErrorOr DeviceControlDevice::ioctl(OpenFileDescription&, unsigned, Userspace) +ErrorOr DeviceControlDevice::ioctl(OpenFileDescription&, unsigned request, Userspace arg) { - return Error::from_errno(ENOTSUP); + switch (request) { + case DEVCTL_CREATE_LOOP_DEVICE: { + unsigned fd { 0 }; + TRY(copy_from_user(&fd, static_ptr_cast(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(arg), &index); + } + case DEVCTL_DESTROY_LOOP_DEVICE: { + unsigned index { 0 }; + TRY(copy_from_user(&index, static_ptr_cast(arg))); + return LoopDevice::all_instances().with([index](auto& list) -> ErrorOr { + for (auto& device : list) { + if (device.index() == index) { + device.remove({}); + return {}; + } + } + return Error::from_errno(ENODEV); + }); + } + default: + return Error::from_errno(EINVAL); + }; } } diff --git a/Kernel/Devices/Loop/LoopDevice.cpp b/Kernel/Devices/Loop/LoopDevice.cpp new file mode 100644 index 00000000000..ebbd3911b05 --- /dev/null +++ b/Kernel/Devices/Loop/LoopDevice.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Kernel { + +static Singleton> s_all_instances; +static Atomic s_loop_device_id { 0 }; + +SpinlockProtected& LoopDevice::all_instances() +{ + return s_all_instances; +} + +void LoopDevice::remove(Badge) +{ + 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(*this).revoke_weak_ptrs(); + return true; + }); + if (did_hit_zero) { + const_cast(*this).will_be_destroyed(); + delete this; + } + return did_hit_zero; +} + +ErrorOr> 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 device = *TRY(DeviceManagement::try_create_device(*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 LoopDevice::read(OpenFileDescription& description, u64 offset, UserOrKernelBuffer& buffer, size_t size) +{ + return m_backing_custody->inode().read_bytes(offset, size, buffer, &description); +} + +ErrorOr 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 backing_custody, unsigned index) + : BlockDevice(20, index, 512) + , m_backing_custody(backing_custody) + , m_index(index) +{ +} + +ErrorOr LoopDevice::ioctl(OpenFileDescription&, unsigned, Userspace) +{ + return EINVAL; +} + +} diff --git a/Kernel/Devices/Loop/LoopDevice.h b/Kernel/Devices/Loop/LoopDevice.h new file mode 100644 index 00000000000..eacfcada09b --- /dev/null +++ b/Kernel/Devices/Loop/LoopDevice.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024, Liav A. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Kernel { + +class LoopDevice final : public BlockDevice { + friend class DeviceManagement; + +public: + virtual bool unref() const override; + virtual ~LoopDevice() = default; + + void remove(Badge); + static ErrorOr> create_with_file_description(OpenFileDescription&); + + virtual StringView class_name() const override { return "LoopDevice"sv; } + + virtual ErrorOr read(OpenFileDescription&, u64, UserOrKernelBuffer&, size_t) override; + virtual ErrorOr 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 ioctl(OpenFileDescription&, unsigned request, Userspace 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, unsigned index); + + NonnullRefPtr const m_backing_custody; + unsigned const m_index { 0 }; + + mutable IntrusiveListNode> m_list_node; + +public: + using List = IntrusiveList<&LoopDevice::m_list_node>; + + static SpinlockProtected& all_instances(); +}; + +} diff --git a/Kernel/FileSystem/Ext2FS/FileSystem.h b/Kernel/FileSystem/Ext2FS/FileSystem.h index 3240638135d..a71f1668ba7 100644 --- a/Kernel/FileSystem/Ext2FS/FileSystem.h +++ b/Kernel/FileSystem/Ext2FS/FileSystem.h @@ -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; diff --git a/Kernel/FileSystem/File.h b/Kernel/FileSystem/File.h index 63e8c6d7a31..be5fb07ceb7 100644 --- a/Kernel/FileSystem/File.h +++ b/Kernel/FileSystem/File.h @@ -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; } diff --git a/Kernel/FileSystem/FileSystem.h b/Kernel/FileSystem/FileSystem.h index b7e6ae7ba2a..a6c2ee78238 100644 --- a/Kernel/FileSystem/FileSystem.h +++ b/Kernel/FileSystem/FileSystem.h @@ -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; } diff --git a/Kernel/FileSystem/RAMFS/FileSystem.h b/Kernel/FileSystem/RAMFS/FileSystem.h index 9033bb7c0cd..92e8cae4914 100644 --- a/Kernel/FileSystem/RAMFS/FileSystem.h +++ b/Kernel/FileSystem/RAMFS/FileSystem.h @@ -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; diff --git a/Kernel/FileSystem/SysFS/Subsystems/Kernel/DiskUsage.cpp b/Kernel/FileSystem/SysFS/Subsystems/Kernel/DiskUsage.cpp index cf06d1149ee..e9e7005b8fa 100644 --- a/Kernel/FileSystem/SysFS/Subsystems/Kernel/DiskUsage.cpp +++ b/Kernel/FileSystem/SysFS/Subsystems/Kernel/DiskUsage.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -40,8 +41,15 @@ ErrorOr 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(fs).file_description().pseudo_path()); - TRY(fs_object.add("source"sv, pseudo_path->view())); + auto& file = static_cast(fs).file(); + if (file.is_loop_device()) { + auto& device = static_cast(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(fs).file_description().pseudo_path()); + TRY(fs_object.add("source"sv, pseudo_path->view())); + } } else { TRY(fs_object.add("source"sv, "none")); } diff --git a/Kernel/FileSystem/VirtualFileSystem.cpp b/Kernel/FileSystem/VirtualFileSystem.cpp index e586d8ba63d..59cd24c2a98 100644 --- a/Kernel/FileSystem/VirtualFileSystem.cpp +++ b/Kernel/FileSystem/VirtualFileSystem.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -213,6 +214,12 @@ ErrorOr VirtualFileSystem::mount(MountFile& mount_file, OpenFileDescriptio })); TRY(fs->initialize()); } + if (source_description->file().is_loop_device()) { + auto& device = static_cast(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(*fs)); return {}; diff --git a/Kernel/Forward.h b/Kernel/Forward.h index 8d3f7f6c9d6..9b372327c01 100644 --- a/Kernel/Forward.h +++ b/Kernel/Forward.h @@ -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;