mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-26 12:41:59 +03:00
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:
parent
a9d240c647
commit
5dcf03ad9a
Notes:
sideshowbarker
2024-07-16 19:42:24 +09:00
Author: https://github.com/supercomputer7 Commit: https://github.com/SerenityOS/serenity/commit/5dcf03ad9a Pull-request: https://github.com/SerenityOS/serenity/pull/23138 Reviewed-by: https://github.com/ADKaster ✅ Reviewed-by: https://github.com/Hendiadyoin1 Reviewed-by: https://github.com/gmta
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
131
Kernel/Devices/Loop/LoopDevice.cpp
Normal file
131
Kernel/Devices/Loop/LoopDevice.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
62
Kernel/Devices/Loop/LoopDevice.h
Normal file
62
Kernel/Devices/Loop/LoopDevice.h
Normal 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();
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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 {};
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user