mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
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:
parent
f923016e0b
commit
a08d1637e2
Notes:
sideshowbarker
2024-07-17 05:05:51 +09:00
Author: https://github.com/implicitfield Commit: https://github.com/SerenityOS/serenity/commit/a08d1637e2 Pull-request: https://github.com/SerenityOS/serenity/pull/23098 Reviewed-by: https://github.com/ADKaster ✅ Reviewed-by: https://github.com/supercomputer7
15 changed files with 1235 additions and 0 deletions
|
@ -21,6 +21,7 @@
|
|||
#include <Kernel/Bus/VirtIO/Transport/PCIe/Detect.h>
|
||||
#include <Kernel/Devices/Audio/Management.h>
|
||||
#include <Kernel/Devices/DeviceManagement.h>
|
||||
#include <Kernel/Devices/FUSEDevice.h>
|
||||
#include <Kernel/Devices/GPU/Console/BootFramebufferConsole.h>
|
||||
#include <Kernel/Devices/GPU/Management.h>
|
||||
#include <Kernel/Devices/Generic/DeviceControlDevice.h>
|
||||
|
@ -442,6 +443,7 @@ void init_stage2(void*)
|
|||
(void)MemoryDevice::must_create().leak_ref();
|
||||
(void)ZeroDevice::must_create().leak_ref();
|
||||
(void)FullDevice::must_create().leak_ref();
|
||||
(void)FUSEDevice::must_create().leak_ref();
|
||||
(void)RandomDevice::must_create().leak_ref();
|
||||
(void)SelfTTYDevice::must_create().leak_ref();
|
||||
PTYMultiplexer::initialize();
|
||||
|
|
|
@ -66,6 +66,7 @@ set(KERNEL_SOURCES
|
|||
Devices/CharacterDevice.cpp
|
||||
Devices/Device.cpp
|
||||
Devices/DeviceManagement.cpp
|
||||
Devices/FUSEDevice.cpp
|
||||
Devices/PCISerialDevice.cpp
|
||||
Devices/SerialDevice.cpp
|
||||
Devices/HID/AllMiceDevice.cpp
|
||||
|
@ -149,6 +150,8 @@ set(KERNEL_SOURCES
|
|||
FileSystem/File.cpp
|
||||
FileSystem/FileBackedFileSystem.cpp
|
||||
FileSystem/FileSystem.cpp
|
||||
FileSystem/FUSE/FileSystem.cpp
|
||||
FileSystem/FUSE/Inode.cpp
|
||||
FileSystem/Inode.cpp
|
||||
FileSystem/InodeFile.cpp
|
||||
FileSystem/InodeMetadata.cpp
|
||||
|
|
|
@ -87,6 +87,10 @@
|
|||
#cmakedefine01 FORK_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef FUSE_DEBUG
|
||||
#cmakedefine01 FUSE_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef FUTEX_DEBUG
|
||||
#cmakedefine01 FUTEX_DEBUG
|
||||
#endif
|
||||
|
|
198
Kernel/Devices/FUSEDevice.cpp
Normal file
198
Kernel/Devices/FUSEDevice.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
53
Kernel/Devices/FUSEDevice.h
Normal file
53
Kernel/Devices/FUSEDevice.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
308
Kernel/FileSystem/FUSE/Definitions.h
Normal file
308
Kernel/FileSystem/FUSE/Definitions.h
Normal 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)
|
142
Kernel/FileSystem/FUSE/FUSEConnection.h
Normal file
142
Kernel/FileSystem/FUSE/FUSEConnection.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
90
Kernel/FileSystem/FUSE/FileSystem.cpp
Normal file
90
Kernel/FileSystem/FUSE/FileSystem.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
45
Kernel/FileSystem/FUSE/FileSystem.h
Normal file
45
Kernel/FileSystem/FUSE/FileSystem.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
333
Kernel/FileSystem/FUSE/Inode.cpp
Normal file
333
Kernel/FileSystem/FUSE/Inode.cpp
Normal 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());
|
||||
}
|
||||
|
||||
}
|
51
Kernel/FileSystem/FUSE/Inode.h
Normal file
51
Kernel/FileSystem/FUSE/Inode.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -29,6 +29,7 @@
|
|||
#include <Kernel/FileSystem/DevPtsFS/FileSystem.h>
|
||||
#include <Kernel/FileSystem/Ext2FS/FileSystem.h>
|
||||
#include <Kernel/FileSystem/FATFS/FileSystem.h>
|
||||
#include <Kernel/FileSystem/FUSE/FileSystem.h>
|
||||
#include <Kernel/FileSystem/ISO9660FS/FileSystem.h>
|
||||
#include <Kernel/FileSystem/Plan9FS/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 },
|
||||
{ "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 },
|
||||
{ "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)
|
||||
|
|
|
@ -55,6 +55,7 @@ set(FILE_WATCHER_DEBUG ON)
|
|||
set(FILL_PATH_DEBUG ON)
|
||||
set(FLAC_ENCODER_DEBUG ON)
|
||||
set(FORK_DEBUG ON)
|
||||
set(FUSE_DEBUG ON)
|
||||
set(FUTEX_DEBUG ON)
|
||||
set(FUTEXQUEUE_DEBUG ON)
|
||||
set(GEMINI_DEBUG ON)
|
||||
|
|
|
@ -77,6 +77,8 @@ def should_check_file(filename):
|
|||
return False
|
||||
if filename == 'Kernel/FileSystem/Ext2FS/Definitions.h':
|
||||
return False
|
||||
if filename == 'Kernel/FileSystem/FUSE/Definitions.h':
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -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/ptmx"sv, 0666, 5, 2));
|
||||
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
|
||||
TRY(Core::System::create_block_device("/dev/kcov"sv, 0666, 30, 0));
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue