Kernel: Add FUSE support

This adds both the fuse device (used for communication between the
kernel and the filesystem) and filesystem implementation itself.
This commit is contained in:
implicitfield 2024-02-04 17:26:38 +04:00 committed by Andrew Kaster
parent f923016e0b
commit a08d1637e2
Notes: sideshowbarker 2024-07-17 05:05:51 +09:00
15 changed files with 1235 additions and 0 deletions

View file

@ -21,6 +21,7 @@
#include <Kernel/Bus/VirtIO/Transport/PCIe/Detect.h> #include <Kernel/Bus/VirtIO/Transport/PCIe/Detect.h>
#include <Kernel/Devices/Audio/Management.h> #include <Kernel/Devices/Audio/Management.h>
#include <Kernel/Devices/DeviceManagement.h> #include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/FUSEDevice.h>
#include <Kernel/Devices/GPU/Console/BootFramebufferConsole.h> #include <Kernel/Devices/GPU/Console/BootFramebufferConsole.h>
#include <Kernel/Devices/GPU/Management.h> #include <Kernel/Devices/GPU/Management.h>
#include <Kernel/Devices/Generic/DeviceControlDevice.h> #include <Kernel/Devices/Generic/DeviceControlDevice.h>
@ -442,6 +443,7 @@ void init_stage2(void*)
(void)MemoryDevice::must_create().leak_ref(); (void)MemoryDevice::must_create().leak_ref();
(void)ZeroDevice::must_create().leak_ref(); (void)ZeroDevice::must_create().leak_ref();
(void)FullDevice::must_create().leak_ref(); (void)FullDevice::must_create().leak_ref();
(void)FUSEDevice::must_create().leak_ref();
(void)RandomDevice::must_create().leak_ref(); (void)RandomDevice::must_create().leak_ref();
(void)SelfTTYDevice::must_create().leak_ref(); (void)SelfTTYDevice::must_create().leak_ref();
PTYMultiplexer::initialize(); PTYMultiplexer::initialize();

View file

