blob: 479b1406c70bcfac7632da141060373c38b752b4 [file] [log] [blame] [edit]
// 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 <lib/svc/dir.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <lib/vfs/cpp/service.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <algorithm>
#include <memory>
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(vfs::PseudoDir* dir, std::string name, vfs::PseudoDir** out) {
auto subdir = std::make_unique<vfs::PseudoDir>();
auto ptr = subdir.get();
zx_status_t status = dir->AddEntry(std::move(name), std::move(subdir));
if (status == ZX_OK) {
*out = ptr;
}
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(vfs::PseudoDir* dir, std::string name, bool create_if_empty,
vfs::PseudoDir** out) {
if (!IsPathValid(name)) {
return ZX_ERR_INVALID_ARGS;
}
vfs::internal::Node* node = nullptr;
zx_status_t status = dir->Lookup(name, &node);
if (status != ZX_OK) {
if (!create_if_empty) {
return status;
}
return AddNewEmptyDirectory(dir, std::move(name), out);
}
*out = static_cast<vfs::PseudoDir*>(node);
return ZX_OK;
}
zx_status_t GetDirectoryByPath(vfs::PseudoDir* root, std::string path, bool create_if_empty,
vfs::PseudoDir** out) {
*out = 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(vfs::PseudoDir* node, const std::string& service_name, void* context,
svc_connector_t handler) {
return node->AddEntry(
service_name,
std::make_unique<vfs::Service>([service_name = std::string(service_name), context, handler](
zx::channel channel, async_dispatcher_t* dispatcher) {
// We drop |dispatcher| on the floor because the libsvc.so
// declaration of |svc_connector_t| doesn't receive a
// |dispatcher|. Callees are likely to use the default
// |async_dispatcher_t| for the current thread.
handler(context, service_name.c_str(), channel.release());
}));
}
} // namespace
struct svc_dir {
std::unique_ptr<vfs::PseudoDir> impl = std::make_unique<vfs::PseudoDir>();
};
zx_status_t svc_dir_create(async_dispatcher_t* dispatcher, zx_handle_t dir_request,
svc_dir_t** result) {
svc_dir_t* dir = new svc_dir_t;
zx_status_t status =
dir->impl->Serve(fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE,
zx::channel(dir_request), dispatcher);
if (status != ZX_OK) {
delete dir;
return status;
}
*result = dir;
return ZX_OK;
}
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) {
vfs::PseudoDir* node = nullptr;
zx_status_t status = GetDirectoryByPath(dir->impl.get(), path, /*create_if_empty=*/true, &node);
if (status != ZX_OK) {
return status;
}
return AddServiceEntry(node, service_name, context, handler);
}
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) {
vfs::PseudoDir* node = nullptr;
zx_status_t status = GetDirectoryByPath(dir->impl.get(), path, /*create_if_empty=*/false, &node);
if (status != ZX_OK) {
return status;
}
return node->RemoveEntry(service_name);
}
zx_status_t svc_dir_destroy(svc_dir_t* dir) {
delete dir;
return ZX_OK;
}