Kernel: Add Plan9FS :^)

This is an (incomplete, and not very stable) implementation of the client side
of the 9P protocol.
This commit is contained in:
Sergey Bugaev 2020-07-02 13:05:56 +03:00 committed by Andreas Kling
parent 187b785a05
commit a8489967a3
Notes: sideshowbarker 2024-07-19 05:09:56 +09:00
5 changed files with 1147 additions and 6 deletions

View file

@ -21,11 +21,13 @@ over `target`.
* `ProcFS` (or `proc`): The process pseudo-filesystem (normally mounted at `/proc`).
* `DevPtsFS` (or `devpts`): The pseudoterminal pseudo-filesystem (normally mounted at `/dev/pts`).
* `TmpFS` (or `tmp`): A non-persistent filesystem that stores all its data in RAM. An instance of this filesystem is normally mounted at `/tmp`.
* `Plan9FS` (or `9p`): A remote filesystem served over the 9P protocol.
For Ext2FS, `source_fd` must refer to an open file descriptor to a file containing
the filesystem image. This may be a device file or any other seekable file. All
the other filesystems ignore the `source_fd` - you can even pass an invalid file
descriptor such as -1.
For Ext2FS, `source_fd` must refer to an open file descriptor to a file
containing the filesystem image. This may be a device file or any other seekable
file. For Plan9FS, `source_fd` must refer to a socket or a device connected to a
9P server. All the other filesystems ignore the `source_fd` - you can even pass
an invalid file descriptor such as -1.
The following `flags` are supported:
@ -80,8 +82,13 @@ launch the initial userspace process.
* `EFAULT`: The `fs_type` or `target` are invalid strings.
* `EPERM`: The current process does not have superuser privileges.
* `ENODEV`: The `fs_type` is unrecognized, or the file descriptor to source is not found, or the source doesn't contain a valid filesystem image. Also, this error occurs if `fs_type` is valid, but the file descriptor from `source_fd` is not seekable.
* `EBADF`: If the `source_fd` is not valid, and either `fs_type` specifies a file-backed filesystem (and not a pseudo filesystem), or `MS_BIND` is specified in flags.
* `ENODEV`: The `fs_type` is unrecognized, or the file descriptor to source is
not found, or the source doesn't contain a valid filesystem image. Also, this
error occurs if `fs_type` is valid and required to be seekable, but the file
descriptor from `source_fd` is not seekable.
* `EBADF`: If the `source_fd` is not valid, and either `fs_type` specifies a
file-backed filesystem (and not a pseudo filesystem), or `MS_BIND` is
specified in flags.
All of the usual path resolution errors may also occur.

View file

