blob: e67fbc0e53eca1ea9045d9f6ddcb01b104e6d834 [file] [log] [blame]
// Copyright 2016 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 <lib/fdio/vfs.h>
#include <lib/zircon-internal/debug.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <threads.h>
#include <memory>
#include <utility>
#include <fbl/alloc_checker.h>
#include <fbl/auto_lock.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/ref_ptr.h>
#include <fs/mount_channel.h>
#include <fs/vfs.h>
#include <fs/vfs_types.h>
#include <fs/vnode.h>
namespace fio = ::llcpp::fuchsia::io;
namespace fs {
constexpr Vfs::MountNode::MountNode() : vn_(nullptr) {}
Vfs::MountNode::~MountNode() { ZX_DEBUG_ASSERT(vn_ == nullptr); }
void Vfs::MountNode::SetNode(fbl::RefPtr<Vnode> vn) {
ZX_DEBUG_ASSERT(vn_ == nullptr);
vn_ = vn;
}
zx::channel Vfs::MountNode::ReleaseRemote() {
ZX_DEBUG_ASSERT(vn_ != nullptr);
zx::channel h = vn_->DetachRemote();
vn_ = nullptr;
return h;
}
bool Vfs::MountNode::VnodeMatch(fbl::RefPtr<Vnode> vn) const {
ZX_DEBUG_ASSERT(vn_ != nullptr);
return vn == vn_;
}
// Installs a remote filesystem on vn and adds it to the remote_list_.
zx_status_t Vfs::InstallRemote(fbl::RefPtr<Vnode> vn, MountChannel 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));
fbl::AutoLock lock(&vfs_lock_);
remote_list_.push_front(std::move(mount_point));
return ZX_OK;
}
// Installs a remote filesystem on vn and adds it to the remote_list_.
zx_status_t Vfs::InstallRemoteLocked(fbl::RefPtr<Vnode> vn, MountChannel 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;
}
zx_status_t Vfs::MountMkdir(fbl::RefPtr<Vnode> vn, fbl::StringPiece name, MountChannel h,
uint32_t flags) {
fbl::AutoLock lock(&vfs_lock_);
return OpenLocked(
vn, name,
fs::VnodeConnectionOptions::ReadOnly().set_create().set_directory().set_no_remote(),
fs::Rights::ReadOnly(), S_IFDIR)
.visit([&](auto&& result) FS_TA_REQUIRES(vfs_lock_) {
using T = std::decay_t<decltype(result)>;
using OpenResult = fs::Vfs::OpenResult;
if constexpr (std::is_same_v<T, OpenResult::Error>) {
return result;
} else {
if (result.vnode->IsRemote()) {
if (flags & fio::MOUNT_CREATE_FLAG_REPLACE) {
// There is an old remote handle on this vnode; shut it down and
// replace it with our own.
zx::channel old_remote;
Vfs::UninstallRemoteLocked(vn, &old_remote);
// Passing |zx::time::infinite_past()| results in a fire-and-forget call.
// TODO(fxb/42264): Add proper tracking of remote filesystem teardown.
Vfs::UnmountHandle(std::move(old_remote), zx::time::infinite_past());
} else {
return ZX_ERR_BAD_STATE;
}
}
return Vfs::InstallRemoteLocked(result.vnode, std::move(h));
}
});
}
zx_status_t Vfs::UninstallRemote(fbl::RefPtr<Vnode> vn, zx::channel* h) {
fbl::AutoLock lock(&vfs_lock_);
return UninstallRemoteLocked(std::move(vn), h);
}
zx_status_t Vfs::ForwardOpenRemote(fbl::RefPtr<Vnode> vn, zx::channel channel,
fbl::StringPiece path, VnodeConnectionOptions options,
uint32_t mode) {
fbl::AutoLock lock(&vfs_lock_);
zx_handle_t h = vn->GetRemote();
if (h == ZX_HANDLE_INVALID) {
return ZX_ERR_NOT_FOUND;
}
auto r = fio::Directory::Call::Open(zx::unowned_channel(h), options.ToIoV1Flags(), mode,
fidl::unowned_str(path), std::move(channel))
.status();
if (r == ZX_ERR_PEER_CLOSED) {
zx::channel c;
UninstallRemoteLocked(std::move(vn), &c);
}
return r;
}
// Uninstall the remote filesystem mounted on vn. Removes vn from the
// remote_list_, and sends its corresponding filesystem an 'unmount' signal.
zx_status_t Vfs::UninstallRemoteLocked(fbl::RefPtr<Vnode> vn, zx::channel* 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;
}
// Uninstall all remote filesystems. Acts like 'UninstallRemote' for all
// known remotes.
zx_status_t Vfs::UninstallAll(zx::time deadline) {
std::unique_ptr<MountNode> mount_point;
for (;;) {
{
fbl::AutoLock lock(&vfs_lock_);
mount_point = remote_list_.pop_front();
}
if (mount_point) {
Vfs::UnmountHandle(mount_point->ReleaseRemote(), deadline);
} else {
return ZX_OK;
}
}
}
} // namespace fs