diff --git a/Base/usr/share/man/man2/mount.md b/Base/usr/share/man/man2/mount.md index df377bb8f4..cd3c4c3e16 100644 --- a/Base/usr/share/man/man2/mount.md +++ b/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. diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 119b24e3c3..efa9234f98 100644 --- a/Kernel/CMakeLists.txt +++ b/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 diff --git a/Kernel/FileSystem/Plan9FileSystem.cpp b/Kernel/FileSystem/Plan9FileSystem.cpp new file mode 100644 index 0000000000..94ac878f58 --- /dev/null +++ b/Kernel/FileSystem/Plan9FileSystem.cpp @@ -0,0 +1,940 @@ +/* + * Copyright (c) 2020, Sergey Bugaev + * 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 +#include + +namespace Kernel { + +NonnullRefPtr 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 + 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 + 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 + Message& append_number(N number) + { + ASSERT(!m_have_been_built); + m_builder.append(reinterpret_cast(&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 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(string.length()); + m_builder.append(string); + return *this; +} + +void Plan9FS::Message::append_data(const StringView& data) +{ + *this << static_cast(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(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(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(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(&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(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::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(*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 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 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> 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; + } +} + +} diff --git a/Kernel/FileSystem/Plan9FileSystem.h b/Kernel/FileSystem/Plan9FileSystem.h new file mode 100644 index 0000000000..db220ec230 --- /dev/null +++ b/Kernel/FileSystem/Plan9FileSystem.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020, Sergey Bugaev + * 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 +#include +#include +#include + +namespace Kernel { + +class Plan9FSInode; + +class Plan9FS final : public FileBackedFS { + friend class Plan9FSInode; + +public: + virtual ~Plan9FS() override; + static NonnullRefPtr create(FileDescription&); + + virtual bool initialize() override; + + virtual bool supports_watchers() const override { return false; } + + virtual NonnullRefPtr 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 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 m_root_inode; + Atomic m_next_tag { (u16)-1 }; + Atomic 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 m_someone_is_reading { false }; + HashMap m_completions; + HashTable 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) const override; + virtual RefPtr lookup(StringView name) override; + virtual KResultOr> 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 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(Inode::fs()); } + Plan9FS& fs() const + { + return const_cast(reinterpret_cast(Inode::fs())); + } +}; + +} diff --git a/Kernel/Process.cpp b/Kernel/Process.cpp index a3ce931dbc..224e103b34 100644 --- a/Kernel/Process.cpp +++ b/Kernel/Process.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -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") {