@ -66,6 +66,7 @@ set(KERNEL_SOURCES
Devices/CharacterDevice.cpp Devices/CharacterDevice.cpp
Devices/Device.cpp Devices/Device.cpp
Devices/DeviceManagement.cpp Devices/DeviceManagement.cpp
Devices/FUSEDevice.cpp
Devices/PCISerialDevice.cpp Devices/PCISerialDevice.cpp
Devices/SerialDevice.cpp Devices/SerialDevice.cpp
Devices/HID/AllMiceDevice.cpp Devices/HID/AllMiceDevice.cpp
@ -149,6 +150,8 @@ set(KERNEL_SOURCES
FileSystem/File.cpp FileSystem/File.cpp
FileSystem/FileBackedFileSystem.cpp FileSystem/FileBackedFileSystem.cpp
FileSystem/FileSystem.cpp FileSystem/FileSystem.cpp
FileSystem/FUSE/FileSystem.cpp
FileSystem/FUSE/Inode.cpp
FileSystem/Inode.cpp FileSystem/Inode.cpp
FileSystem/InodeFile.cpp FileSystem/InodeFile.cpp
FileSystem/InodeMetadata.cpp FileSystem/InodeMetadata.cpp

View file

@ -87,6 +87,10 @@
#cmakedefine01 FORK_DEBUG #cmakedefine01 FORK_DEBUG
#endif #endif
#ifndef FUSE_DEBUG
#cmakedefine01 FUSE_DEBUG
#endif
#ifndef FUTEX_DEBUG #ifndef FUTEX_DEBUG
#cmakedefine01 FUTEX_DEBUG #cmakedefine01 FUTEX_DEBUG
#endif #endif

View file

@ -0,0 +1,198 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Optional.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/Devices/FUSEDevice.h>
#include <Kernel/FileSystem/FUSE/Definitions.h>
#include <Kernel/FileSystem/FUSE/FUSEConnection.h>
namespace Kernel {
UNMAP_AFTER_INIT NonnullLockRefPtr<FUSEDevice> FUSEDevice::must_create()
{
return MUST(DeviceManagement::try_create_device<FUSEDevice>());
}
UNMAP_AFTER_INIT FUSEDevice::FUSEDevice()
: CharacterDevice(10, 229)
{
}
UNMAP_AFTER_INIT FUSEDevice::~FUSEDevice() = default;
ErrorOr<void> FUSEDevice::initialize_instance(OpenFileDescription const& fd)
{
return m_instances.with([&](auto& instances) -> ErrorOr<void> {
for (auto const& instance : instances)
VERIFY(instance.fd != &fd);
TRY(instances.try_append({
&fd,
TRY(KBuffer::try_create_with_size("FUSE: Pending request buffer"sv, 0x21000)),
TRY(KBuffer::try_create_with_size("FUSE: Response buffer"sv, 0x21000)),
}));
return {};
});
}
bool FUSEDevice::can_read(OpenFileDescription const& fd, u64) const
{
return m_instances.with([&](auto& instances) {
Optional<size_t> instance_index = {};
for (size_t i = 0; i < instances.size(); ++i) {
if (instances[i].fd == &fd) {
instance_index = i;
break;
}
}
if (!instance_index.has_value()) {
VERIFY(instances.is_empty());
return false;
}
auto& instance = instances[instance_index.value()];
return instance.buffer_ready || instance.drop_request;
});
}
bool FUSEDevice::can_write(OpenFileDescription const&, u64) const
{
return true;
}
ErrorOr<size_t> FUSEDevice::read(OpenFileDescription& fd, u64, UserOrKernelBuffer& buffer, size_t size)
{
return m_instances.with([&](auto& instances) -> ErrorOr<size_t> {
Optional<size_t> instance_index = {};
for (size_t i = 0; i < instances.size(); ++i) {
if (instances[i].fd == &fd) {
instance_index = i;
break;
}
}
if (!instance_index.has_value())
return Error::from_errno(ENODEV);
auto& instance = instances[instance_index.value()];
if (instance.drop_request) {
instance.drop_request = false;
instances.remove(instance_index.value());
return Error::from_errno(ENODEV);
}
if (size < 0x21000)
return Error::from_errno(EIO);
if (!instance.buffer_ready)
return Error::from_errno(ENOENT);
TRY(buffer.write(instance.pending_request->bytes()));
instance.buffer_ready = false;
return instance.pending_request->size();
});
}
ErrorOr<size_t> FUSEDevice::write(OpenFileDescription& description, u64, UserOrKernelBuffer const& buffer, size_t size)
{
return m_instances.with([&](auto& instances) -> ErrorOr<size_t> {
Optional<size_t> instance_index = {};
for (size_t i = 0; i < instances.size(); ++i) {
if (instances[i].fd == &description) {
instance_index = i;
break;
}
}
if (!instance_index.has_value())
return Error::from_errno(ENODEV);
auto& instance = instances[instance_index.value()];
if (instance.expecting_header) {
memset(instance.response->data(), 0, instance.response->size());
fuse_out_header header;
TRY(buffer.read(&header, 0, sizeof(fuse_out_header)));
dbgln_if(FUSE_DEBUG, "header: length: {}, error: {}, unique: {}", header.len, header.error, header.unique);
memcpy(instance.response->data(), &header, sizeof(fuse_out_header));
if (header.len > sizeof(fuse_out_header))
instance.expecting_header = false;
else
instance.response_ready = true;
} else {
fuse_out_header* existing_header = bit_cast<fuse_out_header*>(instance.response->data());
instance.expecting_header = true;
if (existing_header->len > instance.response->size())
return Error::from_errno(EINVAL);
instance.response_ready = true;
u64 length = existing_header->len - sizeof(fuse_out_header);
dbgln_if(FUSE_DEBUG, "request: response length: {}", length);
TRY(buffer.read(instance.response->data() + sizeof(fuse_out_header), 0, length));
}
return size;
});
}
ErrorOr<NonnullOwnPtr<KBuffer>> FUSEDevice::send_request_and_wait_for_a_reply(OpenFileDescription const& description, Bytes bytes)
{
return m_instances.with([&](auto& instances) -> ErrorOr<NonnullOwnPtr<KBuffer>> {
Optional<size_t> instance_index = {};
for (size_t i = 0; i < instances.size(); ++i) {
if (instances[i].fd == &description) {
instance_index = i;
break;
}
}
VERIFY(instance_index.has_value());
auto& instance = instances[instance_index.value()];
VERIFY(!instance.drop_request);
VERIFY(bytes.size() <= 0x21000);
memset(instance.pending_request->data(), 0, instance.pending_request->size());
memcpy(instance.pending_request->data(), bytes.data(), bytes.size());
instance.buffer_ready = true;
evaluate_block_conditions();
while (!instance.response_ready)
(void)Thread::current()->sleep(Duration::from_microseconds(100));
auto result = KBuffer::try_create_with_bytes("FUSEDevice: Response"sv, instance.response->bytes());
instance.response_ready = false;
return result;
});
}
void FUSEDevice::shutdown_for_description(OpenFileDescription const& description)
{
m_instances.with([&](auto& instances) {
Optional<size_t> instance_index = {};
for (size_t i = 0; i < instances.size(); ++i) {
if (instances[i].fd == &description) {
instance_index = i;
break;
}
}
VERIFY(instance_index.has_value());
instances[instance_index.value()].drop_request = true;
});
evaluate_block_conditions();
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Atomic.h>
#include <Kernel/Devices/CharacterDevice.h>
#include <Kernel/Locking/Mutex.h>
#include <Kernel/Locking/SpinlockProtected.h>
namespace Kernel {
struct FUSEInstance {
OpenFileDescription const* fd = nullptr;
NonnullOwnPtr<KBuffer> pending_request;
NonnullOwnPtr<KBuffer> response;
bool drop_request = false;
bool buffer_ready = false;
bool response_ready = false;
bool expecting_header = true;
};
class FUSEDevice final : public CharacterDevice {
friend class DeviceManagement;
public:
static NonnullLockRefPtr<FUSEDevice> must_create();
virtual ~FUSEDevice() override;
ErrorOr<void> initialize_instance(OpenFileDescription const&);
ErrorOr<NonnullOwnPtr<KBuffer>> send_request_and_wait_for_a_reply(OpenFileDescription const&, Bytes);
void shutdown_for_description(OpenFileDescription const&);
private:
FUSEDevice();
// ^Device
virtual bool is_openable_by_jailed_processes() const override { return false; }
// ^CharacterDevice
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 StringView class_name() const override { return "FUSEDevice"sv; }
SpinlockProtected<Vector<FUSEInstance>, LockRank::None> m_instances;
};
}

View file

@ -0,0 +1,308 @@
/*
* Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#pragma once
#include <AK/Types.h>
#define FUSE_KERNEL_VERSION 7
#define FUSE_KERNEL_MINOR_VERSION 39
struct fuse_attr {
uint64_t ino;
uint64_t size;
uint64_t blocks;
uint64_t atime;
uint64_t mtime;
uint64_t ctime;
uint32_t atimensec;
uint32_t mtimensec;
uint32_t ctimensec;
uint32_t mode;
uint32_t nlink;
uint32_t uid;
uint32_t gid;
uint32_t rdev;
uint32_t blksize;
uint32_t flags;
};
// Bitmasks for fuse_setattr_in.valid
#define FATTR_MODE (1 << 0)
#define FATTR_UID (1 << 1)
#define FATTR_GID (1 << 2)
#define FATTR_SIZE (1 << 3)
#define FATTR_ATIME (1 << 4)
#define FATTR_MTIME (1 << 5)
#define FATTR_FH (1 << 6)
#define FATTR_ATIME_NOW (1 << 7)
#define FATTR_MTIME_NOW (1 << 8)
#define FATTR_LOCKOWNER (1 << 9)
#define FATTR_CTIME (1 << 10)
#define FATTR_KILL_SUIDGID (1 << 11)
enum class FUSEOpcode {
FUSE_LOOKUP = 1,
FUSE_FORGET = 2, // no reply
FUSE_GETATTR = 3,
FUSE_SETATTR = 4,
FUSE_READLINK = 5,
FUSE_SYMLINK = 6,
FUSE_MKNOD = 8,
FUSE_MKDIR = 9,
FUSE_UNLINK = 10,
FUSE_RMDIR = 11,
FUSE_RENAME = 12,
FUSE_LINK = 13,
FUSE_OPEN = 14,
FUSE_READ = 15,
FUSE_WRITE = 16,
FUSE_STATFS = 17,
FUSE_RELEASE = 18,
FUSE_FSYNC = 20,
FUSE_SETXATTR = 21,
FUSE_GETXATTR = 22,
FUSE_LISTXATTR = 23,
FUSE_REMOVEXATTR = 24,
FUSE_FLUSH = 25,
FUSE_INIT = 26,
FUSE_OPENDIR = 27,
FUSE_READDIR = 28,
FUSE_RELEASEDIR = 29,
FUSE_FSYNCDIR = 30,
FUSE_GETLK = 31,
FUSE_SETLK = 32,
FUSE_SETLKW = 33,
FUSE_ACCESS = 34,
FUSE_CREATE = 35,
FUSE_INTERRUPT = 36,
FUSE_BMAP = 37,
FUSE_DESTROY = 38,
FUSE_IOCTL = 39,
FUSE_POLL = 40,
FUSE_NOTIFY_REPLY = 41,
FUSE_BATCH_FORGET = 42,
FUSE_FALLOCATE = 43,
FUSE_READDIRPLUS = 44,
FUSE_RENAME2 = 45,
FUSE_LSEEK = 46,
FUSE_COPY_FILE_RANGE = 47,
FUSE_SETUPMAPPING = 48,
FUSE_REMOVEMAPPING = 49,
FUSE_SYNCFS = 50,
FUSE_TMPFILE = 51,
FUSE_STATX = 52,
// CUSE specific operations
CUSE_INIT = 4096,
// Reserved opcodes: helpful to detect structure endian-ness
CUSE_INIT_BSWAP_RESERVED = 1048576, // CUSE_INIT << 8
FUSE_INIT_BSWAP_RESERVED = 436207616, // FUSE_INIT << 24
};
struct fuse_entry_out {
uint64_t nodeid; /* Inode ID */
uint64_t generation; /* Inode generation: nodeid:gen must
be unique for the fs's lifetime */
uint64_t entry_valid; /* Cache timeout for the name */
uint64_t attr_valid; /* Cache timeout for the attributes */
uint32_t entry_valid_nsec;
uint32_t attr_valid_nsec;
struct fuse_attr attr;
};
struct fuse_getattr_in {
uint32_t getattr_flags;
uint32_t dummy;
uint64_t fh;
};
struct fuse_attr_out {
uint64_t attr_valid; /* Cache timeout for the attributes */
uint32_t attr_valid_nsec;
uint32_t dummy;
struct fuse_attr attr;
};
struct fuse_mknod_in {
uint32_t mode;
uint32_t rdev;
uint32_t umask;
uint32_t padding;
};
struct fuse_mkdir_in {
uint32_t mode;
uint32_t umask;
};
struct fuse_rename_in {
uint64_t newdir;
};
struct fuse_link_in {
uint64_t oldnodeid;
};
struct fuse_setattr_in {
uint32_t valid;
uint32_t padding;
uint64_t fh;
uint64_t size;
uint64_t lock_owner;
uint64_t atime;
uint64_t mtime;
uint64_t ctime;
uint32_t atimensec;
uint32_t mtimensec;
uint32_t ctimensec;
uint32_t mode;
uint32_t unused4;
uint32_t uid;
uint32_t gid;
uint32_t unused5;
};
struct fuse_open_in {
uint32_t flags;
uint32_t open_flags; /* FUSE_OPEN_... */
};
struct fuse_create_in {
uint32_t flags;
uint32_t mode;
uint32_t umask;
uint32_t open_flags; /* FUSE_OPEN_... */
};
struct fuse_open_out {
uint64_t fh;
uint32_t open_flags;
uint32_t padding;
};
struct fuse_release_in {
uint64_t fh;
uint32_t flags;
uint32_t release_flags;
uint64_t lock_owner;
};
struct fuse_flush_in {
uint64_t fh;
uint32_t unused;
uint32_t padding;
uint64_t lock_owner;
};
struct fuse_read_in {
uint64_t fh;
uint64_t offset;
uint32_t size;
uint32_t read_flags;
uint64_t lock_owner;
uint32_t flags;
uint32_t padding;
};
struct fuse_write_in {
uint64_t fh;
uint64_t offset;
uint32_t size;
uint32_t write_flags;
uint64_t lock_owner;
uint32_t flags;
uint32_t padding;
};
struct fuse_write_out {
uint32_t size;
uint32_t padding;
};
struct fuse_access_in {
uint32_t mask;
uint32_t padding;
};
struct fuse_init_in {
uint32_t major;
uint32_t minor;
uint32_t max_readahead;
uint32_t flags;
uint32_t flags2;
uint32_t unused[11];
};
struct fuse_init_out {
uint32_t major;
uint32_t minor;
uint32_t max_readahead;
uint32_t flags;
uint16_t max_background;
uint16_t congestion_threshold;
uint32_t max_write;
uint32_t time_gran;
uint16_t max_pages;
uint16_t map_alignment;
uint32_t flags2;
uint32_t unused[7];
};
struct fuse_in_header {
uint32_t len;
uint32_t opcode;
uint64_t unique;
uint64_t nodeid;
uint32_t uid;
uint32_t gid;
uint32_t pid;
uint16_t total_extlen; /* length of extensions in 8byte units */
uint16_t padding;
};
struct fuse_out_header {
uint32_t len;
int32_t error;
uint64_t unique;
};
struct fuse_dirent {
uint64_t ino;
uint64_t off;
uint32_t namelen;
uint32_t type;
char name[];
};
/* Align variable length records to 64bit boundary */
#define FUSE_REC_ALIGN(x) \
(((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
#define FUSE_DIRENT_SIZE(d) \
FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <Kernel/FileSystem/FUSE/Definitions.h>
#include <Kernel/FileSystem/OpenFileDescription.h>
#include <Kernel/Library/KBuffer.h>
namespace Kernel {
class FUSEConnection : public RefCounted<FUSEConnection> {
public:
static ErrorOr<NonnullRefPtr<FUSEConnection>> try_create(NonnullRefPtr<OpenFileDescription> description)
{
if (!description->is_device())
return Error::from_errno(EINVAL);
if (description->device()->class_name() != "FUSEDevice"sv)
return Error::from_errno(EINVAL);
auto connection = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) FUSEConnection(description)));
auto* device = bit_cast<FUSEDevice*>(description->device());
TRY(device->initialize_instance(*description));
return connection;
}
static ErrorOr<NonnullOwnPtr<KBuffer>> create_request(FUSEOpcode opcode, u32 nodeid, u32 unique, ReadonlyBytes request_body)
{
size_t request_length = sizeof(fuse_in_header) + request_body.size();
auto request = TRY(KBuffer::try_create_with_size("FUSE: Request"sv, request_length));
memset(request->data(), 0, request_length);
fuse_in_header* header = bit_cast<fuse_in_header*>(request->data());
header->len = request_length;
header->opcode = to_underlying(opcode);
header->unique = unique;
header->nodeid = nodeid;
// FIXME: Fill in proper values for these.
header->uid = 0;
header->gid = 0;
header->pid = 1;
u8* payload = bit_cast<u8*>(request->data() + sizeof(fuse_in_header));
memcpy(payload, request_body.data(), request_body.size());
return request;
}
ErrorOr<NonnullOwnPtr<KBuffer>> send_request_and_wait_for_a_reply(FUSEOpcode opcode, u32 nodeid, ReadonlyBytes request_body)
{
auto* device = bit_cast<FUSEDevice*>(m_description->device());
// FIXME: Send the init request from the filesystem itself right after it has been
// mounted. (Without blocking the mount syscall.)
if (!m_initialized)
TRY(handle_init());
auto request = TRY(create_request(opcode, nodeid, m_unique, request_body));
auto response = TRY(device->send_request_and_wait_for_a_reply(m_description, request->bytes()));
if (validate_response(*response, m_unique++).is_error())
return Error::from_errno(EIO);
return response;
}
~FUSEConnection()
{
auto* device = bit_cast<FUSEDevice*>(m_description->device());
// Unblock the userspace daemon and tell it to shut down.
device->shutdown_for_description(m_description);
(void)m_description->close();
}
private:
FUSEConnection(NonnullRefPtr<OpenFileDescription> description)
: m_description(description)
{
}
ErrorOr<void> validate_response(KBuffer const& response, u32 unique)
{
if (response.size() < sizeof(fuse_out_header)) {
dmesgln("FUSE: Received a request with a malformed header");
return Error::from_errno(EINVAL);
}
fuse_out_header* header = bit_cast<fuse_out_header*>(response.data());
if (header->unique != unique) {
dmesgln("FUSE: Received a mismatched request");
return Error::from_errno(EINVAL);
}
if (header->len > response.size()) {
dmesgln("FUSE: Received an excessively large request");
return Error::from_errno(EINVAL);
}
return {};
}
ErrorOr<void> handle_init()
{
fuse_init_in init_request;
init_request.major = FUSE_KERNEL_VERSION;
init_request.minor = FUSE_KERNEL_MINOR_VERSION;
init_request.max_readahead = 512;
init_request.flags = 0;
auto* device = bit_cast<FUSEDevice*>(m_description->device());
auto request = TRY(create_request(FUSEOpcode::FUSE_INIT, 0, 0, { &init_request, sizeof(init_request) }));
auto response = TRY(device->send_request_and_wait_for_a_reply(m_description, request->bytes()));
if (validate_response(*response, 0).is_error())
return Error::from_errno(EIO);
fuse_init_out* init = bit_cast<fuse_init_out*>(response->data() + sizeof(fuse_out_header));
m_major = init->major;
m_minor = init->minor;
m_initialized = true;
return {};
}
NonnullRefPtr<OpenFileDescription> m_description;
bool m_initialized { false };
u32 m_unique { 0 };
u32 m_major { 0 };
u32 m_minor { 0 };
};
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/IntegralMath.h>
#include <Kernel/Devices/DeviceManagement.h>
#include <Kernel/FileSystem/FUSE/FileSystem.h>
#include <Kernel/FileSystem/FUSE/Inode.h>
#include <Kernel/Tasks/Process.h>
#include <Kernel/Time/TimeManagement.h>
namespace Kernel {
struct [[gnu::packed]] FUSESpecificFlagsBytes {
u64 pid;
u64 device_inode;
u64 rootmode;
u64 gid;
u64 uid;
};
ErrorOr<NonnullRefPtr<FileSystem>> FUSE::try_create(ReadonlyBytes mount_flags)
{
auto* fuse_mount_flags = reinterpret_cast<FUSESpecificFlagsBytes const*>(mount_flags.data());
auto description = TRY(Process::current().open_file_description(fuse_mount_flags->device_inode));
auto connection = TRY(FUSEConnection::try_create(description));
u64 rootmode = AK::reinterpret_as_octal(fuse_mount_flags->rootmode);
u64 gid = fuse_mount_flags->gid;
u64 uid = fuse_mount_flags->uid;
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) FUSE(connection, rootmode, uid, gid)));
}
ErrorOr<void> FUSE::handle_mount_unsigned_integer_flag(Bytes mount_file_specific_flags_buffer, StringView key, u64 value)
{
auto* fuse_mount_flags = reinterpret_cast<FUSESpecificFlagsBytes*>(mount_file_specific_flags_buffer.data());
if (key == "fd") {
fuse_mount_flags->device_inode = value;
return {};
}
if (key == "rootmode") {
fuse_mount_flags->rootmode = value;
return {};
}
if (key == "gid") {
fuse_mount_flags->gid = value;
return {};
}
if (key == "uid") {
fuse_mount_flags->uid = value;
return {};
}
return EINVAL;
}
FUSE::FUSE(NonnullRefPtr<FUSEConnection> connection, u64 rootmode, u64 gid, u64 uid)
: m_connection(connection)
, m_rootmode(rootmode)
, m_gid(gid)
, m_uid(uid)
{
}
FUSE::~FUSE() = default;
ErrorOr<void> FUSE::initialize()
{
m_root_inode = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) FUSEInode(*this)));
m_root_inode->m_metadata.mode = m_rootmode;
m_root_inode->m_metadata.uid = m_gid;
m_root_inode->m_metadata.gid = m_uid;
m_root_inode->m_metadata.size = 0;
m_root_inode->m_metadata.mtime = TimeManagement::boot_time();
return {};
}
Inode& FUSE::root_inode()
{
return *m_root_inode;
}
u8 FUSE::internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const
{
return ram_backed_file_type_to_directory_entry_type(entry);
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/Devices/FUSEDevice.h>
#include <Kernel/FileSystem/FUSE/FUSEConnection.h>
#include <Kernel/FileSystem/FileSystem.h>
#include <Kernel/FileSystem/Inode.h>
namespace Kernel {
class FUSEInode;
class FUSE final : public FileSystem {
friend class FUSEInode;
public:
virtual ~FUSE() override;
static ErrorOr<NonnullRefPtr<FileSystem>> try_create(ReadonlyBytes mount_flags);
static ErrorOr<void> handle_mount_unsigned_integer_flag(Bytes mount_file_specific_flags_buffer, StringView key, u64);
virtual ErrorOr<void> initialize() override;
virtual StringView class_name() const override { return "FUSE"sv; }
virtual Inode& root_inode() override;
private:
virtual u8 internal_file_type_to_directory_entry_type(DirectoryEntryView const& entry) const override;
FUSE(NonnullRefPtr<FUSEConnection> connection, u64 rootmode, u64 gid, u64 uid);
RefPtr<FUSEInode> m_root_inode;
NonnullRefPtr<FUSEConnection> m_connection;
u64 m_rootmode { 0 };
u64 m_gid { 0 };
u64 m_uid { 0 };
};
}

View file

@ -0,0 +1,333 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/FileSystem/FUSE/Definitions.h>
#include <Kernel/FileSystem/FUSE/Inode.h>
#include <Kernel/FileSystem/RAMBackedFileType.h>
namespace Kernel {
FUSEInode::FUSEInode(FUSE& fs, InodeIndex index)
: Inode(fs, index)
{
}
FUSEInode::FUSEInode(FUSE& fs)
: Inode(fs, 1)
{
}
FUSEInode::~FUSEInode() = default;
ErrorOr<size_t> FUSEInode::read_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer& buffer, OpenFileDescription*) const
{
VERIFY(m_inode_lock.is_locked());
VERIFY(!is_directory());
constexpr size_t max_read_size = 0x21000 - sizeof(fuse_in_header) - sizeof(fuse_read_in);
u64 id = TRY(try_open(false, O_RDONLY));
u32 nodeid = identifier().index().value();
size_t nread = 0;
size_t target_size = size;
while (target_size) {
size_t chunk_size = min(size, max_read_size);
fuse_read_in payload {};
payload.fh = id;
payload.offset = offset + nread;
payload.size = chunk_size;
auto response = TRY(fs().m_connection->send_request_and_wait_for_a_reply(FUSEOpcode::FUSE_READ, nodeid, { &payload, sizeof(payload) }));
fuse_out_header* header = bit_cast<fuse_out_header*>(response->data());
if (header->error)
return Error::from_errno(-header->error);
u32 data_size = min(target_size, header->len - sizeof(fuse_out_header));
if (data_size == 0)
break;
u8* data = bit_cast<u8*>(response->data() + sizeof(fuse_out_header));
TRY(buffer.write(data, nread, data_size));
nread += data_size;
target_size -= data_size;
}
TRY(try_flush(id));
TRY(try_release(id, false));
return nread;
}
ErrorOr<size_t> FUSEInode::write_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer const& buffer, OpenFileDescription*)
{
VERIFY(m_inode_lock.is_locked());
VERIFY(!is_directory());
VERIFY(offset >= 0);
constexpr size_t max_write_size = 0x21000 - sizeof(fuse_in_header) - sizeof(fuse_write_in);
u64 id = TRY(try_open(false, O_WRONLY));
u32 nodeid = identifier().index().value();
size_t nwritten = 0;
while (size) {
size_t chunk_size = min(size, max_write_size);
auto request_buffer = TRY(KBuffer::try_create_with_size("FUSE: Write buffer"sv, sizeof(fuse_write_in) + chunk_size));
fuse_write_in* write_header = bit_cast<fuse_write_in*>(request_buffer->data());
write_header->fh = id;
write_header->offset = offset + nwritten;
write_header->size = chunk_size;
TRY(buffer.read(request_buffer->data() + sizeof(fuse_write_in), nwritten, chunk_size));
auto response = TRY(fs().m_connection->send_request_and_wait_for_a_reply(FUSEOpcode::FUSE_WRITE, nodeid, request_buffer->bytes()));
if (response->size() < sizeof(fuse_out_header) + sizeof(fuse_write_out))
return Error::from_errno(EIO);
fuse_out_header* header = bit_cast<fuse_out_header*>(response->data());
if (header->error)
return Error::from_errno(-header->error);
fuse_write_out* write_response = bit_cast<fuse_write_out*>(response->data() + sizeof(fuse_out_header));
nwritten += write_response->size;
size -= write_response->size;
}
TRY(try_flush(id));
TRY(try_release(id, false));
return nwritten;
}
InodeMetadata FUSEInode::metadata() const
{
InodeMetadata metadata;
metadata.inode = identifier();
u32 id = identifier().index().value();
fuse_getattr_in payload {};
auto response_or_error = fs().m_connection->send_request_and_wait_for_a_reply(FUSEOpcode::FUSE_GETATTR, id, { &payload, sizeof(payload) });
if (response_or_error.is_error())
return {};
auto response = response_or_error.release_value();
fuse_out_header* header = bit_cast<fuse_out_header*>(response->data());
if (header->error || response->size() < sizeof(fuse_out_header) + sizeof(fuse_attr_out))
return {};
fuse_attr_out* getattr_response = bit_cast<fuse_attr_out*>(response->data() + sizeof(fuse_out_header));
metadata.mode = getattr_response->attr.mode;
metadata.size = getattr_response->attr.size;
metadata.block_size = getattr_response->attr.blksize;
metadata.block_count = getattr_response->attr.blocks;
metadata.uid = getattr_response->attr.uid;
metadata.gid = getattr_response->attr.gid;
metadata.link_count = getattr_response->attr.nlink;
metadata.atime = UnixDateTime::from_seconds_since_epoch(getattr_response->attr.atime);
metadata.ctime = UnixDateTime::from_seconds_since_epoch(getattr_response->attr.ctime);
metadata.mtime = UnixDateTime::from_seconds_since_epoch(getattr_response->attr.mtime);
metadata.major_device = major_from_encoded_device(getattr_response->attr.rdev);
metadata.minor_device = minor_from_encoded_device(getattr_response->attr.rdev);
return metadata;
}
ErrorOr<u64> FUSEInode::try_open(bool directory, u32 flags) const
{
u32 id = identifier().index().value();
fuse_open_in payload {};
payload.flags = flags;
auto opcode = directory ? FUSEOpcode::FUSE_OPENDIR : FUSEOpcode::FUSE_OPEN;
auto response = TRY(fs().m_connection->send_request_and_wait_for_a_reply(opcode, id, { &payload, sizeof(payload) }));
if (response->size() < sizeof(fuse_out_header) + sizeof(fuse_open_out))
return Error::from_errno(EIO);
fuse_out_header* header = bit_cast<fuse_out_header*>(response->data());
if (header->error)
return Error::from_errno(-header->error);
fuse_open_out* open_response = bit_cast<fuse_open_out*>(response->data() + sizeof(fuse_out_header));
return open_response->fh;
}
ErrorOr<void> FUSEInode::try_flush(u64 id) const
{
u32 nodeid = identifier().index().value();
fuse_flush_in payload {};
payload.fh = id;
(void)TRY(fs().m_connection->send_request_and_wait_for_a_reply(FUSEOpcode::FUSE_FLUSH, nodeid, { &payload, sizeof(payload) }));
return {};
}
ErrorOr<void> FUSEInode::try_release(u64 id, bool directory) const
{
u32 nodeid = identifier().index().value();
fuse_release_in payload {};
payload.fh = id;
auto opcode = directory ? FUSEOpcode::FUSE_RELEASEDIR : FUSEOpcode::FUSE_RELEASE;
(void)TRY(fs().m_connection->send_request_and_wait_for_a_reply(opcode, nodeid, { &payload, sizeof(payload) }));
return {};
}
static size_t get_dirent_entry_length(size_t name_length)
{
return name_length + FUSE_NAME_OFFSET;
}
static size_t get_dirent_entry_length_padded(size_t name_length)
{
return FUSE_DIRENT_ALIGN(get_dirent_entry_length(name_length));
}
ErrorOr<void> FUSEInode::traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)> callback) const
{
u64 id = TRY(try_open(true, 0));
u32 nodeid = identifier().index().value();
fuse_read_in payload {};
payload.fh = id;
payload.size = 4096;
while (true) {
auto response = TRY(fs().m_connection->send_request_and_wait_for_a_reply(FUSEOpcode::FUSE_READDIR, nodeid, { &payload, sizeof(payload) }));
fuse_out_header* header = bit_cast<fuse_out_header*>(response->data());
if (header->len == sizeof(fuse_out_header))
break;
char* dirents = bit_cast<char*>(response->data() + sizeof(fuse_out_header));
u32 total_size = header->len - sizeof(fuse_out_header);
u32 offset = 0;
while (offset < total_size) {
fuse_dirent* dirent = bit_cast<fuse_dirent*>(dirents + offset);
if (dirent->ino == 0)
break;
if (dirent->namelen > NAME_MAX)
return Error::from_errno(EIO);
TRY(callback({ { dirent->name, dirent->namelen }, { fsid(), dirent->ino }, to_underlying(ram_backed_file_type_from_mode(dirent->type << 12)) }));
offset += get_dirent_entry_length_padded(dirent->namelen);
}
payload.offset = offset;
}
TRY(try_release(id, true));
return {};
}
ErrorOr<NonnullRefPtr<Inode>> FUSEInode::lookup(StringView name)
{
auto name_buffer = TRY(KBuffer::try_create_with_size("FUSE: Lookup name string"sv, name.length() + 1));
memset(name_buffer->data(), 0, name_buffer->size());
memcpy(name_buffer->data(), name.characters_without_null_termination(), name.length());
auto response = TRY(fs().m_connection->send_request_and_wait_for_a_reply(FUSEOpcode::FUSE_LOOKUP, identifier().index().value(), name_buffer->bytes()));
if (response->size() < sizeof(fuse_out_header) + sizeof(fuse_entry_out))
return Error::from_errno(EIO);
fuse_out_header* header = bit_cast<fuse_out_header*>(response->data());
if (header->error)
return Error::from_errno(-header->error);
fuse_entry_out* entry = bit_cast<fuse_entry_out*>(response->data() + sizeof(fuse_out_header));
return TRY(adopt_nonnull_ref_or_enomem(new (nothrow) FUSEInode(fs(), entry->nodeid)));
}
ErrorOr<void> FUSEInode::flush_metadata()
{
return {};
}
ErrorOr<void> FUSEInode::add_child(Inode&, StringView, mode_t)
{
return ENOTIMPL;
}
ErrorOr<NonnullRefPtr<Inode>> FUSEInode::create_child(StringView, mode_t, dev_t, UserID, GroupID)
{
return ENOTIMPL;
}
ErrorOr<void> FUSEInode::remove_child(StringView)
{
return ENOTIMPL;
}
ErrorOr<void> FUSEInode::replace_child(StringView, Inode&)
{
return ENOTIMPL;
}
ErrorOr<void> FUSEInode::chmod(mode_t)
{
return ENOTIMPL;
}
ErrorOr<void> FUSEInode::chown(UserID, GroupID)
{
return ENOTIMPL;
}
ErrorOr<void> FUSEInode::truncate_locked(u64 new_size)
{
VERIFY(m_inode_lock.is_locked());
VERIFY(!is_directory());
u64 id = TRY(try_open(is_directory(), 0));
fuse_setattr_in setattr {};
setattr.fh = id;
setattr.valid = FATTR_SIZE;
setattr.size = new_size;
auto response = TRY(fs().m_connection->send_request_and_wait_for_a_reply(FUSEOpcode::FUSE_SETATTR, identifier().index().value(), { &setattr, sizeof(setattr) }));
fuse_out_header* header = bit_cast<fuse_out_header*>(response->data());
if (header->error)
return Error::from_errno(-header->error);
return try_release(id, is_directory());
}
ErrorOr<void> FUSEInode::update_timestamps(Optional<UnixDateTime> atime, Optional<UnixDateTime> ctime, Optional<UnixDateTime> mtime)
{
MutexLocker locker(m_inode_lock);
u64 id = TRY(try_open(is_directory(), 0));
fuse_setattr_in setattr {};
setattr.fh = id;
if (atime.has_value()) {
setattr.valid |= FATTR_ATIME;
setattr.atime = atime.value().to_timespec().tv_sec;
}
if (ctime.has_value()) {
setattr.valid |= FATTR_CTIME;
setattr.ctime = ctime.value().to_timespec().tv_sec;
}
if (mtime.has_value()) {
setattr.valid |= FATTR_MTIME;
setattr.mtime = mtime.value().to_timespec().tv_sec;
}
auto response = TRY(fs().m_connection->send_request_and_wait_for_a_reply(FUSEOpcode::FUSE_SETATTR, identifier().index().value(), { &setattr, sizeof(setattr) }));
fuse_out_header* header = bit_cast<fuse_out_header*>(response->data());
if (header->error)
return Error::from_errno(-header->error);
return try_release(id, is_directory());
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <Kernel/FileSystem/FUSE/FileSystem.h>
#include <Kernel/FileSystem/Inode.h>
namespace Kernel {
class FUSEInode final : public Inode {
friend class FUSE;
public:
virtual ~FUSEInode() override;
FUSE& fs() { return static_cast<FUSE&>(Inode::fs()); }
FUSE const& fs() const { return static_cast<FUSE const&>(Inode::fs()); }
private:
FUSEInode(FUSE&, InodeIndex);
FUSEInode(FUSE&);
// ^Inode
virtual ErrorOr<size_t> read_bytes_locked(off_t, size_t, UserOrKernelBuffer& buffer, OpenFileDescription*) const override;
virtual InodeMetadata metadata() const override;
virtual ErrorOr<void> traverse_as_directory(Function<ErrorOr<void>(FileSystem::DirectoryEntryView const&)>) const override;
virtual ErrorOr<NonnullRefPtr<Inode>> lookup(StringView name) override;
virtual ErrorOr<void> flush_metadata() override;
virtual ErrorOr<size_t> write_bytes_locked(off_t, size_t, UserOrKernelBuffer const& buffer, OpenFileDescription*) override;
virtual ErrorOr<NonnullRefPtr<Inode>> create_child(StringView name, mode_t, dev_t, UserID, GroupID) override;
virtual ErrorOr<void> add_child(Inode&, StringView name, mode_t) override;
virtual ErrorOr<void> remove_child(StringView name) override;
virtual ErrorOr<void> replace_child(StringView name, Inode& child) override;
virtual ErrorOr<void> chmod(mode_t) override;
virtual ErrorOr<void> chown(UserID, GroupID) override;
virtual ErrorOr<void> truncate_locked(u64) override;
virtual ErrorOr<void> update_timestamps(Optional<UnixDateTime> atime, Optional<UnixDateTime> ctime, Optional<UnixDateTime> mtime) override;
ErrorOr<u64> try_open(bool directory, u32 flags) const;
ErrorOr<void> try_flush(u64 id) const;
ErrorOr<void> try_release(u64 id, bool directory) const;
InodeMetadata m_metadata;
};
}

View file

@ -29,6 +29,7 @@
#include <Kernel/FileSystem/DevPtsFS/FileSystem.h> #include <Kernel/FileSystem/DevPtsFS/FileSystem.h>
#include <Kernel/FileSystem/Ext2FS/FileSystem.h> #include <Kernel/FileSystem/Ext2FS/FileSystem.h>
#include <Kernel/FileSystem/FATFS/FileSystem.h> #include <Kernel/FileSystem/FATFS/FileSystem.h>
#include <Kernel/FileSystem/FUSE/FileSystem.h>
#include <Kernel/FileSystem/ISO9660FS/FileSystem.h> #include <Kernel/FileSystem/ISO9660FS/FileSystem.h>
#include <Kernel/FileSystem/Plan9FS/FileSystem.h> #include <Kernel/FileSystem/Plan9FS/FileSystem.h>
#include <Kernel/FileSystem/ProcFS/FileSystem.h> #include <Kernel/FileSystem/ProcFS/FileSystem.h>
@ -70,6 +71,7 @@ static constexpr FileSystemInitializer s_initializers[] = {
{ "iso9660"sv, "ISO9660FS"sv, true, true, true, ISO9660FS::try_create, {}, handle_mount_boolean_flag_as_invalid, handle_mount_unsigned_integer_flag_as_invalid, handle_mount_signed_integer_flag_as_invalid, handle_mount_ascii_string_flag_as_invalid }, { "iso9660"sv, "ISO9660FS"sv, true, true, true, ISO9660FS::try_create, {}, handle_mount_boolean_flag_as_invalid, handle_mount_unsigned_integer_flag_as_invalid, handle_mount_signed_integer_flag_as_invalid, handle_mount_ascii_string_flag_as_invalid },
{ "fat"sv, "FATFS"sv, true, true, true, FATFS::try_create, {}, handle_mount_boolean_flag_as_invalid, handle_mount_unsigned_integer_flag_as_invalid, handle_mount_signed_integer_flag_as_invalid, handle_mount_ascii_string_flag_as_invalid }, { "fat"sv, "FATFS"sv, true, true, true, FATFS::try_create, {}, handle_mount_boolean_flag_as_invalid, handle_mount_unsigned_integer_flag_as_invalid, handle_mount_signed_integer_flag_as_invalid, handle_mount_ascii_string_flag_as_invalid },
{ "devloop"sv, "DevLoopFS"sv, false, false, false, {}, DevLoopFS::try_create, handle_mount_boolean_flag_as_invalid, handle_mount_unsigned_integer_flag_as_invalid, handle_mount_signed_integer_flag_as_invalid, handle_mount_ascii_string_flag_as_invalid }, { "devloop"sv, "DevLoopFS"sv, false, false, false, {}, DevLoopFS::try_create, handle_mount_boolean_flag_as_invalid, handle_mount_unsigned_integer_flag_as_invalid, handle_mount_signed_integer_flag_as_invalid, handle_mount_ascii_string_flag_as_invalid },
{ "fuse"sv, "FUSE"sv, false, false, false, {}, FUSE::try_create, handle_mount_boolean_flag_as_invalid, FUSE::handle_mount_unsigned_integer_flag, handle_mount_signed_integer_flag_as_invalid, handle_mount_ascii_string_flag_as_invalid },
}; };
ErrorOr<FileSystemInitializer const*> VirtualFileSystem::find_filesystem_type_initializer(StringView fs_type) ErrorOr<FileSystemInitializer const*> VirtualFileSystem::find_filesystem_type_initializer(StringView fs_type)