@ -43,6 +43,7 @@ set(KERNEL_SOURCES
FileSystem/Inode.cpp
FileSystem/InodeFile.cpp
FileSystem/InodeWatcher.cpp
FileSystem/Plan9FileSystem.cpp
FileSystem/ProcFS.cpp
FileSystem/TmpFS.cpp
FileSystem/VirtualFileSystem.cpp

View file

@ -0,0 +1,940 @@
/*
* Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
* 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 THE COPYRIGHT HOLDERS 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 THE COPYRIGHT HOLDER 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.
*/
#include <Kernel/FileSystem/Plan9FileSystem.h>
#include <Kernel/Process.h>
namespace Kernel {
NonnullRefPtr<Plan9FS> Plan9FS::create(FileDescription& file_description)
{
return adopt(*new Plan9FS(file_description));
}
Plan9FS::Plan9FS(FileDescription& file_description)
: FileBackedFS(file_description)
{
}
Plan9FS::~Plan9FS()
{
// Make sure to destroy the root inode before the FS gets destroyed.
if (m_root_inode) {
ASSERT(m_root_inode->ref_count() == 1);
m_root_inode = nullptr;
}
}
class Plan9FS::Message {
public:
enum class Type : u8 {
// 9P2000.L
Tlerror = 6,
Rlerror = 7,
Tstatfs = 8,
Rstatfs = 9,
Tlopen = 12,
Rlopen = 13,
Tlcreate = 14,
Rlcreate = 15,
Tsymlink = 16,
Rsymlink = 17,
Tmknod = 18,
Rmknod = 19,
Trename = 20,
Rrename = 21,
Treadlink = 22,
Rreadlink = 23,
Tgetattr = 24,
Rgetattr = 25,
Tsetattr = 26,
Rsetattr = 27,
Txattrwalk = 30,
Rxattrwalk = 31,
Txattrcreate = 32,
Rxattrcreate = 33,
Treaddir = 40,
Rreaddir = 41,
Tfsync = 50,
Rfsync = 51,
Tlock = 52,
Rlock = 53,
Tgetlock = 54,
Rgetlock = 55,
Tlink = 70,
Rlink = 71,
Tmkdir = 72,
Rmkdir = 73,
Trenameat = 74,
Rrenameat = 75,
Tunlinkat = 76,
Runlinkat = 77,
// 9P2000
Tversion = 100,
Rversion = 101,
Tauth = 102,
Rauth = 103,
Tattach = 104,
Rattach = 105,
Terror = 106,
Rerror = 107,
Tflush = 108,
Rflush = 109,
Twalk = 110,
Rwalk = 111,
Topen = 112,
Ropen = 113,
Tcreate = 114,
Rcreate = 115,
Tread = 116,
Rread = 117,
Twrite = 118,
Rwrite = 119,
Tclunk = 120,
Rclunk = 121,
Tremove = 122,
Rremove = 123,
Tstat = 124,
Rstat = 125,
Twstat = 126,
Rwstat = 127
};
class Decoder {
public:
explicit Decoder(const StringView& data)
: m_data(data)
{
}
Decoder& operator>>(u8&);
Decoder& operator>>(u16&);
Decoder& operator>>(u32&);
Decoder& operator>>(u64&);
Decoder& operator>>(StringView&);
Decoder& operator>>(qid&);
StringView read_data();
bool has_more_data() const { return !m_data.is_empty(); }
private:
StringView m_data;
template<typename N>
Decoder& read_number(N& number)
{
ASSERT(sizeof(number) <= m_data.length());
memcpy(&number, m_data.characters_without_null_termination(), sizeof(number));
m_data = m_data.substring_view(sizeof(number), m_data.length() - sizeof(number));
return *this;
}
};
Message& operator<<(u8);
Message& operator<<(u16);
Message& operator<<(u32);
Message& operator<<(u64);
Message& operator<<(const StringView&);
void append_data(const StringView&);
template<typename T>
Message& operator>>(T& t)
{
ASSERT(m_have_been_built);
m_built.decoder >> t;
return *this;
}
StringView read_data()
{
ASSERT(m_have_been_built);
return m_built.decoder.read_data();
}
Type type() const { return m_type; }
u16 tag() const { return m_tag; }
Message(Plan9FS&, Type);
Message(KBuffer&&);
~Message();
Message& operator=(Message&&);
const KBuffer& build();
static constexpr ssize_t max_header_size = 24;
private:
template<typename N>
Message& append_number(N number)
{
ASSERT(!m_have_been_built);
m_builder.append(reinterpret_cast<const char*>(&number), sizeof(number));
return *this;
}
union {
KBufferBuilder m_builder;
struct {
KBuffer buffer;
Decoder decoder;
} m_built;
};
u16 m_tag { 0 };
Type m_type { 0 };
bool m_have_been_built { false };
};
bool Plan9FS::initialize()
{
Message version_message { *this, Message::Type::Tversion };
version_message << (u32)m_max_message_size << "9P2000.L";
auto result = post_message_and_wait_for_a_reply(version_message);
if (result.is_error())
return false;
u32 msize;
StringView remote_protocol_version;
version_message >> msize >> remote_protocol_version;
dbg() << "Remote supports msize=" << msize << " and protocol version " << remote_protocol_version;
m_remote_protocol_version = parse_protocol_version(remote_protocol_version);
m_max_message_size = min(m_max_message_size, (size_t)msize);
// TODO: auth
u32 root_fid = allocate_fid();
Message attach_message { *this, Message::Type::Tattach };
// FIXME: This needs a user name and an "export" name; but how do we get them?
// Perhaps initialize() should accept a string of FS-specific options...
attach_message << root_fid << (u32)-1 << "sergey"
<< "/";
if (m_remote_protocol_version >= ProtocolVersion::v9P2000u)
attach_message << (u32)-1;
result = post_message_and_wait_for_a_reply(attach_message);
if (result.is_error()) {
dbg() << "Attaching failed";
return false;
}
m_root_inode = Plan9FSInode::create(*this, root_fid);
return true;
}
Plan9FS::ProtocolVersion Plan9FS::parse_protocol_version(const StringView& s) const
{
if (s == "9P2000.L")
return ProtocolVersion::v9P2000L;
if (s == "9P2000.u")
return ProtocolVersion::v9P2000u;
return ProtocolVersion::v9P2000;
}
NonnullRefPtr<Inode> Plan9FS::root_inode() const
{
return *m_root_inode;
}
Plan9FS::Message& Plan9FS::Message::operator<<(u8 number)
{
return append_number(number);
}
Plan9FS::Message& Plan9FS::Message::operator<<(u16 number)
{
return append_number(number);
}
Plan9FS::Message& Plan9FS::Message::operator<<(u32 number)
{
return append_number(number);
}
Plan9FS::Message& Plan9FS::Message::operator<<(u64 number)
{
return append_number(number);
}
Plan9FS::Message& Plan9FS::Message::operator<<(const StringView& string)
{
*this << static_cast<u16>(string.length());
m_builder.append(string);
return *this;
}
void Plan9FS::Message::append_data(const StringView& data)
{
*this << static_cast<u32>(data.length());
m_builder.append(data);
}
Plan9FS::Message::Decoder& Plan9FS::Message::Decoder::operator>>(u8& number)
{
return read_number(number);
}
Plan9FS::Message::Decoder& Plan9FS::Message::Decoder::operator>>(u16& number)
{
return read_number(number);
}
Plan9FS::Message::Decoder& Plan9FS::Message::Decoder::operator>>(u32& number)
{
return read_number(number);
}
Plan9FS::Message::Decoder& Plan9FS::Message::Decoder::operator>>(u64& number)
{
return read_number(number);
}
Plan9FS::Message::Decoder& Plan9FS::Message::Decoder::operator>>(qid& qid)
{
return *this >> qid.type >> qid.version >> qid.path;
}
Plan9FS::Message::Decoder& Plan9FS::Message::Decoder::operator>>(StringView& string)
{
u16 length;
*this >> length;
ASSERT(length <= m_data.length());
string = m_data.substring_view(0, length);
m_data = m_data.substring_view_starting_after_substring(string);
return *this;
}
StringView Plan9FS::Message::Decoder::read_data()
{
u32 length;
*this >> length;
ASSERT(length <= m_data.length());
auto data = m_data.substring_view(0, length);
m_data = m_data.substring_view_starting_after_substring(data);
return data;
}
Plan9FS::Message::Message(Plan9FS& fs, Type type)
: m_builder()
, m_tag(fs.allocate_tag())
, m_type(type)
, m_have_been_built(false)
{
u32 size_placeholder = 0;
*this << size_placeholder << (u8)type << m_tag;
}
Plan9FS::Message::Message(KBuffer&& buffer)
: m_built { buffer, Decoder({ buffer.data(), buffer.size() }) }
, m_have_been_built(true)
{
u32 size;
u8 raw_type;
*this >> size >> raw_type >> m_tag;
m_type = (Type)raw_type;
}
Plan9FS::Message::~Message()
{
if (m_have_been_built) {
m_built.buffer.~KBuffer();
m_built.decoder.~Decoder();
} else {
m_builder.~KBufferBuilder();
}
}
Plan9FS::Message& Plan9FS::Message::operator=(Message&& message)
{
m_tag = message.m_tag;
m_type = message.m_type;
if (m_have_been_built) {
m_built.buffer.~KBuffer();
m_built.decoder.~Decoder();
} else {
m_builder.~KBufferBuilder();
}
m_have_been_built = message.m_have_been_built;
if (m_have_been_built) {
new (&m_built.buffer) KBuffer(move(message.m_built.buffer));
new (&m_built.decoder) Decoder(move(message.m_built.decoder));
} else {
new (&m_builder) KBufferBuilder(move(message.m_builder));
}
return *this;
}
const KBuffer& Plan9FS::Message::build()
{
ASSERT(!m_have_been_built);
auto tmp_buffer = m_builder.build();
m_have_been_built = true;
m_builder.~KBufferBuilder();
new (&m_built.buffer) KBuffer(move(tmp_buffer));
new (&m_built.decoder) Decoder({ m_built.buffer.data(), m_built.buffer.size() });
u32* size = reinterpret_cast<u32*>(m_built.buffer.data());
*size = m_built.buffer.size();
return m_built.buffer;
}
KResult Plan9FS::post_message(Message& message)
{
auto& buffer = message.build();
const u8* data = buffer.data();
size_t size = buffer.size();
auto& description = file_description();
LOCKER(m_send_lock);
while (size > 0) {
if (!description.can_write()) {
if (Thread::current()->block<Thread::WriteBlocker>(description) != Thread::BlockResult::WokeNormally)
return KResult(-EINTR);
}
ssize_t nwritten = description.write(data, size);
if (nwritten < 0)
return KResult(nwritten);
data += nwritten;
size -= nwritten;
}
return KSuccess;
}
KResult Plan9FS::do_read(u8* data, size_t size)
{
auto& description = file_description();
while (size > 0) {
if (!description.can_read()) {
if (Thread::current()->block<Thread::ReadBlocker>(description) != Thread::BlockResult::WokeNormally)
return KResult(-EINTR);
}
ssize_t nread = description.read(data, size);
if (nread < 0)
return KResult(nread);
if (nread == 0)
return KResult(-EIO);
data += nread;
size -= nread;
}
return KSuccess;
}
KResult Plan9FS::read_and_dispatch_one_message()
{
ASSERT(m_someone_is_reading);
// That someone is us.
struct [[gnu::packed]] Header
{
u32 size;
u8 type;
u16 tag;
};
Header header;
KResult result = do_read(reinterpret_cast<u8*>(&header), sizeof(header));
if (result.is_error())
return result;
auto buffer = KBuffer::create_with_size(header.size, Region::Access::Read | Region::Access::Write);
// Copy the already read header into the buffer.
memcpy(buffer.data(), &header, sizeof(header));
result = do_read(buffer.data() + sizeof(header), header.size - sizeof(header));
if (result.is_error())
return result;
LOCKER(m_lock);
auto optional_completion = m_completions.get(header.tag);
if (!optional_completion.has_value()) {
if (m_tags_to_ignore.contains(header.tag)) {
m_tags_to_ignore.remove(header.tag);
} else {
dbg() << "Received a 9p message of type " << header.type << " with an unexpected tag " << header.tag << ", dropping";
}
return KSuccess;
}
ReceiveCompletion& completion = *optional_completion.value();
completion.result = KSuccess;
completion.message = Message { move(buffer) };
completion.completed = true;
m_completions.remove(header.tag);
return KSuccess;
}
bool Plan9FS::Blocker::should_unblock(Thread&, time_t, long)
{
if (m_completion.completed)
return true;
bool someone_else_is_reading = m_completion.fs.m_someone_is_reading.exchange(true);
if (!someone_else_is_reading) {
// We're gonna start reading ourselves; unblock.
return true;
}
return false;
}
KResult Plan9FS::wait_for_specific_message(u16 tag, Message& out_message)
{
KResult result = KSuccess;
ReceiveCompletion completion { *this, out_message, result, false };
{
LOCKER(m_lock);
m_completions.set(tag, &completion);
}
// Block until either:
// * Someone else reads the message we're waiting for, and hands it to us;
// * Or we become the one to read and dispatch messages.
auto block_result = Thread::current()->block<Plan9FS::Blocker>(completion);
if (block_result != Thread::BlockResult::WokeNormally) {
LOCKER(m_lock);
m_completions.remove(tag);
return KResult(-EINTR);
}
// See for which reason we woke up.
if (completion.completed) {
// Somebody else completed it for us; nothing further to do.
return result;
}
while (!completion.completed && result.is_success()) {
result = read_and_dispatch_one_message();
}
if (result.is_error()) {
// If we fail to read, wake up everyone with an error.
LOCKER(m_lock);
for (auto& it : m_completions) {
it.value->result = result;
it.value->completed = true;
}
m_completions.clear();
}
// Wake up someone else, if anyone is interested...
m_someone_is_reading = false;
// ...and return.
return result;
}
KResult Plan9FS::post_message_and_explicitly_ignore_reply(Message& message)
{
auto tag = message.tag();
{
LOCKER(m_lock);
m_tags_to_ignore.set(tag);
}
auto result = post_message(message);
if (result.is_error()) {
LOCKER(m_lock);
m_tags_to_ignore.remove(tag);
}
return result;
}
KResult Plan9FS::post_message_and_wait_for_a_reply(Message& message, bool auto_convert_error_reply_to_error)
{
auto request_type = message.type();
auto tag = message.tag();
auto result = post_message(message);
if (result.is_error())
return result;
result = wait_for_specific_message(tag, message);
if (result.is_error())
return result;
if (!auto_convert_error_reply_to_error)
return KSuccess;
auto reply_type = message.type();
if (reply_type == Message::Type::Rlerror) {
// Contains a numerical Linux errno; hopefully our errno numbers match.
u32 error_code;
message >> error_code;
return KResult(-error_code);
} else if (reply_type == Message::Type::Rerror) {
// Contains an error message. We could attempt to parse it, but for now
// we simply return -EIO instead. In 9P200.u, it can also contain a
// numerical errno in an unspecified encoding; we ignore those too.
StringView error_name;
message >> error_name;
dbg() << "Plan9FS: Received error name " << error_name;
return KResult(-EIO);
} else if ((u8)reply_type != (u8)request_type + 1) {
// Other than those error messages. we only expect the matching reply
// message type.
dbg() << "Plan9FS: Received unexpected message type " << (u8)reply_type
<< " in response to " << (u8)request_type;
return KResult(-EIO);
} else {
return KSuccess;
}
}
ssize_t Plan9FS::adjust_buffer_size(ssize_t size) const
{
ssize_t max_size = m_max_message_size - Message::max_header_size;
return min(size, max_size);
}
Plan9FSInode::Plan9FSInode(Plan9FS& fs, u32 fid)
: Inode(fs, fid)
{
}
NonnullRefPtr<Plan9FSInode> Plan9FSInode::create(Plan9FS& fs, u32 fid)
{
return adopt(*new Plan9FSInode(fs, fid));
}
Plan9FSInode::~Plan9FSInode()
{
Plan9FS::Message clunk_request { fs(), Plan9FS::Message::Type::Tclunk };
clunk_request << fid();
fs().post_message_and_explicitly_ignore_reply(clunk_request);
}
KResult Plan9FSInode::ensure_open_for_mode(int mode)
{
bool use_lopen = fs().m_remote_protocol_version >= Plan9FS::ProtocolVersion::v9P2000L;
u32 l_mode = 0;
u8 p9_mode = 0;
{
LOCKER(m_lock);
// If it's already open in this mode, we're done.
if ((m_open_mode & mode) == mode)
return KSuccess;
m_open_mode |= mode;
if ((m_open_mode & O_RDWR) == O_RDWR) {
l_mode |= 2;
p9_mode |= 2;
} else if (m_open_mode & O_WRONLY) {
l_mode |= 1;
p9_mode |= 1;
} else if (m_open_mode & O_RDONLY) {
// Leave the values at 0.
}
}
if (use_lopen) {
Plan9FS::Message message { fs(), Plan9FS::Message::Type::Tlopen };
message << fid() << l_mode;
return fs().post_message_and_wait_for_a_reply(message);
} else {
Plan9FS::Message message { fs(), Plan9FS::Message::Type::Topen };
message << fid() << p9_mode;
return fs().post_message_and_wait_for_a_reply(message);
}
}
ssize_t Plan9FSInode::read_bytes(off_t offset, ssize_t size, u8* buffer, FileDescription*) const
{
auto result = const_cast<Plan9FSInode&>(*this).ensure_open_for_mode(O_RDONLY);
if (result.is_error())
return result;
size = fs().adjust_buffer_size(size);
Plan9FS::Message message { fs(), Plan9FS::Message::Type::Treadlink };
StringView data;
// Try readlink first.
bool readlink_succeded = false;
if (fs().m_remote_protocol_version >= Plan9FS::ProtocolVersion::v9P2000L && offset == 0) {
message << fid();
result = fs().post_message_and_wait_for_a_reply(message);
if (result.is_success()) {
readlink_succeded = true;
message >> data;
}
}
if (!readlink_succeded) {
message = Plan9FS::Message { fs(), Plan9FS::Message::Type::Tread };
message << fid() << (u64)offset << (u32)size;
result = fs().post_message_and_wait_for_a_reply(message);
if (result.is_error())
return result.error();
data = message.read_data();
}
// Guard against the server returning more data than requested.
size_t nread = min(data.length(), (size_t)size);
memcpy(buffer, data.characters_without_null_termination(), nread);
return nread;
}
ssize_t Plan9FSInode::write_bytes(off_t offset, ssize_t size, const u8* data, FileDescription*)
{
auto result = ensure_open_for_mode(O_WRONLY);
if (result.is_error())
return result;
size = fs().adjust_buffer_size(size);
Plan9FS::Message message { fs(), Plan9FS::Message::Type::Twrite };
message << fid() << (u64)offset;
message.append_data({ data, (size_t)size });
result = fs().post_message_and_wait_for_a_reply(message);
if (result.is_error())
return result.error();
u32 nwritten;
message >> nwritten;
return nwritten;
}
InodeMetadata Plan9FSInode::metadata() const
{
InodeMetadata metadata;
metadata.inode = identifier();
// 9P2000.L; TODO: 9P2000 & 9P2000.u
Plan9FS::Message message { fs(), Plan9FS::Message::Type::Tgetattr };
message << fid() << (u64)GetAttrMask::Basic;
auto result = fs().post_message_and_wait_for_a_reply(message);
if (result.is_error()) {
// Just return blank metadata; hopefully that's enough to result in an
// error at some upper layer. Ideally, there would be a way for
// Inode::metadata() to return failure.
return metadata;
}
u64 valid;
Plan9FS::qid qid;
u32 mode;
u32 uid;
u32 gid;
u64 nlink;
u64 rdev;
u64 size;
u64 blksize;
u64 blocks;
message >> valid >> qid >> mode >> uid >> gid >> nlink >> rdev >> size >> blksize >> blocks;
// TODO: times...
if (valid & (u64)GetAttrMask::Mode)
metadata.mode = mode;
if (valid & (u64)GetAttrMask::NLink)
metadata.link_count = nlink;
#if 0
// FIXME: Map UID/GID somehow? Or what do we do?
if (valid & (u64)GetAttrMask::UID)
metadata.uid = uid;
if (valid & (u64)GetAttrMask::GID)
metadata.uid = gid;
// FIXME: What about device nodes?
if (valid & (u64)GetAttrMask::RDev)
metadata.encoded_device = 0; // TODO
#endif
if (valid & (u64)GetAttrMask::Size)
metadata.size = size;
if (valid & (u64)GetAttrMask::Blocks) {
metadata.block_size = blksize;
metadata.block_count = blocks;
}
return metadata;
}
void Plan9FSInode::flush_metadata()
{
// Do nothing.
}
size_t Plan9FSInode::directory_entry_count() const
{
size_t count = 0;
traverse_as_directory([&count](const FS::DirectoryEntry&) {
count++;
return true;
});
return count;
}
KResult Plan9FSInode::traverse_as_directory(Function<bool(const FS::DirectoryEntry&)> callback) const
{
KResult result = KSuccess;
// TODO: Should we synthesize "." and ".." here?
if (fs().m_remote_protocol_version >= Plan9FS::ProtocolVersion::v9P2000L) {
// Start by cloning the fid and opening it.
auto clone_fid = fs().allocate_fid();
{
Plan9FS::Message clone_message { fs(), Plan9FS::Message::Type::Twalk };
clone_message << fid() << clone_fid << (u16)0;
result = fs().post_message_and_wait_for_a_reply(clone_message);
if (result.is_error())
return result;
Plan9FS::Message open_message { fs(), Plan9FS::Message::Type::Tlopen };
open_message << clone_fid << (u32)0;
result = fs().post_message_and_wait_for_a_reply(open_message);
if (result.is_error()) {
Plan9FS::Message close_message { fs(), Plan9FS::Message::Type::Tclunk };
close_message << clone_fid;
fs().post_message_and_explicitly_ignore_reply(close_message);
return result;
}
}
u64 offset = 0;
u32 count = fs().adjust_buffer_size(8 * MB);
while (true) {
Plan9FS::Message message { fs(), Plan9FS::Message::Type::Treaddir };
message << clone_fid << offset << count;
result = fs().post_message_and_wait_for_a_reply(message);
if (result.is_error())
break;
StringView data = message.read_data();
if (data.is_empty()) {
// We've reached the end.
break;
}
for (Plan9FS::Message::Decoder decoder { data }; decoder.has_more_data();) {
Plan9FS::qid qid;
u8 type;
StringView name;
decoder >> qid >> offset >> type >> name;
FS::DirectoryEntry entry {
"",
name.length(),
{ fsid(), fs().allocate_fid() },
0
};
size_t size_to_copy = min(sizeof(entry.name) - 1, name.length());
memcpy(entry.name, name.characters_without_null_termination(), size_to_copy);
entry.name[size_to_copy] = 0;
callback(entry);
}
}
Plan9FS::Message close_message { fs(), Plan9FS::Message::Type::Tclunk };
close_message << clone_fid;
fs().post_message_and_explicitly_ignore_reply(close_message);
return result;
} else {
// TODO
return KResult(-ENOTIMPL);
}
}
RefPtr<Inode> Plan9FSInode::lookup(StringView name)
{
u32 newfid = fs().allocate_fid();
Plan9FS::Message message { fs(), Plan9FS::Message::Type::Twalk };
message << fid() << newfid << (u16)1 << name;
auto result = fs().post_message_and_wait_for_a_reply(message);
if (result.is_error())
return nullptr;
return Plan9FSInode::create(fs(), newfid);
}
KResultOr<NonnullRefPtr<Inode>> Plan9FSInode::create_child(const String&, mode_t, dev_t, uid_t, gid_t)
{
// TODO
return KResult(-ENOTIMPL);
}
KResult Plan9FSInode::add_child(Inode&, const StringView&, mode_t)
{
// TODO
return KResult(-ENOTIMPL);
}
KResult Plan9FSInode::remove_child(const StringView&)
{
// TODO
return KResult(-ENOTIMPL);
}
KResult Plan9FSInode::chmod(mode_t)
{
// TODO
return KResult(-ENOTIMPL);
}
KResult Plan9FSInode::chown(uid_t, gid_t)
{
// TODO
return KResult(-ENOTIMPL);
}
KResult Plan9FSInode::truncate(u64 new_size)
{
if (fs().m_remote_protocol_version >= Plan9FS::ProtocolVersion::v9P2000L) {
Plan9FS::Message message { fs(), Plan9FS::Message::Type::Tsetattr };
SetAttrMask valid = SetAttrMask::Size;
u32 mode = 0;
u32 uid = 0;
u32 gid = 0;
u64 atime_sec = 0;
u64 atime_nsec = 0;
u64 mtime_sec = 0;
u64 mtime_nsec = 0;
message << fid() << (u64)valid << mode << uid << gid << new_size << atime_sec << atime_nsec << mtime_sec << mtime_nsec;
return fs().post_message_and_wait_for_a_reply(message);
} else {
// TODO: wstat version
return KSuccess;
}
}
}

View file

@ -0,0 +1,187 @@
/*
* Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
* 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 THE COPYRIGHT HOLDERS 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 THE COPYRIGHT HOLDER 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/Atomic.h>
#include <Kernel/FileSystem/FileBackedFileSystem.h>
#include <Kernel/FileSystem/Inode.h>
#include <Kernel/KBufferBuilder.h>
namespace Kernel {
class Plan9FSInode;
class Plan9FS final : public FileBackedFS {
friend class Plan9FSInode;
public:
virtual ~Plan9FS() override;
static NonnullRefPtr<Plan9FS> create(FileDescription&);
virtual bool initialize() override;
virtual bool supports_watchers() const override { return false; }
virtual NonnullRefPtr<Inode> root_inode() const override;
u16 allocate_tag() { return m_next_tag++; }
u32 allocate_fid() { return m_next_fid++; }
enum class ProtocolVersion {
v9P2000,
v9P2000u,
v9P2000L
};
struct qid {
u8 type;
u32 version;
u64 path;
};
class Message;
private:
Plan9FS(FileDescription&);
struct ReceiveCompletion {
Plan9FS& fs;
Message& message;
KResult& result;
Atomic<bool> completed;
};
class Blocker final : public Thread::Blocker {
public:
Blocker(ReceiveCompletion& completion)
: m_completion(completion)
{
}
virtual bool should_unblock(Thread&, time_t, long) override;
virtual const char* state_string() const override { return "Waiting"; }
private:
ReceiveCompletion& m_completion;
};
virtual const char* class_name() const override { return "Plan9FS"; }
KResult post_message(Message&);
KResult do_read(u8* buffer, size_t);
KResult read_and_dispatch_one_message();
KResult wait_for_specific_message(u16 tag, Message& out_message);
KResult post_message_and_wait_for_a_reply(Message&, bool auto_convert_error_reply_to_error = true);
KResult post_message_and_explicitly_ignore_reply(Message&);
ProtocolVersion parse_protocol_version(const StringView&) const;
ssize_t adjust_buffer_size(ssize_t size) const;
RefPtr<Plan9FSInode> m_root_inode;
Atomic<u16> m_next_tag { (u16)-1 };
Atomic<u32> m_next_fid { 1 };
ProtocolVersion m_remote_protocol_version { ProtocolVersion::v9P2000 };
size_t m_max_message_size { 4 * KB };
Lock m_send_lock { "Plan9FS send" };
Atomic<bool> m_someone_is_reading { false };
HashMap<u16, ReceiveCompletion*> m_completions;
HashTable<u16> m_tags_to_ignore;
};
class Plan9FSInode final : public Inode {
friend class Plan9FS;
public:
virtual ~Plan9FSInode() override;
u32 fid() const { return index(); }
// ^Inode
virtual InodeMetadata metadata() const override;
virtual void flush_metadata() override;
virtual ssize_t read_bytes(off_t, ssize_t, u8* buffer, FileDescription*) const override;
virtual ssize_t write_bytes(off_t, ssize_t, const u8* data, FileDescription*) override;
virtual KResult traverse_as_directory(Function<bool(const FS::DirectoryEntry&)>) const override;
virtual RefPtr<Inode> lookup(StringView name) override;
virtual KResultOr<NonnullRefPtr<Inode>> create_child(const String& name, mode_t, dev_t, uid_t, gid_t) override;
virtual KResult add_child(Inode&, const StringView& name, mode_t) override;
virtual KResult remove_child(const StringView& name) override;
virtual size_t directory_entry_count() const override;
virtual KResult chmod(mode_t) override;
virtual KResult chown(uid_t, gid_t) override;
virtual KResult truncate(u64) override;
private:
Plan9FSInode(Plan9FS&, u32 fid);
static NonnullRefPtr<Plan9FSInode> create(Plan9FS&, u32 fid);
enum class GetAttrMask : u64 {
Mode = 0x1,
NLink = 0x2,
UID = 0x4,
GID = 0x8,
RDev = 0x10,
ATime = 0x20,
MTime = 0x40,
CTime = 0x80,
Ino = 0x100,
Size = 0x200,
Blocks = 0x400,
BTime = 0x800,
Gen = 0x1000,
DataVersion = 0x2000,
Basic = 0x7ff,
All = 0x3fff
};
enum class SetAttrMask : u64 {
Mode = 0x1,
UID = 0x2,
GID = 0x4,
Size = 0x8,
ATime = 0x10,
MTime = 0x20,
CTime = 0x40,
ATimeSet = 0x80,
MTimeSet = 0x100
};
// Mode in which the file is already open, using SerenityOS constants.
int m_open_mode { 0 };
KResult ensure_open_for_mode(int mode);
Plan9FS& fs() { return reinterpret_cast<Plan9FS&>(Inode::fs()); }
Plan9FS& fs() const
{
return const_cast<Plan9FS&>(reinterpret_cast<const Plan9FS&>(Inode::fs()));
}
};
}

View file

@ -46,6 +46,7 @@
#include <Kernel/FileSystem/FIFO.h>
#include <Kernel/FileSystem/FileDescription.h>
#include <Kernel/FileSystem/InodeWatcher.h>
#include <Kernel/FileSystem/Plan9FileSystem.h>
#include <Kernel/FileSystem/ProcFS.h>
#include <Kernel/FileSystem/TmpFS.h>
#include <Kernel/FileSystem/VirtualFileSystem.h>
@ -4302,6 +4303,11 @@ int Process::sys$mount(const Syscall::SC_mount_params* user_params)
dbg() << "mount: attempting to mount " << description->absolute_path() << " on " << target;
fs = Ext2FS::create(*description);
} else if (fs_type == "9p" || fs_type == "Plan9FS") {
if (description.is_null())
return -EBADF;
fs = Plan9FS::create(*description);
} else if (fs_type == "proc" || fs_type == "ProcFS") {
fs = ProcFS::create();
} else if (fs_type == "devpts" || fs_type == "DevPtsFS") {