blob: 74fc35ae190dfe77a4ffae246dae9faf93c2f7e4 [file] [log] [blame] [edit]
// Copyright 2019 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 <fuchsia/io/cpp/fidl.h>
#include <lib/fdio/vfs.h>
#include <lib/vfs/cpp/flags.h>
#include <lib/vfs/cpp/internal/directory.h>
#include <lib/vfs/cpp/internal/directory_connection.h>
#include <zircon/errors.h>
#include <optional>
#include <string_view>
#include "lib/stdcompat/string_view.h"
namespace vfs {
namespace internal {
Directory::Directory() = default;
Directory::~Directory() = default;
void Directory::Describe(fuchsia::io::NodeInfoDeprecated* out_info) {
out_info->set_directory(fuchsia::io::DirectoryObject());
}
zx_status_t Directory::Lookup(std::string_view name, Node** out_node) const {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t Directory::CreateConnection(fuchsia::io::OpenFlags flags,
std::unique_ptr<Connection>* connection) {
*connection = std::make_unique<internal::DirectoryConnection>(flags, this);
return ZX_OK;
}
bool Directory::ValidatePath(std::string_view path) {
if (path.length() > NAME_MAX) {
return false;
}
if (path == "..") {
return false;
}
if (cpp20::starts_with(path, '/')) {
return false;
}
if (cpp20::starts_with(path, "../")) {
return false;
}
return true;
}
bool Directory::IsDirectory() const { return true; }
fuchsia::io::OpenFlags Directory::GetAllowedFlags() const {
return fuchsia::io::OpenFlags::DIRECTORY | fuchsia::io::OpenFlags::RIGHT_READABLE |
fuchsia::io::OpenFlags::RIGHT_WRITABLE | fuchsia::io::OpenFlags::RIGHT_EXECUTABLE;
}
fuchsia::io::OpenFlags Directory::GetProhibitiveFlags() const {
return fuchsia::io::OpenFlags::CREATE | fuchsia::io::OpenFlags::CREATE_IF_ABSENT |
fuchsia::io::OpenFlags::TRUNCATE | fuchsia::io::OpenFlags::APPEND;
}
zx_status_t Directory::GetAttr(fuchsia::io::NodeAttributes* out_attributes) const {
out_attributes->mode = fuchsia::io::MODE_TYPE_DIRECTORY | V_IRUSR;
out_attributes->id = fuchsia::io::INO_UNKNOWN;
out_attributes->content_size = 0;
out_attributes->storage_size = 0;
out_attributes->link_count = 1;
out_attributes->creation_time = 0;
out_attributes->modification_time = 0;
return ZX_OK;
}
zx::result<Directory::WalkPathResult> Directory::WalkPath(std::string_view path) {
if (!ValidatePath(path)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// remove any "./", ".//", etc
while (cpp20::starts_with(path, "./")) {
path.remove_prefix(2);
while (cpp20::starts_with(path, '/')) {
path.remove_prefix(1);
}
}
if (path.empty() || path == ".") {
return zx::ok(Directory::WalkPathResult{
.remaining_path = path,
});
}
// Lookup node
if (size_t index = path.find('/'); index != std::string::npos) {
std::string_view current_node_name = path.substr(0, index);
path.remove_prefix(index + 1);
while (cpp20::starts_with(path, '/')) {
path.remove_prefix(1);
}
return zx::ok(Directory::WalkPathResult{
.current_node_name = current_node_name,
.remaining_path = path,
});
}
return zx::ok(Directory::WalkPathResult{
.current_node_name = path,
});
}
zx::result<Directory::LookupPathResult> Directory::LookupPath(std::string_view path) {
bool is_dir = path.empty() || cpp20::ends_with(path, '/');
Node* current_node = this;
do {
zx::result result = WalkPath(path);
if (result.is_error()) {
return result.take_error();
}
Directory::WalkPathResult& walk_path_result = result.value();
if (!walk_path_result.current_node_name.has_value()) {
return zx::ok(Directory::LookupPathResult{
.node = *current_node,
.remaining_path = walk_path_result.remaining_path,
.is_dir = true,
});
}
path = walk_path_result.remaining_path;
if (zx_status_t status =
current_node->Lookup(walk_path_result.current_node_name.value(), &current_node);
status != ZX_OK) {
return zx::error(status);
}
if (current_node->IsRemote()) {
break;
}
} while (!path.empty());
return zx::ok(LookupPathResult{
.node = *current_node,
.remaining_path = path,
.is_dir = is_dir,
});
}
void Directory::Open(fuchsia::io::OpenFlags open_flags, fuchsia::io::OpenFlags parent_flags,
fuchsia::io::ModeType mode, std::string_view path, zx::channel request,
async_dispatcher_t* dispatcher) {
if (!Flags::InputPrecondition(open_flags)) {
Node::SendOnOpenEventOnError(open_flags, std::move(request), ZX_ERR_INVALID_ARGS);
return;
}
if (Flags::ShouldCloneWithSameRights(open_flags)) {
Node::SendOnOpenEventOnError(open_flags, std::move(request), ZX_ERR_INVALID_ARGS);
return;
}
if (!Flags::StricterOrSameRights(open_flags, parent_flags)) {
Node::SendOnOpenEventOnError(open_flags, std::move(request), ZX_ERR_ACCESS_DENIED);
return;
}
if (path.empty() || path.length() > PATH_MAX - 1) {
Node::SendOnOpenEventOnError(open_flags, std::move(request), ZX_ERR_BAD_PATH);
return;
}
// OPEN_FLAG_NOT_DIRECTORY implies can't open "." because we're a directory and can't open a
// subdirectory indicated by a trailing slash.
if (Flags::IsNotDirectory(open_flags) && (path == "." || path.back() == '/')) {
Node::SendOnOpenEventOnError(open_flags, std::move(request), ZX_ERR_INVALID_ARGS);
return;
}
// TODO(fxbug.dev/119413): Path handling does not conform to in-tree behavior.
zx::result result = LookupPath(path);
if (result.is_error()) {
return SendOnOpenEventOnError(open_flags, std::move(request), result.error_value());
}
LookupPathResult& look_path_result = result.value();
// The POSIX compatibility flags allow the child dir connection to inherit
// write/execute rights from its immediate parent. We remove the right inheritance
// anywhere the parent node does not have the relevant right.
if (Flags::IsPosixWritable(open_flags) && !Flags::IsWritable(parent_flags)) {
open_flags &= ~fuchsia::io::OpenFlags::POSIX_WRITABLE;
}
if (Flags::IsPosixExecutable(open_flags) && !Flags::IsExecutable(parent_flags)) {
open_flags &= ~fuchsia::io::OpenFlags::POSIX_EXECUTABLE;
}
if (look_path_result.node.IsRemote() && !look_path_result.remaining_path.empty()) {
look_path_result.node.OpenRemote(open_flags, mode, look_path_result.remaining_path,
fidl::InterfaceRequest<fuchsia::io::Node>(std::move(request)));
return;
}
// Perform POSIX rights expansion if required.
if (look_path_result.node.IsDirectory()) {
if (Flags::IsPosixWritable(open_flags))
open_flags |= (parent_flags & fuchsia::io::OpenFlags::RIGHT_WRITABLE);
if (Flags::IsPosixExecutable(open_flags))
open_flags |= (parent_flags & fuchsia::io::OpenFlags::RIGHT_EXECUTABLE);
// Strip flags now that rights have been expanded.
open_flags &=
~(fuchsia::io::OpenFlags::POSIX_WRITABLE | fuchsia::io::OpenFlags::POSIX_EXECUTABLE);
}
// TODO(https://fxbug.dev/101092): Remove this when flutter_{touch,views}_test no longer open
// these protocols with RIGHT_READABLE.
const std::string_view protocols[] = {
"fuchsia.logger.LogSink",
"fuchsia.sysmem.Allocator",
"fuchsia.tracing.provider.Registry",
};
if (std::any_of(
std::begin(protocols), std::end(protocols),
[&path](const std::string_view& protocol) { return cpp20::ends_with(path, protocol); })) {
open_flags &= ~fuchsia::io::OpenFlags::RIGHT_READABLE;
}
if (look_path_result.is_dir) {
open_flags |= fuchsia::io::OpenFlags::DIRECTORY;
}
[[maybe_unused]] zx_status_t status =
look_path_result.node.Serve(open_flags, std::move(request), dispatcher);
}
} // namespace internal
} // namespace vfs