View file

@ -55,6 +55,7 @@ set(FILE_WATCHER_DEBUG ON)
set(FILL_PATH_DEBUG ON) set(FILL_PATH_DEBUG ON)
set(FLAC_ENCODER_DEBUG ON) set(FLAC_ENCODER_DEBUG ON)
set(FORK_DEBUG ON) set(FORK_DEBUG ON)
set(FUSE_DEBUG ON)
set(FUTEX_DEBUG ON) set(FUTEX_DEBUG ON)
set(FUTEXQUEUE_DEBUG ON) set(FUTEXQUEUE_DEBUG ON)
set(GEMINI_DEBUG ON) set(GEMINI_DEBUG ON)

View file

@ -77,6 +77,8 @@ def should_check_file(filename):
return False return False
if filename == 'Kernel/FileSystem/Ext2FS/Definitions.h': if filename == 'Kernel/FileSystem/Ext2FS/Definitions.h':
return False return False
if filename == 'Kernel/FileSystem/FUSE/Definitions.h':
return False
return True return True

View file

@ -139,6 +139,7 @@ static ErrorOr<void> prepare_bare_minimum_devtmpfs_directory_structure()
TRY(Core::System::create_char_device("/dev/console"sv, 0666, 5, 1)); TRY(Core::System::create_char_device("/dev/console"sv, 0666, 5, 1));
TRY(Core::System::create_char_device("/dev/ptmx"sv, 0666, 5, 2)); TRY(Core::System::create_char_device("/dev/ptmx"sv, 0666, 5, 2));
TRY(Core::System::create_char_device("/dev/tty"sv, 0666, 5, 0)); TRY(Core::System::create_char_device("/dev/tty"sv, 0666, 5, 0));
TRY(Core::System::create_char_device("/dev/fuse"sv, 0666, 10, 229));
#ifdef ENABLE_KERNEL_COVERAGE_COLLECTION #ifdef ENABLE_KERNEL_COVERAGE_COLLECTION
TRY(Core::System::create_block_device("/dev/kcov"sv, 0666, 30, 0)); TRY(Core::System::create_block_device("/dev/kcov"sv, 0666, 30, 0));
#endif #endif