blob: 796fd3f3ac89dbff06d2f1b4e2142b77e923ac94 [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 "src/lib/storage/vfs/cpp/vfs.h"
#include <lib/fdio/watcher.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include "src/lib/storage/vfs/cpp/debug.h"
#include "src/lib/storage/vfs/cpp/vnode.h"
namespace fs {
namespace {
zx_status_t LookupNode(fbl::RefPtr<Vnode> vn, std::string_view name, fbl::RefPtr<Vnode>* out) {
if (name == "..") {
return ZX_ERR_INVALID_ARGS;
} else if (name == ".") {
*out = std::move(vn);
return ZX_OK;
}
return vn->Lookup(name, out);
}
// Validate open flags as much as they can be validated independently of the target node.
zx_status_t PrevalidateOptions(VnodeConnectionOptions options) {
if (!options.rights.write) {
if (options.flags.truncate) {
return ZX_ERR_INVALID_ARGS;
}
} else if (!options.rights.any()) {
if (!options.flags.node_reference) {
return ZX_ERR_INVALID_ARGS;
}
}
return ZX_OK;
}
} // namespace
Vfs::Vfs() = default;
Vfs::~Vfs() {
// Keep owning references to each vnode in case the callbacks cause any nodes to be deleted.
std::vector<fbl::RefPtr<Vnode>> nodes_to_notify;
{
// This lock should not be necessary since the destructor should be single-threaded but is good
// for completeness.
std::lock_guard lock(vfs_lock_);
nodes_to_notify.reserve(live_nodes_.size());
for (auto& node_ptr : live_nodes_)
nodes_to_notify.push_back(fbl::RefPtr<Vnode>(node_ptr));
}
// Notify all nodes that we're getting deleted.
for (auto& node : nodes_to_notify)
node->WillDestroyVfs();
}
Vfs::OpenResult Vfs::Open(fbl::RefPtr<Vnode> vndir, std::string_view path,
VnodeConnectionOptions options, Rights parent_rights, uint32_t mode) {
std::lock_guard lock(vfs_lock_);
return OpenLocked(std::move(vndir), path, options, parent_rights, mode);
}
Vfs::OpenResult Vfs::OpenLocked(fbl::RefPtr<Vnode> vndir, std::string_view path,
VnodeConnectionOptions options, Rights parent_rights,
uint32_t mode) {
FS_PRETTY_TRACE_DEBUG("VfsOpen: path='", Path(path.data(), path.size()), "' options=", options);
zx_status_t r;
if ((r = PrevalidateOptions(options)) != ZX_OK) {
return r;
}
if ((r = Vfs::Walk(vndir, path, &vndir, &path)) < 0) {
return r;
}
if (vndir->IsRemote()) {
// remote filesystem, return handle and path to caller
return OpenResult::Remote{.vnode = std::move(vndir), .path = path};
}
{
bool must_be_dir = false;
if ((r = TrimName(path, &path, &must_be_dir)) != ZX_OK) {
return r;
} else if (path == "..") {
return ZX_ERR_INVALID_ARGS;
}
if (must_be_dir) {
options.flags.directory = true;
}
}
fbl::RefPtr<Vnode> vn;
bool just_created = false;
if (options.flags.create) {
if ((r = EnsureExists(std::move(vndir), path, &vn, options, mode, parent_rights,
&just_created)) != ZX_OK) {
return r;
}
} else {
if ((r = LookupNode(std::move(vndir), path, &vn)) != ZX_OK) {
return r;
}
}
if (!options.flags.no_remote && vn->IsRemote()) {
// Opening a mount point: Traverse across remote.
return OpenResult::RemoteRoot{.vnode = std::move(vn)};
}
if (ReadonlyLocked() && options.rights.write) {
return ZX_ERR_ACCESS_DENIED;
}
if (vn->Supports(fs::VnodeProtocol::kDirectory) &&
(options.flags.posix_write || options.flags.posix_execute)) {
// This is such that POSIX open() can open a directory with O_RDONLY, and still get the
// write/execute right if the parent directory connection has the write/execute right
// respectively. With the execute right in particular, the resulting connection may be passed
// to fdio_get_vmo_exec() which requires the execute right. This transfers write and execute
// from the parent, if present.
Rights inheritable_rights{};
inheritable_rights.write = options.flags.posix_write;
inheritable_rights.execute = options.flags.posix_execute;
options.rights |= parent_rights & inheritable_rights;
}
auto validated_options = vn->ValidateOptions(options);
if (validated_options.is_error()) {
return validated_options.error();
}
// |node_reference| requests that we don't actually open the underlying Vnode, but use the
// connection as a reference to the Vnode.
if (!options.flags.node_reference && !just_created) {
if ((r = OpenVnode(validated_options.value(), &vn)) != ZX_OK) {
return r;
}
if (!options.flags.no_remote && vn->IsRemote()) {
// |OpenVnode| redirected us to a remote vnode; traverse across mount point.
return OpenResult::RemoteRoot{.vnode = std::move(vn)};
}
if (options.flags.truncate && ((r = vn->Truncate(0)) < 0)) {
vn->Close();
return r;
}
}
return OpenResult::Ok{.vnode = std::move(vn), .validated_options = validated_options.value()};
}
Vfs::TraversePathResult Vfs::TraversePathFetchVnode(fbl::RefPtr<Vnode> vndir,
std::string_view path) {
std::lock_guard lock(vfs_lock_);
return TraversePathFetchVnodeLocked(std::move(vndir), path);
}
Vfs::TraversePathResult Vfs::TraversePathFetchVnodeLocked(fbl::RefPtr<Vnode> vndir,
std::string_view path) {
FS_PRETTY_TRACE_DEBUG("VfsTraversePathFetchVnode: path='", Path(path.data(), path.size()));
if (zx_status_t result = Vfs::Walk(vndir, path, &vndir, &path); result != ZX_OK) {
return result;
}
if (vndir->IsRemote()) {
// remote filesystem, return handle and path to caller
return TraversePathResult::Remote{.vnode = std::move(vndir), .path = path};
}
zx_status_t result;
{
bool must_be_dir = false;
if ((result = TrimName(path, &path, &must_be_dir)) != ZX_OK) {
return result;
} else if (path == "..") {
return ZX_ERR_INVALID_ARGS;
}
}
fbl::RefPtr<Vnode> vn;
if ((result = LookupNode(std::move(vndir), path, &vn)) != ZX_OK) {
return result;
}
if (vn->IsRemote()) {
// Found a mount point: Traverse across remote.
return TraversePathResult::RemoteRoot{.vnode = std::move(vn)};
}
return TraversePathResult::Ok{.vnode = std::move(vn)};
}
zx_status_t Vfs::Unlink(fbl::RefPtr<Vnode> vndir, std::string_view name, bool must_be_dir) {
{
std::lock_guard lock(vfs_lock_);
if (ReadonlyLocked()) {
return ZX_ERR_ACCESS_DENIED;
} else {
if (zx_status_t status = vndir->Unlink(name, must_be_dir); status != ZX_OK) {
return status;
}
}
}
return ZX_OK;
}
void Vfs::RegisterVnode(Vnode* vnode) {
std::lock_guard lock(live_nodes_lock_);
// Should not be registered twice.
ZX_DEBUG_ASSERT(live_nodes_.find(vnode) == live_nodes_.end());
live_nodes_.insert(vnode);
}
void Vfs::UnregisterVnode(Vnode* vnode) {
std::lock_guard lock(live_nodes_lock_);
UnregisterVnodeLocked(vnode);
}
void Vfs::UnregisterVnodeLocked(Vnode* vnode) {
auto found = live_nodes_.find(vnode);
ZX_DEBUG_ASSERT(found != live_nodes_.end()); // Should always be registered first.
live_nodes_.erase(found);
}
zx_status_t Vfs::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) {
zx_status_t status;
if (options.flags.directory && !S_ISDIR(mode)) {
return ZX_ERR_INVALID_ARGS;
} else if (options.flags.not_directory && S_ISDIR(mode)) {
return ZX_ERR_INVALID_ARGS;
} else if (path == ".") {
return ZX_ERR_INVALID_ARGS;
} else if (ReadonlyLocked()) {
return ZX_ERR_ACCESS_DENIED;
} else if (!parent_rights.write) {
return ZX_ERR_ACCESS_DENIED;
}
if ((status = vndir->Create(path, mode, out_vn)) != ZX_OK) {
*did_create = false;
if ((status == ZX_ERR_ALREADY_EXISTS) && !options.flags.fail_if_exists) {
return LookupNode(std::move(vndir), path, out_vn);
}
if (status == ZX_ERR_NOT_SUPPORTED) {
// Filesystem may not support create (like devfs) in which case we should still try to open()
// the file,
return LookupNode(std::move(vndir), path, out_vn);
}
return status;
}
*did_create = true;
return ZX_OK;
}
zx_status_t Vfs::TrimName(std::string_view name, std::string_view* name_out, bool* is_dir_out) {
*is_dir_out = false;
size_t len = name.length();
while ((len > 0) && name[len - 1] == '/') {
len--;
*is_dir_out = true;
}
if (len == 0) {
// 'name' should not contain paths consisting of exclusively '/' characters.
return ZX_ERR_INVALID_ARGS;
} else if (len > NAME_MAX) {
// Name must be less than the maximum-expected length.
return ZX_ERR_BAD_PATH;
} else if (memchr(name.data(), '/', len) != nullptr) {
// Name must not contain '/' characters after being trimmed.
return ZX_ERR_INVALID_ARGS;
}
*name_out = std::string_view(name.data(), len);
return ZX_OK;
}
zx_status_t Vfs::Readdir(Vnode* vn, VdirCookie* cookie, void* dirents, size_t len,
size_t* out_actual) {
std::lock_guard lock(vfs_lock_);
return vn->Readdir(cookie, dirents, len, out_actual);
}
void Vfs::SetReadonly(bool value) {
std::lock_guard lock(vfs_lock_);
readonly_ = value;
}
zx_status_t Vfs::Walk(fbl::RefPtr<Vnode> vn, std::string_view path, fbl::RefPtr<Vnode>* out_vn,
std::string_view* out_path) {
zx_status_t r;
if (path.empty()) {
return ZX_ERR_INVALID_ARGS;
}
// Handle "." and "/".
if (path == "." || path == "/") {
*out_vn = std::move(vn);
*out_path = ".";
return ZX_OK;
}
// Allow leading '/'.
if (path[0] == '/') {
path = path.substr(1);
}
// Allow trailing '/', but only if preceded by something.
if (path.length() > 1 && path.back() == '/') {
path = path.substr(0, path.length() - 1);
}
for (;;) {
if (vn->IsRemote()) {
// Remote filesystem mount, caller must resolve.
*out_vn = std::move(vn);
*out_path = path;
return ZX_OK;
}
// Look for the next '/' separated path component.
size_t slash = path.find('/');
std::string_view component = path.substr(0, slash);
if (component.length() > NAME_MAX) {
return ZX_ERR_BAD_PATH;
}
if (component.empty() || component == "." || component == "..") {
return ZX_ERR_INVALID_ARGS;
}
if (slash == std::string_view::npos) {
// Final path segment.
*out_vn = vn;
*out_path = path;
return ZX_OK;
}
if ((r = LookupNode(std::move(vn), component, &vn)) != ZX_OK) {
return r;
}
// Traverse to the next segment.
path = path.substr(slash + 1);
}
}
} // namespace fs