blob: fce2f343428acf413ef192420c75f61be6a04a58 [file] [log] [blame]
// 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 <string_view>
namespace vfs {
namespace {
/// Validates consistency of mode and flags passed to an Open call.
///
/// As the SDK VFS does not support the CREATE flags, as per the fuchsia.io protocol, the
/// mode only needs to be consistent with the flags/path if the resource already exists
/// (i.e. even if the path points to a file that reports it's mode as MODE_TYPE_FILE, it is
/// not an error to open the file with MODE_TYPE_DIRECTORY).
bool ValidateMode(uint32_t mode, fuchsia::io::OpenFlags flags, std::string_view path) {
if (path.empty()) {
return false;
}
const uint32_t mode_type = mode & fuchsia::io::MODE_TYPE_MASK;
// The mode type must always be consistent with OPEN_FLAG_DIRECTORY/NOT_DIRECTORY.
if ((mode_type == fuchsia::io::MODE_TYPE_DIRECTORY) && Flags::IsNotDirectory(flags)) {
return false;
}
if ((mode_type == fuchsia::io::MODE_TYPE_FILE) && Flags::IsDirectory(flags)) {
return false;
}
// Can't open "." with OPEN_FLAG_NOT_DIRECTORY
if (Flags::IsNotDirectory(flags) && (path == ".")) {
return false;
}
// If the path specifies a directory (indicated by a trailing slash), reject
// either OPEN_FLAG_NOT_DIRECTORY or MODE_TYPE_FILE.
if (path.back() == '/') {
return !(Flags::IsNotDirectory(flags) || (mode_type == fuchsia::io::MODE_TYPE_FILE));
}
return true;
}
} // namespace
namespace internal {
Directory::Directory() = default;
Directory::~Directory() = default;
void Directory::Describe(fuchsia::io::NodeInfo* out_info) {
out_info->set_directory(fuchsia::io::DirectoryObject());
}
void Directory::Describe2(fuchsia::io::ConnectionInfo* out_info) {
out_info->set_representation(
fuchsia::io::Representation::WithDirectory(fuchsia::io::DirectoryInfo()));
}
zx_status_t Directory::Lookup(const std::string& 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;
}
zx_status_t Directory::ValidatePath(const char* path, size_t path_len) {
bool starts_with_dot_dot = (path_len > 1 && path[0] == '.' && path[1] == '.');
if (path_len > NAME_MAX || (path_len == 2 && starts_with_dot_dot) ||
(path_len > 2 && starts_with_dot_dot && path[2] == '/') || (path_len > 0 && path[0] == '/')) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
NodeKind::Type Directory::GetKind() const {
return NodeKind::kDirectory | NodeKind::kReadable | NodeKind::kWritable | NodeKind::kExecutable;
}
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_status_t Directory::WalkPath(const char* path, size_t path_len, const char** out_path,
size_t* out_len, std::string* out_key, bool* out_is_self) {
*out_path = path;
*out_len = path_len;
*out_is_self = false;
zx_status_t status = ValidatePath(path, path_len);
if (status != ZX_OK) {
return status;
}
// remove any "./", ".//", etc
while (path_len > 1 && path[0] == '.' && path[1] == '/') {
path += 2;
path_len -= 2;
size_t index = 0u;
while (index < path_len && path[index] == '/') {
index++;
}
path += index;
path_len -= index;
}
*out_path = path;
*out_len = path_len;
if (path_len == 0 || (path_len == 1 && path[0] == '.')) {
*out_is_self = true;
return ZX_OK;
}
// Lookup node
const char* path_end = path + path_len;
const char* match = std::find(path, path_end, '/');
if (path_end == match) {
// "/" not found
*out_key = std::string(path, path_len);
*out_len = 0;
*out_path = path_end;
} else {
size_t index = std::distance(path, match);
*out_key = std::string(path, index);
// remove all '/'
while (index < path_len && path[index] == '/') {
index++;
}
*out_len -= index;
*out_path += index;
}
return ZX_OK;
}
zx_status_t Directory::LookupPath(const char* path, size_t path_len, bool* out_is_dir,
Node** out_node, const char** out_path, size_t* out_len) {
Node* current_node = this;
size_t new_path_len = path_len;
const char* new_path = path;
*out_is_dir = path_len == 0 || path[path_len - 1] == '/';
do {
std::string key;
bool is_self = false;
zx_status_t status = WalkPath(new_path, new_path_len, &new_path, &new_path_len, &key, &is_self);
if (status != ZX_OK) {
return status;
}
if (is_self) {
*out_is_dir = true;
*out_node = current_node;
return ZX_OK;
}
Node* n = nullptr;
status = current_node->Lookup(key, &n);
if (status != ZX_OK) {
return status;
}
current_node = n;
if (current_node->IsRemote()) {
break;
}
} while (new_path_len > 0);
*out_node = current_node;
*out_len = new_path_len;
*out_path = new_path;
return ZX_OK;
}
void Directory::Open(fuchsia::io::OpenFlags open_flags, fuchsia::io::OpenFlags parent_flags,
uint32_t mode, const char* path, size_t path_len, 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_len < 1) || (path_len > PATH_MAX)) {
Node::SendOnOpenEventOnError(open_flags, std::move(request), ZX_ERR_BAD_PATH);
return;
}
if (!ValidateMode(mode, open_flags, std::string_view{path, path_len})) {
Node::SendOnOpenEventOnError(open_flags, std::move(request), ZX_ERR_INVALID_ARGS);
return;
}
Node* n = nullptr;
bool path_is_dir = false;
size_t new_path_len = path_len;
const char* new_path = path;
/// TODO(fxbug.dev/82672): Path handling does not conform to in-tree behavior. Update to comply
/// with open_path() test in io1_tests.rs, and remove `non_conformant_path_handling` option.
zx_status_t status = LookupPath(path, path_len, &path_is_dir, &n, &new_path, &new_path_len);
if (status != ZX_OK) {
return SendOnOpenEventOnError(open_flags, std::move(request), status);
}
// Explicitly expand OPEN_FLAG_POSIX_DEPRECATED to prevent any right escalations from occurring if
// we cross a remote mount point. This ensures binary compatibility with older clients who have
// not yet transitioned to the new set of flags.
// TODO(fxbug.dev/81185): Remove once all clients are updated to use the latest SDK.
if ((open_flags & fuchsia::io::OpenFlags::POSIX_DEPRECATED) != fuchsia::io::OpenFlags()) {
open_flags |= fuchsia::io::OpenFlags::POSIX_WRITABLE | fuchsia::io::OpenFlags::POSIX_EXECUTABLE;
open_flags &= ~fuchsia::io::OpenFlags::POSIX_DEPRECATED;
}
// 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 (n->IsRemote() && new_path_len > 0) {
n->OpenRemote(open_flags, mode, std::string_view(new_path, new_path_len),
fidl::InterfaceRequest<fuchsia::io::Node>(std::move(request)));
return;
}
// Perform POSIX rights expansion if required.
if (n->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);
}
if (path_is_dir) {
open_flags |= fuchsia::io::OpenFlags::DIRECTORY;
}
n->Serve(open_flags, std::move(request), dispatcher);
}
} // namespace internal
} // namespace vfs