blob: 30becc91c6a1fbecb72f3008e29e4b1c3833e722 [file] [log] [blame]
// Copyright 2018 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 <fidl/fuchsia.io/cpp/wire.h>
#include <lib/svc/dir.h>
#include <lib/zx/channel.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <fbl/ref_ptr.h>
#include <src/lib/storage/vfs/cpp/pseudo_dir.h>
#include <src/lib/storage/vfs/cpp/remote_dir.h>
#include <src/lib/storage/vfs/cpp/service.h>
#include <src/lib/storage/vfs/cpp/synchronous_vfs.h>
#include <src/lib/storage/vfs/cpp/vnode.h>
namespace {
constexpr char kPathDelimiter = '/';
constexpr size_t kPathDelimiterSize = 1;
// Adds a new empty directory |name| to |dir| and sets |out| to new directory.
zx_status_t AddNewEmptyDirectory(fbl::RefPtr<fs::PseudoDir> dir, const std::string& name,
fbl::RefPtr<fs::PseudoDir>* out) {
auto subdir = fbl::MakeRefCounted<fs::PseudoDir>();
zx_status_t status = dir->AddEntry(name, subdir);
if (status == ZX_OK) {
*out = subdir;
}
return status;
}
// Disallow empty paths and paths like `.`, `..`, and so on.
bool IsPathValid(const std::string& path) {
return !path.empty() && !std::all_of(path.cbegin(), path.cend(), [](char c) { return c == '.'; });
}
zx_status_t GetDirectory(fbl::RefPtr<fs::PseudoDir> dir, const std::string& name,
bool create_if_empty, fbl::RefPtr<fs::PseudoDir>* out) {
if (!IsPathValid(name)) {
return ZX_ERR_INVALID_ARGS;
}
fbl::RefPtr<fs::Vnode> node = nullptr;
zx_status_t status = dir->Lookup(name, &node);
if (status != ZX_OK) {
if (!create_if_empty) {
return status;
}
return AddNewEmptyDirectory(dir, name, out);
}
*out = fbl::RefPtr<fs::PseudoDir>::Downcast(node);
return ZX_OK;
}
zx_status_t GetDirectoryByPath(fbl::RefPtr<fs::PseudoDir> root, const std::string& path,
bool create_if_empty, fbl::RefPtr<fs::PseudoDir>* out) {
*out = std::move(root);
// If empty, return root directory.
if (path.empty()) {
return ZX_OK;
}
// Don't allow leading nor trailing slashes.
if (path[0] == kPathDelimiter || path[path.size() - 1] == kPathDelimiter) {
return ZX_ERR_INVALID_ARGS;
}
size_t start_pos = 0;
size_t end_pos = path.find(kPathDelimiter);
while (end_pos != std::string::npos) {
std::string current_path = path.substr(start_pos, end_pos - start_pos);
zx_status_t status = GetDirectory(*out, current_path, create_if_empty, out);
if (status != ZX_OK) {
return status;
}
start_pos = end_pos + kPathDelimiterSize;
end_pos = path.find(kPathDelimiter, start_pos);
}
return GetDirectory(*out, path.substr(start_pos), create_if_empty, out);
}
zx_status_t AddServiceEntry(fbl::RefPtr<fs::PseudoDir> node, const std::string& service_name,
void* context, svc_connector_t handler) {
return node->AddEntry(service_name,
fbl::MakeRefCounted<fs::Service>([service_name = std::string(service_name),
context, handler](zx::channel channel) {
handler(context, service_name.c_str(), channel.release());
return ZX_OK;
}));
}
} // namespace
struct svc_dir {
std::unique_ptr<fs::SynchronousVfs> vfs = nullptr;
fbl::RefPtr<fs::PseudoDir> root = fbl::MakeRefCounted<fs::PseudoDir>();
};
zx_status_t svc_dir_create(async_dispatcher_t* dispatcher, zx_handle_t dir_request,
svc_dir_t** result) {
svc_dir_create_without_serve(result);
zx_status_t status = svc_dir_serve(*result, dispatcher, dir_request);
if (status != ZX_OK) {
svc_dir_destroy(*result);
return status;
}
return ZX_OK;
}
zx_status_t svc_dir_create_without_serve(svc_dir_t** result) {
*result = new svc_dir_t;
return ZX_OK;
}
zx_status_t svc_dir_serve(svc_dir_t* dir, async_dispatcher_t* dispatcher, zx_handle_t request) {
if (dir == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
if (dir->vfs == nullptr) {
dir->vfs = std::make_unique<fs::SynchronousVfs>(dispatcher);
}
return dir->vfs->Serve(dir->root, zx::channel(request), fs::VnodeConnectionOptions::ReadWrite());
}
zx_status_t svc_dir_add_service(svc_dir_t* dir, const char* type, const char* service_name,
void* context, svc_connector_t handler) {
const char* path = type == nullptr ? "" : type;
return svc_dir_add_service_by_path(dir, path, service_name, context, handler);
}
zx_status_t svc_dir_add_service_by_path(svc_dir_t* dir, const char* path, const char* service_name,
void* context, svc_connector_t* handler) {
fbl::RefPtr<fs::PseudoDir> node;
zx_status_t status = GetDirectoryByPath(dir->root, path, /*create_if_empty=*/true, &node);
if (status != ZX_OK) {
return status;
}
return AddServiceEntry(node, service_name, context, handler);
}
zx_status_t svc_dir_add_directory(svc_dir_t* dir, const char* name, zx_handle_t subdir) {
if (dir == nullptr || name == nullptr || subdir == ZX_HANDLE_INVALID) {
return ZX_ERR_INVALID_ARGS;
}
fidl::ClientEnd<fuchsia_io::Directory> client_end((zx::channel(subdir)));
auto remote_dir = fbl::MakeRefCounted<fs::RemoteDir>(std::move(client_end));
return dir->root->AddEntry(name, std::move(remote_dir));
}
zx_status_t svc_dir_remove_service(svc_dir_t* dir, const char* type, const char* service_name) {
const char* path = type == nullptr ? "" : type;
return svc_dir_remove_service_by_path(dir, path, service_name);
}
zx_status_t svc_dir_remove_service_by_path(svc_dir_t* dir, const char* path,
const char* service_name) {
fbl::RefPtr<fs::PseudoDir> parent_directory;
zx_status_t status =
GetDirectoryByPath(dir->root, path, /*create_if_empty=*/false, &parent_directory);
if (status != ZX_OK) {
return status;
}
fbl::RefPtr<fs::Vnode> service_node;
status = parent_directory->Lookup(service_name, &service_node);
if (status != ZX_OK) {
return status;
}
status = parent_directory->RemoveEntry(service_name, service_node.get());
if (status == ZX_OK) {
dir->vfs->CloseAllConnectionsForVnode(*service_node, nullptr);
}
return status;
}
zx_status_t svc_dir_remove_directory(svc_dir_t* dir, const char* name) {
if (dir == nullptr || name == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
return dir->root->RemoveEntry(name);
}
zx_status_t svc_dir_destroy(svc_dir_t* dir) {
delete dir;
return ZX_OK;
}