Forráskód Böngészése

Kernel: Add Plan9FS :^)

This is an (incomplete, and not very stable) implementation of the client side
of the 9P protocol.
Sergey Bugaev 5 éve
szülő
commit
a8489967a3

+ 13 - 6
Base/usr/share/man/man2/mount.md

@@ -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.
 

+ 1 - 0
Kernel/CMakeLists.txt

@@ -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

+ 940 - 0
Kernel/FileSystem/Plan9FileSystem.cpp

@@ -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;
+    }
+}
+
+}

+ 187 - 0
Kernel/FileSystem/Plan9FileSystem.h

@@ -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()));
+    }
+};
+
+}

+ 6 - 0
Kernel/Process.cpp

@@ -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") {