| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/lib/storage/vfs/cpp/fuchsia_vfs.h" |
| |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/zx/event.h> |
| #include <lib/zx/process.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include <fbl/auto_lock.h> |
| #include <fbl/ref_ptr.h> |
| |
| #include "src/lib/storage/vfs/cpp/connection.h" |
| #include "src/lib/storage/vfs/cpp/debug.h" |
| #include "src/lib/storage/vfs/cpp/directory_connection.h" |
| #include "src/lib/storage/vfs/cpp/node_connection.h" |
| #include "src/lib/storage/vfs/cpp/remote_file_connection.h" |
| #include "src/lib/storage/vfs/cpp/stream_file_connection.h" |
| #include "src/lib/storage/vfs/cpp/vnode.h" |
| |
| namespace fio = fuchsia_io; |
| |
| namespace fs { |
| namespace { |
| |
| zx_koid_t GetTokenKoid(const zx::event& token) { |
| zx_info_handle_basic_t info = {}; |
| token.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); |
| return info.koid; |
| } |
| |
| uint32_t ToStreamOptions(const VnodeConnectionOptions& options) { |
| uint32_t stream_options = 0u; |
| if (options.rights.read) { |
| stream_options |= ZX_STREAM_MODE_READ; |
| } |
| if (options.rights.write) { |
| stream_options |= ZX_STREAM_MODE_WRITE; |
| } |
| return stream_options; |
| } |
| |
| } // namespace |
| |
| void FilesystemInfo::SetFsId(const zx::event& event) { |
| zx_info_handle_basic_t handle_info; |
| if (zx_status_t status = |
| event.get_info(ZX_INFO_HANDLE_BASIC, &handle_info, sizeof(handle_info), nullptr, nullptr); |
| status == ZX_OK) { |
| fs_id = handle_info.koid; |
| } else { |
| fs_id = ZX_KOID_INVALID; |
| } |
| } |
| |
| fuchsia_io::wire::FilesystemInfo FilesystemInfo::ToFidl() const { |
| fuchsia_io::wire::FilesystemInfo out = {}; |
| |
| out.total_bytes = total_bytes; |
| out.used_bytes = used_bytes; |
| out.total_nodes = total_nodes; |
| out.used_nodes = used_nodes; |
| out.free_shared_pool_bytes = free_shared_pool_bytes; |
| out.fs_id = fs_id; |
| out.block_size = block_size; |
| out.max_filename_size = max_filename_size; |
| out.fs_type = fs_type; |
| |
| ZX_DEBUG_ASSERT(name.size() < fuchsia_io::wire::kMaxFsNameBuffer); |
| out.name[name.copy(reinterpret_cast<char*>(out.name.data()), |
| fuchsia_io::wire::kMaxFsNameBuffer - 1)] = '\0'; |
| |
| return out; |
| } |
| |
| constexpr FuchsiaVfs::MountNode::MountNode() = default; |
| FuchsiaVfs::MountNode::~MountNode() { ZX_DEBUG_ASSERT(vn_ == nullptr); } |
| |
| void FuchsiaVfs::MountNode::SetNode(fbl::RefPtr<Vnode> vn) { |
| ZX_DEBUG_ASSERT(vn_ == nullptr); |
| vn_ = vn; |
| } |
| |
| fidl::ClientEnd<fio::Directory> FuchsiaVfs::MountNode::ReleaseRemote() { |
| ZX_DEBUG_ASSERT(vn_ != nullptr); |
| fidl::ClientEnd<fio::Directory> h = vn_->DetachRemote(); |
| vn_ = nullptr; |
| return h; |
| } |
| |
| bool FuchsiaVfs::MountNode::VnodeMatch(fbl::RefPtr<Vnode> vn) const { |
| ZX_DEBUG_ASSERT(vn_ != nullptr); |
| return vn == vn_; |
| } |
| |
| FuchsiaVfs::FuchsiaVfs(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {} |
| |
| FuchsiaVfs::~FuchsiaVfs() = default; |
| |
| void FuchsiaVfs::SetDispatcher(async_dispatcher_t* dispatcher) { |
| ZX_ASSERT_MSG(!dispatcher_, |
| "FuchsiaVfs::SetDispatcher maybe only be called when dispatcher_ is not set."); |
| dispatcher_ = dispatcher; |
| } |
| |
| zx_status_t FuchsiaVfs::Unlink(fbl::RefPtr<Vnode> vndir, std::string_view name, bool must_be_dir) { |
| if (zx_status_t s = Vfs::Unlink(vndir, name, must_be_dir); s != ZX_OK) |
| return s; |
| |
| vndir->Notify(name, fio::wire::WatchEvent::kRemoved); |
| return ZX_OK; |
| } |
| |
| void FuchsiaVfs::TokenDiscard(zx::event ios_token) { |
| std::lock_guard lock(vfs_lock_); |
| if (ios_token) { |
| // The token is cleared here to prevent the following race condition: |
| // 1) Open |
| // 2) GetToken |
| // 3) Close + Release Vnode |
| // 4) Use token handle to access defunct vnode (or a different vnode, if the memory for it is |
| // reallocated). |
| // |
| // By cleared the token cookie, any remaining handles to the event will be ignored by the |
| // filesystem server. |
| auto rename_request = vnode_tokens_.erase(GetTokenKoid(ios_token)); |
| } |
| } |
| |
| zx_status_t FuchsiaVfs::VnodeToToken(fbl::RefPtr<Vnode> vn, zx::event* ios_token, zx::event* out) { |
| zx_status_t r; |
| |
| std::lock_guard lock(vfs_lock_); |
| if (ios_token->is_valid()) { |
| // Token has already been set for this iostate |
| if ((r = ios_token->duplicate(ZX_RIGHTS_BASIC, out) != ZX_OK)) { |
| return r; |
| } |
| return ZX_OK; |
| } |
| |
| zx::event new_token; |
| zx::event new_ios_token; |
| if ((r = zx::event::create(0, &new_ios_token)) != ZX_OK) { |
| return r; |
| } else if ((r = new_ios_token.duplicate(ZX_RIGHTS_BASIC, &new_token) != ZX_OK)) { |
| return r; |
| } |
| auto koid = GetTokenKoid(new_ios_token); |
| vnode_tokens_.insert(std::make_unique<VnodeToken>(koid, std::move(vn))); |
| *ios_token = std::move(new_ios_token); |
| *out = std::move(new_token); |
| return ZX_OK; |
| } |
| |
| bool FuchsiaVfs::IsTokenAssociatedWithVnode(zx::event token) { |
| std::lock_guard lock(vfs_lock_); |
| return TokenToVnode(std::move(token), nullptr) == ZX_OK; |
| } |
| |
| zx_status_t FuchsiaVfs::EnsureExists(fbl::RefPtr<Vnode> vndir, std::string_view path, |
| fbl::RefPtr<Vnode>* out_vn, fs::VnodeConnectionOptions options, |
| uint32_t mode, Rights parent_rights, bool* did_create) { |
| if (zx_status_t s = |
| Vfs::EnsureExists(vndir, path, out_vn, options, mode, parent_rights, did_create); |
| s != ZX_OK) |
| return s; |
| |
| vndir->Notify(path, fio::wire::WatchEvent::kAdded); |
| return ZX_OK; |
| } |
| |
| zx_status_t FuchsiaVfs::TokenToVnode(zx::event token, fbl::RefPtr<Vnode>* out) { |
| const auto& vnode_token = vnode_tokens_.find(GetTokenKoid(token)); |
| if (vnode_token == vnode_tokens_.end()) { |
| // TODO(smklein): Return a more specific error code for "token not from this server" |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (out) { |
| *out = vnode_token->get_vnode(); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t FuchsiaVfs::Rename(zx::event token, fbl::RefPtr<Vnode> oldparent, |
| std::string_view oldStr, std::string_view newStr) { |
| // Local filesystem |
| bool old_must_be_dir; |
| { |
| zx::status result = TrimName(oldStr); |
| if (result.is_error()) { |
| return result.status_value(); |
| } |
| old_must_be_dir = result.value(); |
| if (oldStr == ".") { |
| return ZX_ERR_UNAVAILABLE; |
| } |
| if (oldStr == "..") { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| bool new_must_be_dir; |
| { |
| zx::status result = TrimName(newStr); |
| if (result.is_error()) { |
| return result.status_value(); |
| } |
| new_must_be_dir = result.value(); |
| if (newStr == "." || newStr == "..") { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| fbl::RefPtr<fs::Vnode> newparent; |
| { |
| std::lock_guard lock(vfs_lock_); |
| if (ReadonlyLocked()) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| if (zx_status_t status = TokenToVnode(std::move(token), &newparent); status != ZX_OK) { |
| return status; |
| } |
| |
| if (zx_status_t status = |
| oldparent->Rename(newparent, oldStr, newStr, old_must_be_dir, new_must_be_dir); |
| status != ZX_OK) { |
| return status; |
| } |
| } |
| oldparent->Notify(oldStr, fio::wire::WatchEvent::kRemoved); |
| newparent->Notify(newStr, fio::wire::WatchEvent::kAdded); |
| return ZX_OK; |
| } |
| |
| zx::status<FilesystemInfo> FuchsiaVfs::GetFilesystemInfo() { |
| return zx::error(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| zx_status_t FuchsiaVfs::Link(zx::event token, fbl::RefPtr<Vnode> oldparent, std::string_view oldStr, |
| std::string_view newStr) { |
| std::lock_guard lock(vfs_lock_); |
| fbl::RefPtr<fs::Vnode> newparent; |
| if (zx_status_t status = TokenToVnode(std::move(token), &newparent); status != ZX_OK) { |
| return status; |
| } |
| // Local filesystem |
| if (ReadonlyLocked()) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| { |
| zx::status result = TrimName(oldStr); |
| if (result.is_error()) { |
| return result.status_value(); |
| } |
| if (result.value()) { |
| return ZX_ERR_NOT_DIR; |
| } |
| if (oldStr == ".") { |
| return ZX_ERR_UNAVAILABLE; |
| } |
| if (oldStr == "..") { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| { |
| zx::status result = TrimName(newStr); |
| if (result.is_error()) { |
| return result.status_value(); |
| } |
| if (result.value()) { |
| return ZX_ERR_NOT_DIR; |
| } |
| if (newStr == "." || newStr == "..") { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| // Look up the target vnode |
| fbl::RefPtr<Vnode> target; |
| if (zx_status_t status = oldparent->Lookup(oldStr, &target); status != ZX_OK) { |
| return status; |
| } |
| if (zx_status_t status = newparent->Link(newStr, target); status != ZX_OK) { |
| return status; |
| } |
| newparent->Notify(newStr, fio::wire::WatchEvent::kAdded); |
| return ZX_OK; |
| } |
| |
| zx_status_t FuchsiaVfs::Serve(fbl::RefPtr<Vnode> vnode, zx::channel channel, |
| VnodeConnectionOptions options) { |
| zx::status result = vnode->ValidateOptions(options); |
| if (result.is_error()) { |
| return result.status_value(); |
| } |
| return Serve(std::move(vnode), std::move(channel), result.value()); |
| } |
| |
| zx_status_t FuchsiaVfs::AddInotifyFilterToVnode(fbl::RefPtr<Vnode> vnode, |
| const fbl::RefPtr<Vnode>& parent_vnode, |
| fuchsia_io::wire::InotifyWatchMask filter, |
| uint32_t watch_descriptor, zx::socket socket) { |
| // TODO we need parent vnode for inotify events when a directory is being watched for events on |
| // its directory entries. |
| vnode->InsertInotifyFilter(filter, watch_descriptor, std::move(socket)); |
| return ZX_OK; |
| } |
| |
| zx_status_t FuchsiaVfs::Serve(fbl::RefPtr<Vnode> vnode, zx::channel server_end, |
| Vnode::ValidatedOptions options) { |
| // At this point, the protocol that will be spoken over |server_end| is not |
| // yet determined. |
| // |
| // To determine the protocol, we pick one that is both requested by the user |
| // and supported by the vnode, deferring to |Vnode::Negotiate| if there are |
| // multiple. |
| // |
| // In addition, if the |describe| option is set, then the channel always first |
| // speaks the |fuchsia.io/Node| protocol, and then switches to the determined |
| // protocol after sending the initial event. |
| |
| auto candidate_protocols = options->protocols() & vnode->GetProtocols(); |
| // |ValidateOptions| was called, hence at least one protocol must be supported. |
| ZX_DEBUG_ASSERT(candidate_protocols.any()); |
| auto maybe_protocol = candidate_protocols.which(); |
| VnodeProtocol protocol; |
| if (maybe_protocol.has_value()) { |
| protocol = maybe_protocol.value(); |
| } else { |
| protocol = vnode->Negotiate(candidate_protocols); |
| } |
| |
| // Send an |fuchsia.io/OnOpen| event if requested. |
| if (options->flags.describe) { |
| zx::status<VnodeRepresentation> result = internal::Describe(vnode, protocol, *options); |
| if (result.is_error()) { |
| // TODO(fxbug.dev/95144) Use the returned fidl::Status's status value. |
| (void)fidl::WireSendEvent(fidl::ServerEnd<fuchsia_io::Node>(std::move(server_end))) |
| ->OnOpen(result.status_value(), fio::wire::NodeInfo()); |
| return result.status_value(); |
| } |
| ConvertToIoV1NodeInfo(std::move(result).value(), [&](fio::wire::NodeInfo&& info) { |
| // The channel may switch from |Node| protocol back to a custom protocol, after sending the |
| // event, in the case of |VnodeProtocol::kConnector|. |
| fidl::ServerEnd<fuchsia_io::Node> typed_server_end(std::move(server_end)); |
| // TODO(fxbug.dev/95144) Use the returned fidl::Status's status value. |
| (void)fidl::WireSendEvent(typed_server_end)->OnOpen(ZX_OK, std::move(info)); |
| server_end = typed_server_end.TakeChannel(); |
| }); |
| } |
| |
| // If |node_reference| is specified, serve |fuchsia.io/Node| even for |VnodeProtocol::kConnector| |
| // nodes. Otherwise, connect the raw channel to the custom service. |
| if (!options->flags.node_reference && protocol == VnodeProtocol::kConnector) { |
| return vnode->ConnectService(std::move(server_end)); |
| } |
| |
| std::unique_ptr<internal::Connection> connection; |
| zx_status_t status = ([&] { |
| switch (protocol) { |
| case VnodeProtocol::kFile: |
| case VnodeProtocol::kDevice: |
| case VnodeProtocol::kTty: |
| // In memfs and bootfs, memory objects (vmo-files) appear to support |fuchsia.io/File.Read|. |
| // Therefore choosing a file connection here is the closest approximation. |
| case VnodeProtocol::kMemory: { |
| zx::stream stream; |
| zx_status_t status = vnode->CreateStream(ToStreamOptions(*options), &stream); |
| if (status == ZX_OK) { |
| connection = std::make_unique<internal::StreamFileConnection>( |
| this, std::move(vnode), std::move(stream), protocol, *options); |
| return ZX_OK; |
| } |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| connection = std::make_unique<internal::RemoteFileConnection>(this, std::move(vnode), |
| protocol, *options); |
| return ZX_OK; |
| } |
| return status; |
| } |
| case VnodeProtocol::kDirectory: |
| connection = std::make_unique<internal::DirectoryConnection>(this, std::move(vnode), |
| protocol, *options); |
| return ZX_OK; |
| case VnodeProtocol::kConnector: |
| case VnodeProtocol::kPipe: |
| connection = |
| std::make_unique<internal::NodeConnection>(this, std::move(vnode), protocol, *options); |
| return ZX_OK; |
| case VnodeProtocol::kSynchronousDatagramSocket: |
| // The posix socket protocol is served by netstack. |
| ZX_PANIC("fuchsia.posix.socket/SynchronousDatagramSocket is not implemented"); |
| case VnodeProtocol::kStreamSocket: |
| // The posix socket protocol is served by netstack. |
| ZX_PANIC("fuchsia.posix.socket/StreamSocket is not implemented"); |
| case VnodeProtocol::kDatagramSocket: |
| // The posix socket protocol is served by netstack. |
| ZX_PANIC("fuchsia.posix.socket/DatagramSocket is not implemented"); |
| } |
| #ifdef __GNUC__ |
| // GCC does not infer that the above switch statement will always return by handling all defined |
| // enum members. |
| __builtin_abort(); |
| #endif |
| }()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| return RegisterConnection(std::move(connection), std::move(server_end)); |
| } |
| |
| void FuchsiaVfs::OnConnectionClosedRemotely(internal::Connection* connection) { |
| ZX_DEBUG_ASSERT(connection); |
| |
| UnregisterConnection(connection); |
| } |
| |
| zx_status_t FuchsiaVfs::ServeDirectory(fbl::RefPtr<fs::Vnode> vn, |
| fidl::ServerEnd<fuchsia_io::Directory> server_end, |
| Rights rights) { |
| VnodeConnectionOptions options; |
| options.flags.directory = true; |
| options.rights = rights; |
| zx::status validated_options = vn->ValidateOptions(options); |
| if (validated_options.is_error()) { |
| return validated_options.status_value(); |
| } |
| if (zx_status_t status = OpenVnode(validated_options.value(), &vn); status != ZX_OK) { |
| return status; |
| } |
| |
| return Serve(std::move(vn), server_end.TakeChannel(), validated_options.value()); |
| } |
| |
| // Installs a remote filesystem on vn and adds it to the remote_list_. |
| zx_status_t FuchsiaVfs::InstallRemote(fbl::RefPtr<Vnode> vn, |
| fidl::ClientEnd<fuchsia_io::Directory> h) { |
| if (vn == nullptr) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| // Allocate a node to track the remote handle |
| fbl::AllocChecker ac; |
| std::unique_ptr<MountNode> mount_point(new (&ac) MountNode()); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| zx_status_t status = vn->AttachRemote(std::move(h)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| // Save this node in the list of mounted vnodes |
| mount_point->SetNode(std::move(vn)); |
| std::lock_guard lock(vfs_lock_); |
| remote_list_.push_front(std::move(mount_point)); |
| return ZX_OK; |
| } |
| |
| zx_status_t FuchsiaVfs::UninstallRemote(fbl::RefPtr<Vnode> vn, fidl::ClientEnd<fio::Directory>* h) { |
| std::lock_guard lock(vfs_lock_); |
| return UninstallRemoteLocked(std::move(vn), h); |
| } |
| |
| zx_status_t FuchsiaVfs::ForwardOpenRemote(fbl::RefPtr<Vnode> vn, fidl::ServerEnd<fio::Node> channel, |
| std::string_view path, VnodeConnectionOptions options, |
| uint32_t mode) { |
| std::lock_guard lock(vfs_lock_); |
| auto h = vn->GetRemote(); |
| if (!h.is_valid()) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| auto r = fidl::WireCall(h) |
| ->Open(options.ToIoV1Flags(), mode, fidl::StringView::FromExternal(path), |
| std::move(channel)) |
| .status(); |
| if (r == ZX_ERR_PEER_CLOSED) { |
| fidl::ClientEnd<fio::Directory> c; |
| UninstallRemoteLocked(std::move(vn), &c); |
| } |
| return r; |
| } |
| |
| // Uninstall all remote filesystems. Acts like 'UninstallRemote' for all known remotes. |
| zx_status_t FuchsiaVfs::UninstallAll(zx::time deadline) { |
| MountNode::ListType remote_list; |
| { |
| std::lock_guard lock(vfs_lock_); |
| std::swap(remote_list, remote_list_); |
| } |
| std::unique_ptr<MountNode> mount_point; |
| while ((mount_point = remote_list.pop_front())) { |
| mount_point->ReleaseRemote(); |
| } |
| return ZX_OK; |
| } |
| |
| // Installs a remote filesystem on vn and adds it to the remote_list_. |
| zx_status_t FuchsiaVfs::InstallRemoteLocked(fbl::RefPtr<Vnode> vn, |
| fidl::ClientEnd<fuchsia_io::Directory> h) { |
| if (vn == nullptr) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| // Allocate a node to track the remote handle |
| fbl::AllocChecker ac; |
| std::unique_ptr<MountNode> mount_point(new (&ac) MountNode()); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| zx_status_t status = vn->AttachRemote(std::move(h)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| // Save this node in the list of mounted vnodes |
| mount_point->SetNode(std::move(vn)); |
| remote_list_.push_front(std::move(mount_point)); |
| return ZX_OK; |
| } |
| |
| // Uninstall the remote filesystem mounted on vn. Removes vn from the remote_list_. |
| zx_status_t FuchsiaVfs::UninstallRemoteLocked(fbl::RefPtr<Vnode> vn, |
| fidl::ClientEnd<fio::Directory>* h) { |
| std::unique_ptr<MountNode> mount_point; |
| { |
| mount_point = |
| remote_list_.erase_if([&vn](const MountNode& node) { return node.VnodeMatch(vn); }); |
| if (!mount_point) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| } |
| *h = mount_point->ReleaseRemote(); |
| return ZX_OK; |
| } |
| |
| } // namespace fs |