mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Refer to SDK rather than locally checked out packages (#314)
This commit is contained in:
parent
9c762bea4e
commit
25b164b5f4
@ -2,6 +2,8 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
# TODO(dnfield): Remove this, it's not in the SDK.
|
||||
|
||||
config("c_config") {
|
||||
include_dirs = [ "../../../" ]
|
||||
}
|
||||
|
@ -5,29 +5,11 @@
|
||||
assert(is_fuchsia)
|
||||
import("//build/fuchsia/sdk.gni")
|
||||
|
||||
config("config") {
|
||||
include_dirs = [ "../../.." ]
|
||||
}
|
||||
|
||||
source_set("cpp") {
|
||||
public_configs = [ ":config" ]
|
||||
|
||||
sources = [
|
||||
"component_context.cc",
|
||||
"component_context.h",
|
||||
"file_descriptor.cc",
|
||||
"file_descriptor.h",
|
||||
"outgoing_directory.cc",
|
||||
"outgoing_directory.h",
|
||||
"service_directory.cc",
|
||||
"service_directory.h",
|
||||
"termination_reason.cc",
|
||||
"termination_reason.h",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.io",
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.sys",
|
||||
"$fuchsia_sdk_root/pkg:fdio",
|
||||
]
|
||||
# TODO(dnfield): Remove this once Dart rules have been fixed:
|
||||
# https://dart-review.googlesource.com/c/sdk/+/117770
|
||||
|
||||
group("cpp") {
|
||||
visibility = [ "//third_party/dart/runtime/vm:*" ]
|
||||
|
||||
public_deps = [ "$fuchsia_sdk_root/pkg:sys_cpp" ]
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
abarth@google.com
|
||||
anmittal@google.com
|
||||
jeffbrown@google.com
|
@ -1,32 +0,0 @@
|
||||
// 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 <lib/sys/cpp/component_context.h>
|
||||
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <lib/sys/cpp/outgoing_directory.h>
|
||||
#include <lib/zx/channel.h>
|
||||
#include <zircon/process.h>
|
||||
#include <zircon/processargs.h>
|
||||
|
||||
namespace sys {
|
||||
|
||||
ComponentContext::ComponentContext(MakePrivate make_private,
|
||||
std::shared_ptr<ServiceDirectory> svc,
|
||||
zx::channel directory_request,
|
||||
async_dispatcher_t* dispatcher)
|
||||
: svc_(std::move(svc)), outgoing_(std::make_shared<OutgoingDirectory>()) {
|
||||
outgoing_->Serve(std::move(directory_request), dispatcher);
|
||||
}
|
||||
|
||||
ComponentContext::~ComponentContext() = default;
|
||||
|
||||
std::unique_ptr<ComponentContext> ComponentContext::Create() {
|
||||
zx_handle_t directory_request = zx_take_startup_handle(PA_DIRECTORY_REQUEST);
|
||||
return std::make_unique<ComponentContext>(
|
||||
MakePrivate{}, ServiceDirectory::CreateFromNamespace(),
|
||||
zx::channel(directory_request));
|
||||
}
|
||||
|
||||
} // namespace sys
|
@ -1,152 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_COMPONENT_CONTEXT_H_
|
||||
#define LIB_SYS_CPP_COMPONENT_CONTEXT_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/sys/cpp/outgoing_directory.h>
|
||||
#include <lib/sys/cpp/service_directory.h>
|
||||
|
||||
namespace sys {
|
||||
|
||||
namespace testing {
|
||||
|
||||
class ComponentContextProvider;
|
||||
|
||||
}
|
||||
|
||||
// Context information that this component received at startup.
|
||||
//
|
||||
// Upon creation, components are given a namespace, which is file system local
|
||||
// to the component. A components namespace lets the component interact with
|
||||
// other components and the system at large. One important part of this
|
||||
// namespace is the directory of services, typically located at "/svc" in the
|
||||
// components namespace. The |ComponentContext| provides an ergonomic interface
|
||||
// to this service bundle through its |svc()| property.
|
||||
//
|
||||
// In addition to receiving services, components can also publish services and
|
||||
// data to other components through their outgoing namespace, which is also a
|
||||
// directory. The |ComponentContext| provides an ergonomic interface for
|
||||
// services and other file system objects through its |outgoing()| property.
|
||||
//
|
||||
// Instances of this class are thread-safe.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// The |ComponentContext| object is typically created early in the startup
|
||||
// sequence for components, typically after creating the |async::Loop| for the
|
||||
// main thread.
|
||||
//
|
||||
// ```
|
||||
// int main(int argc, const char** argv) {
|
||||
// async::Loop loop(&kAsyncLoopConfigAttachToThread);
|
||||
// auto context = sys::ComponentContext::Create();
|
||||
// my::App app(std::move(context))
|
||||
// loop.Run();
|
||||
// return 0;
|
||||
// }
|
||||
// ```
|
||||
class ComponentContext final {
|
||||
struct MakePrivate;
|
||||
|
||||
public:
|
||||
// Create a component context.
|
||||
//
|
||||
// This constructor is rarely used directly. Instead, most clients create a
|
||||
// component context using the |Create()| static method.
|
||||
ComponentContext(MakePrivate make_private,
|
||||
std::shared_ptr<ServiceDirectory> svc,
|
||||
zx::channel directory_request,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
~ComponentContext();
|
||||
|
||||
// ComponentContext objects cannot be copied.
|
||||
ComponentContext(const ComponentContext&) = delete;
|
||||
ComponentContext& operator=(const ComponentContext&) = delete;
|
||||
|
||||
// Creates a component context from the process startup info.
|
||||
//
|
||||
// Call this function once during process initialization to retrieve the
|
||||
// handles supplied to the component by the component manager. This function
|
||||
// consumes some of those handles, which means subsequent calls to this
|
||||
// function will not return a functional component context.
|
||||
//
|
||||
// Prefer creating the |ComponentContext| in the |main| function for a
|
||||
// component and passing the object to any |App| class. This pattern makes
|
||||
// testing easier because tests can pass a |FakeComponentContext| to the |App|
|
||||
// class to inject dependencies.
|
||||
//
|
||||
// The returned unique_ptr is never null.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// int main(int argc, const char** argv) {
|
||||
// async::Loop loop(&kAsyncLoopConfigAttachToThread);
|
||||
// auto context = sys::ComponentContext::Create();
|
||||
// my::App app(std::move(context))
|
||||
// loop.Run();
|
||||
// return 0;
|
||||
// }
|
||||
// ```
|
||||
static std::unique_ptr<ComponentContext> Create();
|
||||
|
||||
// The directory of services.
|
||||
//
|
||||
// Use this object to connect to services offered by other components.
|
||||
//
|
||||
// The directory of services is thread-safe and is commonly used on multiple
|
||||
// threads.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// auto controller = context.svc()->Connect<fuchsia::foo::Controller>();
|
||||
// ```
|
||||
const std::shared_ptr<ServiceDirectory>& svc() const { return svc_; }
|
||||
|
||||
// The outgoing namespace.
|
||||
//
|
||||
// Use this object to publish services and data to the component manager and
|
||||
// other components.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// class App : public fuchsia::foo::Controller {
|
||||
// public:
|
||||
// App(std::unique_ptr<ComponentContext> context)
|
||||
// : context_(std::move(context) {
|
||||
// context_.outgoing()->AddPublicService(bindings_.GetHandler(this));
|
||||
// }
|
||||
//
|
||||
// // fuchsia::foo::Controller implementation:
|
||||
// [...]
|
||||
//
|
||||
// private:
|
||||
// fidl::BindingSet<fuchsia::foo::Controller> bindings_;
|
||||
// }
|
||||
// ```
|
||||
const std::shared_ptr<OutgoingDirectory>& outgoing() const {
|
||||
return outgoing_;
|
||||
}
|
||||
std::shared_ptr<OutgoingDirectory>& outgoing() { return outgoing_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<ServiceDirectory> svc_;
|
||||
std::shared_ptr<OutgoingDirectory> outgoing_;
|
||||
|
||||
// makes constructor private and only accessible by
|
||||
// |sys::testing::ComponentContextProvider|.
|
||||
struct MakePrivate {};
|
||||
friend class sys::testing::ComponentContextProvider;
|
||||
};
|
||||
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_COMPONENT_CONTEXT_H_
|
@ -1,24 +0,0 @@
|
||||
// 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 <lib/sys/cpp/file_descriptor.h>
|
||||
|
||||
#include <lib/zx/handle.h>
|
||||
#include <lib/fdio/fd.h>
|
||||
#include <zircon/processargs.h>
|
||||
|
||||
namespace sys {
|
||||
|
||||
fuchsia::sys::FileDescriptorPtr CloneFileDescriptor(int fd) {
|
||||
zx::handle handle;
|
||||
zx_status_t status = fdio_fd_clone(fd, handle.reset_and_get_address());
|
||||
if (status != ZX_OK)
|
||||
return nullptr;
|
||||
fuchsia::sys::FileDescriptorPtr result = fuchsia::sys::FileDescriptor::New();
|
||||
result->type0 = PA_HND(PA_FD, fd);
|
||||
result->handle0 = std::move(handle);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace sys
|
@ -1,24 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_FILE_DESCRIPTOR_H_
|
||||
#define LIB_SYS_CPP_FILE_DESCRIPTOR_H_
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
|
||||
namespace sys {
|
||||
|
||||
// Clone the given file descriptor as a |fuchsia::sys::FileDescriptorPtr|.
|
||||
//
|
||||
// For example, the returned |fuchsia::sys::FileDescriptorPtr| is suitable for
|
||||
// use as the stdout or stderr when creating a component. To obtain only a
|
||||
// |zx_handle_t|, consider calling |fdio_fd_clone| directory instead.
|
||||
//
|
||||
// Returns |nullptr| if |fd| is invalid or cannot be cloned.
|
||||
fuchsia::sys::FileDescriptorPtr CloneFileDescriptor(int fd);
|
||||
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_FILE_DESCRIPTOR_H_
|
||||
|
@ -1,57 +0,0 @@
|
||||
// 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 <lib/sys/cpp/outgoing_directory.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <zircon/process.h>
|
||||
#include <zircon/processargs.h>
|
||||
|
||||
namespace sys {
|
||||
|
||||
OutgoingDirectory::OutgoingDirectory()
|
||||
: root_(std::make_unique<vfs::PseudoDir>()),
|
||||
public_(GetOrCreateDirectory("public")),
|
||||
debug_(GetOrCreateDirectory("debug")),
|
||||
ctrl_(GetOrCreateDirectory("ctrl")) {}
|
||||
|
||||
OutgoingDirectory::~OutgoingDirectory() = default;
|
||||
|
||||
zx_status_t OutgoingDirectory::Serve(zx::channel directory_request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
return root_->Serve(fuchsia::io::OPEN_RIGHT_READABLE |
|
||||
fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
std::move(directory_request), dispatcher);
|
||||
}
|
||||
|
||||
zx_status_t OutgoingDirectory::ServeFromStartupInfo(
|
||||
async_dispatcher_t* dispatcher) {
|
||||
return Serve(zx::channel(zx_take_startup_handle(PA_DIRECTORY_REQUEST)),
|
||||
dispatcher);
|
||||
}
|
||||
|
||||
vfs::PseudoDir* OutgoingDirectory::GetOrCreateDirectory(
|
||||
const std::string& name) {
|
||||
vfs::Node* node;
|
||||
zx_status_t status = root_->Lookup(name, &node);
|
||||
if (status != ZX_OK) {
|
||||
return AddNewEmptyDirectory(name);
|
||||
}
|
||||
return static_cast<vfs::PseudoDir*>(node);
|
||||
}
|
||||
|
||||
vfs::PseudoDir* OutgoingDirectory::AddNewEmptyDirectory(std::string name) {
|
||||
auto dir = std::make_unique<vfs::PseudoDir>();
|
||||
auto ptr = dir.get();
|
||||
root_->AddEntry(std::move(name), std::move(dir));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
zx_status_t OutgoingDirectory::AddPublicService(
|
||||
std::unique_ptr<vfs::Service> service, std::string service_name) const {
|
||||
return public_->AddEntry(std::move(service_name), std::move(service));
|
||||
}
|
||||
|
||||
} // namespace sys
|
@ -1,182 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_OUTGOING_DIRECTORY_H_
|
||||
#define LIB_SYS_CPP_OUTGOING_DIRECTORY_H_
|
||||
|
||||
#include <lib/async/dispatcher.h>
|
||||
#include <lib/fit/function.h>
|
||||
#include <lib/vfs/cpp/pseudo_dir.h>
|
||||
#include <lib/vfs/cpp/service.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace sys {
|
||||
|
||||
// The directory provided by this component to the component manager.
|
||||
//
|
||||
// A components outgoing directory contains services, data, and other objects
|
||||
// that can be consumed by either the component manager itself or by other
|
||||
// components in the system.
|
||||
//
|
||||
// The root directory contains serveral directories with well-known names:
|
||||
//
|
||||
// * public. This directory contains the services offered by this component to
|
||||
// other components.
|
||||
// * debug. This directory contains arbitrary debugging output offered by this
|
||||
// component.
|
||||
// * ctrl. This directory contains read-write files the component exposes for
|
||||
// controlling its behavior.
|
||||
//
|
||||
// The root directory may optionally contain other directories constructed using
|
||||
// |GetOrCreateDirectory|. Common optional directories include:
|
||||
//
|
||||
// * objects. This directory contains Inspect API files and interfaces for use
|
||||
// in component inspection.
|
||||
//
|
||||
// The root directory is typically used to service the |PA_DIRECTORY_REQUEST|
|
||||
// process argument.
|
||||
//
|
||||
// Instances of this class are thread-safe.
|
||||
class OutgoingDirectory final {
|
||||
public:
|
||||
OutgoingDirectory();
|
||||
~OutgoingDirectory();
|
||||
|
||||
// Outgoing objects cannot be copied.
|
||||
OutgoingDirectory(const OutgoingDirectory&) = delete;
|
||||
OutgoingDirectory& operator=(const OutgoingDirectory&) = delete;
|
||||
|
||||
// Start serving the root directory on the given channel.
|
||||
//
|
||||
// This object will implement the |fuchsia.io.Directory| interface using this
|
||||
// channel.
|
||||
//
|
||||
// If |dispatcher| is NULL, this object will serve the root directory using
|
||||
// the |async_dispatcher_t| from |async_get_default_dispatcher()|.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_BAD_HANDLE: |directory_request| is not a valid handle.
|
||||
//
|
||||
// ZX_ERR_ACCESS_DENIED: |directory_request| has insufficient rights.
|
||||
//
|
||||
// TODO: Document more errors.
|
||||
zx_status_t Serve(zx::channel directory_request,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// Start serving the root directory on the channel provided to this process at
|
||||
// startup as |PA_DIRECTORY_REQUEST|.
|
||||
//
|
||||
// This object will implement the |fuchsia.io.Directory| interface using this
|
||||
// channel.
|
||||
//
|
||||
// If |dispatcher| is NULL, this object will serve the root directory using
|
||||
// the |async_dispatcher_t| from |async_get_default_dispatcher()|.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_BAD_HANDLE: |directory_request| is not a valid handle.
|
||||
//
|
||||
// ZX_ERR_ACCESS_DENIED: |directory_request| has insufficient rights.
|
||||
//
|
||||
// TODO: Document more errors.
|
||||
zx_status_t ServeFromStartupInfo(async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// Adds the specified interface to the set of public interfaces.
|
||||
//
|
||||
// Adds a supported service with the given |service_name|, using the given
|
||||
// |interface_request_handler|. |interface_request_handler| should
|
||||
// remain valid for the lifetime of this object.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_ALREADY_EXISTS: The public directory already contains an entry for
|
||||
// this service.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// fidl::BindingSet<fuchsia::foo::Controller> bindings;
|
||||
// outgoing.AddPublicService(bindings.GetHandler(this));
|
||||
// ```
|
||||
template <typename Interface>
|
||||
zx_status_t AddPublicService(
|
||||
fidl::InterfaceRequestHandler<Interface> handler,
|
||||
std::string service_name = Interface::Name_) const {
|
||||
return AddPublicService(std::make_unique<vfs::Service>(std::move(handler)),
|
||||
std::move(service_name));
|
||||
}
|
||||
|
||||
// Adds the specified service to the set of public services.
|
||||
//
|
||||
// Adds a supported service with the given |service_name|, using the given
|
||||
// |service|.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_ALREADY_EXISTS: The public directory already contains an entry for
|
||||
// this service.
|
||||
zx_status_t AddPublicService(std::unique_ptr<vfs::Service> service,
|
||||
std::string service_name) const;
|
||||
|
||||
// Removes the specified interface from the set of public interfaces.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_NOT_FOUND: The public directory does not contain an entry for this
|
||||
// service.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// outgoing.RemovePublicService<fuchsia::foo::Controller>();
|
||||
// ```
|
||||
template <typename Interface>
|
||||
zx_status_t RemovePublicService(
|
||||
const std::string& name = Interface::Name_) const {
|
||||
return public_->RemoveEntry(name);
|
||||
}
|
||||
|
||||
// Get access to debug directory to publish debug data.
|
||||
// This directory is owned by this class.
|
||||
vfs::PseudoDir* debug_dir() { return debug_; }
|
||||
|
||||
// Get access to ctrl directory to publish ctrl data.
|
||||
// This directory is owned by this class.
|
||||
vfs::PseudoDir* ctrl_dir() { return ctrl_; }
|
||||
|
||||
// Get a directory under the output namespace. If the directory was not
|
||||
// previously obtained by this method, it will be created.
|
||||
// The returned directory is owned by this class.
|
||||
vfs::PseudoDir* GetOrCreateDirectory(const std::string& name);
|
||||
|
||||
private:
|
||||
// Adds a new empty directory to |root_| and returns pointer to new directory.
|
||||
// Will fail silently if directory with that name already exists.
|
||||
vfs::PseudoDir* AddNewEmptyDirectory(std::string name);
|
||||
|
||||
// The root outgoing directory itself.
|
||||
std::unique_ptr<vfs::PseudoDir> root_;
|
||||
|
||||
// The public subdirectory of the root directory.
|
||||
//
|
||||
// The underlying |vfs::PseudoDir| object is owned by |root_|.
|
||||
vfs::PseudoDir* public_;
|
||||
|
||||
// The debug subdirectory of the root directory.
|
||||
//
|
||||
// The underlying |vfs::PseudoDir| object is owned by |root_|.
|
||||
vfs::PseudoDir* debug_;
|
||||
|
||||
// The ctrl subdirectory of the root directory.
|
||||
//
|
||||
// The underlying |vfs::PseudoDir| object is owned by |root_|.
|
||||
vfs::PseudoDir* ctrl_;
|
||||
};
|
||||
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_OUTGOING_DIRECTORY_H_
|
@ -1,75 +0,0 @@
|
||||
// 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 <lib/sys/cpp/service_directory.h>
|
||||
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <lib/zx/channel.h>
|
||||
|
||||
namespace sys {
|
||||
namespace {
|
||||
|
||||
zx::channel OpenServiceRoot() {
|
||||
zx::channel request;
|
||||
zx::channel service_root;
|
||||
if (zx::channel::create(0, &request, &service_root) != ZX_OK)
|
||||
return zx::channel();
|
||||
if (fdio_service_connect("/svc/.", request.release()) != ZX_OK)
|
||||
return zx::channel();
|
||||
return service_root;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ServiceDirectory::ServiceDirectory(zx::channel directory)
|
||||
: directory_(std::move(directory)) {}
|
||||
|
||||
ServiceDirectory::ServiceDirectory(
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> directory)
|
||||
: ServiceDirectory(directory.TakeChannel()) {}
|
||||
|
||||
ServiceDirectory::~ServiceDirectory() = default;
|
||||
|
||||
std::shared_ptr<ServiceDirectory> ServiceDirectory::CreateFromNamespace() {
|
||||
return std::make_shared<ServiceDirectory>(OpenServiceRoot());
|
||||
}
|
||||
|
||||
std::shared_ptr<ServiceDirectory> ServiceDirectory::CreateWithRequest(
|
||||
zx::channel* out_request) {
|
||||
zx::channel directory;
|
||||
// no need to check status, even if this fails, service directory would be
|
||||
// backed by invalid channel and Connect will return correct error.
|
||||
zx::channel::create(0, &directory, out_request);
|
||||
|
||||
return std::make_shared<ServiceDirectory>(
|
||||
ServiceDirectory(std::move(directory)));
|
||||
}
|
||||
|
||||
std::shared_ptr<ServiceDirectory> ServiceDirectory::CreateWithRequest(
|
||||
fidl::InterfaceRequest<fuchsia::io::Directory>* out_request) {
|
||||
zx::channel request;
|
||||
auto directory = CreateWithRequest(&request);
|
||||
out_request->set_channel(std::move(request));
|
||||
return directory;
|
||||
}
|
||||
|
||||
zx_status_t ServiceDirectory::Connect(const std::string& interface_name,
|
||||
zx::channel channel) const {
|
||||
return fdio_service_connect_at(directory_.get(), interface_name.c_str(),
|
||||
channel.release());
|
||||
}
|
||||
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> ServiceDirectory::CloneChannel()
|
||||
const {
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> dir;
|
||||
CloneChannel(dir.NewRequest());
|
||||
return dir;
|
||||
}
|
||||
|
||||
zx_status_t ServiceDirectory::CloneChannel(
|
||||
fidl::InterfaceRequest<fuchsia::io::Directory> dir) const {
|
||||
return fdio_service_clone_to(directory_.get(), dir.TakeChannel().release());
|
||||
}
|
||||
|
||||
} // namespace sys
|
@ -1,185 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_SERVICE_DIRECTORY_H_
|
||||
#define LIB_SYS_CPP_SERVICE_DIRECTORY_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/interface_ptr.h>
|
||||
#include <lib/fidl/cpp/interface_request.h>
|
||||
#include <lib/zx/channel.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace sys {
|
||||
|
||||
// A directory of services provided by another component.
|
||||
//
|
||||
// These services are typically received by the component through its namespace,
|
||||
// specifically through the "/svc" entry.
|
||||
//
|
||||
// Instances of this class are thread-safe.
|
||||
class ServiceDirectory final {
|
||||
public:
|
||||
// Create an directory of services backed by given |directory|.
|
||||
//
|
||||
// Requests for services are routed to entries in this directory.
|
||||
//
|
||||
// The directory is expected to implement the |fuchsia.io.Directory| protocol.
|
||||
explicit ServiceDirectory(zx::channel directory);
|
||||
explicit ServiceDirectory(
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> directory);
|
||||
|
||||
~ServiceDirectory();
|
||||
|
||||
// ServiceDirectory objects cannot be copied.
|
||||
ServiceDirectory(const ServiceDirectory&) = delete;
|
||||
ServiceDirectory& operator=(const ServiceDirectory&) = delete;
|
||||
|
||||
// ServiceDirectory objects can be moved.
|
||||
ServiceDirectory(ServiceDirectory&& other)
|
||||
: directory_(std::move(other.directory_)) {}
|
||||
ServiceDirectory& operator=(ServiceDirectory&& other) {
|
||||
directory_ = std::move(other.directory_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Create an directory of services from this component's namespace.
|
||||
//
|
||||
// Uses the "/svc" entry in the namespace as the backing directory for the
|
||||
// returned directory of services.
|
||||
//
|
||||
// Rather than creating a new |ServiceDirectory| consider passing |svc()| from
|
||||
// your |ComponentContext| around as that makes your code unit testable and
|
||||
// consumes one less kernel handle.
|
||||
static std::shared_ptr<ServiceDirectory> CreateFromNamespace();
|
||||
|
||||
// Create a directory of services and return a request for an implementation
|
||||
// of the underlying directory in |out_request|.
|
||||
//
|
||||
// Useful when creating components.
|
||||
static std::shared_ptr<ServiceDirectory> CreateWithRequest(
|
||||
zx::channel* out_request);
|
||||
static std::shared_ptr<ServiceDirectory> CreateWithRequest(
|
||||
fidl::InterfaceRequest<fuchsia::io::Directory>* out_request);
|
||||
|
||||
// Connect to an interface in the directory.
|
||||
//
|
||||
// The discovery name of the interface is inferred from the C++ type of the
|
||||
// interface. Callers can supply an interface name explicitly to override
|
||||
// the default name.
|
||||
//
|
||||
// This overload for |Connect| discards the status of the underlying
|
||||
// connection operation. Callers that wish to recieve that status should use
|
||||
// one of the other overloads that returns a |zx_status_t|.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// auto controller = directory.Connect<fuchsia::foo::Controller>();
|
||||
// ```
|
||||
template <typename Interface>
|
||||
fidl::InterfacePtr<Interface> Connect(
|
||||
const std::string& interface_name = Interface::Name_) const {
|
||||
fidl::InterfacePtr<Interface> result;
|
||||
Connect(result.NewRequest(), interface_name);
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
// Connect to an interface in the directory.
|
||||
//
|
||||
// The discovery name of the interface is inferred from the C++ type of the
|
||||
// interface request. Callers can supply an interface name explicitly to
|
||||
// override the default name.
|
||||
//
|
||||
// Returns whether the request was successfully sent to the remote directory
|
||||
// backing this service bundle.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_UNAVAILABLE: The directory backing this service bundle is invalid.
|
||||
//
|
||||
// ZX_ERR_ACCESS_DENIED: This service bundle has insufficient rights to
|
||||
// connect to services.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// fuchsia::foo::ControllerPtr controller;
|
||||
// directory.Connect(controller.NewRequest());
|
||||
// ```
|
||||
template <typename Interface>
|
||||
zx_status_t Connect(
|
||||
fidl::InterfaceRequest<Interface> request,
|
||||
const std::string& interface_name = Interface::Name_) const {
|
||||
return Connect(interface_name, request.TakeChannel());
|
||||
}
|
||||
|
||||
// Connect to an interface in the directory.
|
||||
//
|
||||
// The interface name and the channel must be supplied explicitly.
|
||||
//
|
||||
// Returns whether the request was successfully sent to the remote directory
|
||||
// backing this service bundle.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_UNAVAILABLE: The directory backing this service bundle is invalid.
|
||||
//
|
||||
// ZX_ERR_ACCESS_DENIED: This service bundle has insufficient rights to
|
||||
// connect to services.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// zx::channel controller, request;
|
||||
// zx_status_t status = zx::channel::create(0, &controller, &request);
|
||||
// if (status != ZX_OK) {
|
||||
// [...]
|
||||
// }
|
||||
// directory.Connect("fuchsia.foo.Controller", std::move(request));
|
||||
// ```
|
||||
zx_status_t Connect(const std::string& interface_name,
|
||||
zx::channel request) const;
|
||||
|
||||
// Clone underlying directory channel.
|
||||
//
|
||||
// This overload for |CloneHandle| discards the status of the underlying
|
||||
// operation. Callers that wish to recieve that status should use
|
||||
// other overload that returns a |zx_status_t|.
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> CloneChannel() const;
|
||||
|
||||
// Clone underlying directory channel.
|
||||
//
|
||||
// Returns whether the request was successfully sent to the remote directory
|
||||
// backing this service bundle.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_UNAVAILABLE: The directory backing this service bundle is invalid.
|
||||
//
|
||||
// Other transport and application-level errors associated with
|
||||
// |fuchsia.io.Node/Clone|.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// fuchsia::io::DirectoryPtr dir;
|
||||
// directory.CloneHandle(dir.NewRequest());
|
||||
// ```
|
||||
zx_status_t CloneChannel(
|
||||
fidl::InterfaceRequest<fuchsia::io::Directory>) const;
|
||||
|
||||
private:
|
||||
// The directory to which connection requests are routed.
|
||||
//
|
||||
// Implements |fuchsia.io.Directory| protocol.
|
||||
zx::channel directory_;
|
||||
};
|
||||
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_SERVICE_DIRECTORY_H_
|
@ -1,59 +0,0 @@
|
||||
// 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 "lib/sys/cpp/termination_reason.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace sys {
|
||||
|
||||
std::string TerminationReasonToString(
|
||||
fuchsia::sys::TerminationReason termination_reason) {
|
||||
switch (termination_reason) {
|
||||
case fuchsia::sys::TerminationReason::UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
case fuchsia::sys::TerminationReason::EXITED:
|
||||
return "EXITED";
|
||||
case fuchsia::sys::TerminationReason::URL_INVALID:
|
||||
return "URL_INVALID";
|
||||
case fuchsia::sys::TerminationReason::PACKAGE_NOT_FOUND:
|
||||
return "PACKAGE_NOT_FOUND";
|
||||
case fuchsia::sys::TerminationReason::INTERNAL_ERROR:
|
||||
return "INTERNAL_ERROR";
|
||||
case fuchsia::sys::TerminationReason::PROCESS_CREATION_ERROR:
|
||||
return "PROCESS_CREATION_ERROR";
|
||||
case fuchsia::sys::TerminationReason::RUNNER_FAILED:
|
||||
return "RUNNER_FAILED";
|
||||
case fuchsia::sys::TerminationReason::RUNNER_TERMINATED:
|
||||
return "RUNNER_TERMINATED";
|
||||
default:
|
||||
return std::to_string(static_cast<int>(termination_reason));
|
||||
}
|
||||
}
|
||||
|
||||
std::string HumanReadableTerminationReason(
|
||||
fuchsia::sys::TerminationReason termination_reason) {
|
||||
switch (termination_reason) {
|
||||
case fuchsia::sys::TerminationReason::EXITED:
|
||||
return "exited";
|
||||
case fuchsia::sys::TerminationReason::URL_INVALID:
|
||||
return "url invalid";
|
||||
case fuchsia::sys::TerminationReason::PACKAGE_NOT_FOUND:
|
||||
return "not found";
|
||||
case fuchsia::sys::TerminationReason::PROCESS_CREATION_ERROR:
|
||||
return "failed to spawn process";
|
||||
case fuchsia::sys::TerminationReason::RUNNER_FAILED:
|
||||
return "failed to start runner for process";
|
||||
case fuchsia::sys::TerminationReason::RUNNER_TERMINATED:
|
||||
return "runner failed to execute";
|
||||
default:
|
||||
std::ostringstream out;
|
||||
out << "failed to create component ("
|
||||
<< TerminationReasonToString(termination_reason) << ")";
|
||||
return out.str();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sys
|
@ -1,23 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// TODO(FIDL-549): Delete this class.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TERMINATION_REASON_H_
|
||||
#define LIB_SYS_CPP_TERMINATION_REASON_H_
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <string>
|
||||
|
||||
namespace sys {
|
||||
|
||||
std::string TerminationReasonToString(
|
||||
fuchsia::sys::TerminationReason termination_reason);
|
||||
|
||||
std::string HumanReadableTerminationReason(
|
||||
fuchsia::sys::TerminationReason termination_reason);
|
||||
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TERMINATION_REASON_H_
|
@ -1,78 +0,0 @@
|
||||
# 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.
|
||||
|
||||
source_set("unit") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"component_context_provider.cc",
|
||||
"component_context_provider.h",
|
||||
"fake_component.cc",
|
||||
"fake_component.h",
|
||||
"fake_launcher.cc",
|
||||
"fake_launcher.h",
|
||||
"service_directory_provider.cc",
|
||||
"service_directory_provider.h",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
"//garnet/public/lib/gtest",
|
||||
"//sdk/lib/sys/cpp",
|
||||
"//sdk/lib/vfs/cpp",
|
||||
"//zircon/public/fidl/fuchsia-io",
|
||||
"//zircon/public/lib/fit",
|
||||
"//zircon/public/lib/zx",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//zircon/public/lib/fdio",
|
||||
]
|
||||
|
||||
public_configs = [ "//sdk/config" ]
|
||||
}
|
||||
|
||||
source_set("integration") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"test_with_environment.cc",
|
||||
"test_with_environment.h",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
":enclosing_environment",
|
||||
"//garnet/public/lib/gtest",
|
||||
]
|
||||
|
||||
public_configs = [ "//sdk/config" ]
|
||||
}
|
||||
|
||||
source_set("enclosing_environment") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"component_interceptor.cc",
|
||||
"component_interceptor.h",
|
||||
"enclosing_environment.cc",
|
||||
"enclosing_environment.h",
|
||||
"launcher_impl.cc",
|
||||
"launcher_impl.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//sdk/lib/sys/cpp/testing/environment_delegating_runner:bin",
|
||||
"//third_party/rapidjson",
|
||||
]
|
||||
|
||||
public_configs = [ "//sdk/config" ]
|
||||
|
||||
public_deps = [
|
||||
"//sdk/fidl/fuchsia.sys",
|
||||
"//sdk/lib/fidl/cpp",
|
||||
"//sdk/lib/sys/cpp",
|
||||
"//sdk/lib/vfs/cpp",
|
||||
"//sdk/lib/vfs/cpp",
|
||||
"//zircon/public/lib/async-default",
|
||||
]
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
// 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 <lib/sys/cpp/testing/component_context_provider.h>
|
||||
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <lib/sys/cpp/testing/service_directory_provider.h>
|
||||
#include <zircon/processargs.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
ComponentContextProvider::ComponentContextProvider(
|
||||
async_dispatcher_t* dispatcher)
|
||||
: svc_provider_(std::make_shared<ServiceDirectoryProvider>()) {
|
||||
// remove this handle from namespace so that no one is using it.
|
||||
zx_take_startup_handle(PA_DIRECTORY_REQUEST);
|
||||
|
||||
component_context_ = std::make_unique<sys::ComponentContext>(
|
||||
sys::ComponentContext::MakePrivate{}, svc_provider_->service_directory(),
|
||||
outgoing_directory_ptr_.NewRequest(dispatcher).TakeChannel(), dispatcher);
|
||||
|
||||
fdio_service_connect_at(
|
||||
outgoing_directory_ptr_.channel().get(), "public",
|
||||
public_directory_ptr_.NewRequest().TakeChannel().release());
|
||||
}
|
||||
|
||||
ComponentContextProvider::~ComponentContextProvider() = default;
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
@ -1,91 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTING_COMPONENT_CONTEXT_PROVIDER_H_
|
||||
#define LIB_SYS_CPP_TESTING_COMPONENT_CONTEXT_PROVIDER_H_
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <lib/sys/cpp/component_context.h>
|
||||
#include <lib/sys/cpp/testing/service_directory_provider.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
// Provides fake |ComponentContext| for unit testing.
|
||||
// Provides access to services that have been added to this object.
|
||||
// The object of this class should be kept alive for fake |ComponentContext| to
|
||||
// work.
|
||||
class ComponentContextProvider {
|
||||
public:
|
||||
explicit ComponentContextProvider(async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
~ComponentContextProvider();
|
||||
|
||||
// Points to outgoing root directory of outgoing directory, test can get it
|
||||
// and try to connect to internal directories/objects/files/services to test
|
||||
// code which published them.
|
||||
fuchsia::io::DirectoryPtr& outgoing_directory_ptr() {
|
||||
return outgoing_directory_ptr_;
|
||||
}
|
||||
|
||||
// Connect to public service which was published in "public" directory by
|
||||
// code under test.
|
||||
template <typename Interface>
|
||||
fidl::InterfacePtr<Interface> ConnectToPublicService(
|
||||
const std::string& name = Interface::Name_,
|
||||
async_dispatcher_t* dispatcher = nullptr) const {
|
||||
fidl::InterfacePtr<Interface> ptr;
|
||||
ConnectToPublicService(ptr.NewRequest(dispatcher), name);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Connect to public service which was published in "public" directory by
|
||||
// code under test.
|
||||
template <typename Interface>
|
||||
void ConnectToPublicService(
|
||||
fidl::InterfaceRequest<Interface> request,
|
||||
const std::string& name = Interface::Name_) const {
|
||||
fdio_service_connect_at(public_directory_ptr_.channel().get(), name.c_str(),
|
||||
request.TakeChannel().release());
|
||||
}
|
||||
|
||||
// This can be used to get fake service directory provider and inject services
|
||||
// which can be accessed by code under test.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// fidl::BindingSet<fuchsia::foo::Controller> bindings;
|
||||
// context()->service_directory_provider()->AddService(bindings.GetHandler(this));
|
||||
// auto services = context()->service_directory_provider();
|
||||
// ...
|
||||
// ...
|
||||
// ...
|
||||
// services->Connect(...);
|
||||
// ```
|
||||
const std::shared_ptr<ServiceDirectoryProvider>& service_directory_provider()
|
||||
const {
|
||||
return svc_provider_;
|
||||
};
|
||||
|
||||
// Relinquishes the ownership of fake context. This object should be alive for
|
||||
// lifetime of returned context.
|
||||
std::unique_ptr<sys::ComponentContext> TakeContext() {
|
||||
return std::move(component_context_);
|
||||
}
|
||||
|
||||
sys::ComponentContext* context() { return component_context_.get(); }
|
||||
|
||||
private:
|
||||
fuchsia::io::DirectoryPtr outgoing_directory_ptr_;
|
||||
fuchsia::io::DirectoryPtr public_directory_ptr_;
|
||||
std::shared_ptr<ServiceDirectoryProvider> svc_provider_;
|
||||
std::unique_ptr<sys::ComponentContext> component_context_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTING_COMPONENT_CONTEXT_PROVIDER_H_
|
@ -1,222 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// The intercepting mechanism works by creating an Environment containing a
|
||||
// custom |fuchsia.sys.Loader| and |fuchsia.sys.Runner|. This custom environment
|
||||
// loader, which answers to all components launches under this environment,
|
||||
// responds with an autogenerated package directory with a .cmx pointing to a
|
||||
// custom runner component. The runner component, which will also under the
|
||||
// environment, forwards its requests back up to environment's injected
|
||||
// |fuchsia.sys.Runner| implemented here.
|
||||
|
||||
#include <lib/sys/cpp/testing/component_interceptor.h>
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/interface_handle.h>
|
||||
#include <lib/vfs/cpp/pseudo_dir.h>
|
||||
#include <lib/vfs/cpp/pseudo_file.h>
|
||||
#include <lib/zx/channel.h>
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
#include <rapidjson/writer.h>
|
||||
#include <zircon/assert.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
namespace {
|
||||
// The runner we inject in autogenerated .cmx files.
|
||||
constexpr char kEnvironmentDelegatingRunner[] =
|
||||
"fuchsia-pkg://fuchsia.com/environment_delegating_runner#meta/"
|
||||
"environment_delegating_runner.cmx";
|
||||
|
||||
// Relative path within the autogenerated package directory to the manifest.
|
||||
constexpr char kAutogenPkgDirManifestPath[] = "autogenerated_manifest.cmx";
|
||||
// Path to the autogenerated cmx file of the intercepted component.
|
||||
constexpr char kAutogenCmxPath[] =
|
||||
"fuchsia-pkg://example.com/fake_pkg#autogenerated_manifest.cmx";
|
||||
} // namespace
|
||||
|
||||
ComponentInterceptor::ComponentInterceptor(
|
||||
fuchsia::sys::LoaderPtr fallback_loader, async_dispatcher_t* dispatcher)
|
||||
: fallback_loader_(std::move(fallback_loader)), dispatcher_(dispatcher) {
|
||||
loader_svc_ = std::make_shared<vfs::Service>(
|
||||
[this](zx::channel h, async_dispatcher_t* dispatcher) mutable {
|
||||
loader_bindings_.AddBinding(
|
||||
this, fidl::InterfaceRequest<fuchsia::sys::Loader>(std::move(h)),
|
||||
dispatcher_);
|
||||
});
|
||||
}
|
||||
|
||||
ComponentInterceptor::~ComponentInterceptor() = default;
|
||||
|
||||
// static
|
||||
ComponentInterceptor ComponentInterceptor::CreateWithEnvironmentLoader(
|
||||
const fuchsia::sys::EnvironmentPtr& env, async_dispatcher_t* dispatcher) {
|
||||
// The fallback loader comes from |parent_env|.
|
||||
fuchsia::sys::LoaderPtr fallback_loader;
|
||||
fuchsia::sys::ServiceProviderPtr sp;
|
||||
env->GetServices(sp.NewRequest());
|
||||
sp->ConnectToService(fuchsia::sys::Loader::Name_,
|
||||
fallback_loader.NewRequest().TakeChannel());
|
||||
|
||||
return ComponentInterceptor(std::move(fallback_loader), dispatcher);
|
||||
}
|
||||
|
||||
std::unique_ptr<EnvironmentServices>
|
||||
ComponentInterceptor::MakeEnvironmentServices(
|
||||
const fuchsia::sys::EnvironmentPtr& parent_env) {
|
||||
auto env_services = EnvironmentServices::CreateWithCustomLoader(
|
||||
parent_env, loader_svc_, dispatcher_);
|
||||
|
||||
env_services->AddService(runner_bindings_.GetHandler(this, dispatcher_));
|
||||
return env_services;
|
||||
}
|
||||
|
||||
// Modifies the supplied |cmx| such that:
|
||||
// * required fields in .cmx are set if not present:
|
||||
// - program.binary
|
||||
// * the runner is the environment delegating runner.
|
||||
void SetDefaultsForCmx(rapidjson::Document* cmx) {
|
||||
// 1. Enforce that it has delegating runner.
|
||||
cmx->RemoveMember("runner");
|
||||
cmx->AddMember("runner", kEnvironmentDelegatingRunner, cmx->GetAllocator());
|
||||
|
||||
// 2. If "program" is not set, give it a default one with an empty binary.
|
||||
if (!cmx->HasMember("program")) {
|
||||
rapidjson::Value program;
|
||||
program.SetObject();
|
||||
program.AddMember("binary", "", cmx->GetAllocator());
|
||||
cmx->AddMember("program", program, cmx->GetAllocator());
|
||||
}
|
||||
}
|
||||
|
||||
bool ComponentInterceptor::InterceptURL(std::string component_url,
|
||||
std::string extra_cmx_contents,
|
||||
ComponentLaunchHandler handler) {
|
||||
ZX_DEBUG_ASSERT_MSG(handler, "Must be a valid handler.");
|
||||
|
||||
// 1. Parse the extra_cmx_contents. Enforce that our delgating runner is
|
||||
// specified, and give it defaults for required fields.
|
||||
rapidjson::Document cmx;
|
||||
cmx.Parse(extra_cmx_contents);
|
||||
if (!cmx.IsObject() && !cmx.IsNull()) {
|
||||
return false;
|
||||
}
|
||||
if (cmx.IsNull()) {
|
||||
cmx.SetObject();
|
||||
}
|
||||
SetDefaultsForCmx(&cmx);
|
||||
|
||||
// 2. Construct a package directory and put the |cmx| manifest in it
|
||||
// for this particular component URL.
|
||||
rapidjson::StringBuffer buf;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
|
||||
cmx.Accept(writer);
|
||||
std::string cmx_str = buf.GetString();
|
||||
|
||||
ComponentLoadInfo info;
|
||||
info.pkg_dir = std::make_unique<vfs::PseudoDir>();
|
||||
info.pkg_dir->AddEntry(
|
||||
kAutogenPkgDirManifestPath,
|
||||
std::make_unique<vfs::BufferedPseudoFile>(
|
||||
[cmx_str = std::move(cmx_str)](std::vector<uint8_t>* out) {
|
||||
std::copy(cmx_str.begin(), cmx_str.end(), std::back_inserter(*out));
|
||||
return ZX_OK;
|
||||
}));
|
||||
info.handler = std::move(handler);
|
||||
|
||||
std::lock_guard<std::mutex> lock(intercept_urls_mu_);
|
||||
intercepted_component_load_info_[component_url] = std::move(info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ComponentInterceptor::LoadUrl(std::string url, LoadUrlCallback response) {
|
||||
std::lock_guard<std::mutex> lock(intercept_urls_mu_);
|
||||
|
||||
auto it = intercepted_component_load_info_.find(url);
|
||||
if (it == intercepted_component_load_info_.end()) {
|
||||
fallback_loader_->LoadUrl(url, std::move(response));
|
||||
return;
|
||||
}
|
||||
|
||||
auto pkg = std::make_unique<fuchsia::sys::Package>();
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> dir_handle;
|
||||
it->second.pkg_dir->Serve(fuchsia::io::OPEN_RIGHT_READABLE,
|
||||
dir_handle.NewRequest().TakeChannel());
|
||||
|
||||
pkg->directory = dir_handle.TakeChannel();
|
||||
pkg->resolved_url = kAutogenCmxPath;
|
||||
response(std::move(pkg));
|
||||
// After this point, the runner specified in the autogenerated manifest should
|
||||
// forward its requests back to us over our Runner fidl binding.
|
||||
}
|
||||
|
||||
void ComponentInterceptor::StartComponent(
|
||||
fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) {
|
||||
// This is a buffer to store the move-only handler while we invoke it.
|
||||
ComponentLaunchHandler handler;
|
||||
auto url = startup_info.launch_info.url;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(intercept_urls_mu_);
|
||||
auto it = intercepted_component_load_info_.find(url);
|
||||
ZX_DEBUG_ASSERT(it != intercepted_component_load_info_.end());
|
||||
|
||||
// This allows that handler to re-entrantly call InterceptURL() without
|
||||
// deadlocking this on |intercept_urls_mu_|
|
||||
handler = std::move(it->second.handler);
|
||||
}
|
||||
|
||||
handler(std::move(startup_info), std::make_unique<InterceptedComponent>(
|
||||
std::move(controller), dispatcher_));
|
||||
|
||||
// Put the |handler| back where it came from.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(intercept_urls_mu_);
|
||||
auto it = intercepted_component_load_info_.find(url);
|
||||
ZX_DEBUG_ASSERT(it != intercepted_component_load_info_.end());
|
||||
|
||||
it->second.handler = std::move(handler);
|
||||
}
|
||||
}
|
||||
|
||||
InterceptedComponent::InterceptedComponent(
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request,
|
||||
async_dispatcher_t* dispatcher)
|
||||
: binding_(this),
|
||||
termination_reason_(TerminationReason::EXITED),
|
||||
exit_code_(ZX_OK) {
|
||||
binding_.Bind(std::move(request), dispatcher);
|
||||
binding_.set_error_handler([this](zx_status_t status) {
|
||||
termination_reason_ = TerminationReason::UNKNOWN;
|
||||
Kill();
|
||||
});
|
||||
}
|
||||
|
||||
InterceptedComponent::~InterceptedComponent() {
|
||||
on_kill_ = nullptr;
|
||||
Kill();
|
||||
}
|
||||
|
||||
void InterceptedComponent::Exit(int64_t exit_code, TerminationReason reason) {
|
||||
exit_code_ = exit_code;
|
||||
termination_reason_ = reason;
|
||||
Kill();
|
||||
}
|
||||
|
||||
void InterceptedComponent::Kill() {
|
||||
if (on_kill_) {
|
||||
on_kill_();
|
||||
}
|
||||
binding_.events().OnTerminated(exit_code_, termination_reason_);
|
||||
binding_.Unbind();
|
||||
}
|
||||
|
||||
void InterceptedComponent::Detach() { binding_.set_error_handler(nullptr); }
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
@ -1,146 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTING_COMPONENT_INTERCEPTOR_H_
|
||||
#define LIB_SYS_CPP_TESTING_COMPONENT_INTERCEPTOR_H_
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
#include <lib/sys/cpp/testing/enclosing_environment.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
using fuchsia::sys::TerminationReason;
|
||||
|
||||
// A Wrapper class which implements a basic version of
|
||||
// |fuchsia::sys::ComponentController| and gives owner control over lifetime of
|
||||
// this component.
|
||||
class InterceptedComponent : public fuchsia::sys::ComponentController {
|
||||
public:
|
||||
// Called when this component is killed.
|
||||
using OnKill = fit::function<void()>;
|
||||
|
||||
InterceptedComponent(
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// resets |on_kill_| to nullptr and calls |Kill()|.
|
||||
~InterceptedComponent() override;
|
||||
|
||||
void Exit(int64_t exit_code,
|
||||
TerminationReason reason = TerminationReason::EXITED);
|
||||
|
||||
void set_on_kill(OnKill on_kill) { on_kill_ = std::move(on_kill); }
|
||||
|
||||
private:
|
||||
// |ComponentController|.
|
||||
void Detach() override;
|
||||
|
||||
// |ComponentController|.
|
||||
//
|
||||
// Calls |on_kill_| and call Terminated event on component before clearing the
|
||||
// bindings
|
||||
void Kill() override;
|
||||
|
||||
fidl::Binding<fuchsia::sys::ComponentController> binding_;
|
||||
TerminationReason termination_reason_;
|
||||
int64_t exit_code_;
|
||||
OnKill on_kill_;
|
||||
};
|
||||
|
||||
// ComponentInterceptor is a utility that helps users construct an
|
||||
// EnvironmentService (to be used alongside EnclosingEnvironment) that is able
|
||||
// to intercept and mock components launched under the EnclosingEnvironment.
|
||||
//
|
||||
// This class is thread-safe. Underlying FIDL communication is processed on the
|
||||
// async dispatcher supplied to this class.
|
||||
class ComponentInterceptor : fuchsia::sys::Loader, fuchsia::sys::Runner {
|
||||
public:
|
||||
using ComponentLaunchHandler = fit::function<void(
|
||||
fuchsia::sys::StartupInfo, std::unique_ptr<InterceptedComponent>)>;
|
||||
|
||||
ComponentInterceptor(fuchsia::sys::LoaderPtr fallback_loader,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
virtual ~ComponentInterceptor() override;
|
||||
|
||||
// Constructs a fallback loader from the given |env|.
|
||||
static ComponentInterceptor CreateWithEnvironmentLoader(
|
||||
const fuchsia::sys::EnvironmentPtr& env,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// Creates an |EnvironmentServices| which contains custom Loader and
|
||||
// Runner services which intercept component launch URLs configured using
|
||||
// |InterceptURL|. Calls to |InterceptURL| are effective regardless of if
|
||||
// they're called before or after calls to this method.
|
||||
//
|
||||
// Restrictions:
|
||||
// * Users must not override the fuchsia::sys::Loader and
|
||||
// fuchsia::sys::Runner services.
|
||||
// * An instance of |ComponentInterceptor| must outlive instances of
|
||||
// vended |EnvironmentServices|
|
||||
std::unique_ptr<EnvironmentServices> MakeEnvironmentServices(
|
||||
const fuchsia::sys::EnvironmentPtr& env);
|
||||
|
||||
// Intercepts |component_url| from being launched under this environment, and
|
||||
// calls the supplied |handler| to handle the runtime of this component.
|
||||
//
|
||||
// |extra_cmx_contents| contains additional component manifest contents
|
||||
// supplied for this component.
|
||||
// * If |extra_cmx_contents| is empty a default one is used:
|
||||
// * {"program": {"binary": ""}}
|
||||
// * The "runner" is always overwritten.
|
||||
//
|
||||
// Returns |false| if |extra_cmx_contents| contains invalid JSON.
|
||||
[[nodiscard]] bool InterceptURL(std::string component_url,
|
||||
std::string extra_cmx_contents,
|
||||
ComponentLaunchHandler handler);
|
||||
|
||||
private:
|
||||
// Returns a faked fuchsia.sys.Package with a custom runner which forwards
|
||||
// the StartComponent request to environment's fuchsia::sys::Runner
|
||||
// service hosted by this object instance.
|
||||
//
|
||||
// |fuchsia::sys::Loader|
|
||||
void LoadUrl(std::string url, LoadUrlCallback response) override;
|
||||
|
||||
// We arrive here if our fuchsia::sys::Loader sends a component launch to
|
||||
// the test harness runner component, which forwards it to here.
|
||||
//
|
||||
// |fuchsia::sys::Runner|
|
||||
void StartComponent(fuchsia::sys::Package package,
|
||||
fuchsia::sys::StartupInfo startup_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController>
|
||||
controller) override;
|
||||
|
||||
// Ensures that calls to intercepting URLs remains thread-safe.
|
||||
std::mutex intercept_urls_mu_;
|
||||
|
||||
struct ComponentLoadInfo {
|
||||
ComponentLaunchHandler handler;
|
||||
// Fake component package directory where we host our fake manifest.
|
||||
std::unique_ptr<vfs::PseudoDir> pkg_dir;
|
||||
};
|
||||
std::map<std::string, ComponentLoadInfo> intercepted_component_load_info_
|
||||
__TA_GUARDED(intercept_urls_mu_);
|
||||
|
||||
fuchsia::sys::LoaderPtr fallback_loader_;
|
||||
|
||||
std::shared_ptr<vfs::Service> loader_svc_;
|
||||
fidl::BindingSet<fuchsia::sys::Loader> loader_bindings_;
|
||||
fidl::BindingSet<fuchsia::sys::Runner> runner_bindings_;
|
||||
|
||||
async_dispatcher_t* dispatcher_;
|
||||
|
||||
std::unique_ptr<EnclosingEnvironment> env_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTING_COMPONENT_INTERCEPTOR_H_
|
@ -1,238 +0,0 @@
|
||||
// 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/sys/cpp/testing/enclosing_environment.h>
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/clone.h>
|
||||
#include <lib/fidl/cpp/interface_handle.h>
|
||||
#include <lib/fidl/cpp/interface_request.h>
|
||||
#include <lib/fit/function.h>
|
||||
#include <lib/sys/cpp/service_directory.h>
|
||||
#include <lib/vfs/cpp/pseudo_dir.h>
|
||||
#include <zircon/assert.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
EnvironmentServices::EnvironmentServices(
|
||||
const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
const std::shared_ptr<vfs::Service>& loader_service,
|
||||
async_dispatcher_t* dispatcher)
|
||||
: dispatcher_(dispatcher) {
|
||||
zx::channel request;
|
||||
parent_svc_ = sys::ServiceDirectory::CreateWithRequest(&request);
|
||||
parent_env->GetDirectory(std::move(request));
|
||||
if (loader_service) {
|
||||
AddSharedService(loader_service, fuchsia::sys::Loader::Name_);
|
||||
} else {
|
||||
AllowParentService(fuchsia::sys::Loader::Name_);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<EnvironmentServices> EnvironmentServices::Create(
|
||||
const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
return std::unique_ptr<EnvironmentServices>(
|
||||
new EnvironmentServices(parent_env, nullptr, dispatcher));
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<EnvironmentServices>
|
||||
EnvironmentServices::CreateWithCustomLoader(
|
||||
const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
const std::shared_ptr<vfs::Service>& loader_service,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
return std::unique_ptr<EnvironmentServices>(
|
||||
new EnvironmentServices(parent_env, loader_service, dispatcher));
|
||||
}
|
||||
|
||||
zx_status_t EnvironmentServices::AddSharedService(
|
||||
const std::shared_ptr<vfs::Service>& service,
|
||||
const std::string& service_name) {
|
||||
svc_names_.push_back(service_name);
|
||||
return svc_.AddSharedEntry(service_name, service);
|
||||
}
|
||||
|
||||
zx_status_t EnvironmentServices::AddService(
|
||||
std::unique_ptr<vfs::Service> service, const std::string& service_name) {
|
||||
svc_names_.push_back(service_name);
|
||||
return svc_.AddEntry(service_name, std::move(service));
|
||||
}
|
||||
|
||||
zx_status_t EnvironmentServices::AddServiceWithLaunchInfo(
|
||||
fuchsia::sys::LaunchInfo launch_info, const std::string& service_name) {
|
||||
return AddServiceWithLaunchInfo(
|
||||
launch_info.url,
|
||||
[launch_info = std::move(launch_info)]() {
|
||||
// clone only URL and Arguments
|
||||
fuchsia::sys::LaunchInfo dup_launch_info;
|
||||
fidl::Clone(launch_info.url, &dup_launch_info.url);
|
||||
fidl::Clone(launch_info.arguments, &dup_launch_info.arguments);
|
||||
return dup_launch_info;
|
||||
},
|
||||
service_name);
|
||||
}
|
||||
|
||||
zx_status_t EnvironmentServices::AddServiceWithLaunchInfo(
|
||||
std::string singleton_id, fit::function<fuchsia::sys::LaunchInfo()> handler,
|
||||
const std::string& service_name) {
|
||||
auto child = std::make_unique<vfs::Service>(
|
||||
[this, service_name, handler = std::move(handler),
|
||||
singleton_id = std::move(singleton_id),
|
||||
controller = fuchsia::sys::ComponentControllerPtr()](
|
||||
zx::channel client_handle, async_dispatcher_t* dispatcher) mutable {
|
||||
auto it = singleton_services_.find(singleton_id);
|
||||
if (it == singleton_services_.end()) {
|
||||
fuchsia::sys::LaunchInfo launch_info = handler();
|
||||
auto services = sys::ServiceDirectory::CreateWithRequest(
|
||||
&launch_info.directory_request);
|
||||
|
||||
enclosing_env_->CreateComponent(std::move(launch_info),
|
||||
controller.NewRequest());
|
||||
controller.set_error_handler(
|
||||
[this, singleton_id, &controller](zx_status_t status) {
|
||||
// TODO: show error? where on stderr?
|
||||
controller.Unbind(); // kills the singleton application
|
||||
singleton_services_.erase(singleton_id);
|
||||
});
|
||||
|
||||
std::tie(it, std::ignore) =
|
||||
singleton_services_.emplace(singleton_id, std::move(services));
|
||||
}
|
||||
|
||||
it->second->Connect(service_name, std::move(client_handle));
|
||||
});
|
||||
svc_names_.push_back(service_name);
|
||||
return svc_.AddEntry(service_name, std::move(child));
|
||||
}
|
||||
|
||||
zx_status_t EnvironmentServices::AllowParentService(
|
||||
const std::string& service_name) {
|
||||
svc_names_.push_back(service_name);
|
||||
return svc_.AddEntry(
|
||||
service_name.c_str(),
|
||||
std::make_unique<vfs::Service>(
|
||||
[this, service_name](zx::channel channel,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
parent_svc_->Connect(service_name, std::move(channel));
|
||||
}));
|
||||
}
|
||||
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory>
|
||||
EnvironmentServices::ServeServiceDir(uint32_t flags) {
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> dir;
|
||||
ZX_ASSERT(ServeServiceDir(dir.NewRequest(), flags) == ZX_OK);
|
||||
return dir;
|
||||
}
|
||||
|
||||
zx_status_t EnvironmentServices::ServeServiceDir(
|
||||
fidl::InterfaceRequest<fuchsia::io::Directory> request, uint32_t flags) {
|
||||
return ServeServiceDir(request.TakeChannel(), flags);
|
||||
}
|
||||
|
||||
zx_status_t EnvironmentServices::ServeServiceDir(zx::channel request,
|
||||
uint32_t flags) {
|
||||
return svc_.Serve(flags, std::move(request), dispatcher_);
|
||||
}
|
||||
|
||||
EnclosingEnvironment::EnclosingEnvironment(
|
||||
const std::string& label, const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
std::unique_ptr<EnvironmentServices> services,
|
||||
const fuchsia::sys::EnvironmentOptions& options)
|
||||
: label_(label), services_(std::move(services)) {
|
||||
services_->set_enclosing_env(this);
|
||||
|
||||
// Start environment with services.
|
||||
fuchsia::sys::ServiceListPtr service_list(new fuchsia::sys::ServiceList);
|
||||
service_list->names = std::move(services_->svc_names_);
|
||||
service_list->host_directory = services_->ServeServiceDir().TakeChannel();
|
||||
fuchsia::sys::EnvironmentPtr env;
|
||||
|
||||
parent_env->CreateNestedEnvironment(env.NewRequest(),
|
||||
env_controller_.NewRequest(), label_,
|
||||
std::move(service_list), options);
|
||||
env_controller_.set_error_handler(
|
||||
[this](zx_status_t status) { SetRunning(false); });
|
||||
// Connect to launcher
|
||||
env->GetLauncher(launcher_.NewRequest());
|
||||
|
||||
zx::channel request;
|
||||
service_provider_ = sys::ServiceDirectory::CreateWithRequest(&request);
|
||||
// Connect to service
|
||||
env->GetDirectory(std::move(request));
|
||||
|
||||
env_controller_.events().OnCreated = [this]() { SetRunning(true); };
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<EnclosingEnvironment> EnclosingEnvironment::Create(
|
||||
const std::string& label, const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
std::unique_ptr<EnvironmentServices> services,
|
||||
const fuchsia::sys::EnvironmentOptions& options) {
|
||||
auto* env =
|
||||
new EnclosingEnvironment(label, parent_env, std::move(services), options);
|
||||
return std::unique_ptr<EnclosingEnvironment>(env);
|
||||
}
|
||||
|
||||
EnclosingEnvironment::~EnclosingEnvironment() {
|
||||
auto channel = env_controller_.Unbind();
|
||||
if (channel) {
|
||||
fuchsia::sys::EnvironmentControllerSyncPtr controller;
|
||||
controller.Bind(std::move(channel));
|
||||
controller->Kill();
|
||||
}
|
||||
}
|
||||
|
||||
void EnclosingEnvironment::Kill(fit::function<void()> callback) {
|
||||
env_controller_->Kill([this, callback = std::move(callback)]() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<EnclosingEnvironment>
|
||||
EnclosingEnvironment::CreateNestedEnclosingEnvironment(
|
||||
const std::string& label) {
|
||||
fuchsia::sys::EnvironmentPtr env;
|
||||
service_provider_->Connect(env.NewRequest());
|
||||
return Create(label, env, EnvironmentServices::Create(env));
|
||||
}
|
||||
|
||||
void EnclosingEnvironment::CreateComponent(
|
||||
fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request) {
|
||||
launcher_.CreateComponent(std::move(launch_info), std::move(request));
|
||||
}
|
||||
|
||||
fuchsia::sys::ComponentControllerPtr EnclosingEnvironment::CreateComponent(
|
||||
fuchsia::sys::LaunchInfo launch_info) {
|
||||
fuchsia::sys::ComponentControllerPtr controller;
|
||||
CreateComponent(std::move(launch_info), controller.NewRequest());
|
||||
return controller;
|
||||
}
|
||||
|
||||
fuchsia::sys::ComponentControllerPtr
|
||||
EnclosingEnvironment::CreateComponentFromUrl(std::string component_url) {
|
||||
fuchsia::sys::LaunchInfo launch_info;
|
||||
launch_info.url = component_url;
|
||||
|
||||
return CreateComponent(std::move(launch_info));
|
||||
}
|
||||
|
||||
void EnclosingEnvironment::SetRunning(bool running) {
|
||||
if (running_ != running) {
|
||||
running_ = running;
|
||||
if (running_changed_callback_) {
|
||||
running_changed_callback_(running_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
@ -1,266 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTING_ENCLOSING_ENVIRONMENT_H_
|
||||
#define LIB_SYS_CPP_TESTING_ENCLOSING_ENVIRONMENT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/async/default.h>
|
||||
#include <lib/fidl/cpp/interface_handle.h>
|
||||
#include <lib/fidl/cpp/interface_request.h>
|
||||
#include <lib/fit/function.h>
|
||||
#include <lib/sys/cpp/service_directory.h>
|
||||
#include <lib/sys/cpp/testing/launcher_impl.h>
|
||||
#include <lib/vfs/cpp/pseudo_dir.h>
|
||||
#include <lib/vfs/cpp/service.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
class EnclosingEnvironment;
|
||||
|
||||
// EnvironmentServices acts as a container of services to EnclosingEnvironment.
|
||||
//
|
||||
// By default, EnvironmentServices supplies only the parent environment's loader
|
||||
// service. Additional services can be provided through |AddService| and
|
||||
// friends. Typically, this is used to inject fake services for tests, or to
|
||||
// pass through services from the parent environment.
|
||||
//
|
||||
// Every EnclosingEnvironment takes EnvironmentServices as an argument to
|
||||
// instantiation. Services should not be added after the EnclosingEnvironment is
|
||||
// created.
|
||||
class EnvironmentServices {
|
||||
public:
|
||||
EnvironmentServices(const EnvironmentServices&) = delete;
|
||||
EnvironmentServices& operator=(const EnvironmentServices&) = delete;
|
||||
EnvironmentServices(EnvironmentServices&&) = delete;
|
||||
|
||||
// Creates services with parent's loader service.
|
||||
static std::unique_ptr<EnvironmentServices> Create(
|
||||
const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
// Creates services with custom loader service.
|
||||
static std::unique_ptr<EnvironmentServices> CreateWithCustomLoader(
|
||||
const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
const std::shared_ptr<vfs::Service>& loader_service,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// Adds the specified interface to the set of services.
|
||||
//
|
||||
// Adds a supported service with the given |service_name|, using the given
|
||||
// |interface_request_handler|, which should remain valid for the lifetime
|
||||
// of this object.
|
||||
//
|
||||
// A typical usage may be:
|
||||
//
|
||||
// AddService(foobar_bindings_.GetHandler(this));
|
||||
//
|
||||
template <typename Interface>
|
||||
zx_status_t AddService(fidl::InterfaceRequestHandler<Interface> handler,
|
||||
const std::string& service_name = Interface::Name_) {
|
||||
svc_names_.push_back(service_name);
|
||||
return svc_.AddEntry(
|
||||
service_name,
|
||||
std::make_unique<vfs::Service>(
|
||||
[handler = std::move(handler)](zx::channel channel,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
handler(fidl::InterfaceRequest<Interface>(std::move(channel)));
|
||||
}));
|
||||
}
|
||||
|
||||
// Adds the specified service to the set of services.
|
||||
zx_status_t AddSharedService(const std::shared_ptr<vfs::Service>& service,
|
||||
const std::string& service_name);
|
||||
|
||||
// Adds the specified service to the set of services.
|
||||
zx_status_t AddService(std::unique_ptr<vfs::Service> service,
|
||||
const std::string& service_name);
|
||||
|
||||
// Adds the specified service to the set of services.
|
||||
//
|
||||
// Adds a supported service with the given |service_name|, using the given
|
||||
// |launch_info|, it only starts the component when the service is
|
||||
// requested.
|
||||
// Note: Only url and arguments fields of provided launch_info are used, if
|
||||
// you need to use other fields, use the Handler signature.
|
||||
zx_status_t AddServiceWithLaunchInfo(fuchsia::sys::LaunchInfo launch_info,
|
||||
const std::string& service_name);
|
||||
|
||||
// Adds the specified service to the set of services.
|
||||
//
|
||||
// Adds a supported service with the given |service_name|, using the given
|
||||
// handler to generate launch info, it only starts the component when the
|
||||
// service is requested.
|
||||
// The provided singleton_id argument is used to keep track of singleton
|
||||
// instances, generally you want to use the URL that'll be used for launch
|
||||
// info.
|
||||
zx_status_t AddServiceWithLaunchInfo(
|
||||
std::string singleton_id,
|
||||
fit::function<fuchsia::sys::LaunchInfo()> handler,
|
||||
const std::string& service_name);
|
||||
|
||||
// Allows child components to access parent service with name
|
||||
// |service_name|.
|
||||
//
|
||||
// This will only work if parent environment actually provides said service
|
||||
// and the service is in the test component's service whitelist.
|
||||
zx_status_t AllowParentService(const std::string& service_name);
|
||||
|
||||
// Serve service directory using |flags| and returns a new |InterfaceHandle|;
|
||||
// Will cause exception if serving fails.
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> ServeServiceDir(
|
||||
uint32_t flags = fuchsia::io::OPEN_RIGHT_READABLE);
|
||||
|
||||
// Serves service directory using passed |request| and returns status.
|
||||
zx_status_t ServeServiceDir(
|
||||
fidl::InterfaceRequest<fuchsia::io::Directory> request,
|
||||
uint32_t flags = fuchsia::io::OPEN_RIGHT_READABLE);
|
||||
|
||||
// Serves service directory using passed |request| and returns status.
|
||||
zx_status_t ServeServiceDir(
|
||||
zx::channel request, uint32_t flags = fuchsia::io::OPEN_RIGHT_READABLE);
|
||||
|
||||
private:
|
||||
friend class EnclosingEnvironment;
|
||||
EnvironmentServices(const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
const std::shared_ptr<vfs::Service>& loader_service,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
void set_enclosing_env(EnclosingEnvironment* e) { enclosing_env_ = e; }
|
||||
|
||||
vfs::PseudoDir svc_;
|
||||
fidl::VectorPtr<std::string> svc_names_;
|
||||
std::shared_ptr<sys::ServiceDirectory> parent_svc_;
|
||||
// Pointer to containing environment. Not owned.
|
||||
EnclosingEnvironment* enclosing_env_ = nullptr;
|
||||
async_dispatcher_t* dispatcher_;
|
||||
|
||||
// Keep track of all singleton services, indexed by url.
|
||||
std::unordered_map<std::string, std::shared_ptr<sys::ServiceDirectory>>
|
||||
singleton_services_;
|
||||
};
|
||||
|
||||
// EnclosingEnvironment wraps a new isolated environment for test |parent_env|
|
||||
// and provides a way to use that environment for integration testing.
|
||||
//
|
||||
// It provides a way to add custom fake services using handlers and singleton
|
||||
// components. By default components under this environment have no access to
|
||||
// any of system services. You need to add your own services by using
|
||||
// |AddService| or |AddServiceWithLaunchInfo| methods.
|
||||
//
|
||||
// It also provides a way to access parent services if needed.
|
||||
class EnclosingEnvironment {
|
||||
public:
|
||||
// Creates environment with the given services.
|
||||
//
|
||||
// |label| is human readable environment name, it can be seen in /hub, for eg
|
||||
// /hub/r/sys/<koid>/r/<label>/<koid>
|
||||
//
|
||||
// |services| are the services the environment will provide. See
|
||||
// |EnvironmentServices| for details.
|
||||
static std::unique_ptr<EnclosingEnvironment> Create(
|
||||
const std::string& label, const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
std::unique_ptr<EnvironmentServices> services,
|
||||
const fuchsia::sys::EnvironmentOptions& options = {});
|
||||
|
||||
~EnclosingEnvironment();
|
||||
|
||||
fuchsia::sys::LauncherPtr launcher_ptr() {
|
||||
fuchsia::sys::LauncherPtr launcher;
|
||||
launcher_.AddBinding(launcher.NewRequest());
|
||||
return launcher;
|
||||
}
|
||||
|
||||
// Returns true if underlying environment is running.
|
||||
bool is_running() const { return running_; }
|
||||
|
||||
// Kills the underlying environment.
|
||||
void Kill(fit::function<void()> callback = nullptr);
|
||||
|
||||
// Creates a real component from |launch_info| in underlying environment.
|
||||
//
|
||||
// That component will only have access to the services added and
|
||||
// any allowed parent service.
|
||||
void CreateComponent(
|
||||
fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request);
|
||||
|
||||
// Creates a real component from |launch_info| in underlying environment and
|
||||
// returns controller ptr.
|
||||
//
|
||||
// That component will only have access to the services added and
|
||||
// any allowed parent service.
|
||||
fuchsia::sys::ComponentControllerPtr CreateComponent(
|
||||
fuchsia::sys::LaunchInfo launch_info);
|
||||
|
||||
// Creates a real component in underlying environment for a url and returns
|
||||
// controller ptr.
|
||||
//
|
||||
// That component will only have access to the services added and
|
||||
// any allowed parent service.
|
||||
fuchsia::sys::ComponentControllerPtr CreateComponentFromUrl(
|
||||
std::string component_url);
|
||||
|
||||
// Creates a nested enclosing environment on top of underlying environment.
|
||||
std::unique_ptr<EnclosingEnvironment> CreateNestedEnclosingEnvironment(
|
||||
const std::string& label);
|
||||
|
||||
// Creates a nested enclosing environment on top of underlying environment
|
||||
// with custom loader service.
|
||||
std::unique_ptr<EnclosingEnvironment>
|
||||
CreateNestedEnclosingEnvironmentWithLoader(
|
||||
const std::string& label, std::shared_ptr<vfs::Service> loader_service);
|
||||
|
||||
// Connects to service provided by this environment.
|
||||
void ConnectToService(fidl::StringPtr service_name, zx::channel channel) {
|
||||
service_provider_->Connect(service_name, std::move(channel));
|
||||
}
|
||||
|
||||
// Connects to service provided by this environment.
|
||||
template <typename Interface>
|
||||
void ConnectToService(fidl::InterfaceRequest<Interface> request,
|
||||
const std::string& service_name = Interface::Name_) {
|
||||
ConnectToService(service_name, request.TakeChannel());
|
||||
}
|
||||
|
||||
// Connects to service provided by this environment.
|
||||
template <typename Interface>
|
||||
fidl::InterfacePtr<Interface> ConnectToService(
|
||||
const std::string& service_name = Interface::Name_) {
|
||||
fidl::InterfacePtr<Interface> ptr;
|
||||
ConnectToService(service_name, ptr.NewRequest().TakeChannel());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Sets a listener for changes in the running status
|
||||
void SetRunningChangedCallback(fit::function<void(bool)> cb) {
|
||||
running_changed_callback_ = std::move(cb);
|
||||
}
|
||||
|
||||
private:
|
||||
EnclosingEnvironment(const std::string& label,
|
||||
const fuchsia::sys::EnvironmentPtr& parent_env,
|
||||
std::unique_ptr<EnvironmentServices> services,
|
||||
const fuchsia::sys::EnvironmentOptions& options);
|
||||
|
||||
void SetRunning(bool running);
|
||||
|
||||
bool running_ = false;
|
||||
const std::string label_;
|
||||
fuchsia::sys::EnvironmentControllerPtr env_controller_;
|
||||
std::shared_ptr<sys::ServiceDirectory> service_provider_;
|
||||
LauncherImpl launcher_;
|
||||
std::unique_ptr<EnvironmentServices> services_;
|
||||
fit::function<void(bool)> running_changed_callback_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTING_ENCLOSING_ENVIRONMENT_H_
|
@ -1,41 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import("//build/package.gni")
|
||||
|
||||
package("environment_delegating_runner") {
|
||||
testonly = true
|
||||
|
||||
binaries = [
|
||||
{
|
||||
name = "environment_delegating_runner"
|
||||
},
|
||||
]
|
||||
meta = [
|
||||
{
|
||||
path = rebase_path("meta/environment_delegating_runner.cmx")
|
||||
dest = "environment_delegating_runner.cmx"
|
||||
},
|
||||
]
|
||||
|
||||
deps = [
|
||||
":bin",
|
||||
]
|
||||
}
|
||||
|
||||
executable("bin") {
|
||||
output_name = "environment_delegating_runner"
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"environment_delegating_runner.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//sdk/fidl/fuchsia.sys",
|
||||
"//sdk/lib/fidl/cpp",
|
||||
"//sdk/lib/sys/cpp",
|
||||
"//zircon/public/lib/async-loop-cpp",
|
||||
]
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// 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 <lib/async-loop/cpp/loop.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
#include <lib/sys/cpp/component_context.h>
|
||||
|
||||
int main() {
|
||||
async::Loop loop(&kAsyncLoopConfigAttachToThread);
|
||||
auto startup_ctx = sys::ComponentContext::Create();
|
||||
auto env_runner = startup_ctx->svc()->Connect<fuchsia::sys::Runner>();
|
||||
env_runner.set_error_handler([](zx_status_t) {
|
||||
// This program dies here to prevent proxying any further calls from our
|
||||
// own environment runner implementation.
|
||||
fprintf(stderr, "Lost connection to the environment's fuchsia.sys.Runner");
|
||||
exit(1);
|
||||
});
|
||||
|
||||
fidl::BindingSet<fuchsia::sys::Runner> runner_bindings;
|
||||
startup_ctx->outgoing()->AddPublicService(
|
||||
runner_bindings.GetHandler(env_runner.get()));
|
||||
|
||||
loop.Run();
|
||||
return 0;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"program": {
|
||||
"binary": "bin/environment_delegating_runner"
|
||||
},
|
||||
"sandbox": {
|
||||
"services": [
|
||||
"fuchsia.sys.Runner"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
// 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 <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/sys/cpp/testing/fake_component.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
FakeComponent::FakeComponent() {}
|
||||
|
||||
FakeComponent::~FakeComponent() = default;
|
||||
|
||||
void FakeComponent::Register(std::string url, FakeLauncher& fake_launcher,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
fake_launcher.RegisterComponent(
|
||||
url, [this, dispatcher](
|
||||
fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> ctrl) {
|
||||
ctrls_.push_back(std::move(ctrl));
|
||||
zx_status_t status = directory_.Serve(
|
||||
fuchsia::io::OPEN_RIGHT_READABLE,
|
||||
std::move(launch_info.directory_request), dispatcher);
|
||||
ZX_ASSERT(status == ZX_OK);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
@ -1,55 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTING_FAKE_COMPONENT_H_
|
||||
#define LIB_SYS_CPP_TESTING_FAKE_COMPONENT_H_
|
||||
|
||||
#include <lib/async/dispatcher.h>
|
||||
#include <lib/sys/cpp/testing/fake_launcher.h>
|
||||
#include <lib/vfs/cpp/pseudo_dir.h>
|
||||
#include <lib/vfs/cpp/service.h>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
// A fake component which can be used to intercept component launch using
|
||||
// |FakeLauncher| and publish fake services for unit testing.
|
||||
class FakeComponent {
|
||||
public:
|
||||
FakeComponent();
|
||||
~FakeComponent();
|
||||
|
||||
// Adds specified interface to the set of public interfaces.
|
||||
//
|
||||
// Adds a supported service with the given |service_name|, using the given
|
||||
// |interface_request_handler|, which should remain valid for the lifetime of
|
||||
// this object.
|
||||
//
|
||||
// A typical usage may be:
|
||||
//
|
||||
// AddPublicService(foobar_bindings_.GetHandler(this));
|
||||
template <typename Interface>
|
||||
zx_status_t AddPublicService(
|
||||
fidl::InterfaceRequestHandler<Interface> handler,
|
||||
const std::string& service_name = Interface::Name_) {
|
||||
return directory_.AddEntry(
|
||||
service_name.c_str(),
|
||||
std::make_unique<vfs::Service>(std::move(handler)));
|
||||
}
|
||||
|
||||
// Registers this component with a FakeLauncher.
|
||||
void Register(std::string url, FakeLauncher& fake_launcher,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
private:
|
||||
vfs::PseudoDir directory_;
|
||||
std::vector<fidl::InterfaceRequest<fuchsia::sys::ComponentController>> ctrls_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTING_FAKE_COMPONENT_H_
|
@ -1,38 +0,0 @@
|
||||
// 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/sys/cpp/testing/fake_launcher.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
using fuchsia::sys::Launcher;
|
||||
|
||||
FakeLauncher::FakeLauncher() {}
|
||||
|
||||
FakeLauncher::~FakeLauncher() = default;
|
||||
|
||||
void FakeLauncher::CreateComponent(
|
||||
fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) {
|
||||
auto it = connectors_.find(launch_info.url);
|
||||
if (it != connectors_.end()) {
|
||||
it->second(std::move(launch_info), std::move(controller));
|
||||
}
|
||||
}
|
||||
|
||||
void FakeLauncher::RegisterComponent(std::string url,
|
||||
ComponentConnector connector) {
|
||||
connectors_[url] = std::move(connector);
|
||||
}
|
||||
|
||||
fidl::InterfaceRequestHandler<Launcher> FakeLauncher::GetHandler(
|
||||
async_dispatcher_t* dispatcher) {
|
||||
return [this, dispatcher](fidl::InterfaceRequest<Launcher> request) {
|
||||
binding_set_.AddBinding(this, std::move(request), dispatcher);
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
@ -1,57 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTING_FAKE_LAUNCHER_H_
|
||||
#define LIB_SYS_CPP_TESTING_FAKE_LAUNCHER_H_
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/async/dispatcher.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
#include <lib/fidl/cpp/interface_request.h>
|
||||
#include <lib/fit/function.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
// A fake |Launcher| for testing.
|
||||
// Used to intercept component component launch from code under test.
|
||||
class FakeLauncher : public fuchsia::sys::Launcher {
|
||||
public:
|
||||
FakeLauncher();
|
||||
~FakeLauncher() override;
|
||||
|
||||
FakeLauncher(const FakeLauncher&) = delete;
|
||||
FakeLauncher& operator=(const FakeLauncher&) = delete;
|
||||
|
||||
using ComponentConnector = fit::function<void(
|
||||
fuchsia::sys::LaunchInfo,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController>)>;
|
||||
|
||||
// Registers a component located at "url" with a connector. When someone
|
||||
// tries to CreateComponent() with this |url|, the supplied |connector| is
|
||||
// called with the the LaunchInfo and associated ComponentController request.
|
||||
// The connector may implement the |LaunchInfo.services| and
|
||||
// |ComponentController| interfaces to communicate with its connector and
|
||||
// listen for component signals.
|
||||
void RegisterComponent(std::string url, ComponentConnector connector);
|
||||
|
||||
// Forwards this |CreateComponent| request to a registered connector, if an
|
||||
// associated one exists. If one is not registered for |launch_info.url|, then
|
||||
// this call is dropped.
|
||||
void CreateComponent(fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController>
|
||||
controller) override;
|
||||
|
||||
fidl::InterfaceRequestHandler<fuchsia::sys::Launcher> GetHandler(
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
private:
|
||||
std::map<std::string, ComponentConnector> connectors_;
|
||||
fidl::BindingSet<Launcher> binding_set_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTING_FAKE_LAUNCHER_H_
|
@ -1,26 +0,0 @@
|
||||
// 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/sys/cpp/testing/launcher_impl.h>
|
||||
|
||||
#include <lib/sys/cpp/file_descriptor.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
void LauncherImpl::CreateComponent(
|
||||
fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request) {
|
||||
if (!launch_info.out) {
|
||||
launch_info.out = sys::CloneFileDescriptor(STDOUT_FILENO);
|
||||
}
|
||||
if (!launch_info.err) {
|
||||
launch_info.err = sys::CloneFileDescriptor(STDERR_FILENO);
|
||||
}
|
||||
launcher_->CreateComponent(std::move(launch_info), std::move(request));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
@ -1,39 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTING_LAUNCHER_IMPL_H_
|
||||
#define LIB_SYS_CPP_TESTING_LAUNCHER_IMPL_H_
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
// Launcher impl to wrap and override CreateComponent of real launcher service.
|
||||
class LauncherImpl : public fuchsia::sys::Launcher {
|
||||
public:
|
||||
void AddBinding(fidl::InterfaceRequest<fuchsia::sys::Launcher> launcher) {
|
||||
bindings_.AddBinding(this, std::move(launcher));
|
||||
}
|
||||
|
||||
::fidl::InterfaceRequest<fuchsia::sys::Launcher> NewRequest() {
|
||||
return launcher_.NewRequest();
|
||||
}
|
||||
|
||||
// Overrides stdout and stderr to current stdout and stderr if not passed in
|
||||
// |launch_info| and creates a component.
|
||||
void CreateComponent(fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController>
|
||||
request) override;
|
||||
|
||||
private:
|
||||
fuchsia::sys::LauncherPtr launcher_;
|
||||
fidl::BindingSet<fuchsia::sys::Launcher> bindings_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTING_LAUNCHER_IMPL_H_
|
@ -1,28 +0,0 @@
|
||||
// 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 <lib/sys/cpp/testing/service_directory_provider.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
ServiceDirectoryProvider::ServiceDirectoryProvider(
|
||||
async_dispatcher_t* dispatcher)
|
||||
: svc_dir_(std::make_unique<vfs::PseudoDir>()) {
|
||||
fidl::InterfaceHandle<fuchsia::io::Directory> directory_ptr;
|
||||
svc_dir_->Serve(fuchsia::io::OPEN_RIGHT_READABLE,
|
||||
directory_ptr.NewRequest().TakeChannel(), dispatcher);
|
||||
service_directory_ =
|
||||
std::make_shared<sys::ServiceDirectory>(directory_ptr.TakeChannel());
|
||||
}
|
||||
|
||||
ServiceDirectoryProvider::~ServiceDirectoryProvider() = default;
|
||||
|
||||
zx_status_t ServiceDirectoryProvider::AddService(
|
||||
std::unique_ptr<vfs::Service> service, const std::string& name) const {
|
||||
return svc_dir_->AddEntry(name, std::move(service));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
@ -1,77 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTING_SERVICE_DIRECTORY_PROVIDER_H_
|
||||
#define LIB_SYS_CPP_TESTING_SERVICE_DIRECTORY_PROVIDER_H_
|
||||
|
||||
#include "lib/sys/cpp/service_directory.h"
|
||||
|
||||
#include <lib/vfs/cpp/pseudo_dir.h>
|
||||
#include <lib/vfs/cpp/service.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
// This provides a fake |ServiceDirectory| for unit testing.
|
||||
// Provides access to services that have been added to this object.
|
||||
// The object of this class should be kept alive for fake |ServiceDirectory| to
|
||||
// work.
|
||||
class ServiceDirectoryProvider {
|
||||
public:
|
||||
explicit ServiceDirectoryProvider(async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
~ServiceDirectoryProvider();
|
||||
|
||||
// Injects a service which can be accessed by calling Connect on
|
||||
// |sys::ServiceDirectory| by code under test.
|
||||
//
|
||||
// Adds a supported service with the given |service_name|, using the given
|
||||
// |interface_request_handler|. |interface_request_handler| should
|
||||
// remain valid for the lifetime of this object.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_ALREADY_EXISTS: This already contains an entry for
|
||||
// this service.
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// ```
|
||||
// fidl::BindingSet<fuchsia::foo::Controller> bindings;
|
||||
// svc->AddService(bindings.GetHandler(this));
|
||||
// ```
|
||||
template <typename Interface>
|
||||
zx_status_t AddService(fidl::InterfaceRequestHandler<Interface> handler,
|
||||
const std::string& name = Interface::Name_) const {
|
||||
return AddService(std::make_unique<vfs::Service>(std::move(handler)), name);
|
||||
}
|
||||
|
||||
// Injects a service which can be accessed by calling Connect on
|
||||
// |sys::ServiceDirectory| by code under test.
|
||||
//
|
||||
// Adds a supported service with the given |service_name|, using the given
|
||||
// |service|. |service| closure should
|
||||
// remain valid for the lifetime of this object.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// ZX_ERR_ALREADY_EXISTS: This already contains an entry for
|
||||
// this service.
|
||||
zx_status_t AddService(std::unique_ptr<vfs::Service> service,
|
||||
const std::string& name) const;
|
||||
|
||||
std::shared_ptr<ServiceDirectory>& service_directory() {
|
||||
return service_directory_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<ServiceDirectory> service_directory_;
|
||||
std::unique_ptr<vfs::PseudoDir> svc_dir_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTING_SERVICE_DIRECTORY_PROVIDER_H_
|
@ -1,43 +0,0 @@
|
||||
// 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/sys/cpp/testing/test_with_environment.h>
|
||||
|
||||
#include <lib/sys/cpp/service_directory.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
TestWithEnvironment::TestWithEnvironment()
|
||||
: real_services_(sys::ServiceDirectory::CreateFromNamespace()) {
|
||||
real_services_->Connect(real_env_.NewRequest());
|
||||
real_env_->GetLauncher(real_launcher_.NewRequest());
|
||||
}
|
||||
|
||||
void TestWithEnvironment::CreateComponentInCurrentEnvironment(
|
||||
fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request) {
|
||||
real_launcher_.CreateComponent(std::move(launch_info), std::move(request));
|
||||
}
|
||||
|
||||
bool TestWithEnvironment::RunComponentUntilTerminated(
|
||||
fuchsia::sys::ComponentControllerPtr component_controller,
|
||||
TerminationResult* termination_result) {
|
||||
bool is_terminated = false;
|
||||
component_controller.events().OnTerminated =
|
||||
[&](int64_t return_code, fuchsia::sys::TerminationReason reason) {
|
||||
is_terminated = true;
|
||||
if (termination_result != nullptr) {
|
||||
*termination_result = {
|
||||
.return_code = return_code,
|
||||
.reason = reason,
|
||||
};
|
||||
}
|
||||
};
|
||||
RunLoopUntil([&]() { return is_terminated; });
|
||||
return is_terminated;
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
@ -1,136 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTING_TEST_WITH_ENVIRONMENT_H_
|
||||
#define LIB_SYS_CPP_TESTING_TEST_WITH_ENVIRONMENT_H_
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
#include <lib/gtest/real_loop_fixture.h>
|
||||
#include <lib/sys/cpp/testing/enclosing_environment.h>
|
||||
#include <lib/sys/cpp/testing/launcher_impl.h>
|
||||
|
||||
namespace sys {
|
||||
namespace testing {
|
||||
|
||||
// Combines the return code and termination reason from a Component termination.
|
||||
struct TerminationResult {
|
||||
int64_t return_code;
|
||||
fuchsia::sys::TerminationReason reason;
|
||||
};
|
||||
|
||||
// Test fixture for tests to run Components inside a new isolated Environment,
|
||||
// wrapped in a enclosing Environment.
|
||||
//
|
||||
// The new isolated Environment, provided to the Component under test, is not
|
||||
// visible to any real Environments, such as the Environment that the test
|
||||
// program was launched in.
|
||||
//
|
||||
// That isloated environment needs to be created using
|
||||
// |CreateNewEnclosingEnvironment*| APIs.
|
||||
//
|
||||
// The isolated Environment is enclosed in a enclosing Environment, allowing the
|
||||
// test to provide Loader, Services, and other Directories that are visible to
|
||||
// Components under test, and only to those Components.
|
||||
//
|
||||
//
|
||||
// This fixture also allows you to create components in the real environment in
|
||||
// which this test was launched. Those components should only be used to
|
||||
// validate real system state.
|
||||
//
|
||||
// To use this fixture you need to whitelist "fuchsia.sys.Environment" and
|
||||
// "fuchsia.sys.Loader" in your component manifest file in "sandbox.services".
|
||||
// It is necessary because this fixture needs to access
|
||||
// "fuchsia.sys.Environment" and if you create a |EnclosingEnvironment| then it
|
||||
// will need access to parent's "fuchsia.sys.Loader" to serve it own Loader
|
||||
// service.
|
||||
//
|
||||
// If you are going to create a new EnclosingEnvironment then you also need to
|
||||
// run your own loop to serve services provided by that environment. So use of
|
||||
// SyncPtrs is not advisable unless you can run a loop safely in a new thread
|
||||
// which stops running before you kill your EnclosingEnvironment.
|
||||
class TestWithEnvironment : public gtest::RealLoopFixture {
|
||||
protected:
|
||||
TestWithEnvironment();
|
||||
|
||||
fuchsia::sys::LauncherPtr launcher_ptr() {
|
||||
fuchsia::sys::LauncherPtr launcher;
|
||||
real_launcher_.AddBinding(launcher.NewRequest());
|
||||
return launcher;
|
||||
}
|
||||
|
||||
const std::shared_ptr<sys::ServiceDirectory>& real_services() {
|
||||
return real_services_;
|
||||
}
|
||||
|
||||
const fuchsia::sys::EnvironmentPtr& real_env() { return real_env_; }
|
||||
|
||||
// Creates a new enclosing environment inside current real environment with
|
||||
// the given services.
|
||||
//
|
||||
// This environment and components created in it will not have access to any
|
||||
// of services(except Loader) and resources from the real environment unless
|
||||
// explicitly allowed by calling AllowPublicService.
|
||||
//
|
||||
// After all services are added/passed through to the environment, you must
|
||||
// call Launch() to actually start it.
|
||||
std::unique_ptr<EnclosingEnvironment> CreateNewEnclosingEnvironment(
|
||||
const std::string& label, std::unique_ptr<EnvironmentServices> services,
|
||||
const fuchsia::sys::EnvironmentOptions& options = {}) const {
|
||||
return EnclosingEnvironment::Create(label, real_env_, std::move(services),
|
||||
options);
|
||||
}
|
||||
|
||||
// Returns an EnvironmentServices object that the caller can use to pass
|
||||
// services to a new EnclosingEnvironment.
|
||||
//
|
||||
// The returned object has the parent's loader, but no other services by
|
||||
// default.
|
||||
std::unique_ptr<EnvironmentServices> CreateServices() {
|
||||
return EnvironmentServices::Create(real_env_);
|
||||
}
|
||||
|
||||
std::unique_ptr<EnvironmentServices> CreateServicesWithCustomLoader(
|
||||
const std::shared_ptr<vfs::Service>& loader_service) {
|
||||
return EnvironmentServices::CreateWithCustomLoader(real_env_,
|
||||
loader_service);
|
||||
}
|
||||
|
||||
// Creates component in current real environment. This component will have
|
||||
// access to the services, directories and other resources from the
|
||||
// environment in which your test was launched.
|
||||
//
|
||||
// This should be mostly used for observing the state of system and for
|
||||
// nothing else. For eg. Try launching "glob" component and validate how it
|
||||
// behaves in various environments.
|
||||
void CreateComponentInCurrentEnvironment(
|
||||
fuchsia::sys::LaunchInfo launch_info,
|
||||
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request);
|
||||
|
||||
// Returns true if environment was created.
|
||||
//
|
||||
// You should either use this function to wait or run your own loop if you
|
||||
// want CreateComponent* to succed on |enclosing_environment|.
|
||||
bool WaitForEnclosingEnvToStart(
|
||||
const EnclosingEnvironment* enclosing_environment) {
|
||||
return RunLoopUntil([enclosing_environment] {
|
||||
return enclosing_environment->is_running();
|
||||
});
|
||||
}
|
||||
|
||||
// Run a loop until the given component is terminated or |timeout| elapses.
|
||||
bool RunComponentUntilTerminated(
|
||||
fuchsia::sys::ComponentControllerPtr component_controller,
|
||||
TerminationResult* termination_result = nullptr);
|
||||
|
||||
private:
|
||||
std::shared_ptr<sys::ServiceDirectory> real_services_;
|
||||
fuchsia::sys::EnvironmentPtr real_env_;
|
||||
LauncherImpl real_launcher_;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
} // namespace sys
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTING_TEST_WITH_ENVIRONMENT_H_
|
@ -1,131 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import("//build/package/component.gni")
|
||||
import("//build/test.gni")
|
||||
import("//build/test/test_package.gni")
|
||||
import("//build/testing/environments.gni")
|
||||
|
||||
test("component_cpp_unittests") {
|
||||
sources = [
|
||||
"echo_server.h",
|
||||
"file_descriptor_unittest.cc",
|
||||
"outgoing_directory_unittest.cc",
|
||||
"service_directory_unittest.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//garnet/examples/fidl/services:echo",
|
||||
"//garnet/public/lib/gtest",
|
||||
"//sdk/lib/sys/cpp",
|
||||
"//third_party/googletest:gtest_main",
|
||||
"//zircon/public/fidl/fuchsia-io",
|
||||
"//zircon/public/lib/fdio",
|
||||
"//zircon/public/lib/fidl",
|
||||
]
|
||||
}
|
||||
|
||||
test("component_cpp_testing_unittests") {
|
||||
sources = [
|
||||
"component_context_provider_unittest.cc",
|
||||
"echo_server.h",
|
||||
"service_directory_provider_unittest.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//garnet/examples/fidl/services:echo",
|
||||
"//garnet/public/lib/gtest",
|
||||
"//sdk/lib/sys/cpp/testing:unit",
|
||||
"//third_party/googletest:gtest_main",
|
||||
"//zircon/public/fidl/fuchsia-io",
|
||||
"//zircon/public/lib/fidl",
|
||||
]
|
||||
}
|
||||
|
||||
test("component_cpp_testing_tests") {
|
||||
sources = [
|
||||
"component_interceptor_unittest.cc",
|
||||
"enclosing_environment_test.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//garnet/examples/fidl/services:echo",
|
||||
"//garnet/public/lib/gtest",
|
||||
"//sdk/lib/sys/cpp/testing:integration",
|
||||
"//third_party/googletest:gtest_main",
|
||||
"//zircon/public/fidl/fuchsia-io",
|
||||
"//zircon/public/lib/fidl",
|
||||
]
|
||||
}
|
||||
|
||||
executable("helper_proc") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"helper.cc",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//garnet/examples/fidl/services:echo",
|
||||
"//src/lib/fxl",
|
||||
"//sdk/lib/fidl/cpp",
|
||||
"//sdk/lib/sys/cpp",
|
||||
"//zircon/public/lib/async-cpp",
|
||||
"//zircon/public/lib/async-loop-cpp",
|
||||
]
|
||||
}
|
||||
|
||||
# TODO(IN-933): Reenable once fuchsia_test_component is supported.
|
||||
# fuchsia_test_component("component_cpp_unittests_component") {
|
||||
# deps = [
|
||||
# ":component_cpp_unittests",
|
||||
# ]
|
||||
# binary = "component_cpp_unittests"
|
||||
# }
|
||||
|
||||
# TODO(IN-933): Reenable once fuchsia_test_component is supported.
|
||||
# fuchsia_test_component("component_cpp_testing_unittests_component") {
|
||||
# deps = [
|
||||
# ":component_cpp_testing_unittests",
|
||||
# ]
|
||||
# binary = "component_cpp_testing_unittests"
|
||||
# }
|
||||
|
||||
# TODO(IN-933): Convert back to package
|
||||
test_package("component_cpp_tests") {
|
||||
deps = [
|
||||
":component_cpp_testing_tests",
|
||||
":component_cpp_testing_unittests",
|
||||
":component_cpp_unittests",
|
||||
":helper_proc",
|
||||
]
|
||||
|
||||
binaries = [
|
||||
{
|
||||
name = "helper_proc"
|
||||
},
|
||||
]
|
||||
|
||||
meta = [
|
||||
{
|
||||
path = rebase_path("meta/helper_proc.cmx")
|
||||
dest = "helper_proc.cmx"
|
||||
},
|
||||
]
|
||||
|
||||
tests = [
|
||||
{
|
||||
name = "component_cpp_unittests"
|
||||
environments = basic_envs
|
||||
},
|
||||
{
|
||||
name = "component_cpp_testing_unittests"
|
||||
environments = basic_envs
|
||||
},
|
||||
{
|
||||
name = "component_cpp_testing_tests"
|
||||
environments = basic_envs
|
||||
},
|
||||
]
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
// 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 <lib/sys/cpp/testing/component_context_provider.h>
|
||||
|
||||
#include "echo_server.h"
|
||||
|
||||
#include <lib/gtest/real_loop_fixture.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class ComponentContextProviderTests : public gtest::RealLoopFixture {
|
||||
protected:
|
||||
void PublishOutgoingService() {
|
||||
ASSERT_EQ(ZX_OK, provider_.context()->outgoing()->AddPublicService(
|
||||
echo_impl_.GetHandler(dispatcher())));
|
||||
}
|
||||
|
||||
void PublishIncomingService() {
|
||||
ASSERT_EQ(ZX_OK, provider_.service_directory_provider()->AddService(
|
||||
echo_impl_.GetHandler(dispatcher())));
|
||||
}
|
||||
|
||||
EchoImpl echo_impl_;
|
||||
sys::testing::ComponentContextProvider provider_;
|
||||
};
|
||||
|
||||
TEST_F(ComponentContextProviderTests, TestOutgoingPublicServices) {
|
||||
PublishOutgoingService();
|
||||
|
||||
auto echo = provider_.ConnectToPublicService<fidl::examples::echo::Echo>();
|
||||
|
||||
std::string result;
|
||||
echo->EchoString("hello",
|
||||
[&result](fidl::StringPtr value) { result = *value; });
|
||||
|
||||
RunLoopUntilIdle();
|
||||
EXPECT_EQ("hello", result);
|
||||
}
|
||||
|
||||
TEST_F(ComponentContextProviderTests, TestIncomingServices) {
|
||||
PublishIncomingService();
|
||||
|
||||
fidl::examples::echo::EchoPtr echo;
|
||||
|
||||
auto services = provider_.service_directory_provider()->service_directory();
|
||||
|
||||
services->Connect(echo.NewRequest());
|
||||
|
||||
std::string result;
|
||||
echo->EchoString("hello",
|
||||
[&result](fidl::StringPtr value) { result = *value; });
|
||||
|
||||
RunLoopUntilIdle();
|
||||
EXPECT_EQ("hello", result);
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,188 +0,0 @@
|
||||
// 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 <string>
|
||||
#include <vector>
|
||||
|
||||
#include <fuchsia/sys/cpp/fidl.h>
|
||||
#include <lib/async-loop/cpp/loop.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
#include <lib/gtest/real_loop_fixture.h>
|
||||
#include <lib/sys/cpp/testing/component_interceptor.h>
|
||||
#include <lib/sys/cpp/testing/test_with_environment.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using fuchsia::sys::TerminationReason;
|
||||
|
||||
// Records |LoadUrl|s, but forwards requests to a fallback loader.
|
||||
class TestLoader : fuchsia::sys::Loader {
|
||||
public:
|
||||
// Fallback loader comes from the supplied |env|.
|
||||
TestLoader(const fuchsia::sys::EnvironmentPtr& env) {
|
||||
fuchsia::sys::ServiceProviderPtr sp;
|
||||
env->GetServices(sp.NewRequest());
|
||||
sp->ConnectToService(fuchsia::sys::Loader::Name_,
|
||||
fallback_loader_.NewRequest().TakeChannel());
|
||||
}
|
||||
|
||||
virtual ~TestLoader() = default;
|
||||
|
||||
fuchsia::sys::LoaderPtr NewRequest() {
|
||||
fuchsia::sys::LoaderPtr loader;
|
||||
loader.Bind(bindings_.AddBinding(this));
|
||||
return loader;
|
||||
}
|
||||
|
||||
// |fuchsia::sys::Loader|
|
||||
void LoadUrl(std::string url, LoadUrlCallback response) override {
|
||||
requested_urls.push_back(url);
|
||||
fallback_loader_->LoadUrl(url, std::move(response));
|
||||
}
|
||||
|
||||
std::vector<std::string> requested_urls;
|
||||
|
||||
private:
|
||||
fidl::BindingSet<fuchsia::sys::Loader> bindings_;
|
||||
fuchsia::sys::LoaderPtr fallback_loader_;
|
||||
};
|
||||
|
||||
// This fixture gives us a real_env().
|
||||
class ComponentInterceptorTest : public sys::testing::TestWithEnvironment {};
|
||||
|
||||
// This tests fallback-loader and intercept-url cases using the same enclosing
|
||||
// environment.
|
||||
TEST_F(ComponentInterceptorTest, TestFallbackAndInterceptingUrls) {
|
||||
TestLoader test_loader(real_env());
|
||||
|
||||
sys::testing::ComponentInterceptor interceptor(test_loader.NewRequest());
|
||||
auto env = sys::testing::EnclosingEnvironment::Create(
|
||||
"test_harness", real_env(),
|
||||
interceptor.MakeEnvironmentServices(real_env()));
|
||||
|
||||
constexpr char kInterceptUrl[] = "file://intercept_url";
|
||||
constexpr char kFallbackUrl[] = "file://fallback_url";
|
||||
|
||||
// Test the intercepting case.
|
||||
{
|
||||
std::string actual_url;
|
||||
|
||||
bool intercepted_url = false;
|
||||
ASSERT_TRUE(interceptor.InterceptURL(
|
||||
kInterceptUrl, "",
|
||||
[&actual_url, &intercepted_url](
|
||||
fuchsia::sys::StartupInfo startup_info,
|
||||
std::unique_ptr<sys::testing::InterceptedComponent> component) {
|
||||
intercepted_url = true;
|
||||
actual_url = startup_info.launch_info.url;
|
||||
}));
|
||||
|
||||
fuchsia::sys::ComponentControllerPtr controller;
|
||||
fuchsia::sys::LaunchInfo info;
|
||||
info.url = kInterceptUrl;
|
||||
env->CreateComponent(std::move(info), controller.NewRequest());
|
||||
|
||||
ASSERT_TRUE(RunLoopUntil([&] { return intercepted_url; }));
|
||||
EXPECT_EQ(kInterceptUrl, actual_url);
|
||||
}
|
||||
|
||||
test_loader.requested_urls.clear();
|
||||
|
||||
// Test the fallback loader case.
|
||||
{
|
||||
fuchsia::sys::ComponentControllerPtr controller;
|
||||
fuchsia::sys::LaunchInfo info;
|
||||
info.url = kFallbackUrl;
|
||||
// Should this call into our TestLoader.
|
||||
env->CreateComponent(std::move(info), controller.NewRequest());
|
||||
|
||||
ASSERT_TRUE(
|
||||
RunLoopUntil([&] { return test_loader.requested_urls.size() > 0u; }));
|
||||
|
||||
EXPECT_EQ(kFallbackUrl, test_loader.requested_urls[0]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComponentInterceptorTest, TestOnKill) {
|
||||
TestLoader test_loader(real_env());
|
||||
|
||||
sys::testing::ComponentInterceptor interceptor(test_loader.NewRequest());
|
||||
auto env = sys::testing::EnclosingEnvironment::Create(
|
||||
"test_harness", real_env(),
|
||||
interceptor.MakeEnvironmentServices(real_env()));
|
||||
|
||||
constexpr char kInterceptUrl[] = "file://intercept_url";
|
||||
|
||||
// Test the intercepting case.
|
||||
std::string actual_url;
|
||||
|
||||
bool killed = false;
|
||||
std::unique_ptr<sys::testing::InterceptedComponent> component;
|
||||
ASSERT_TRUE(interceptor.InterceptURL(
|
||||
kInterceptUrl, "",
|
||||
[&](fuchsia::sys::StartupInfo startup_info,
|
||||
std::unique_ptr<sys::testing::InterceptedComponent>
|
||||
intercepted_component) {
|
||||
component = std::move(intercepted_component);
|
||||
component->set_on_kill([startup_info = std::move(startup_info),
|
||||
&killed]() { killed = true; });
|
||||
}));
|
||||
|
||||
{
|
||||
fuchsia::sys::ComponentControllerPtr controller;
|
||||
fuchsia::sys::LaunchInfo info;
|
||||
info.url = kInterceptUrl;
|
||||
env->CreateComponent(std::move(info), controller.NewRequest());
|
||||
|
||||
ASSERT_TRUE(RunLoopUntil([&] { return !!component; }));
|
||||
ASSERT_FALSE(killed);
|
||||
}
|
||||
// should be killed
|
||||
ASSERT_TRUE(RunLoopUntil([&] { return killed; }));
|
||||
}
|
||||
|
||||
TEST_F(ComponentInterceptorTest, ExtraCmx) {
|
||||
auto interceptor =
|
||||
sys::testing::ComponentInterceptor::CreateWithEnvironmentLoader(
|
||||
real_env());
|
||||
auto env = sys::testing::EnclosingEnvironment::Create(
|
||||
"test_harness", real_env(),
|
||||
interceptor.MakeEnvironmentServices(real_env()));
|
||||
|
||||
constexpr char kUrl[] = "file://fake_url";
|
||||
bool intercepted_url = false;
|
||||
std::map<std::string, std::string> program_metadata;
|
||||
ASSERT_TRUE(interceptor.InterceptURL(
|
||||
kUrl, R"({
|
||||
"runner": "fake",
|
||||
"program": {
|
||||
"binary": "",
|
||||
"data": "randomstring"
|
||||
}
|
||||
})",
|
||||
[&](fuchsia::sys::StartupInfo startup_info,
|
||||
std::unique_ptr<sys::testing::InterceptedComponent> c) {
|
||||
intercepted_url = true;
|
||||
for (const auto& metadata : startup_info.program_metadata.get()) {
|
||||
program_metadata[metadata.key] = metadata.value;
|
||||
}
|
||||
}));
|
||||
|
||||
fuchsia::sys::ComponentControllerPtr controller;
|
||||
fuchsia::sys::LaunchInfo info;
|
||||
info.url = kUrl;
|
||||
|
||||
// This should call into our TestLoader.
|
||||
env->CreateComponent(std::move(info), controller.NewRequest());
|
||||
|
||||
// Test that we intercepting URL
|
||||
ASSERT_TRUE(
|
||||
RunLoopWithTimeoutOrUntil([&] { return intercepted_url; }, zx::sec(2)));
|
||||
EXPECT_TRUE(intercepted_url);
|
||||
EXPECT_EQ("randomstring", program_metadata["data"]);
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,38 +0,0 @@
|
||||
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_SYS_CPP_TESTS_ECHO_SERVER_H_
|
||||
#define LIB_SYS_CPP_TESTS_ECHO_SERVER_H_
|
||||
|
||||
#include <fidl/examples/echo/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
#include <lib/fidl/cpp/interface_request.h>
|
||||
|
||||
namespace {
|
||||
|
||||
class EchoImpl : public fidl::examples::echo::Echo {
|
||||
public:
|
||||
void EchoString(fidl::StringPtr value, EchoStringCallback callback) override {
|
||||
callback(std::move(value));
|
||||
}
|
||||
fidl::InterfaceRequestHandler<fidl::examples::echo::Echo> GetHandler(
|
||||
async_dispatcher_t* dispatcher) {
|
||||
return bindings_.GetHandler(this, dispatcher);
|
||||
}
|
||||
|
||||
void AddBinding(zx::channel request, async_dispatcher_t* dispatcher) {
|
||||
bindings_.AddBinding(
|
||||
this,
|
||||
fidl::InterfaceRequest<fidl::examples::echo::Echo>(std::move(request)),
|
||||
dispatcher);
|
||||
}
|
||||
|
||||
private:
|
||||
fidl::BindingSet<fidl::examples::echo::Echo> bindings_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // LIB_SYS_CPP_TESTS_ECHO_SERVER_H_
|
@ -1,317 +0,0 @@
|
||||
// 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 <zircon/processargs.h>
|
||||
|
||||
#include "fidl/examples/echo/cpp/fidl.h"
|
||||
#include "lib/async-loop/cpp/loop.h"
|
||||
#include "lib/async/cpp/wait.h"
|
||||
#include "lib/async/dispatcher.h"
|
||||
#include "lib/sys/cpp/testing/enclosing_environment.h"
|
||||
#include "lib/sys/cpp/testing/test_with_environment.h"
|
||||
#include "src/lib/fxl/strings/string_printf.h"
|
||||
|
||||
using namespace fuchsia::sys;
|
||||
namespace echo = ::fidl::examples::echo;
|
||||
|
||||
namespace sys::testing::test {
|
||||
|
||||
constexpr char kHelperProc[] =
|
||||
"fuchsia-pkg://fuchsia.com/component_cpp_tests#meta/helper_proc.cmx";
|
||||
constexpr int kNumberOfTries = 3;
|
||||
|
||||
// helper class that creates and listens on
|
||||
// a socket while appending to a std::stringstream
|
||||
class SocketReader {
|
||||
public:
|
||||
SocketReader() : wait_(this) {}
|
||||
|
||||
zx::handle OpenSocket() {
|
||||
ZX_ASSERT(!socket_.is_valid());
|
||||
zx::socket ret;
|
||||
zx::socket::create(0, &ret, &socket_);
|
||||
wait_.set_object(socket_.get());
|
||||
wait_.set_trigger(ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED);
|
||||
wait_.Begin(async_get_default_dispatcher());
|
||||
return zx::handle(std::move(ret));
|
||||
}
|
||||
|
||||
std::string GetString() { return stream_.str(); }
|
||||
|
||||
void OnData(async_dispatcher_t* dispatcher, async::WaitBase* wait,
|
||||
zx_status_t status, const zx_packet_signal_t* signal) {
|
||||
if (status != ZX_OK) {
|
||||
return;
|
||||
}
|
||||
if (signal->observed & ZX_SOCKET_READABLE) {
|
||||
char buff[1024];
|
||||
size_t actual;
|
||||
status = socket_.read(0, buff, sizeof(buff) - 1, &actual);
|
||||
ASSERT_EQ(status, ZX_OK);
|
||||
buff[actual] = '\0';
|
||||
stream_ << buff;
|
||||
}
|
||||
|
||||
if (!(signal->observed & ZX_SOCKET_PEER_CLOSED)) {
|
||||
wait_.Begin(dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
zx::socket socket_;
|
||||
std::stringstream stream_;
|
||||
async::WaitMethod<SocketReader, &SocketReader::OnData> wait_;
|
||||
};
|
||||
|
||||
class EnclosingEnvTest : public TestWithEnvironment {
|
||||
public:
|
||||
// Tries to connect and communicate with echo service
|
||||
bool TryEchoService(const std::unique_ptr<EnclosingEnvironment>& env) {
|
||||
// We give this part of the test 3 shots to complete
|
||||
// this is the safest way to do this and prevent flakiness.
|
||||
// Because EnvironmentServices is listening on a ComponentController channel
|
||||
// to restart the service, we can't control that it'll actually recreate the
|
||||
// service in 100% deterministic order.
|
||||
echo::EchoPtr echo;
|
||||
for (int tries = 0; tries < kNumberOfTries; tries++) {
|
||||
// reset flag again and communicate with the service,
|
||||
// it must be spun back up
|
||||
bool req_done = false;
|
||||
// dismiss old channel
|
||||
|
||||
// connect again
|
||||
env->ConnectToService(echo.NewRequest());
|
||||
|
||||
bool channel_closed = false;
|
||||
echo.set_error_handler(
|
||||
[&](zx_status_t status) { channel_closed = true; });
|
||||
// talk with the service once and assert it's ok
|
||||
echo->EchoString("hello", [&req_done](::fidl::StringPtr rsp) {
|
||||
EXPECT_EQ(rsp, "hello");
|
||||
req_done = true;
|
||||
});
|
||||
RunLoopUntil([&]() { return req_done || channel_closed; });
|
||||
if (req_done) {
|
||||
return true;
|
||||
} else {
|
||||
std::cerr << "Didn't receive echo response in attempt number "
|
||||
<< (tries + 1) << std::endl;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(EnclosingEnvTest, RespawnService) {
|
||||
auto svc = CreateServices();
|
||||
LaunchInfo linfo;
|
||||
linfo.url = kHelperProc;
|
||||
linfo.arguments.reset({"--echo", "--kill=die"});
|
||||
svc->AddServiceWithLaunchInfo(std::move(linfo), echo::Echo::Name_);
|
||||
auto env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
|
||||
ASSERT_TRUE(WaitForEnclosingEnvToStart(env.get()));
|
||||
// attempt to connect to service:
|
||||
bool req_done = false;
|
||||
bool got_error = false;
|
||||
echo::EchoPtr echo;
|
||||
echo.set_error_handler(
|
||||
[&got_error](zx_status_t status) { got_error = true; });
|
||||
env->ConnectToService(echo.NewRequest());
|
||||
|
||||
// talk with the service once and assert it's done
|
||||
echo->EchoString("hello", [&req_done](::fidl::StringPtr rsp) {
|
||||
ASSERT_EQ(rsp, "hello");
|
||||
req_done = true;
|
||||
});
|
||||
ASSERT_TRUE(RunLoopUntil([&req_done]() { return req_done; }));
|
||||
|
||||
// reset flag, and send the kill string
|
||||
req_done = false;
|
||||
// talk with the service once and assert it's done
|
||||
echo->EchoString("die", [&req_done](::fidl::StringPtr rsp) {
|
||||
ASSERT_EQ(rsp, "die");
|
||||
req_done = true;
|
||||
});
|
||||
|
||||
// wait until we see the response AND the channel closing
|
||||
ASSERT_TRUE(RunLoopUntil(
|
||||
[&req_done, &got_error]() { return req_done && got_error; }));
|
||||
|
||||
// Try to communicate with server again, we expect
|
||||
// it to be spun up once more
|
||||
ASSERT_TRUE(TryEchoService(env));
|
||||
}
|
||||
|
||||
TEST_F(EnclosingEnvTest, EnclosingEnvOnASeperateThread) {
|
||||
std::unique_ptr<sys::testing::EnclosingEnvironment> env = nullptr;
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto svc =
|
||||
sys::testing::EnvironmentServices::Create(real_env(), loop.dispatcher());
|
||||
|
||||
LaunchInfo linfo;
|
||||
linfo.url = kHelperProc;
|
||||
linfo.arguments.reset({"--echo", "--kill=die"});
|
||||
svc->AddServiceWithLaunchInfo(std::move(linfo), echo::Echo::Name_);
|
||||
env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
|
||||
ASSERT_TRUE(WaitForEnclosingEnvToStart(env.get()));
|
||||
|
||||
echo::EchoSyncPtr echo_ptr;
|
||||
env->ConnectToService(echo_ptr.NewRequest());
|
||||
fidl::StringPtr response;
|
||||
echo_ptr->EchoString("hello1", &response);
|
||||
|
||||
ASSERT_EQ(response, "hello1");
|
||||
}
|
||||
|
||||
TEST_F(EnclosingEnvTest, RespawnServiceWithHandler) {
|
||||
auto svc = CreateServices();
|
||||
|
||||
int call_counter = 0;
|
||||
svc->AddServiceWithLaunchInfo(
|
||||
kHelperProc,
|
||||
[&call_counter]() {
|
||||
LaunchInfo linfo;
|
||||
linfo.url = kHelperProc;
|
||||
linfo.arguments.reset({"--echo", "--kill=die"});
|
||||
call_counter++;
|
||||
return linfo;
|
||||
},
|
||||
echo::Echo::Name_);
|
||||
auto env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
|
||||
ASSERT_TRUE(WaitForEnclosingEnvToStart(env.get()));
|
||||
// attempt to connect to service:
|
||||
bool req_done = false;
|
||||
bool got_error = false;
|
||||
echo::EchoPtr echo;
|
||||
echo.set_error_handler(
|
||||
[&got_error](zx_status_t status) { got_error = true; });
|
||||
env->ConnectToService(echo.NewRequest());
|
||||
|
||||
// talk with the service once and assert it's done
|
||||
echo->EchoString("hello", [&req_done](::fidl::StringPtr rsp) {
|
||||
ASSERT_EQ(rsp, "hello");
|
||||
req_done = true;
|
||||
});
|
||||
ASSERT_TRUE(RunLoopUntil([&req_done]() { return req_done; }));
|
||||
// check that the launch info factory function was called only once
|
||||
EXPECT_EQ(call_counter, 1);
|
||||
|
||||
// reset flag, and send the kill string
|
||||
req_done = false;
|
||||
// talk with the service once and assert it's done
|
||||
echo->EchoString("die", [&req_done](::fidl::StringPtr rsp) {
|
||||
ASSERT_EQ(rsp, "die");
|
||||
req_done = true;
|
||||
});
|
||||
|
||||
// wait until we see the response AND the channel closing
|
||||
ASSERT_TRUE(RunLoopUntil(
|
||||
[&req_done, &got_error]() { return req_done && got_error; }));
|
||||
|
||||
// Try to communicate with server again, we expect
|
||||
// it to be spun up once more
|
||||
ASSERT_TRUE(TryEchoService(env));
|
||||
|
||||
// check that the launch info factory function was called only TWICE
|
||||
EXPECT_EQ(call_counter, 2);
|
||||
}
|
||||
|
||||
TEST_F(EnclosingEnvTest, OutErrPassing) {
|
||||
auto svc = CreateServices();
|
||||
|
||||
SocketReader cout_reader;
|
||||
SocketReader cerr_reader;
|
||||
svc->AddServiceWithLaunchInfo(
|
||||
kHelperProc,
|
||||
[&cout_reader, &cerr_reader]() {
|
||||
LaunchInfo linfo;
|
||||
linfo.url = kHelperProc;
|
||||
linfo.arguments.reset({"--echo", "--cout=potato", "--cerr=tomato"});
|
||||
|
||||
linfo.out = FileDescriptor::New();
|
||||
linfo.out->type0 = PA_FD;
|
||||
linfo.out->handle0 = cout_reader.OpenSocket();
|
||||
linfo.err = FileDescriptor::New();
|
||||
linfo.err->type0 = PA_FD;
|
||||
linfo.err->handle0 = cerr_reader.OpenSocket();
|
||||
|
||||
return linfo;
|
||||
},
|
||||
echo::Echo::Name_);
|
||||
auto env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
|
||||
ASSERT_TRUE(WaitForEnclosingEnvToStart(env.get()));
|
||||
// attempt to connect to service:
|
||||
echo::EchoPtr echo;
|
||||
|
||||
// this should trigger hello_proc to start and
|
||||
// print "potato" to cout and "tomato" to err
|
||||
env->ConnectToService(echo.NewRequest());
|
||||
|
||||
// now it's just a matter of waiting for the socket readers to
|
||||
// have seen those strings:
|
||||
ASSERT_TRUE(RunLoopUntil([&cout_reader, &cerr_reader]() {
|
||||
return cout_reader.GetString().find("potato") != std::string::npos &&
|
||||
cerr_reader.GetString().find("tomato") != std::string::npos;
|
||||
}));
|
||||
}
|
||||
|
||||
class FakeLoader : public fuchsia::sys::Loader {
|
||||
public:
|
||||
FakeLoader() {
|
||||
loader_service_ = std::make_shared<vfs::Service>(
|
||||
[this](zx::channel channel, async_dispatcher_t* dispatcher) {
|
||||
bindings_.AddBinding(
|
||||
this,
|
||||
fidl::InterfaceRequest<fuchsia::sys::Loader>(std::move(channel)),
|
||||
dispatcher);
|
||||
});
|
||||
}
|
||||
|
||||
void LoadUrl(std::string url, LoadUrlCallback callback) override {
|
||||
ASSERT_TRUE(!url.empty());
|
||||
component_urls_.push_back(url);
|
||||
}
|
||||
std::vector<std::string>& component_urls() { return component_urls_; };
|
||||
|
||||
std::shared_ptr<vfs::Service> loader_service() { return loader_service_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<vfs::Service> loader_service_;
|
||||
fidl::BindingSet<fuchsia::sys::Loader> bindings_;
|
||||
std::vector<std::string> component_urls_;
|
||||
};
|
||||
|
||||
TEST_F(EnclosingEnvTest, CanLaunchMoreThanOneService) {
|
||||
FakeLoader loader;
|
||||
auto loader_service = loader.loader_service();
|
||||
auto svc = CreateServicesWithCustomLoader(loader_service);
|
||||
|
||||
std::vector<std::string> urls;
|
||||
std::vector<std::string> svc_names;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
auto url = fxl::StringPrintf(
|
||||
"fuchsia-pkg://fuchsia.com/dummy%d#meta/dummy%d.cmx", i, i);
|
||||
auto svc_name = fxl::StringPrintf("service%d", i);
|
||||
LaunchInfo linfo;
|
||||
linfo.url = url;
|
||||
svc->AddServiceWithLaunchInfo(std::move(linfo), svc_name);
|
||||
urls.push_back(url);
|
||||
svc_names.push_back(svc_name);
|
||||
}
|
||||
auto env = CreateNewEnclosingEnvironment("test-env", std::move(svc));
|
||||
ASSERT_TRUE(WaitForEnclosingEnvToStart(env.get()));
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
echo::EchoPtr echo;
|
||||
env->ConnectToService(echo.NewRequest(), svc_names[i]);
|
||||
}
|
||||
ASSERT_TRUE(RunLoopUntil([&loader]() {
|
||||
return loader.component_urls().size() == 3;
|
||||
})) << loader.component_urls().size();
|
||||
ASSERT_EQ(loader.component_urls(), urls);
|
||||
}
|
||||
|
||||
} // namespace sys::testing::test
|
@ -1,24 +0,0 @@
|
||||
// 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 <lib/sys/cpp/file_descriptor.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(FileDescriptorTest, CloneStdin) {
|
||||
auto file_descriptor = sys::CloneFileDescriptor(0);
|
||||
EXPECT_NE(nullptr, file_descriptor);
|
||||
EXPECT_TRUE(file_descriptor->handle0.is_valid());
|
||||
EXPECT_FALSE(file_descriptor->handle1.is_valid());
|
||||
EXPECT_FALSE(file_descriptor->handle2.is_valid());
|
||||
}
|
||||
|
||||
TEST(FileDescriptorTest, CloneBogus) {
|
||||
auto file_descriptor = sys::CloneFileDescriptor(53);
|
||||
EXPECT_EQ(nullptr, file_descriptor);
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,101 +0,0 @@
|
||||
// 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 <iostream>
|
||||
#include "fidl/examples/echo/cpp/fidl.h"
|
||||
#include "lib/async-loop/cpp/loop.h"
|
||||
#include "lib/fidl/cpp/binding_set.h"
|
||||
#include "src/lib/fxl/command_line.h"
|
||||
#include "lib/sys/cpp/component_context.h"
|
||||
|
||||
static constexpr char kCmdHelp[] = "help";
|
||||
static constexpr char kCmdEcho[] = "echo";
|
||||
static constexpr char kCmdKill[] = "kill";
|
||||
static constexpr char kCmdCout[] = "cout";
|
||||
static constexpr char kCmdCerr[] = "cerr";
|
||||
|
||||
static constexpr char kUsage[] = R"(
|
||||
Usage: helper_proc [-e] [-k kill_string]
|
||||
|
||||
Arguments:
|
||||
--help: Shows this help page and exits
|
||||
--echo: Exposes an echo service (fidl.examples.echo.Echo)
|
||||
--kill=kill_string: will kill the process after echoing a string that equals to kill_string
|
||||
--cout=what: Prints argument to standard output
|
||||
--cerr=what: Prints argument to standard err
|
||||
)";
|
||||
|
||||
// This helper process can be used in lib component's unittest. You can control
|
||||
// what it'll do by passing different command-line arguments
|
||||
|
||||
class EchoServer : public fidl::examples::echo::Echo {
|
||||
public:
|
||||
void EchoString(::fidl::StringPtr value,
|
||||
EchoStringCallback callback) override {
|
||||
std::string intercept = value;
|
||||
callback(std::move(value));
|
||||
if (listener_) {
|
||||
listener_(std::move(intercept));
|
||||
}
|
||||
}
|
||||
|
||||
fidl::InterfaceRequestHandler<fidl::examples::echo::Echo> GetHandler() {
|
||||
return bindings_.GetHandler(this);
|
||||
}
|
||||
|
||||
void SetListener(fit::function<void(std::string)> list) {
|
||||
listener_ = std::move(list);
|
||||
}
|
||||
|
||||
private:
|
||||
fidl::BindingSet<fidl::examples::echo::Echo> bindings_;
|
||||
fit::function<void(std::string)> listener_;
|
||||
};
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
std::cout << "Hello from helper proc." << std::endl;
|
||||
async::Loop loop(&kAsyncLoopConfigAttachToThread);
|
||||
auto cmdline = fxl::CommandLineFromArgcArgv(argc, argv);
|
||||
if (cmdline.HasOption(kCmdHelp)) {
|
||||
std::cout << kUsage;
|
||||
return 0;
|
||||
}
|
||||
auto startup = sys::ComponentContext::Create();
|
||||
std::unique_ptr<EchoServer> echo_server;
|
||||
|
||||
if (cmdline.HasOption(kCmdCout)) {
|
||||
std::string cout;
|
||||
cmdline.GetOptionValue(kCmdCout, &cout);
|
||||
std::cout << cout << std::endl;
|
||||
}
|
||||
|
||||
if (cmdline.HasOption(kCmdCerr)) {
|
||||
std::string cerr;
|
||||
cmdline.GetOptionValue(kCmdCerr, &cerr);
|
||||
std::cerr << cerr << std::endl;
|
||||
}
|
||||
|
||||
if (cmdline.HasOption(kCmdEcho)) {
|
||||
echo_server = std::make_unique<EchoServer>();
|
||||
startup->outgoing()->AddPublicService(echo_server->GetHandler());
|
||||
}
|
||||
|
||||
if (echo_server && cmdline.HasOption(kCmdKill)) {
|
||||
std::string kill_str;
|
||||
cmdline.GetOptionValue(kCmdKill, &kill_str);
|
||||
echo_server->SetListener(
|
||||
[&loop, kill_str = std::move(kill_str)](std::string str) {
|
||||
if (str == kill_str) {
|
||||
loop.Quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (echo_server) {
|
||||
loop.Run();
|
||||
}
|
||||
|
||||
std::cout << "Goodbye from helper proc" << std::endl;
|
||||
return 0;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"program": {
|
||||
"binary": "test/component_cpp_testing_tests"
|
||||
},
|
||||
"sandbox": {
|
||||
"services": [
|
||||
"fuchsia.sys.Environment",
|
||||
"fuchsia.sys.Loader"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"program": {
|
||||
"binary": "test/component_cpp_testing_unittests"
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"program": {
|
||||
"binary": "test/component_cpp_unittests"
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"program": {
|
||||
"binary": "bin/helper_proc"
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
// 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 <lib/sys/cpp/outgoing_directory.h>
|
||||
|
||||
#include "echo_server.h"
|
||||
|
||||
#include <fuchsia/io/c/fidl.h>
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <lib/fidl/cpp/message_buffer.h>
|
||||
#include <lib/gtest/real_loop_fixture.h>
|
||||
#include <lib/zx/channel.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using OutgoingDirectorySetupTest = gtest::RealLoopFixture;
|
||||
|
||||
class OutgoingDirectoryTest : public gtest::RealLoopFixture {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
gtest::RealLoopFixture::SetUp();
|
||||
zx::channel svc_server;
|
||||
ASSERT_EQ(ZX_OK, zx::channel::create(0, &svc_client_, &svc_server));
|
||||
|
||||
ASSERT_EQ(ZX_OK, outgoing_.Serve(std::move(svc_server), dispatcher()));
|
||||
}
|
||||
|
||||
void TestCanAccessEchoService(const char* service_path,
|
||||
bool succeeds = true) {
|
||||
fidl::examples::echo::EchoPtr echo;
|
||||
fdio_service_connect_at(
|
||||
svc_client_.get(), service_path,
|
||||
echo.NewRequest(dispatcher()).TakeChannel().release());
|
||||
|
||||
std::string result = "no callback";
|
||||
echo->EchoString("hello",
|
||||
[&result](fidl::StringPtr value) { result = *value; });
|
||||
|
||||
RunLoopUntilIdle();
|
||||
EXPECT_EQ(succeeds ? "hello" : "no callback", result);
|
||||
}
|
||||
|
||||
void AddEchoService(vfs::PseudoDir* dir) {
|
||||
ASSERT_EQ(ZX_OK, dir->AddEntry(fidl::examples::echo::Echo::Name_,
|
||||
std::make_unique<vfs::Service>(
|
||||
echo_impl_.GetHandler(dispatcher()))));
|
||||
}
|
||||
|
||||
EchoImpl echo_impl_;
|
||||
zx::channel svc_client_;
|
||||
sys::OutgoingDirectory outgoing_;
|
||||
};
|
||||
|
||||
TEST_F(OutgoingDirectoryTest, Control) {
|
||||
ASSERT_EQ(ZX_OK,
|
||||
outgoing_.AddPublicService(echo_impl_.GetHandler(dispatcher())));
|
||||
|
||||
TestCanAccessEchoService("public/fidl.examples.echo.Echo");
|
||||
|
||||
// Ensure GetOrCreateDirectory refers to the same "public" directory.
|
||||
outgoing_.GetOrCreateDirectory("public")->RemoveEntry(
|
||||
"fidl.examples.echo.Echo");
|
||||
TestCanAccessEchoService("public/fidl.examples.echo.Echo", false);
|
||||
}
|
||||
|
||||
TEST_F(OutgoingDirectoryTest, AddAndRemove) {
|
||||
ASSERT_EQ(ZX_ERR_NOT_FOUND,
|
||||
outgoing_.RemovePublicService<fidl::examples::echo::Echo>());
|
||||
|
||||
ASSERT_EQ(ZX_OK,
|
||||
outgoing_.AddPublicService(echo_impl_.GetHandler(dispatcher())));
|
||||
|
||||
ASSERT_EQ(ZX_ERR_ALREADY_EXISTS,
|
||||
outgoing_.AddPublicService(echo_impl_.GetHandler(dispatcher())));
|
||||
|
||||
TestCanAccessEchoService("public/fidl.examples.echo.Echo");
|
||||
|
||||
ASSERT_EQ(ZX_OK, outgoing_.RemovePublicService<fidl::examples::echo::Echo>());
|
||||
ASSERT_EQ(ZX_ERR_NOT_FOUND,
|
||||
outgoing_.RemovePublicService<fidl::examples::echo::Echo>());
|
||||
|
||||
TestCanAccessEchoService("public/fidl.examples.echo.Echo", false);
|
||||
}
|
||||
|
||||
TEST_F(OutgoingDirectoryTest, DebugDir) {
|
||||
AddEchoService(outgoing_.debug_dir());
|
||||
|
||||
TestCanAccessEchoService("debug/fidl.examples.echo.Echo");
|
||||
outgoing_.GetOrCreateDirectory("debug")->RemoveEntry(
|
||||
"fidl.examples.echo.Echo");
|
||||
TestCanAccessEchoService("debug/fidl.examples.echo.Echo", false);
|
||||
}
|
||||
|
||||
TEST_F(OutgoingDirectoryTest, CtrlDir) {
|
||||
AddEchoService(outgoing_.ctrl_dir());
|
||||
|
||||
TestCanAccessEchoService("ctrl/fidl.examples.echo.Echo");
|
||||
outgoing_.GetOrCreateDirectory("ctrl")->RemoveEntry(
|
||||
"fidl.examples.echo.Echo");
|
||||
TestCanAccessEchoService("ctrl/fidl.examples.echo.Echo", false);
|
||||
}
|
||||
|
||||
TEST_F(OutgoingDirectoryTest, GetOrCreateDirectory) {
|
||||
outgoing_.GetOrCreateDirectory("objects")->AddEntry(
|
||||
"test_svc_a",
|
||||
std::make_unique<vfs::Service>(echo_impl_.GetHandler(dispatcher())));
|
||||
outgoing_.GetOrCreateDirectory("objects")->AddEntry(
|
||||
"test_svc_b",
|
||||
std::make_unique<vfs::Service>(echo_impl_.GetHandler(dispatcher())));
|
||||
TestCanAccessEchoService("objects/test_svc_a");
|
||||
TestCanAccessEchoService("objects/test_svc_b");
|
||||
}
|
||||
|
||||
TEST_F(OutgoingDirectorySetupTest, Invalid) {
|
||||
sys::OutgoingDirectory outgoing;
|
||||
// TODO: This should return ZX_ERR_BAD_HANDLE.
|
||||
ASSERT_EQ(ZX_OK, outgoing.Serve(zx::channel(), dispatcher()));
|
||||
}
|
||||
|
||||
TEST_F(OutgoingDirectorySetupTest, AccessDenied) {
|
||||
zx::channel svc_client, svc_server;
|
||||
ASSERT_EQ(ZX_OK, zx::channel::create(0, &svc_client, &svc_server));
|
||||
|
||||
svc_server.replace(ZX_RIGHT_NONE, &svc_server);
|
||||
|
||||
sys::OutgoingDirectory outgoing;
|
||||
ASSERT_EQ(ZX_ERR_ACCESS_DENIED,
|
||||
outgoing.Serve(std::move(svc_server), dispatcher()));
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,72 +0,0 @@
|
||||
// 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 <lib/sys/cpp/testing/service_directory_provider.h>
|
||||
|
||||
#include "echo_server.h"
|
||||
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <lib/gtest/real_loop_fixture.h>
|
||||
#include <zircon/types.h>
|
||||
#include <memory>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "lib/async/dispatcher.h"
|
||||
#include "lib/fidl/cpp/interface_request.h"
|
||||
#include "lib/vfs/cpp/service.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class ServiceDirectoryProviderTests : public gtest::RealLoopFixture {
|
||||
protected:
|
||||
void ConnectToService(const std::shared_ptr<sys::ServiceDirectory>& svc,
|
||||
fidl::examples::echo::EchoPtr& echo) {
|
||||
svc->Connect(echo.NewRequest());
|
||||
}
|
||||
|
||||
EchoImpl echo_impl_;
|
||||
};
|
||||
|
||||
TEST_F(ServiceDirectoryProviderTests, TestInjectedServiceUsingMethod1) {
|
||||
sys::testing::ServiceDirectoryProvider svc_provider_;
|
||||
|
||||
ASSERT_EQ(ZX_OK,
|
||||
svc_provider_.AddService(echo_impl_.GetHandler(dispatcher())));
|
||||
|
||||
fidl::examples::echo::EchoPtr echo;
|
||||
|
||||
ConnectToService(svc_provider_.service_directory(), echo);
|
||||
|
||||
std::string result;
|
||||
echo->EchoString("hello",
|
||||
[&result](fidl::StringPtr value) { result = *value; });
|
||||
|
||||
RunLoopUntilIdle();
|
||||
EXPECT_EQ("hello", result);
|
||||
}
|
||||
|
||||
TEST_F(ServiceDirectoryProviderTests, TestInjectedServiceUsingMethod2) {
|
||||
sys::testing::ServiceDirectoryProvider svc_provider_;
|
||||
|
||||
ASSERT_EQ(ZX_OK,
|
||||
svc_provider_.AddService(
|
||||
std::make_unique<vfs::Service>(
|
||||
[&](zx::channel channel, async_dispatcher_t* dispatcher) {
|
||||
echo_impl_.AddBinding((std::move(channel)), dispatcher);
|
||||
}),
|
||||
echo_impl_.Name_));
|
||||
|
||||
fidl::examples::echo::EchoPtr echo;
|
||||
|
||||
ConnectToService(svc_provider_.service_directory(), echo);
|
||||
|
||||
std::string result;
|
||||
echo->EchoString("hello",
|
||||
[&result](fidl::StringPtr value) { result = *value; });
|
||||
|
||||
RunLoopUntilIdle();
|
||||
EXPECT_EQ("hello", result);
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,80 +0,0 @@
|
||||
// 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 <lib/sys/cpp/service_directory.h>
|
||||
|
||||
#include <fidl/examples/echo/cpp/fidl.h>
|
||||
#include <fuchsia/io/c/fidl.h>
|
||||
#include <lib/fidl/cpp/message_buffer.h>
|
||||
#include <lib/zx/channel.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
TEST(ServiceDirectoryTest, Control) {
|
||||
zx::channel svc_client, svc_server;
|
||||
ASSERT_EQ(ZX_OK, zx::channel::create(0, &svc_client, &svc_server));
|
||||
|
||||
sys::ServiceDirectory directory(std::move(svc_client));
|
||||
|
||||
fidl::InterfaceHandle<fidl::examples::echo::Echo> echo;
|
||||
EXPECT_EQ(ZX_OK, directory.Connect(echo.NewRequest()));
|
||||
|
||||
fidl::MessageBuffer buffer;
|
||||
auto message = buffer.CreateEmptyMessage();
|
||||
message.Read(svc_server.get(), 0);
|
||||
|
||||
EXPECT_TRUE(message.has_header());
|
||||
EXPECT_EQ(fuchsia_io_DirectoryOpenOrdinal, message.ordinal());
|
||||
}
|
||||
|
||||
TEST(ServiceDirectoryTest, CreateWithRequest) {
|
||||
zx::channel svc_server;
|
||||
|
||||
auto directory = sys::ServiceDirectory::CreateWithRequest(&svc_server);
|
||||
|
||||
fidl::InterfaceHandle<fidl::examples::echo::Echo> echo;
|
||||
EXPECT_EQ(ZX_OK, directory->Connect(echo.NewRequest()));
|
||||
|
||||
fidl::MessageBuffer buffer;
|
||||
auto message = buffer.CreateEmptyMessage();
|
||||
message.Read(svc_server.get(), 0);
|
||||
|
||||
EXPECT_TRUE(message.has_header());
|
||||
EXPECT_EQ(fuchsia_io_DirectoryOpenOrdinal, message.ordinal());
|
||||
}
|
||||
|
||||
TEST(ServiceDirectoryTest, Clone) {
|
||||
zx::channel svc_server;
|
||||
|
||||
auto directory = sys::ServiceDirectory::CreateWithRequest(&svc_server);
|
||||
|
||||
fidl::InterfaceHandle<fidl::examples::echo::Echo> echo;
|
||||
EXPECT_TRUE(directory->CloneChannel().is_valid());
|
||||
|
||||
fidl::MessageBuffer buffer;
|
||||
auto message = buffer.CreateEmptyMessage();
|
||||
message.Read(svc_server.get(), 0);
|
||||
|
||||
EXPECT_TRUE(message.has_header());
|
||||
EXPECT_EQ(fuchsia_io_DirectoryCloneOrdinal, message.ordinal());
|
||||
}
|
||||
|
||||
TEST(ServiceDirectoryTest, Invalid) {
|
||||
sys::ServiceDirectory directory((zx::channel()));
|
||||
|
||||
fidl::InterfaceHandle<fidl::examples::echo::Echo> echo;
|
||||
EXPECT_EQ(ZX_ERR_UNAVAILABLE, directory.Connect(echo.NewRequest()));
|
||||
}
|
||||
|
||||
TEST(ServiceDirectoryTest, AccessDenied) {
|
||||
zx::channel svc_client, svc_server;
|
||||
ASSERT_EQ(ZX_OK, zx::channel::create(0, &svc_client, &svc_server));
|
||||
|
||||
svc_client.replace(ZX_RIGHT_NONE, &svc_client);
|
||||
|
||||
sys::ServiceDirectory directory(std::move(svc_client));
|
||||
|
||||
fidl::InterfaceHandle<fidl::examples::echo::Echo> echo;
|
||||
EXPECT_EQ(ZX_ERR_ACCESS_DENIED, directory.Connect(echo.NewRequest()));
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import("//build/fuchsia/sdk.gni")
|
||||
|
||||
source_set("cpp") {
|
||||
include_dirs = [ "../../.." ]
|
||||
|
||||
sources = [
|
||||
"connection.cc",
|
||||
"connection.h",
|
||||
"directory.cc",
|
||||
"directory.h",
|
||||
"file.cc",
|
||||
"file.h",
|
||||
"flags.h",
|
||||
"internal/directory_connection.cc",
|
||||
"internal/directory_connection.h",
|
||||
"internal/dirent_filler.cc",
|
||||
"internal/dirent_filler.h",
|
||||
"internal/file_connection.cc",
|
||||
"internal/file_connection.h",
|
||||
"internal/node_connection.cc",
|
||||
"internal/node_connection.h",
|
||||
"lazy_dir.cc",
|
||||
"lazy_dir.h",
|
||||
"node.cc",
|
||||
"node.h",
|
||||
"pseudo_dir.cc",
|
||||
"pseudo_dir.h",
|
||||
"pseudo_file.cc",
|
||||
"pseudo_file.h",
|
||||
"remote_dir.cc",
|
||||
"remote_dir.h",
|
||||
"service.cc",
|
||||
"service.h",
|
||||
"vmo_file.cc",
|
||||
"vmo_file.h",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
"$fuchsia_sdk_root/fidl:fuchsia.io",
|
||||
"$fuchsia_sdk_root/pkg:fdio",
|
||||
]
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
abarth@google.com
|
||||
anmittal@google.com
|
@ -1,76 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/connection.h>
|
||||
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/vfs/cpp/node.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
Connection::Connection(uint32_t flags) : flags_(flags) {}
|
||||
|
||||
Connection::~Connection() = default;
|
||||
|
||||
void Connection::Clone(Node* vn, uint32_t flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
vn->Clone(flags, flags_, std::move(object), dispatcher);
|
||||
}
|
||||
|
||||
void Connection::Close(Node* vn, fuchsia::io::Node::CloseCallback callback) {
|
||||
callback(ZX_OK);
|
||||
vn->Close(this);
|
||||
// |this| is destroyed at this point.
|
||||
}
|
||||
|
||||
void Connection::Describe(Node* vn,
|
||||
fuchsia::io::Node::DescribeCallback callback) {
|
||||
fuchsia::io::NodeInfo info{};
|
||||
vn->Describe(&info);
|
||||
if (info.has_invalid_tag()) {
|
||||
vn->Close(this);
|
||||
} else {
|
||||
callback(std::move(info));
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::Sync(Node* vn, fuchsia::io::Node::SyncCallback callback) {
|
||||
// TODO: Check flags.
|
||||
callback(vn->Sync());
|
||||
}
|
||||
|
||||
void Connection::GetAttr(Node* vn,
|
||||
fuchsia::io::Node::GetAttrCallback callback) {
|
||||
// TODO: Check flags.
|
||||
fuchsia::io::NodeAttributes attributes{};
|
||||
zx_status_t status = vn->GetAttr(&attributes);
|
||||
callback(status, attributes);
|
||||
}
|
||||
|
||||
void Connection::SetAttr(Node* vn, uint32_t flags,
|
||||
fuchsia::io::NodeAttributes attributes,
|
||||
fuchsia::io::Node::SetAttrCallback callback) {
|
||||
// TODO: Check flags.
|
||||
callback(vn->SetAttr(flags, attributes));
|
||||
}
|
||||
|
||||
void Connection::Ioctl(Node* vn, uint32_t opcode, uint64_t max_out,
|
||||
std::vector<zx::handle> handles, std::vector<uint8_t> in,
|
||||
fuchsia::io::Node::IoctlCallback callback) {
|
||||
callback(ZX_ERR_NOT_SUPPORTED, std::vector<zx::handle>(),
|
||||
std::vector<uint8_t>());
|
||||
}
|
||||
|
||||
std::unique_ptr<fuchsia::io::NodeInfo> Connection::NodeInfoIfStatusOk(
|
||||
Node* vn, zx_status_t status) {
|
||||
std::unique_ptr<fuchsia::io::NodeInfo> node_info;
|
||||
if (status == ZX_OK) {
|
||||
node_info = std::make_unique<fuchsia::io::NodeInfo>();
|
||||
vn->Describe(node_info.get());
|
||||
}
|
||||
return node_info;
|
||||
}
|
||||
|
||||
} // namespace vfs
|
@ -1,99 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_CONNECTION_H_
|
||||
#define LIB_VFS_CPP_CONNECTION_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/async/dispatcher.h>
|
||||
#include <lib/zx/channel.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace vfs {
|
||||
class Node;
|
||||
|
||||
// A connection to a file system object.
|
||||
//
|
||||
// A connection manages a single zx::channel, typically to another process.
|
||||
class Connection {
|
||||
public:
|
||||
// Create a connection with the given |flags|.
|
||||
//
|
||||
// TODO: Document the list of supported flags.
|
||||
explicit Connection(uint32_t flags);
|
||||
virtual ~Connection();
|
||||
|
||||
Connection(const Connection&) = delete;
|
||||
Connection& operator=(const Connection&) = delete;
|
||||
|
||||
// The flags associated with this connection.
|
||||
//
|
||||
// These flags are typically received from |fuchsia.io.Node/Clone| or
|
||||
// |fuchsia.io.Directory/Open|.
|
||||
//
|
||||
// For example, |ZX_FS_RIGHT_READABLE|.
|
||||
uint32_t flags() const { return flags_; }
|
||||
|
||||
// The current file offset.
|
||||
//
|
||||
// Typically used to position |read| and |write| operations. Can be adjusted
|
||||
// using |lseek|.
|
||||
uint64_t offset() const { return offset_; }
|
||||
void set_offset(uint64_t offset) { offset_ = offset; }
|
||||
|
||||
// Send OnOpen event for |fuchsia::io::Node|.
|
||||
//
|
||||
// This function will not check for |OPEN_FLAG_DESCRIBE|. Caller should do
|
||||
// that. Every subclass must implement this.
|
||||
virtual void SendOnOpenEvent(zx_status_t status) = 0;
|
||||
|
||||
// Associate |request| with this connection.
|
||||
//
|
||||
// Waits for messages asynchronously on the |request| channel using
|
||||
// |dispatcher|. If |dispatcher| is |nullptr|, the implementation will call
|
||||
// |async_get_default_dispatcher| to obtain the default dispatcher for the
|
||||
// current thread.
|
||||
//
|
||||
// Typically called during connection setup.
|
||||
virtual zx_status_t Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) = 0;
|
||||
|
||||
protected:
|
||||
// Implementations for common |fuchsia.io.Node| operations. Used by subclasses
|
||||
// to avoid code duplication.
|
||||
|
||||
void Clone(Node* vn, uint32_t flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object,
|
||||
async_dispatcher_t* dispatcher);
|
||||
void Close(Node* vn, fuchsia::io::Node::CloseCallback callback);
|
||||
void Describe(Node* vn, fuchsia::io::Node::DescribeCallback callback);
|
||||
void Sync(Node* vn, fuchsia::io::Node::SyncCallback callback);
|
||||
void GetAttr(Node* vn, fuchsia::io::Node::GetAttrCallback callback);
|
||||
void SetAttr(Node* vn, uint32_t flags, fuchsia::io::NodeAttributes attributes,
|
||||
fuchsia::io::Node::SetAttrCallback callback);
|
||||
void Ioctl(Node* vn, uint32_t opcode, uint64_t max_out,
|
||||
std::vector<zx::handle> handles, std::vector<uint8_t> in,
|
||||
fuchsia::io::Node::IoctlCallback callback);
|
||||
|
||||
// returns |fuchsia.io.NodeInfo| if status is |ZX_OK|, else returns null
|
||||
// inside unique_ptr.
|
||||
std::unique_ptr<fuchsia::io::NodeInfo> NodeInfoIfStatusOk(Node* vn,
|
||||
zx_status_t status);
|
||||
|
||||
private:
|
||||
// The flags associated with this connection.
|
||||
//
|
||||
// See |flags()| for more information.
|
||||
uint32_t flags_ = 0u;
|
||||
|
||||
// The current file offset.
|
||||
//
|
||||
// See |offset()| for more information.
|
||||
uint64_t offset_ = 0u;
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_CONNECTION_H_
|
@ -1,182 +0,0 @@
|
||||
|
||||
// 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 <lib/vfs/cpp/directory.h>
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/vfs/cpp/internal/directory_connection.h>
|
||||
#include <zircon/errors.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
Directory::Directory() = default;
|
||||
|
||||
Directory::~Directory() = default;
|
||||
|
||||
void Directory::Describe(fuchsia::io::NodeInfo* out_info) {
|
||||
out_info->set_directory(fuchsia::io::DirectoryObject());
|
||||
}
|
||||
|
||||
zx_status_t Directory::Lookup(const std::string& name, Node** out_node) const {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
zx_status_t Directory::CreateConnection(
|
||||
uint32_t flags, std::unique_ptr<Connection>* connection) {
|
||||
*connection = std::make_unique<internal::DirectoryConnection>(flags, this);
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
uint32_t Directory::GetAdditionalAllowedFlags() const {
|
||||
// TODO(ZX-3251): overide this in PseudoDir and Lazydir and remove
|
||||
// OPEN_RIGHT_WRITABLE flag.
|
||||
return fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE |
|
||||
fuchsia::io::OPEN_FLAG_DIRECTORY;
|
||||
}
|
||||
|
||||
uint32_t Directory::GetProhibitiveFlags() const {
|
||||
return fuchsia::io::OPEN_FLAG_CREATE |
|
||||
fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT |
|
||||
fuchsia::io::OPEN_FLAG_TRUNCATE | fuchsia::io::OPEN_FLAG_APPEND;
|
||||
}
|
||||
|
||||
bool Directory::IsDirectory() const { return true; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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(uint32_t flags, uint32_t mode, const char* path,
|
||||
size_t path_len, zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
Node* n = nullptr;
|
||||
bool is_dir = false;
|
||||
size_t new_path_len = path_len;
|
||||
const char* new_path = path;
|
||||
zx_status_t status =
|
||||
LookupPath(path, path_len, &is_dir, &n, &new_path, &new_path_len);
|
||||
if (status != ZX_OK) {
|
||||
return SendOnOpenEventOnError(flags, std::move(request), status);
|
||||
}
|
||||
if (n->IsRemote() && new_path_len > 0) {
|
||||
fuchsia::io::DirectoryPtr temp_dir;
|
||||
zx_status_t status = n->Serve(fuchsia::io::OPEN_RIGHT_READABLE |
|
||||
fuchsia::io::OPEN_RIGHT_WRITABLE |
|
||||
fuchsia::io::OPEN_FLAG_DIRECTORY,
|
||||
temp_dir.NewRequest().TakeChannel());
|
||||
if (status != ZX_OK) {
|
||||
return SendOnOpenEventOnError(flags, std::move(request), status);
|
||||
}
|
||||
temp_dir->Open(
|
||||
flags, mode, std::string(new_path, new_path_len),
|
||||
fidl::InterfaceRequest<fuchsia::io::Node>(std::move(request)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dir) {
|
||||
// append directory flag
|
||||
flags = flags | fuchsia::io::OPEN_FLAG_DIRECTORY;
|
||||
}
|
||||
n->ServeWithMode(flags, mode, std::move(request), dispatcher);
|
||||
}
|
||||
|
||||
} // namespace vfs
|
@ -1,110 +0,0 @@
|
||||
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_DIRECTORY_H_
|
||||
#define LIB_VFS_CPP_DIRECTORY_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/vfs/cpp/node.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
// A directory object in a file system.
|
||||
//
|
||||
// Implements the |fuchsia.io.Directory| interface. Incoming connections are
|
||||
// owned by this object and will be destroyed when this object is destroyed.
|
||||
//
|
||||
// Subclass to implement specific directory semantics.
|
||||
//
|
||||
// See also:
|
||||
//
|
||||
// * File, which represents file objects.
|
||||
class Directory : public Node {
|
||||
public:
|
||||
Directory();
|
||||
~Directory() override;
|
||||
|
||||
// |Node| implementation
|
||||
zx_status_t Lookup(const std::string& name, Node** out_node) const override;
|
||||
|
||||
// Override that describes this object as a directory.
|
||||
void Describe(fuchsia::io::NodeInfo* out_info) override;
|
||||
|
||||
// Enumerates Directory
|
||||
//
|
||||
// |offset| will start with 0 and then implementation can set offset as it
|
||||
// pleases.
|
||||
//
|
||||
// Returns |ZX_OK| if able to read atleast one dentry else returns
|
||||
// |ZX_ERR_INVALID_ARGS| with |out_actual| as 0 and |out_offset| as |offset|.
|
||||
virtual zx_status_t Readdir(uint64_t offset, void* data, uint64_t len,
|
||||
uint64_t* out_offset, uint64_t* out_actual) = 0;
|
||||
|
||||
// Parses path and opens correct node.
|
||||
//
|
||||
// Called from |fuchsia.io.Directory#Open|.
|
||||
void Open(uint32_t flags, uint32_t mode, const char* path, size_t path_len,
|
||||
zx::channel request, async_dispatcher_t* dispatcher);
|
||||
|
||||
// Validates passed path
|
||||
//
|
||||
// Returns |ZX_ERR_INVALID_ARGS| if path_len is more than |NAME_MAX| or if
|
||||
// |path| starts with ".." or "/".
|
||||
// Returns |ZX_OK| on valid path.
|
||||
static zx_status_t ValidatePath(const char* path, size_t path_len);
|
||||
|
||||
// Walks provided path to find first node name in from path and then
|
||||
// sets |out_path| and |out_len| to correct position in path beyond current
|
||||
// node name and sets |out_key| to node name.
|
||||
//
|
||||
// Calls |ValidatePath| and returns |status| on error.
|
||||
// Sets |out_is_self| to true if path is empty or '.' or './'
|
||||
//
|
||||
// Supports paths like 'a/./b//.'
|
||||
// Supports repetitive '/'
|
||||
// Doesn't support 'a/../a/b'
|
||||
//
|
||||
// eg:
|
||||
// path ="a/b/c/d", out_path would be "b/c/d"
|
||||
// path =".", out_path would be ""
|
||||
// path ="./", out_path would be ""
|
||||
// path ="a/b/", out_path would be "b/"
|
||||
static zx_status_t WalkPath(const char* path, size_t path_len,
|
||||
const char** out_path, size_t* out_len,
|
||||
std::string* out_key, bool* out_is_self);
|
||||
|
||||
protected:
|
||||
zx_status_t CreateConnection(
|
||||
uint32_t flags, std::unique_ptr<Connection>* connection) override;
|
||||
|
||||
// Walks path and returns correct |node| inside this and containing directory
|
||||
// if found.
|
||||
// Sets |out_is_dir| to true if path has '/' or '/.' at the end.
|
||||
//
|
||||
// This function will return intermidiate node if |IsRemote| on that node is
|
||||
// true and will sets |out_path| and |out_len| as rest of the remaining path.
|
||||
// For example: if path is /a/b/c/d/f/g and c is a remote node, it will return
|
||||
// c in |out_node|, "d/f/g" in |out_path| and |out_len|.
|
||||
//
|
||||
// Calls |WalkPath| in loop and returns status on error. Returns
|
||||
// |ZX_ERR_NOT_DIR| if path tries to search for node within a non-directory
|
||||
// node type.
|
||||
zx_status_t LookupPath(const char* path, size_t path_len, bool* out_is_dir,
|
||||
Node** out_node, const char** out_path,
|
||||
size_t* out_len);
|
||||
|
||||
bool IsDirectory() const override;
|
||||
|
||||
uint32_t GetAdditionalAllowedFlags() const override;
|
||||
|
||||
uint32_t GetProhibitiveFlags() const override;
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_DIRECTORY_H_
|
@ -1,46 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/file.h>
|
||||
|
||||
#include <lib/vfs/cpp/internal/file_connection.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
File::File() = default;
|
||||
|
||||
File::~File() = default;
|
||||
|
||||
void File::Describe(fuchsia::io::NodeInfo* out_info) {
|
||||
out_info->set_file(fuchsia::io::FileObject());
|
||||
}
|
||||
|
||||
uint32_t File::GetAdditionalAllowedFlags() const {
|
||||
return fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE |
|
||||
fuchsia::io::OPEN_FLAG_TRUNCATE;
|
||||
}
|
||||
|
||||
zx_status_t File::ReadAt(uint64_t count, uint64_t offset,
|
||||
std::vector<uint8_t>* out_data) {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
zx_status_t File::WriteAt(std::vector<uint8_t> data, uint64_t offset,
|
||||
uint64_t* out_actual) {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
zx_status_t File::Truncate(uint64_t length) { return ZX_ERR_NOT_SUPPORTED; }
|
||||
|
||||
zx_status_t File::CreateConnection(uint32_t flags,
|
||||
std::unique_ptr<Connection>* connection) {
|
||||
*connection = std::make_unique<internal::FileConnection>(flags, this);
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
size_t File::GetCapacity() { return std::numeric_limits<size_t>::max(); }
|
||||
|
||||
bool File::IsDirectory() const { return false; }
|
||||
|
||||
} // namespace vfs
|
@ -1,75 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_FILE_H_
|
||||
#define LIB_VFS_CPP_FILE_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/vfs/cpp/node.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
// A file object in a file system.
|
||||
//
|
||||
// Implements the |fuchsia.io.File| interface. Incoming connections are
|
||||
// owned by this object and will be destroyed when this object is destroyed.
|
||||
//
|
||||
// Subclass to implement specific file semantics.
|
||||
//
|
||||
// See also:
|
||||
//
|
||||
// * Directory, which represents directory objects.
|
||||
class File : public Node {
|
||||
public:
|
||||
File();
|
||||
~File() override;
|
||||
|
||||
// Create |count| bytes of data from the file at the given |offset|.
|
||||
//
|
||||
// The data read should be copied to |out_data|, which should be empty when
|
||||
// passed as an argument. When |ReadAt| returns, |out_data| should contain no
|
||||
// more than |count| bytes.
|
||||
virtual zx_status_t ReadAt(uint64_t count, uint64_t offset,
|
||||
std::vector<uint8_t>* out_data);
|
||||
|
||||
// Write the given |data| to the file at the given |offset|.
|
||||
//
|
||||
// Data should be copied into the file starting at the beginning of |data|.
|
||||
// If |WriteAt| returns |ZX_OK|, |out_actual| should contain the number of
|
||||
// bytes actually written to the file.
|
||||
virtual zx_status_t WriteAt(std::vector<uint8_t> data, uint64_t offset,
|
||||
uint64_t* out_actual);
|
||||
|
||||
// Resize the file to the given |length|.
|
||||
virtual zx_status_t Truncate(uint64_t length);
|
||||
|
||||
// Override that describes this object as a file.
|
||||
void Describe(fuchsia::io::NodeInfo* out_info) override;
|
||||
|
||||
// Returns current file length.
|
||||
//
|
||||
// All implementations should implement this.
|
||||
virtual uint64_t GetLength() = 0;
|
||||
|
||||
// Returns file capacity.
|
||||
//
|
||||
// Seek() uses this to return ZX_ERR_OUT_OF_RANGE if new seek is more than
|
||||
// this value.
|
||||
virtual size_t GetCapacity();
|
||||
|
||||
protected:
|
||||
zx_status_t CreateConnection(
|
||||
uint32_t flags, std::unique_ptr<Connection>* connection) override;
|
||||
|
||||
uint32_t GetAdditionalAllowedFlags() const override;
|
||||
|
||||
bool IsDirectory() const override;
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_FILE_H_
|
@ -1,129 +0,0 @@
|
||||
// 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 <lib/async-loop/cpp/loop.h>
|
||||
#include <lib/fdio/limits.h>
|
||||
#include <lib/fdio/fd.h>
|
||||
#include <lib/fdio/fdio.h>
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <unistd.h>
|
||||
#include <zircon/processargs.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "lib/vfs/cpp/file.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class TestFile : public vfs::File {
|
||||
public:
|
||||
explicit TestFile(std::vector<uint8_t>* buffer) : buffer_(buffer) {}
|
||||
~TestFile() override = default;
|
||||
|
||||
zx_status_t ReadAt(uint64_t count, uint64_t offset,
|
||||
std::vector<uint8_t>* out_data) override {
|
||||
if (offset >= buffer_->size()) {
|
||||
return ZX_OK;
|
||||
}
|
||||
size_t actual = std::min(count, buffer_->size() - offset);
|
||||
out_data->resize(actual);
|
||||
std::copy_n(buffer_->begin() + offset, actual, out_data->begin());
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t WriteAt(std::vector<uint8_t> data, uint64_t offset,
|
||||
uint64_t* out_actual) override {
|
||||
if (offset >= buffer_->size()) {
|
||||
*out_actual = 0u;
|
||||
return ZX_OK;
|
||||
}
|
||||
size_t actual = std::min(data.size(), buffer_->size() - offset);
|
||||
std::copy_n(data.begin(), actual, buffer_->begin() + offset);
|
||||
*out_actual = actual;
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
uint64_t GetLength() override { return buffer_->size(); }
|
||||
|
||||
size_t GetCapacity() override { return buffer_->size(); }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t>* buffer_;
|
||||
};
|
||||
|
||||
int OpenAsFD(vfs::Node* node, async_dispatcher_t* dispatcher) {
|
||||
zx::channel local, remote;
|
||||
EXPECT_EQ(ZX_OK, zx::channel::create(0, &local, &remote));
|
||||
EXPECT_EQ(ZX_OK, node->Serve(fuchsia::io::OPEN_RIGHT_READABLE |
|
||||
fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
std::move(remote), dispatcher));
|
||||
int fd = -1;
|
||||
EXPECT_EQ(ZX_OK, fdio_fd_create(local.release(), &fd));
|
||||
return fd;
|
||||
}
|
||||
|
||||
TEST(File, Control) {
|
||||
std::vector<uint8_t> store(12u);
|
||||
TestFile file(&store);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
int fd = OpenAsFD(&file, loop.dispatcher());
|
||||
ASSERT_LE(0, fd);
|
||||
|
||||
ASSERT_EQ(4, write(fd, "abcd", 4));
|
||||
EXPECT_EQ(0, strcmp("abcd", reinterpret_cast<char*>(store.data())));
|
||||
ASSERT_EQ(5, write(fd, "exxxi", 5));
|
||||
EXPECT_EQ(0, strcmp("abcdexxxi", reinterpret_cast<char*>(store.data())));
|
||||
ASSERT_EQ(3, pwrite(fd, "fgh", 3, 5));
|
||||
EXPECT_EQ(0, strcmp("abcdefghi", reinterpret_cast<char*>(store.data())));
|
||||
|
||||
char buffer[1024];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
ASSERT_EQ(7, pread(fd, buffer, 7, 1));
|
||||
EXPECT_EQ(0, strcmp("bcdefgh", buffer));
|
||||
|
||||
ASSERT_EQ(3, write(fd, "jklmn", 5));
|
||||
EXPECT_EQ('l', store[store.size() - 1]);
|
||||
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
ASSERT_EQ(4, pread(fd, buffer, 10, 8));
|
||||
EXPECT_EQ(0, strcmp("ijkl", buffer));
|
||||
|
||||
ASSERT_GE(0, close(fd));
|
||||
}
|
||||
|
||||
TEST(File, Clone) {
|
||||
std::vector<uint8_t> store(12u);
|
||||
TestFile file(&store);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
int fd = OpenAsFD(&file, loop.dispatcher());
|
||||
ASSERT_LE(0, fd);
|
||||
|
||||
ASSERT_EQ(4, write(fd, "abcd", 4));
|
||||
EXPECT_EQ(0, strcmp("abcd", reinterpret_cast<char*>(store.data())));
|
||||
|
||||
zx_handle_t handle = ZX_HANDLE_INVALID;
|
||||
ASSERT_EQ(ZX_OK, fdio_fd_clone(fd, &handle));
|
||||
|
||||
int cloned = -1;
|
||||
EXPECT_EQ(ZX_OK, fdio_fd_create(handle, &cloned));
|
||||
ASSERT_LE(0, cloned);
|
||||
|
||||
ASSERT_EQ(3, write(cloned, "xyz", 3));
|
||||
EXPECT_EQ(0, strcmp("xyzd", reinterpret_cast<char*>(store.data())));
|
||||
|
||||
ASSERT_GE(0, close(fd));
|
||||
ASSERT_GE(0, close(cloned));
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,39 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_FLAGS_H_
|
||||
#define LIB_VFS_CPP_FLAGS_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
class Flags {
|
||||
public:
|
||||
Flags() = delete;
|
||||
|
||||
static bool IsReadable(uint32_t flags) {
|
||||
return (flags & fuchsia::io::OPEN_RIGHT_READABLE) != 0;
|
||||
}
|
||||
|
||||
static bool IsWritable(uint32_t flags) {
|
||||
return (flags & fuchsia::io::OPEN_RIGHT_WRITABLE) != 0;
|
||||
}
|
||||
|
||||
static bool IsDirectory(uint32_t flags) {
|
||||
return (flags & fuchsia::io::OPEN_FLAG_DIRECTORY) != 0;
|
||||
}
|
||||
|
||||
static bool ShouldDescribe(uint32_t flags) {
|
||||
return (flags & fuchsia::io::OPEN_FLAG_DESCRIBE) != 0;
|
||||
}
|
||||
|
||||
static bool IsPathOnly(uint32_t flags) {
|
||||
return (flags & fuchsia::io::OPEN_FLAG_NODE_REFERENCE) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_FLAGS_H_
|
@ -1,119 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/internal/directory_connection.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <lib/vfs/cpp/directory.h>
|
||||
#include <lib/vfs/cpp/flags.h>
|
||||
|
||||
namespace vfs {
|
||||
namespace internal {
|
||||
|
||||
DirectoryConnection::DirectoryConnection(uint32_t flags, vfs::Directory* vn)
|
||||
: Connection(flags), vn_(vn), binding_(this) {}
|
||||
|
||||
DirectoryConnection::~DirectoryConnection() = default;
|
||||
|
||||
zx_status_t DirectoryConnection::Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
zx_status_t status = binding_.Bind(std::move(request), dispatcher);
|
||||
if (status != ZX_OK) {
|
||||
return status;
|
||||
}
|
||||
binding_.set_error_handler([this](zx_status_t status) { vn_->Close(this); });
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
void DirectoryConnection::Clone(
|
||||
uint32_t flags, fidl::InterfaceRequest<fuchsia::io::Node> object) {
|
||||
Connection::Clone(vn_, flags, std::move(object), binding_.dispatcher());
|
||||
}
|
||||
|
||||
void DirectoryConnection::Close(CloseCallback callback) {
|
||||
Connection::Close(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void DirectoryConnection::Describe(DescribeCallback callback) {
|
||||
Connection::Describe(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void DirectoryConnection::Sync(SyncCallback callback) {
|
||||
Connection::Sync(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void DirectoryConnection::GetAttr(GetAttrCallback callback) {
|
||||
Connection::GetAttr(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void DirectoryConnection::SetAttr(uint32_t flags,
|
||||
fuchsia::io::NodeAttributes attributes,
|
||||
SetAttrCallback callback) {
|
||||
Connection::SetAttr(vn_, flags, attributes, std::move(callback));
|
||||
}
|
||||
|
||||
void DirectoryConnection::Ioctl(uint32_t opcode, uint64_t max_out,
|
||||
std::vector<zx::handle> handles,
|
||||
std::vector<uint8_t> in,
|
||||
IoctlCallback callback) {
|
||||
Connection::Ioctl(vn_, opcode, max_out, std::move(handles), std::move(in),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void DirectoryConnection::Open(
|
||||
uint32_t flags, uint32_t mode, std::string path,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object) {
|
||||
vn_->Open(flags, mode, path.data(), path.length(), object.TakeChannel(),
|
||||
binding_.dispatcher());
|
||||
}
|
||||
|
||||
void DirectoryConnection::Unlink(::std::string path, UnlinkCallback callback) {
|
||||
callback(ZX_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
void DirectoryConnection::ReadDirents(uint64_t max_bytes,
|
||||
ReadDirentsCallback callback) {
|
||||
uint64_t new_offset = 0, out_bytes = 0;
|
||||
std::vector<uint8_t> vec(max_bytes);
|
||||
zx_status_t status =
|
||||
vn_->Readdir(offset(), vec.data(), max_bytes, &new_offset, &out_bytes);
|
||||
ZX_DEBUG_ASSERT(out_bytes <= max_bytes);
|
||||
vec.resize(out_bytes);
|
||||
if (status == ZX_OK) {
|
||||
set_offset(new_offset);
|
||||
}
|
||||
callback(status, std::move(vec));
|
||||
}
|
||||
|
||||
void DirectoryConnection::Rewind(RewindCallback callback) {
|
||||
set_offset(0);
|
||||
callback(ZX_OK);
|
||||
}
|
||||
|
||||
void DirectoryConnection::GetToken(GetTokenCallback callback) {
|
||||
callback(ZX_ERR_NOT_SUPPORTED, zx::handle());
|
||||
}
|
||||
|
||||
void DirectoryConnection::Rename(::std::string src, zx::handle dst_parent_token,
|
||||
std::string dst, RenameCallback callback) {
|
||||
callback(ZX_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
void DirectoryConnection::Link(::std::string src, zx::handle dst_parent_token,
|
||||
std::string dst, LinkCallback callback) {
|
||||
callback(ZX_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
void DirectoryConnection::Watch(uint32_t mask, uint32_t options,
|
||||
zx::channel watcher, WatchCallback callback) {
|
||||
// TODO: Implement watch.
|
||||
}
|
||||
|
||||
void DirectoryConnection::SendOnOpenEvent(zx_status_t status) {
|
||||
binding_.events().OnOpen(status, NodeInfoIfStatusOk(vn_, status));
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace vfs
|
@ -1,66 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_INTERNAL_DIRECTORY_CONNECTION_H_
|
||||
#define LIB_VFS_CPP_INTERNAL_DIRECTORY_CONNECTION_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/binding.h>
|
||||
#include <lib/vfs/cpp/connection.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace vfs {
|
||||
class Directory;
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Binds an implementation of |fuchsia.io.Directory| to a |vfs::Directory|.
|
||||
class DirectoryConnection final : public Connection,
|
||||
public fuchsia::io::Directory {
|
||||
public:
|
||||
// Create a connection to |vn| with the given |flags|.
|
||||
DirectoryConnection(uint32_t flags, vfs::Directory* vn);
|
||||
~DirectoryConnection() override;
|
||||
|
||||
// Start listening for |fuchsia.io.Directory| messages on |request|.
|
||||
zx_status_t Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) override;
|
||||
|
||||
// |fuchsia::io::Directory| Implementation:
|
||||
void Clone(uint32_t flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object) override;
|
||||
void Close(CloseCallback callback) override;
|
||||
void Describe(DescribeCallback callback) override;
|
||||
void Sync(SyncCallback callback) override;
|
||||
void GetAttr(GetAttrCallback callback) override;
|
||||
void SetAttr(uint32_t flags, fuchsia::io::NodeAttributes attributes,
|
||||
SetAttrCallback callback) override;
|
||||
void Ioctl(uint32_t opcode, uint64_t max_out, std::vector<zx::handle> handles,
|
||||
std::vector<uint8_t> in, IoctlCallback callback) override;
|
||||
void Open(uint32_t flags, uint32_t mode, std::string path,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object) override;
|
||||
void Unlink(std::string path, UnlinkCallback callback) override;
|
||||
void ReadDirents(uint64_t max_bytes, ReadDirentsCallback callback) override;
|
||||
void Rewind(RewindCallback callback) override;
|
||||
void GetToken(GetTokenCallback callback) override;
|
||||
void Rename(std::string src, zx::handle dst_parent_token, std::string dst,
|
||||
RenameCallback callback) override;
|
||||
void Link(std::string src, zx::handle dst_parent_token, std::string dst,
|
||||
LinkCallback callback) override;
|
||||
void Watch(uint32_t mask, uint32_t options, zx::channel watcher,
|
||||
WatchCallback callback) override;
|
||||
|
||||
// |Connection| Implementation:
|
||||
void SendOnOpenEvent(zx_status_t status) override;
|
||||
|
||||
private:
|
||||
vfs::Directory* vn_;
|
||||
fidl::Binding<fuchsia::io::Directory> binding_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_INTERNAL_DIRECTORY_CONNECTION_H_
|
@ -1,38 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/internal/dirent_filler.h>
|
||||
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <limits.h>
|
||||
|
||||
namespace vfs {
|
||||
namespace internal {
|
||||
|
||||
DirentFiller::DirentFiller(void* ptr, uint64_t len)
|
||||
: ptr_(static_cast<char*>(ptr)), pos_(0), len_(len) {}
|
||||
|
||||
zx_status_t DirentFiller::Next(const std::string& name, uint8_t type,
|
||||
uint64_t ino) {
|
||||
return Next(name.data(), name.length(), type, ino);
|
||||
}
|
||||
|
||||
zx_status_t DirentFiller::Next(const char* name, size_t name_len, uint8_t type,
|
||||
uint64_t ino) {
|
||||
vdirent_t* de = reinterpret_cast<vdirent_t*>(ptr_ + pos_);
|
||||
size_t sz = sizeof(vdirent_t) + name_len;
|
||||
|
||||
if (sz > len_ - pos_ || name_len > NAME_MAX) {
|
||||
return ZX_ERR_INVALID_ARGS;
|
||||
}
|
||||
de->ino = ino;
|
||||
de->size = static_cast<uint8_t>(name_len);
|
||||
de->type = type;
|
||||
memcpy(de->name, name, name_len);
|
||||
pos_ += sz;
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace vfs
|
@ -1,46 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_INTERNAL_DIRENT_FILLER_H_
|
||||
#define LIB_VFS_CPP_INTERNAL_DIRENT_FILLER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <zircon/types.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace vfs {
|
||||
namespace internal {
|
||||
|
||||
// Helper class used to fill direntries during calls to Readdir.
|
||||
class DirentFiller {
|
||||
public:
|
||||
DirentFiller(const DirentFiller&) = delete;
|
||||
DirentFiller& operator=(const DirentFiller&) = delete;
|
||||
|
||||
DirentFiller(void* ptr, uint64_t len);
|
||||
|
||||
// Attempts to add the name to the end of the dirent buffer
|
||||
// which is returned by readdir.
|
||||
// Will not write anything incase of error.
|
||||
zx_status_t Next(const std::string& name, uint8_t type, uint64_t ino);
|
||||
|
||||
// Attempts to add the name to the end of the dirent buffer
|
||||
// which is returned by readdir.
|
||||
// Will not write anything incase of error.
|
||||
zx_status_t Next(const char* name, size_t name_len, uint8_t type,
|
||||
uint64_t ino);
|
||||
|
||||
uint64_t GetBytesFilled() const { return pos_; }
|
||||
|
||||
private:
|
||||
char* ptr_;
|
||||
uint64_t pos_;
|
||||
const uint64_t len_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_INTERNAL_DIRENT_FILLER_H_
|
@ -1,166 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/internal/file_connection.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <lib/vfs/cpp/file.h>
|
||||
#include <lib/vfs/cpp/flags.h>
|
||||
|
||||
namespace vfs {
|
||||
namespace internal {
|
||||
|
||||
FileConnection::FileConnection(uint32_t flags, vfs::File* vn)
|
||||
: Connection(flags), vn_(vn), binding_(this) {}
|
||||
|
||||
FileConnection::~FileConnection() = default;
|
||||
|
||||
zx_status_t FileConnection::Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
zx_status_t status = binding_.Bind(std::move(request), dispatcher);
|
||||
if (status != ZX_OK) {
|
||||
return status;
|
||||
}
|
||||
binding_.set_error_handler([this](zx_status_t status) { vn_->Close(this); });
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
void FileConnection::Clone(uint32_t flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object) {
|
||||
Connection::Clone(vn_, flags, std::move(object), binding_.dispatcher());
|
||||
}
|
||||
|
||||
void FileConnection::Close(CloseCallback callback) {
|
||||
Connection::Close(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void FileConnection::Describe(DescribeCallback callback) {
|
||||
Connection::Describe(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void FileConnection::Sync(SyncCallback callback) {
|
||||
Connection::Sync(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void FileConnection::GetAttr(GetAttrCallback callback) {
|
||||
Connection::GetAttr(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void FileConnection::SetAttr(uint32_t flags,
|
||||
fuchsia::io::NodeAttributes attributes,
|
||||
SetAttrCallback callback) {
|
||||
Connection::SetAttr(vn_, flags, attributes, std::move(callback));
|
||||
}
|
||||
|
||||
void FileConnection::Ioctl(uint32_t opcode, uint64_t max_out,
|
||||
std::vector<zx::handle> handles,
|
||||
std::vector<uint8_t> in, IoctlCallback callback) {
|
||||
Connection::Ioctl(vn_, opcode, max_out, std::move(handles), std::move(in),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void FileConnection::Read(uint64_t count, ReadCallback callback) {
|
||||
std::vector<uint8_t> data;
|
||||
if (!Flags::IsReadable(flags())) {
|
||||
callback(ZX_ERR_ACCESS_DENIED, std::move(data));
|
||||
return;
|
||||
}
|
||||
zx_status_t status = vn_->ReadAt(count, offset(), &data);
|
||||
if (status == ZX_OK) {
|
||||
set_offset(offset() + data.size());
|
||||
}
|
||||
callback(status, std::move(data));
|
||||
}
|
||||
|
||||
void FileConnection::ReadAt(uint64_t count, uint64_t offset,
|
||||
ReadAtCallback callback) {
|
||||
std::vector<uint8_t> data;
|
||||
if (!Flags::IsReadable(flags())) {
|
||||
callback(ZX_ERR_ACCESS_DENIED, std::move(data));
|
||||
return;
|
||||
}
|
||||
zx_status_t status = vn_->ReadAt(count, offset, &data);
|
||||
callback(status, std::move(data));
|
||||
}
|
||||
|
||||
void FileConnection::Write(std::vector<uint8_t> data, WriteCallback callback) {
|
||||
if (!Flags::IsWritable(flags())) {
|
||||
callback(ZX_ERR_ACCESS_DENIED, 0);
|
||||
return;
|
||||
}
|
||||
uint64_t actual = 0u;
|
||||
zx_status_t status = vn_->WriteAt(std::move(data), offset(), &actual);
|
||||
if (status == ZX_OK) {
|
||||
set_offset(offset() + actual);
|
||||
}
|
||||
callback(status, actual);
|
||||
}
|
||||
|
||||
void FileConnection::WriteAt(std::vector<uint8_t> data, uint64_t offset,
|
||||
WriteAtCallback callback) {
|
||||
if (!Flags::IsWritable(flags())) {
|
||||
callback(ZX_ERR_ACCESS_DENIED, 0);
|
||||
return;
|
||||
}
|
||||
uint64_t actual = 0u;
|
||||
zx_status_t status = vn_->WriteAt(std::move(data), offset, &actual);
|
||||
callback(status, actual);
|
||||
}
|
||||
|
||||
void FileConnection::Seek(int64_t new_offset, fuchsia::io::SeekOrigin seek,
|
||||
SeekCallback callback) {
|
||||
int64_t cur_len = vn_->GetLength();
|
||||
size_t capacity = vn_->GetCapacity();
|
||||
uint64_t calculated_offset = 0u;
|
||||
switch (seek) {
|
||||
case fuchsia::io::SeekOrigin::START:
|
||||
calculated_offset = new_offset;
|
||||
break;
|
||||
case fuchsia::io::SeekOrigin::CURRENT:
|
||||
calculated_offset = offset() + new_offset;
|
||||
break;
|
||||
case fuchsia::io::SeekOrigin::END:
|
||||
calculated_offset = cur_len + new_offset;
|
||||
break;
|
||||
default:
|
||||
callback(ZX_ERR_INVALID_ARGS, 0u);
|
||||
return;
|
||||
}
|
||||
|
||||
if (static_cast<size_t>(calculated_offset) > capacity) {
|
||||
callback(ZX_ERR_OUT_OF_RANGE, offset());
|
||||
return;
|
||||
}
|
||||
set_offset(calculated_offset);
|
||||
callback(ZX_OK, offset());
|
||||
}
|
||||
|
||||
void FileConnection::Truncate(uint64_t length, TruncateCallback callback) {
|
||||
if (!Flags::IsWritable(flags())) {
|
||||
callback(ZX_ERR_ACCESS_DENIED);
|
||||
return;
|
||||
}
|
||||
callback(vn_->Truncate(length));
|
||||
}
|
||||
|
||||
void FileConnection::GetFlags(GetFlagsCallback callback) {
|
||||
callback(ZX_OK, flags());
|
||||
}
|
||||
|
||||
void FileConnection::SetFlags(uint32_t flags, SetFlagsCallback callback) {
|
||||
// TODO: Implement set flags.
|
||||
callback(ZX_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
void FileConnection::GetBuffer(uint32_t flags, GetBufferCallback callback) {
|
||||
callback(ZX_ERR_NOT_SUPPORTED, nullptr);
|
||||
}
|
||||
|
||||
void FileConnection::SendOnOpenEvent(zx_status_t status) {
|
||||
binding_.events().OnOpen(status, NodeInfoIfStatusOk(vn_, status));
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace vfs
|
@ -1,65 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_INTERNAL_FILE_CONNECTION_H_
|
||||
#define LIB_VFS_CPP_INTERNAL_FILE_CONNECTION_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/binding.h>
|
||||
#include <lib/vfs/cpp/connection.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace vfs {
|
||||
class File;
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Binds an implementation of |fuchsia.io.File| to a |vfs::File|.
|
||||
class FileConnection final : public Connection, public fuchsia::io::File {
|
||||
public:
|
||||
// Create a connection to |vn| with the given |flags|.
|
||||
FileConnection(uint32_t flags, vfs::File* vn);
|
||||
~FileConnection() override;
|
||||
|
||||
// Start listening for |fuchsia.io.File| messages on |request|.
|
||||
zx_status_t Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) override;
|
||||
|
||||
// |fuchsia::io::File| Implementation:
|
||||
void Clone(uint32_t flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object) override;
|
||||
void Close(CloseCallback callback) override;
|
||||
void Describe(DescribeCallback callback) override;
|
||||
void Sync(SyncCallback callback) override;
|
||||
void GetAttr(GetAttrCallback callback) override;
|
||||
void SetAttr(uint32_t flags, fuchsia::io::NodeAttributes attributes,
|
||||
SetAttrCallback callback) override;
|
||||
void Ioctl(uint32_t opcode, uint64_t max_out, std::vector<zx::handle> handles,
|
||||
std::vector<uint8_t> in, IoctlCallback callback) override;
|
||||
void Read(uint64_t count, ReadCallback callback) override;
|
||||
void ReadAt(uint64_t count, uint64_t offset,
|
||||
ReadAtCallback callback) override;
|
||||
void Write(std::vector<uint8_t> data, WriteCallback callback) override;
|
||||
void WriteAt(std::vector<uint8_t> data, uint64_t offset,
|
||||
WriteAtCallback callback) override;
|
||||
void Seek(int64_t offset, fuchsia::io::SeekOrigin start,
|
||||
SeekCallback callback) override;
|
||||
void Truncate(uint64_t length, TruncateCallback callback) override;
|
||||
void GetFlags(GetFlagsCallback callback) override;
|
||||
void SetFlags(uint32_t flags, SetFlagsCallback callback) override;
|
||||
void GetBuffer(uint32_t flags, GetBufferCallback callback) override;
|
||||
|
||||
// |Connection| Implementation:
|
||||
void SendOnOpenEvent(zx_status_t status) override;
|
||||
|
||||
private:
|
||||
vfs::File* vn_;
|
||||
fidl::Binding<fuchsia::io::File> binding_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_INTERNAL_FILE_CONNECTION_H_
|
@ -1,69 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/internal/node_connection.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <lib/vfs/cpp/flags.h>
|
||||
#include <lib/vfs/cpp/node.h>
|
||||
|
||||
namespace vfs {
|
||||
namespace internal {
|
||||
|
||||
NodeConnection::NodeConnection(uint32_t flags, vfs::Node* vn)
|
||||
: Connection(flags), vn_(vn), binding_(this) {}
|
||||
|
||||
NodeConnection::~NodeConnection() = default;
|
||||
|
||||
zx_status_t NodeConnection::Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
zx_status_t status = binding_.Bind(std::move(request), dispatcher);
|
||||
if (status != ZX_OK) {
|
||||
return status;
|
||||
}
|
||||
binding_.set_error_handler([this](zx_status_t status) { vn_->Close(this); });
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
void NodeConnection::Clone(uint32_t flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object) {
|
||||
Connection::Clone(vn_, flags, std::move(object), binding_.dispatcher());
|
||||
}
|
||||
|
||||
void NodeConnection::Close(CloseCallback callback) {
|
||||
Connection::Close(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void NodeConnection::Describe(DescribeCallback callback) {
|
||||
Connection::Describe(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void NodeConnection::Sync(SyncCallback callback) {
|
||||
Connection::Sync(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void NodeConnection::GetAttr(GetAttrCallback callback) {
|
||||
Connection::GetAttr(vn_, std::move(callback));
|
||||
}
|
||||
|
||||
void NodeConnection::SetAttr(uint32_t flags,
|
||||
fuchsia::io::NodeAttributes attributes,
|
||||
SetAttrCallback callback) {
|
||||
Connection::SetAttr(vn_, flags, attributes, std::move(callback));
|
||||
}
|
||||
|
||||
void NodeConnection::Ioctl(uint32_t opcode, uint64_t max_out,
|
||||
std::vector<zx::handle> handles,
|
||||
std::vector<uint8_t> in, IoctlCallback callback) {
|
||||
Connection::Ioctl(vn_, opcode, max_out, std::move(handles), std::move(in),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void NodeConnection::SendOnOpenEvent(zx_status_t status) {
|
||||
binding_.events().OnOpen(status, NodeInfoIfStatusOk(vn_, status));
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace vfs
|
@ -1,53 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_INTERNAL_NODE_CONNECTION_H_
|
||||
#define LIB_VFS_CPP_INTERNAL_NODE_CONNECTION_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/fidl/cpp/binding.h>
|
||||
#include <lib/vfs/cpp/connection.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace vfs {
|
||||
class Node;
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Binds an implementation of |fuchsia.io.Node| to a |vfs::Node|.
|
||||
class NodeConnection final : public Connection, public fuchsia::io::Node {
|
||||
public:
|
||||
// Create a connection to |vn| with the given |flags|.
|
||||
NodeConnection(uint32_t flags, vfs::Node* vn);
|
||||
~NodeConnection() override;
|
||||
|
||||
// Start listening for |fuchsia.io.Node| messages on |request|.
|
||||
zx_status_t Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) override;
|
||||
|
||||
// |fuchsia::io::Node| Implementation:
|
||||
void Clone(uint32_t flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object) override;
|
||||
void Close(CloseCallback callback) override;
|
||||
void Describe(DescribeCallback callback) override;
|
||||
void Sync(SyncCallback callback) override;
|
||||
void GetAttr(GetAttrCallback callback) override;
|
||||
void SetAttr(uint32_t flags, fuchsia::io::NodeAttributes attributes,
|
||||
SetAttrCallback callback) override;
|
||||
void Ioctl(uint32_t opcode, uint64_t max_out, std::vector<zx::handle> handles,
|
||||
std::vector<uint8_t> in, IoctlCallback callback) override;
|
||||
|
||||
// |Connection| Implementation:
|
||||
void SendOnOpenEvent(zx_status_t status) override;
|
||||
|
||||
private:
|
||||
vfs::Node* vn_;
|
||||
fidl::Binding<fuchsia::io::Node> binding_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_INTERNAL_NODE_CONNECTION_H_
|
@ -1,84 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/lazy_dir.h>
|
||||
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/vfs/cpp/internal/dirent_filler.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
bool LazyDir::LazyEntry::operator<(const LazyDir::LazyEntry& rhs) const {
|
||||
return id < rhs.id;
|
||||
}
|
||||
|
||||
LazyDir::LazyDir() {}
|
||||
|
||||
LazyDir::~LazyDir() = default;
|
||||
|
||||
zx_status_t LazyDir::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 LazyDir::Lookup(const std::string& name, Node** out_node) const {
|
||||
LazyEntryVector entries;
|
||||
GetContents(&entries);
|
||||
for (const auto& entry : entries) {
|
||||
if (name == entry.name) {
|
||||
return GetFile(out_node, entry.id, entry.name);
|
||||
}
|
||||
}
|
||||
return ZX_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
zx_status_t LazyDir::Readdir(uint64_t offset, void* data, uint64_t len,
|
||||
uint64_t* out_offset, uint64_t* out_actual) {
|
||||
LazyEntryVector entries;
|
||||
GetContents(&entries);
|
||||
std::sort(entries.begin(), entries.end());
|
||||
|
||||
vfs::internal::DirentFiller filler(data, len);
|
||||
|
||||
const uint64_t ino = fuchsia::io::INO_UNKNOWN;
|
||||
if (offset < kDotId) {
|
||||
if (filler.Next(".", 1, fuchsia::io::DIRENT_TYPE_DIRECTORY, ino) != ZX_OK) {
|
||||
*out_actual = filler.GetBytesFilled();
|
||||
return ZX_ERR_INVALID_ARGS; // out_actual would be 0
|
||||
}
|
||||
offset++;
|
||||
*out_offset = kDotId;
|
||||
}
|
||||
for (auto it = std::upper_bound(
|
||||
entries.begin(), entries.end(), offset,
|
||||
[](uint64_t b_id, const LazyEntry&a) { return b_id < a.id; });
|
||||
it != entries.end(); ++it) {
|
||||
auto dtype = ((fuchsia::io::MODE_TYPE_MASK & it->type) >> 12);
|
||||
if (filler.Next(it->name, dtype, ino) != ZX_OK) {
|
||||
*out_actual = filler.GetBytesFilled();
|
||||
if (*out_actual == 0) {
|
||||
// no space to fill even 1 dentry
|
||||
return ZX_ERR_INVALID_ARGS;
|
||||
}
|
||||
return ZX_OK;
|
||||
}
|
||||
*out_offset = it->id;
|
||||
}
|
||||
|
||||
*out_actual = filler.GetBytesFilled();
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
uint64_t LazyDir::GetStartingId() const { return kDotId + 1; }
|
||||
|
||||
} // namespace vfs
|
@ -1,63 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_LAZY_DIR_H_
|
||||
#define LIB_VFS_CPP_LAZY_DIR_H_
|
||||
|
||||
#include <lib/vfs/cpp/directory.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
// A |LazyDir| a base class for directories that dynamically update their
|
||||
// contents on each operation. Clients should derive from this class
|
||||
// and implement GetContents and GetFile for their use case. The base
|
||||
// implementation of this class is thread-safe, but it is up to implementers
|
||||
// to ensure their implementations are thread safe as well.
|
||||
class LazyDir : public Directory {
|
||||
public:
|
||||
// Structure storing a single entry in the directory.
|
||||
struct LazyEntry {
|
||||
// Should be more than or equal to |GetStartingId()|, must remain stable
|
||||
// across calls.
|
||||
uint64_t id;
|
||||
std::string name;
|
||||
uint32_t type;
|
||||
|
||||
bool operator<(const LazyEntry& rhs) const;
|
||||
};
|
||||
using LazyEntryVector = std::vector<LazyEntry>;
|
||||
|
||||
LazyDir();
|
||||
~LazyDir() override;
|
||||
|
||||
// |Directory| implementation:
|
||||
zx_status_t Readdir(uint64_t offset, void* data, uint64_t len,
|
||||
uint64_t* out_offset, uint64_t* out_actual) override;
|
||||
|
||||
// |Node| implementations:
|
||||
zx_status_t GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const override;
|
||||
|
||||
zx_status_t Lookup(const std::string& name, Node** out_node) const final;
|
||||
|
||||
protected:
|
||||
// Get the contents of the directory in an output vector.
|
||||
virtual void GetContents(LazyEntryVector* out_vector) const = 0;
|
||||
|
||||
// Get the reference to a single file. The id and name of the entry as
|
||||
// returned from GetContents are passed in to assist locating the file.
|
||||
virtual zx_status_t GetFile(Node** out_node, uint64_t id,
|
||||
std::string name) const = 0;
|
||||
|
||||
// Ids returned by |GetContent| should be more than or equal to id returned by
|
||||
// this function.
|
||||
uint64_t GetStartingId() const;
|
||||
|
||||
private:
|
||||
static constexpr uint64_t kDotId = 1u;
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_LAZY_DIR_H_
|
@ -1,189 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/lazy_dir.h>
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/async/dispatcher.h>
|
||||
#include <lib/gtest/real_loop_fixture.h>
|
||||
#include <lib/vfs/cpp/pseudo_file.h>
|
||||
#include <lib/vfs/cpp/testing/dir_test_util.h>
|
||||
#include <zircon/system/public/zircon/errors.h>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
using vfs_tests::Dirent;
|
||||
|
||||
class TestLazyDir : public vfs::LazyDir {
|
||||
public:
|
||||
struct TestContent {
|
||||
public:
|
||||
TestContent(std::string name, std::unique_ptr<vfs::Node> node)
|
||||
: name_(std::move(name)), node_(std::move(node)) {}
|
||||
std::string name_;
|
||||
std::unique_ptr<vfs::Node> node_;
|
||||
};
|
||||
|
||||
TestLazyDir()
|
||||
: next_id_(GetStartingId()), loop_(&kAsyncLoopConfigNoAttachToThread) {
|
||||
loop_.StartThread("vfs test thread");
|
||||
}
|
||||
|
||||
void AddContent(TestContent content) {
|
||||
contents_.emplace(next_id_++, std::move(content));
|
||||
}
|
||||
|
||||
void ClearContent() {
|
||||
contents_.clear();
|
||||
next_id_ = GetStartingId();
|
||||
}
|
||||
|
||||
fuchsia::io::DirectorySyncPtr ServeForTest(
|
||||
int flags = fuchsia::io::OPEN_RIGHT_READABLE) {
|
||||
fuchsia::io::DirectorySyncPtr ptr;
|
||||
Serve(flags, ptr.NewRequest().TakeChannel(), loop_.dispatcher());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
async_dispatcher_t* dispatcher() { return loop_.dispatcher(); }
|
||||
|
||||
protected:
|
||||
void GetContents(LazyEntryVector* out_vector) const override {
|
||||
out_vector->reserve(contents_.size());
|
||||
for (const auto& content : contents_) {
|
||||
out_vector->push_back(
|
||||
{content.first, content.second.name_, fuchsia::io::MODE_TYPE_FILE});
|
||||
}
|
||||
}
|
||||
|
||||
zx_status_t GetFile(Node** out_node, uint64_t id,
|
||||
std::string name) const override {
|
||||
auto search = contents_.find(id);
|
||||
if (search != contents_.end()) {
|
||||
*out_node = search->second.node_.get();
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
return ZX_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t next_id_;
|
||||
std::map<uint64_t, TestContent> contents_;
|
||||
async::Loop loop_;
|
||||
};
|
||||
|
||||
class LazyDirConnection : public vfs_tests::DirConnection {
|
||||
protected:
|
||||
vfs::Directory* GetDirectoryNode() override { return &dir_; }
|
||||
|
||||
TestLazyDir::TestContent CreateTestFile(std::string name,
|
||||
std::string content) {
|
||||
auto file = std::make_unique<vfs::BufferedPseudoFile>(
|
||||
[content = std::move(content)](std::vector<uint8_t>* output) {
|
||||
output->resize(content.length());
|
||||
std::copy(content.begin(), content.end(), output->begin());
|
||||
return ZX_OK;
|
||||
});
|
||||
return TestLazyDir::TestContent(std::move(name), std::move(file));
|
||||
}
|
||||
|
||||
TestLazyDir dir_;
|
||||
};
|
||||
|
||||
TEST_F(LazyDirConnection, ReadDirEmpty) {
|
||||
auto ptr = dir_.ServeForTest();
|
||||
|
||||
std::vector<Dirent> expected_dirents = {
|
||||
Dirent::DirentForDot(),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
}
|
||||
|
||||
TEST_F(LazyDirConnection, ReadSimple) {
|
||||
auto ptr = dir_.ServeForTest();
|
||||
|
||||
dir_.AddContent(CreateTestFile("file1", "file1"));
|
||||
|
||||
std::vector<Dirent> expected_dirents = {Dirent::DirentForDot(),
|
||||
Dirent::DirentForFile("file1")};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
}
|
||||
|
||||
TEST_F(LazyDirConnection, DynamicRead) {
|
||||
auto ptr = dir_.ServeForTest();
|
||||
|
||||
dir_.AddContent(CreateTestFile("file1", "file1"));
|
||||
dir_.AddContent(CreateTestFile("file2", "file2"));
|
||||
|
||||
std::vector<Dirent> expected_dirents = {Dirent::DirentForDot(),
|
||||
Dirent::DirentForFile("file1"),
|
||||
Dirent::DirentForFile("file2")};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
|
||||
dir_.ClearContent();
|
||||
|
||||
dir_.AddContent(CreateTestFile("file3", "file3"));
|
||||
|
||||
// should not get any dirent before we rewind.
|
||||
expected_dirents = {};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
|
||||
AssertRewind(ptr);
|
||||
|
||||
expected_dirents = {Dirent::DirentForDot(), Dirent::DirentForFile("file3")};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
}
|
||||
|
||||
TEST_F(LazyDirConnection, MultipleReads) {
|
||||
auto ptr = dir_.ServeForTest();
|
||||
|
||||
dir_.AddContent(CreateTestFile("file1", "file1"));
|
||||
dir_.AddContent(CreateTestFile("file2", "file2"));
|
||||
dir_.AddContent(CreateTestFile("file3", "file3"));
|
||||
dir_.AddContent(CreateTestFile("file4", "file4"));
|
||||
|
||||
std::vector<Dirent> expected_dirents = {Dirent::DirentForDot(),
|
||||
Dirent::DirentForFile("file1"),
|
||||
Dirent::DirentForFile("file2")};
|
||||
AssertReadDirents(ptr, 3 * sizeof(vdirent_t) + 15, expected_dirents);
|
||||
|
||||
expected_dirents = {Dirent::DirentForFile("file3"),
|
||||
Dirent::DirentForFile("file4")};
|
||||
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
expected_dirents = {};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
}
|
||||
|
||||
TEST_F(LazyDirConnection, LookupWorks) {
|
||||
dir_.AddContent(CreateTestFile("file1", "file1"));
|
||||
dir_.AddContent(CreateTestFile("file2", "file2"));
|
||||
dir_.AddContent(CreateTestFile("file3", "file3"));
|
||||
dir_.AddContent(CreateTestFile("file4", "file4"));
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_OK, dir_.Lookup("file3", &n));
|
||||
fuchsia::io::FileSyncPtr file_ptr;
|
||||
n->Serve(fuchsia::io::OPEN_RIGHT_READABLE,
|
||||
file_ptr.NewRequest().TakeChannel(), dir_.dispatcher());
|
||||
zx_status_t status;
|
||||
std::vector<uint8_t> data;
|
||||
file_ptr->Read(20, &status, &data);
|
||||
ASSERT_EQ(ZX_OK, status);
|
||||
std::string str = "file3";
|
||||
std::vector<uint8_t> expected_data(str.begin(), str.end());
|
||||
ASSERT_EQ(expected_data, data);
|
||||
}
|
||||
|
||||
TEST_F(LazyDirConnection, LookupReturnsNotFound) {
|
||||
dir_.AddContent(CreateTestFile("file1", "file1"));
|
||||
dir_.AddContent(CreateTestFile("file2", "file2"));
|
||||
dir_.AddContent(CreateTestFile("file3", "file3"));
|
||||
dir_.AddContent(CreateTestFile("file4", "file4"));
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.Lookup("file5", &n));
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"program": {
|
||||
"binary": "test/vfs_cpp_unittests"
|
||||
}
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/node.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <fuchsia/io/c/fidl.h>
|
||||
#include <lib/vfs/cpp/connection.h>
|
||||
#include <lib/vfs/cpp/flags.h>
|
||||
#include <lib/vfs/cpp/internal/node_connection.h>
|
||||
#include <zircon/assert.h>
|
||||
|
||||
namespace vfs {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kCommonAllowedFlags =
|
||||
fuchsia::io::OPEN_FLAG_DESCRIBE | fuchsia::io::OPEN_FLAG_NODE_REFERENCE |
|
||||
fuchsia::io::OPEN_FLAG_POSIX | fuchsia::io::CLONE_FLAG_SAME_RIGHTS;
|
||||
|
||||
constexpr uint32_t FS_RIGHTS = 0x0000FFFF;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsValidName(const std::string& name) {
|
||||
return name.length() <= NAME_MAX &&
|
||||
memchr(name.data(), '/', name.length()) == nullptr && name != "." &&
|
||||
name != "..";
|
||||
}
|
||||
|
||||
Node::Node() = default;
|
||||
|
||||
Node::~Node() = default;
|
||||
|
||||
std::unique_ptr<Connection> Node::Close(Connection* connection) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
|
||||
auto connection_iterator = std::find_if(
|
||||
connections_.begin(), connections_.end(),
|
||||
[connection](const auto& entry) { return entry.get() == connection; });
|
||||
auto ret = std::move(*connection_iterator);
|
||||
connections_.erase(connection_iterator);
|
||||
return ret;
|
||||
}
|
||||
|
||||
zx_status_t Node::Sync() { return ZX_ERR_NOT_SUPPORTED; }
|
||||
|
||||
bool Node::IsRemote() const { return false; }
|
||||
|
||||
zx_status_t Node::GetAttr(fuchsia::io::NodeAttributes* out_attributes) const {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void Node::Clone(uint32_t flags, uint32_t parent_flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
// TODO(ZX-3417): This is how libfs clones a node, we should fix this once we
|
||||
// have clear picture what clone should do.
|
||||
flags |= (parent_flags & (FS_RIGHTS | fuchsia::io::OPEN_FLAG_NODE_REFERENCE |
|
||||
fuchsia::io::OPEN_FLAG_APPEND));
|
||||
Serve(flags, object.TakeChannel(), dispatcher);
|
||||
}
|
||||
|
||||
zx_status_t Node::ValidateFlags(uint32_t flags) const {
|
||||
bool is_directory = IsDirectory();
|
||||
if (!is_directory && Flags::IsDirectory(flags)) {
|
||||
return ZX_ERR_NOT_DIR;
|
||||
}
|
||||
|
||||
uint32_t allowed_flags = kCommonAllowedFlags | GetAdditionalAllowedFlags();
|
||||
if (is_directory) {
|
||||
allowed_flags = allowed_flags | fuchsia::io::OPEN_FLAG_DIRECTORY;
|
||||
}
|
||||
|
||||
uint32_t prohibitive_flags = GetProhibitiveFlags();
|
||||
|
||||
if ((flags & prohibitive_flags) != 0) {
|
||||
return ZX_ERR_INVALID_ARGS;
|
||||
}
|
||||
if ((flags & ~allowed_flags) != 0) {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t Node::ValidateMode(uint32_t mode) const {
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
uint32_t mode_from_attr = 0;
|
||||
zx_status_t status = GetAttr(&attr);
|
||||
if (status == ZX_OK) {
|
||||
mode_from_attr = attr.mode & fuchsia::io::MODE_TYPE_MASK;
|
||||
}
|
||||
|
||||
if (((mode & ~fuchsia::io::MODE_PROTECTION_MASK) & ~mode_from_attr) != 0) {
|
||||
return ZX_ERR_INVALID_ARGS;
|
||||
}
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t Node::Lookup(const std::string& name, Node** out_node) const {
|
||||
ZX_ASSERT(!IsDirectory());
|
||||
return ZX_ERR_NOT_DIR;
|
||||
}
|
||||
|
||||
uint32_t Node::GetAdditionalAllowedFlags() const { return 0; }
|
||||
|
||||
uint32_t Node::GetProhibitiveFlags() const { return 0; }
|
||||
|
||||
zx_status_t Node::SetAttr(uint32_t flags,
|
||||
const fuchsia::io::NodeAttributes& attributes) {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
uint32_t Node::FilterRefFlags(uint32_t flags) {
|
||||
if (Flags::IsPathOnly(flags)) {
|
||||
return flags & (kCommonAllowedFlags | fuchsia::io::OPEN_FLAG_DIRECTORY);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
zx_status_t Node::Serve(uint32_t flags, zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
flags = FilterRefFlags(flags);
|
||||
zx_status_t status = ValidateFlags(flags);
|
||||
if (status != ZX_OK) {
|
||||
SendOnOpenEventOnError(flags, std::move(request), status);
|
||||
return status;
|
||||
}
|
||||
return Connect(flags, std::move(request), dispatcher);
|
||||
}
|
||||
|
||||
zx_status_t Node::Connect(uint32_t flags, zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
zx_status_t status;
|
||||
std::unique_ptr<Connection> connection;
|
||||
if (Flags::IsPathOnly(flags)) {
|
||||
status = Node::CreateConnection(flags, &connection);
|
||||
} else {
|
||||
status = CreateConnection(flags, &connection);
|
||||
}
|
||||
if (status != ZX_OK) {
|
||||
SendOnOpenEventOnError(flags, std::move(request), status);
|
||||
return status;
|
||||
}
|
||||
status = connection->Bind(std::move(request), dispatcher);
|
||||
if (status == ZX_OK) {
|
||||
if (Flags::ShouldDescribe(flags)) {
|
||||
connection->SendOnOpenEvent(status);
|
||||
}
|
||||
AddConnection(std::move(connection));
|
||||
} // can't send status as request object is gone.
|
||||
return status;
|
||||
}
|
||||
|
||||
zx_status_t Node::ServeWithMode(uint32_t flags, uint32_t mode,
|
||||
zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
zx_status_t status = ValidateMode(mode);
|
||||
if (status != ZX_OK) {
|
||||
SendOnOpenEventOnError(flags, std::move(request), status);
|
||||
return status;
|
||||
}
|
||||
return Serve(flags, std::move(request), dispatcher);
|
||||
}
|
||||
|
||||
void Node::SendOnOpenEventOnError(uint32_t flags, zx::channel request,
|
||||
zx_status_t status) {
|
||||
ZX_DEBUG_ASSERT(status != ZX_OK);
|
||||
|
||||
if (!Flags::ShouldDescribe(flags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fuchsia_io_NodeOnOpenEvent msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.hdr.ordinal = fuchsia_io_NodeOnOpenOrdinal;
|
||||
msg.s = status;
|
||||
request.write(0, &msg, sizeof(msg), nullptr, 0);
|
||||
}
|
||||
|
||||
void Node::AddConnection(std::unique_ptr<Connection> connection) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
connections_.push_back(std::move(connection));
|
||||
}
|
||||
|
||||
zx_status_t Node::CreateConnection(uint32_t flags,
|
||||
std::unique_ptr<Connection>* connection) {
|
||||
*connection = std::make_unique<internal::NodeConnection>(flags, this);
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
} // namespace vfs
|
@ -1,179 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_NODE_H_
|
||||
#define LIB_VFS_CPP_NODE_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/async/dispatcher.h>
|
||||
#include <lib/fidl/cpp/binding.h>
|
||||
#include <limits.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
bool IsValidName(const std::string& name);
|
||||
|
||||
class Connection;
|
||||
|
||||
// An object in a file system.
|
||||
//
|
||||
// Implements the |fuchsia.io.Node| interface. Incoming connections are owned by
|
||||
// this object and will be destroyed when this object is destroyed.
|
||||
//
|
||||
// Subclass to implement a particular kind of file system object.
|
||||
//
|
||||
// See also:
|
||||
//
|
||||
// * File, which is a subclass for file objects.
|
||||
// * Directory, which is a subclass for directory objects.
|
||||
//
|
||||
// This class is thread safe.
|
||||
class Node {
|
||||
public:
|
||||
Node();
|
||||
virtual ~Node();
|
||||
|
||||
Node(const Node&) = delete;
|
||||
Node& operator=(const Node&) = delete;
|
||||
|
||||
// Notifies |Node| that it should remove and return
|
||||
// |connection| from its list as it is getting closed.
|
||||
virtual std::unique_ptr<Connection> Close(Connection* connection);
|
||||
|
||||
// Implementation of |fuchsia.io.Node/Describe|.
|
||||
//
|
||||
// Subclass must override this method to describe themselves accurately.
|
||||
virtual void Describe(fuchsia::io::NodeInfo* out_info) = 0;
|
||||
|
||||
// Implementation of |fuchsia.io.Node/Sync|.
|
||||
virtual zx_status_t Sync();
|
||||
|
||||
// Implementation of |fuchsia.io.Node/GetAttr|.
|
||||
virtual zx_status_t GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const;
|
||||
|
||||
// Implementation of |fuchsia.io.Node/SetAttr|.
|
||||
virtual zx_status_t SetAttr(uint32_t flags,
|
||||
const fuchsia::io::NodeAttributes& attributes);
|
||||
|
||||
// Implementation of |fuchsia.io.Node/Clone|.
|
||||
virtual void Clone(uint32_t flags, uint32_t parent_flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object,
|
||||
async_dispatcher_t* dispatcher);
|
||||
|
||||
// Validate flags on |Serve|.
|
||||
//
|
||||
// Returns |ZX_ERR_NOT_DIR| if |OPEN_FLAG_DIRECTORY| is set and |IsDirectory|
|
||||
// returns false.
|
||||
//
|
||||
// Calls |GetProhibitiveFlags| flags and if one of the flag is in prohibitive
|
||||
// list, returns |ZX_ERR_INVALID_ARGS|.
|
||||
//
|
||||
// Calls |GetAdditionalAllowedFlags|, appends |OPEN_FLAG_DESCRIBE|,
|
||||
// |OPEN_FLAG_NODE_REFERENCE|, |OPEN_FLAG_DIRECTORY| (only if |IsDirectory|
|
||||
// returns true) to those flags and returns |ZX_ERR_NOT_SUPPORTED| if flags
|
||||
// are not found in allowed list.
|
||||
//
|
||||
// Returns ZX_OK if none of the above cases are true.
|
||||
zx_status_t ValidateFlags(uint32_t flags) const;
|
||||
|
||||
// Validate flags on |ServeWithMode|.
|
||||
//
|
||||
// Calls |GetAttr| and checks that mode should not be anything other than
|
||||
// |MODE_PROTECTION_MASK| and one in attr. Returns |ZX_ERR_INVALID_ARGS| if it
|
||||
// is, else returns |ZX_OK|.
|
||||
zx_status_t ValidateMode(uint32_t mode) const;
|
||||
|
||||
// Establishes a connection for |request| using the given |flags|.
|
||||
//
|
||||
// Waits for messages asynchronously on the |request| channel using
|
||||
// |dispatcher|. If |dispatcher| is |nullptr|, the implementation will call
|
||||
// |async_get_default_dispatcher| to obtain the default dispatcher for the
|
||||
// current thread.
|
||||
//
|
||||
// Calls |Connect| after validating flags and modes.
|
||||
zx_status_t Serve(uint32_t flags, zx::channel request,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// Validates |mode| and passes request to serve
|
||||
//
|
||||
// Would be called by |Open|.
|
||||
zx_status_t ServeWithMode(uint32_t flags, uint32_t mode, zx::channel request,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// Find an entry in this directory with the given |name|.
|
||||
//
|
||||
// The entry is returned via |out_node|. The returned entry is owned by this
|
||||
// directory.
|
||||
//
|
||||
// Returns |ZX_ERR_NOT_FOUND| if no entry exists.
|
||||
// Default implementation in this class return |ZX_ERR_NOT_DIR| if
|
||||
// |IsDirectory| is false, else throws error with |ZX_ASSERT|.
|
||||
//
|
||||
// All directory types should implement this method.
|
||||
virtual zx_status_t Lookup(const std::string& name, Node** out_node) const;
|
||||
|
||||
// Return true if |Node| is a remote node.
|
||||
// This function is used in |Directory::Open| to correctly open those kind of
|
||||
// nodes.
|
||||
virtual bool IsRemote() const;
|
||||
|
||||
protected:
|
||||
// Called by |Serve| after validating flags and modes.
|
||||
// This should be implemented by sub classes which doesn't create a
|
||||
// connection class.
|
||||
//
|
||||
// Default implementation:
|
||||
// Uses |CreateConnection| to create a connection appropriate for the
|
||||
// concrete type of this object.
|
||||
virtual zx_status_t Connect(uint32_t flags, zx::channel request,
|
||||
async_dispatcher_t* dispatcher);
|
||||
|
||||
// Filters out flags that are invalid when combined with
|
||||
// |OPEN_FLAG_NODE_REFERENCE|.
|
||||
// Allowed flags are |OPEN_FLAG_DIRECTORY| and |OPEN_FLAG_DESCRIBE|.
|
||||
uint32_t FilterRefFlags(uint32_t flags);
|
||||
|
||||
// Sends OnOpen event on error status if |OPEN_FLAG_DESCRIBE| is set.
|
||||
void SendOnOpenEventOnError(uint32_t flags, zx::channel request,
|
||||
zx_status_t status);
|
||||
|
||||
// Store given connection.
|
||||
void AddConnection(std::unique_ptr<Connection> connection);
|
||||
|
||||
// Creates a |Connection| appropriate for the concrete type of this object.
|
||||
//
|
||||
// Subclasses must override this method to create an appropriate connection.
|
||||
// The returned connection should be in an "unbound" state.
|
||||
//
|
||||
// Typically called by |Serve|.
|
||||
virtual zx_status_t CreateConnection(
|
||||
uint32_t flags, std::unique_ptr<Connection>* connection) = 0;
|
||||
|
||||
// Return true if |Node| is a directory.
|
||||
// This function is used in |ValidateFlags| and |Lookup| to return correct
|
||||
// error.
|
||||
//
|
||||
// This should be overridden by every implementation.
|
||||
virtual bool IsDirectory() const = 0;
|
||||
|
||||
// Additional Allowed flags for use in |ValidateFlags|.
|
||||
//
|
||||
// See documentation of |ValidateFlags| for exact details.
|
||||
virtual uint32_t GetAdditionalAllowedFlags() const;
|
||||
|
||||
// Prohibitive flags use in |ValidateFlags|.
|
||||
//
|
||||
// See documentation of |ValidateFlags| for exact details.
|
||||
virtual uint32_t GetProhibitiveFlags() const;
|
||||
|
||||
// guards connection_
|
||||
mutable std::mutex mutex_;
|
||||
// The active connections associated with this object.
|
||||
std::vector<std::unique_ptr<Connection>> connections_ __TA_GUARDED(mutex_);
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_NODE_H_
|
@ -1,156 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/pseudo_dir.h>
|
||||
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/vfs/cpp/internal/dirent_filler.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
PseudoDir::PseudoDir() = default;
|
||||
|
||||
PseudoDir::~PseudoDir() = default;
|
||||
|
||||
zx_status_t PseudoDir::AddSharedEntry(std::string name,
|
||||
std::shared_ptr<Node> vn) {
|
||||
ZX_DEBUG_ASSERT(vn);
|
||||
|
||||
auto id = next_node_id_++;
|
||||
return AddEntry(
|
||||
std::make_unique<SharedEntry>(id, std::move(name), std::move(vn)));
|
||||
}
|
||||
|
||||
zx_status_t PseudoDir::AddEntry(std::string name, std::unique_ptr<Node> vn) {
|
||||
ZX_DEBUG_ASSERT(vn);
|
||||
|
||||
auto id = next_node_id_++;
|
||||
return AddEntry(
|
||||
std::make_unique<UniqueEntry>(id, std::move(name), std::move(vn)));
|
||||
}
|
||||
|
||||
zx_status_t PseudoDir::AddEntry(std::unique_ptr<Entry> entry) {
|
||||
ZX_DEBUG_ASSERT(entry);
|
||||
|
||||
if (!IsValidName(entry->name())) {
|
||||
return ZX_ERR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
|
||||
if (entries_by_name_.find(entry->name()) != entries_by_name_.end()) {
|
||||
return ZX_ERR_ALREADY_EXISTS;
|
||||
}
|
||||
entries_by_name_[entry->name()] = entry.get();
|
||||
auto id = entry->id();
|
||||
entries_by_id_.emplace_hint(entries_by_id_.end(), id, std::move(entry));
|
||||
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t PseudoDir::RemoveEntry(const std::string& name) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
auto entry = entries_by_name_.find(name);
|
||||
if (entry == entries_by_name_.end()) {
|
||||
return ZX_ERR_NOT_FOUND;
|
||||
}
|
||||
entries_by_id_.erase(entry->second->id());
|
||||
entries_by_name_.erase(name);
|
||||
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t PseudoDir::Lookup(const std::string& name, Node** out_node) const {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
|
||||
auto search = entries_by_name_.find(name);
|
||||
if (search != entries_by_name_.end()) {
|
||||
*out_node = search->second->node();
|
||||
return ZX_OK;
|
||||
} else {
|
||||
return ZX_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
zx_status_t PseudoDir::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 PseudoDir::Readdir(uint64_t offset, void* data, uint64_t len,
|
||||
uint64_t* out_offset, uint64_t* out_actual) {
|
||||
vfs::internal::DirentFiller df(data, len);
|
||||
*out_actual = 0;
|
||||
*out_offset = offset;
|
||||
if (offset < kDotId) {
|
||||
if (df.Next(".", 1, fuchsia::io::DIRENT_TYPE_DIRECTORY,
|
||||
fuchsia::io::INO_UNKNOWN) != ZX_OK) {
|
||||
*out_actual = df.GetBytesFilled();
|
||||
return ZX_ERR_INVALID_ARGS; // out_actual would be 0
|
||||
}
|
||||
(*out_offset)++;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
|
||||
for (auto it = entries_by_id_.upper_bound(*out_offset);
|
||||
it != entries_by_id_.end(); ++it) {
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
auto d_type = fuchsia::io::DIRENT_TYPE_UNKNOWN;
|
||||
auto ino = fuchsia::io::INO_UNKNOWN;
|
||||
if (it->second->node()->GetAttr(&attr) == ZX_OK) {
|
||||
d_type = ((fuchsia::io::MODE_TYPE_MASK & attr.mode) >> 12);
|
||||
ino = attr.id;
|
||||
}
|
||||
|
||||
if (df.Next(it->second->name(), d_type, ino) != ZX_OK) {
|
||||
*out_actual = df.GetBytesFilled();
|
||||
if (*out_actual == 0) {
|
||||
// no space to fill even 1 dentry
|
||||
return ZX_ERR_INVALID_ARGS;
|
||||
}
|
||||
return ZX_OK;
|
||||
}
|
||||
*out_offset = it->second->id();
|
||||
}
|
||||
|
||||
*out_actual = df.GetBytesFilled();
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
bool PseudoDir::IsEmpty() const {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
return entries_by_name_.size() == 0;
|
||||
}
|
||||
|
||||
PseudoDir::Entry::Entry(uint64_t id, std::string name)
|
||||
: id_(id), name_(std::move(name)) {}
|
||||
|
||||
PseudoDir::Entry::~Entry() = default;
|
||||
|
||||
PseudoDir::SharedEntry::SharedEntry(uint64_t id, std::string name,
|
||||
std::shared_ptr<Node> node)
|
||||
: Entry(id, std::move(name)), node_(std::move(node)) {}
|
||||
|
||||
Node* PseudoDir::SharedEntry::node() const { return node_.get(); }
|
||||
|
||||
PseudoDir::SharedEntry::~SharedEntry() = default;
|
||||
|
||||
PseudoDir::UniqueEntry::UniqueEntry(uint64_t id, std::string name,
|
||||
std::unique_ptr<Node> node)
|
||||
: Entry(id, std::move(name)), node_(std::move(node)) {}
|
||||
|
||||
Node* PseudoDir::UniqueEntry::node() const { return node_.get(); }
|
||||
|
||||
PseudoDir::UniqueEntry::~UniqueEntry() = default;
|
||||
|
||||
} // namespace vfs
|
@ -1,125 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_PSEUDO_DIR_H_
|
||||
#define LIB_VFS_CPP_PSEUDO_DIR_H_
|
||||
|
||||
#include <lib/vfs/cpp/directory.h>
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
// A pseudo-directory is a directory-like object whose entries are constructed
|
||||
// by a program at runtime. The client can lookup, enumerate, and watch(not yet
|
||||
// implemented) these directory entries but it cannot create, remove, or rename
|
||||
// them.
|
||||
//
|
||||
// Instances of this class are thread-safe.
|
||||
class PseudoDir : public Directory {
|
||||
public:
|
||||
// Creates a directory which is initially empty.
|
||||
PseudoDir();
|
||||
|
||||
// Destroys the directory and releases the nodes it contains.
|
||||
~PseudoDir() override;
|
||||
|
||||
// Adds a directory entry associating the given |name| with |vn|.
|
||||
// It is ok to add the same Node multiple times with different names.
|
||||
//
|
||||
// Returns |ZX_OK| on success.
|
||||
// Returns |ZX_ERR_ALREADY_EXISTS| if there is already a node with the given
|
||||
// name.
|
||||
zx_status_t AddSharedEntry(std::string name, std::shared_ptr<Node> vn);
|
||||
|
||||
// Adds a directory entry associating the given |name| with |vn|.
|
||||
//
|
||||
// Returns |ZX_OK| on success.
|
||||
// Returns |ZX_ERR_ALREADY_EXISTS| if there is already a node with the given
|
||||
// name.
|
||||
zx_status_t AddEntry(std::string name, std::unique_ptr<Node> vn);
|
||||
|
||||
// Removes a directory entry with the given |name|.
|
||||
//
|
||||
// Returns |ZX_OK| on success.
|
||||
// Returns |ZX_ERR_NOT_FOUND| if there is no node with the given name.
|
||||
zx_status_t RemoveEntry(const std::string& name);
|
||||
|
||||
// Removes all directory entries.
|
||||
void RemoveAllEntries();
|
||||
|
||||
// Checks if directory is empty.
|
||||
// Be careful while using this function if using this Dir in multiple
|
||||
// threads.
|
||||
bool IsEmpty() const;
|
||||
|
||||
// |Directory| implementation:
|
||||
zx_status_t Lookup(const std::string& name, Node** out_node) const final;
|
||||
|
||||
// |Node| implementations:
|
||||
zx_status_t GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const override;
|
||||
|
||||
zx_status_t Readdir(uint64_t offset,
|
||||
void* data,
|
||||
uint64_t len,
|
||||
uint64_t* out_offset,
|
||||
uint64_t* out_actual) override;
|
||||
|
||||
private:
|
||||
class Entry {
|
||||
public:
|
||||
Entry(uint64_t id, std::string name);
|
||||
virtual ~Entry();
|
||||
|
||||
uint64_t id() const { return id_; }
|
||||
const std::string& name() const { return name_; }
|
||||
virtual Node* node() const = 0;
|
||||
|
||||
private:
|
||||
uint64_t const id_;
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
class SharedEntry : public Entry {
|
||||
public:
|
||||
SharedEntry(uint64_t id, std::string name, std::shared_ptr<Node> node);
|
||||
~SharedEntry() override;
|
||||
|
||||
Node* node() const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Node> node_;
|
||||
};
|
||||
|
||||
class UniqueEntry : public Entry {
|
||||
public:
|
||||
UniqueEntry(uint64_t id, std::string name, std::unique_ptr<Node> node);
|
||||
~UniqueEntry() override;
|
||||
|
||||
Node* node() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Node> node_;
|
||||
};
|
||||
|
||||
zx_status_t AddEntry(std::unique_ptr<Entry> entry);
|
||||
|
||||
static constexpr uint64_t kDotId = 1u;
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
|
||||
std::atomic_uint16_t next_node_id_ = {kDotId + 1};
|
||||
|
||||
// for enumeration
|
||||
std::map<uint64_t, std::unique_ptr<Entry>> entries_by_id_
|
||||
__TA_GUARDED(mutex_);
|
||||
|
||||
// for lookup
|
||||
std::map<std::string, Entry*> entries_by_name_ __TA_GUARDED(mutex_);
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
#endif // LIB_VFS_CPP_PSEUDO_DIR_H_
|
@ -1,838 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/pseudo_dir.h>
|
||||
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/gtest/real_loop_fixture.h>
|
||||
#include <lib/vfs/cpp/pseudo_file.h>
|
||||
#include <lib/vfs/cpp/testing/dir_test_util.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using vfs_tests::Dirent;
|
||||
|
||||
class TestNode : public vfs::Node {
|
||||
public:
|
||||
TestNode(std::function<void()> death_callback = nullptr)
|
||||
: death_callback_(death_callback) {}
|
||||
~TestNode() override {
|
||||
if (death_callback_) {
|
||||
death_callback_();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsDirectory() const override { return false; }
|
||||
|
||||
void Describe(fuchsia::io::NodeInfo* out_info) override {}
|
||||
|
||||
zx_status_t CreateConnection(
|
||||
uint32_t flags, std::unique_ptr<vfs::Connection>* connection) override {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
std::function<void()> death_callback_;
|
||||
};
|
||||
|
||||
class PseudoDirUnit : public ::testing::Test {
|
||||
protected:
|
||||
void Init(int number_of_nodes) {
|
||||
nodes_.resize(number_of_nodes);
|
||||
node_names_.resize(number_of_nodes);
|
||||
|
||||
for (int i = 0; i < number_of_nodes; i++) {
|
||||
node_names_[i] = "node" + std::to_string(i);
|
||||
nodes_[i] = std::make_shared<TestNode>();
|
||||
ASSERT_EQ(ZX_OK, dir_.AddSharedEntry(node_names_[i], nodes_[i]));
|
||||
}
|
||||
}
|
||||
|
||||
vfs::PseudoDir dir_;
|
||||
std::vector<std::string> node_names_;
|
||||
std::vector<std::shared_ptr<TestNode>> nodes_;
|
||||
};
|
||||
|
||||
TEST_F(PseudoDirUnit, NotEmpty) {
|
||||
Init(1);
|
||||
ASSERT_FALSE(dir_.IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirUnit, Empty) {
|
||||
Init(0);
|
||||
ASSERT_TRUE(dir_.IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirUnit, Lookup) {
|
||||
Init(10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_OK, dir_.Lookup(node_names_[i], &n))
|
||||
<< "for " << node_names_[i];
|
||||
ASSERT_EQ(nodes_[i].get(), n) << "for " << node_names_[i];
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirUnit, LookupUniqueNode) {
|
||||
Init(1);
|
||||
|
||||
auto node = std::make_unique<TestNode>();
|
||||
vfs::Node* node_ptr = node.get();
|
||||
ASSERT_EQ(ZX_OK, dir_.AddEntry("un", std::move(node)));
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_OK, dir_.Lookup(node_names_[0], &n));
|
||||
ASSERT_EQ(nodes_[0].get(), n);
|
||||
|
||||
ASSERT_EQ(ZX_OK, dir_.Lookup("un", &n));
|
||||
ASSERT_EQ(node_ptr, n);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirUnit, InvalidLookup) {
|
||||
Init(3);
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.Lookup("invalid", &n));
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirUnit, RemoveEntry) {
|
||||
Init(5);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(2, nodes_[i].use_count());
|
||||
ASSERT_EQ(ZX_OK, dir_.RemoveEntry(node_names_[i]))
|
||||
<< "for " << node_names_[i];
|
||||
|
||||
// cannot access
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.Lookup(node_names_[i], &n))
|
||||
<< "for " << node_names_[i];
|
||||
// check that use count went doen by 1
|
||||
ASSERT_EQ(1, nodes_[i].use_count());
|
||||
}
|
||||
ASSERT_TRUE(dir_.IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirUnit, RemoveUniqueNode) {
|
||||
Init(0);
|
||||
|
||||
bool node_died = false;
|
||||
auto node = std::make_unique<TestNode>([&]() { node_died = true; });
|
||||
EXPECT_FALSE(node_died);
|
||||
ASSERT_EQ(ZX_OK, dir_.AddEntry("un", std::move(node)));
|
||||
ASSERT_EQ(ZX_OK, dir_.RemoveEntry("un"));
|
||||
EXPECT_TRUE(node_died);
|
||||
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.Lookup("un", &n));
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirUnit, RemoveInvalidEntry) {
|
||||
Init(5);
|
||||
ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.RemoveEntry("invalid"));
|
||||
|
||||
// make sure nothing was removed
|
||||
for (int i = 0; i < 5; i++) {
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_OK, dir_.Lookup(node_names_[i], &n))
|
||||
<< "for " << node_names_[i];
|
||||
ASSERT_EQ(nodes_[i].get(), n) << "for " << node_names_[i];
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirUnit, AddAfterRemove) {
|
||||
Init(5);
|
||||
ASSERT_EQ(ZX_OK, dir_.RemoveEntry(node_names_[2]));
|
||||
|
||||
auto new_node = std::make_shared<TestNode>();
|
||||
ASSERT_EQ(ZX_OK, dir_.AddSharedEntry("new_node", new_node));
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
zx_status_t expected_status = ZX_OK;
|
||||
if (i == 2) {
|
||||
expected_status = ZX_ERR_NOT_FOUND;
|
||||
}
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(expected_status, dir_.Lookup(node_names_[i], &n))
|
||||
<< "for " << node_names_[i];
|
||||
if (expected_status == ZX_OK) {
|
||||
ASSERT_EQ(nodes_[i].get(), n) << "for " << node_names_[i];
|
||||
}
|
||||
}
|
||||
|
||||
vfs::Node* n;
|
||||
ASSERT_EQ(ZX_OK, dir_.Lookup("new_node", &n));
|
||||
ASSERT_EQ(new_node.get(), n);
|
||||
}
|
||||
|
||||
class DirectoryWrapper {
|
||||
public:
|
||||
DirectoryWrapper(bool start_loop = true)
|
||||
: dir_(std::make_shared<vfs::PseudoDir>()),
|
||||
loop_(&kAsyncLoopConfigNoAttachToThread) {
|
||||
if (start_loop) {
|
||||
loop_.StartThread("vfs test thread");
|
||||
}
|
||||
}
|
||||
|
||||
void AddEntry(const std::string& name, std::unique_ptr<vfs::Node> node,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
ASSERT_EQ(expected_status, dir_->AddEntry(name, std::move(node)));
|
||||
}
|
||||
|
||||
void AddSharedEntry(const std::string& name, std::shared_ptr<vfs::Node> node,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
ASSERT_EQ(expected_status, dir_->AddSharedEntry(name, std::move(node)));
|
||||
}
|
||||
|
||||
fuchsia::io::DirectorySyncPtr Serve(
|
||||
int flags = fuchsia::io::OPEN_RIGHT_READABLE) {
|
||||
fuchsia::io::DirectorySyncPtr ptr;
|
||||
dir_->Serve(flags, ptr.NewRequest().TakeChannel(), loop_.dispatcher());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void AddReadOnlyFile(const std::string& file_name,
|
||||
const std::string& file_content,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
auto read_fn = [file_content](std::vector<uint8_t>* output) {
|
||||
output->resize(file_content.length());
|
||||
std::copy(file_content.begin(), file_content.end(), output->begin());
|
||||
return ZX_OK;
|
||||
};
|
||||
|
||||
auto file =
|
||||
std::make_unique<vfs::BufferedPseudoFile>(std::move(read_fn), nullptr);
|
||||
|
||||
AddEntry(file_name, std::move(file));
|
||||
}
|
||||
|
||||
std::shared_ptr<vfs::PseudoDir>& dir() { return dir_; };
|
||||
|
||||
private:
|
||||
std::shared_ptr<vfs::PseudoDir> dir_;
|
||||
async::Loop loop_;
|
||||
};
|
||||
|
||||
class PseudoDirConnection : public vfs_tests::DirConnection {
|
||||
protected:
|
||||
vfs::Directory* GetDirectoryNode() override { return dir_.dir().get(); }
|
||||
|
||||
DirectoryWrapper dir_;
|
||||
};
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirSimple) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
dir_.AddReadOnlyFile("file2", "file2");
|
||||
dir_.AddReadOnlyFile("file3", "file3");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents = {
|
||||
Dirent::DirentForDot(), Dirent::DirentForDirectory("subdir"),
|
||||
Dirent::DirentForFile("file1"), Dirent::DirentForFile("file2"),
|
||||
Dirent::DirentForFile("file3"),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirOnEmptyDirectory) {
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents = {
|
||||
Dirent::DirentForDot(),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirSizeLessThanFirstEntry) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents;
|
||||
AssertReadDirents(ptr, sizeof(vdirent_t), expected_dirents,
|
||||
ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirSizeLessThanEntry) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents = {Dirent::DirentForDot()};
|
||||
AssertReadDirents(ptr, sizeof(vdirent_t) + 1, expected_dirents);
|
||||
std::vector<Dirent> empty_dirents;
|
||||
AssertReadDirents(ptr, sizeof(vdirent_t), empty_dirents, ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirInParts) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
dir_.AddReadOnlyFile("file2", "file2");
|
||||
dir_.AddReadOnlyFile("file3", "file3");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents1 = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
};
|
||||
|
||||
std::vector<Dirent> expected_dirents2 = {
|
||||
Dirent::DirentForFile("file1"),
|
||||
Dirent::DirentForFile("file2"),
|
||||
Dirent::DirentForFile("file3"),
|
||||
};
|
||||
AssertReadDirents(ptr, 2 * sizeof(vdirent_t) + 10, expected_dirents1);
|
||||
AssertReadDirents(ptr, 3 * sizeof(vdirent_t) + 20, expected_dirents2);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirWithExactBytes) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
dir_.AddReadOnlyFile("file2", "file2");
|
||||
dir_.AddReadOnlyFile("file3", "file3");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents = {
|
||||
Dirent::DirentForDot(), Dirent::DirentForDirectory("subdir"),
|
||||
Dirent::DirentForFile("file1"), Dirent::DirentForFile("file2"),
|
||||
Dirent::DirentForFile("file3"),
|
||||
};
|
||||
uint64_t exact_size = 0;
|
||||
for (auto& d : expected_dirents) {
|
||||
exact_size += d.size_in_bytes();
|
||||
}
|
||||
|
||||
AssertReadDirents(ptr, exact_size, expected_dirents);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirInPartsWithExactBytes) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
dir_.AddReadOnlyFile("file2", "file2");
|
||||
dir_.AddReadOnlyFile("file3", "file3");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents1 = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
};
|
||||
|
||||
std::vector<Dirent> expected_dirents2 = {
|
||||
Dirent::DirentForFile("file1"),
|
||||
Dirent::DirentForFile("file2"),
|
||||
Dirent::DirentForFile("file3"),
|
||||
};
|
||||
uint64_t exact_size1 = 0;
|
||||
for (auto& d : expected_dirents1) {
|
||||
exact_size1 += d.size_in_bytes();
|
||||
}
|
||||
|
||||
uint64_t exact_size2 = 0;
|
||||
for (auto& d : expected_dirents2) {
|
||||
exact_size2 += d.size_in_bytes();
|
||||
}
|
||||
|
||||
AssertReadDirents(ptr, exact_size1, expected_dirents1);
|
||||
AssertReadDirents(ptr, exact_size2, expected_dirents2);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirAfterFullRead) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
};
|
||||
|
||||
std::vector<Dirent> empty_dirents;
|
||||
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
AssertReadDirents(ptr, 1024, empty_dirents);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, RewindWorksAfterFullRead) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
};
|
||||
|
||||
std::vector<Dirent> empty_dirents;
|
||||
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
AssertReadDirents(ptr, 1024, empty_dirents);
|
||||
|
||||
AssertRewind(ptr);
|
||||
|
||||
AssertReadDirents(ptr, 1024, expected_dirents);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, RewindWorksAfterPartialRead) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
dir_.AddReadOnlyFile("file2", "file2");
|
||||
dir_.AddReadOnlyFile("file3", "file3");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents1 = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
};
|
||||
|
||||
std::vector<Dirent> expected_dirents2 = {
|
||||
Dirent::DirentForFile("file1"),
|
||||
Dirent::DirentForFile("file2"),
|
||||
Dirent::DirentForFile("file3"),
|
||||
};
|
||||
AssertReadDirents(ptr, 2 * sizeof(vdirent_t) + 10, expected_dirents1);
|
||||
AssertRewind(ptr);
|
||||
AssertReadDirents(ptr, 2 * sizeof(vdirent_t) + 10, expected_dirents1);
|
||||
AssertReadDirents(ptr, 3 * sizeof(vdirent_t) + 20, expected_dirents2);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirAfterAddingEntry) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents1 = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents1);
|
||||
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
std::vector<Dirent> expected_dirents2 = {
|
||||
Dirent::DirentForFile("file1"),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents2);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirAndRewindAfterAddingEntry) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents1 = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents1);
|
||||
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
AssertRewind(ptr);
|
||||
std::vector<Dirent> expected_dirents2 = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
Dirent::DirentForFile("file1"),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents2);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ReadDirAfterRemovingEntry) {
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<Dirent> expected_dirents1 = {
|
||||
Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir"),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents1);
|
||||
std::vector<Dirent> empty_dirents;
|
||||
ASSERT_EQ(ZX_OK, dir_.dir()->RemoveEntry("subdir"));
|
||||
AssertReadDirents(ptr, 1024, empty_dirents);
|
||||
|
||||
// rewind and check again
|
||||
AssertRewind(ptr);
|
||||
|
||||
std::vector<Dirent> expected_dirents2 = {
|
||||
Dirent::DirentForDot(),
|
||||
};
|
||||
AssertReadDirents(ptr, 1024, expected_dirents2);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, CantReadNodeReferenceDir) {
|
||||
auto ptr = dir_.Serve(fuchsia::io::OPEN_FLAG_NODE_REFERENCE);
|
||||
// make sure node reference was opened
|
||||
zx_status_t status;
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
ASSERT_EQ(ZX_OK, ptr->GetAttr(&status, &attr));
|
||||
ASSERT_EQ(ZX_OK, status);
|
||||
ASSERT_NE(0u, attr.mode | fuchsia::io::MODE_TYPE_DIRECTORY);
|
||||
|
||||
std::vector<uint8_t> out_dirents;
|
||||
ASSERT_EQ(ZX_ERR_PEER_CLOSED, ptr->ReadDirents(100, &status, &out_dirents));
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ServeOnInValidFlags) {
|
||||
uint32_t prohibitive_flags[] = {fuchsia::io::OPEN_RIGHT_ADMIN,
|
||||
fuchsia::io::OPEN_FLAG_NO_REMOTE};
|
||||
uint32_t not_allowed_flags[] = {
|
||||
fuchsia::io::OPEN_FLAG_CREATE, fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT,
|
||||
fuchsia::io::OPEN_FLAG_TRUNCATE, fuchsia::io::OPEN_FLAG_APPEND};
|
||||
|
||||
for (auto not_allowed_flag : not_allowed_flags) {
|
||||
SCOPED_TRACE(std::to_string(not_allowed_flag));
|
||||
AssertOpen(dispatcher(), not_allowed_flag, ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
|
||||
for (auto prohibitive_flag : prohibitive_flags) {
|
||||
SCOPED_TRACE(std::to_string(prohibitive_flag));
|
||||
AssertOpen(dispatcher(), prohibitive_flag, ZX_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, ServeOnValidFlags) {
|
||||
uint32_t allowed_flags[] = {
|
||||
fuchsia::io::OPEN_RIGHT_READABLE, fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
fuchsia::io::OPEN_FLAG_NODE_REFERENCE, fuchsia::io::OPEN_FLAG_DIRECTORY};
|
||||
for (auto allowed_flag : allowed_flags) {
|
||||
SCOPED_TRACE(std::to_string(allowed_flag));
|
||||
AssertOpen(dispatcher(), allowed_flag, ZX_OK);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenSelf) {
|
||||
std::string paths[] = {
|
||||
"", ".", "./",
|
||||
".//", "././", "././/.",
|
||||
"././//", "././/", "././././/././././////./././//././/./././/././."};
|
||||
auto subdir = std::make_shared<vfs::PseudoDir>();
|
||||
dir_.AddSharedEntry("subdir", subdir);
|
||||
auto ptr = dir_.Serve();
|
||||
std::vector<Dirent> expected_dirents = {Dirent::DirentForDot(),
|
||||
Dirent::DirentForDirectory("subdir")};
|
||||
for (auto& path : paths) {
|
||||
SCOPED_TRACE("path: " + path);
|
||||
|
||||
fuchsia::io::DirectorySyncPtr new_ptr;
|
||||
AssertOpenPath(ptr, path, new_ptr);
|
||||
|
||||
// assert correct directory was opened
|
||||
AssertReadDirents(new_ptr, 1024, expected_dirents);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenSubDir) {
|
||||
DirectoryWrapper subdir1(false);
|
||||
DirectoryWrapper subdir2(false);
|
||||
dir_.AddSharedEntry("subdir1", subdir1.dir());
|
||||
dir_.AddSharedEntry("subdir2", subdir2.dir());
|
||||
subdir1.AddReadOnlyFile("file1", "file1");
|
||||
subdir2.AddReadOnlyFile("file2", "file2");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
std::vector<Dirent> expected_dirents_sub1 = {Dirent::DirentForDot(),
|
||||
Dirent::DirentForFile("file1")};
|
||||
std::vector<Dirent> expected_dirents_sub2 = {Dirent::DirentForDot(),
|
||||
Dirent::DirentForFile("file2")};
|
||||
|
||||
std::string paths1[] = {"./subdir1",
|
||||
"././subdir1",
|
||||
".//./././././/./subdir1",
|
||||
"subdir1",
|
||||
"subdir1/",
|
||||
"subdir1/.",
|
||||
"subdir1//",
|
||||
"subdir1///",
|
||||
"subdir1/./",
|
||||
"subdir1/.//",
|
||||
"subdir1/.//.",
|
||||
"subdir1/.//././//./",
|
||||
"subdir1/.//././/./."};
|
||||
for (auto& path : paths1) {
|
||||
SCOPED_TRACE("path: " + path);
|
||||
|
||||
fuchsia::io::DirectorySyncPtr new_ptr;
|
||||
AssertOpenPath(ptr, path, new_ptr);
|
||||
|
||||
// assert correct directory was opened
|
||||
AssertReadDirents(new_ptr, 1024, expected_dirents_sub1);
|
||||
}
|
||||
|
||||
// test with other directory
|
||||
std::string paths2[] = {"./subdir2",
|
||||
"././subdir2",
|
||||
".//./././././/./subdir2",
|
||||
"subdir2",
|
||||
"subdir2/",
|
||||
"subdir2/.",
|
||||
"subdir2//",
|
||||
"subdir2///",
|
||||
"subdir2/./",
|
||||
"subdir2/.//",
|
||||
"subdir2/.//.",
|
||||
"subdir2/.//././//./",
|
||||
"subdir2/.//././/./."};
|
||||
for (auto& path : paths2) {
|
||||
SCOPED_TRACE("path: " + path);
|
||||
|
||||
fuchsia::io::DirectorySyncPtr new_ptr;
|
||||
AssertOpenPath(ptr, path, new_ptr);
|
||||
|
||||
// assert correct directory was opened
|
||||
AssertReadDirents(new_ptr, 1024, expected_dirents_sub2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenFile) {
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
dir_.AddReadOnlyFile("file2", "file2");
|
||||
dir_.AddReadOnlyFile("..foo", "..foo");
|
||||
dir_.AddReadOnlyFile("...foo", "...foo");
|
||||
dir_.AddReadOnlyFile(".foo", ".foo");
|
||||
|
||||
DirectoryWrapper subdir1(false);
|
||||
DirectoryWrapper subdir2(false);
|
||||
|
||||
dir_.AddSharedEntry("subdir1", subdir1.dir());
|
||||
dir_.AddSharedEntry("subdir2", subdir2.dir());
|
||||
subdir1.AddReadOnlyFile("file2", "subdir1/file2");
|
||||
subdir1.AddReadOnlyFile("file1", "subdir1/file1");
|
||||
subdir1.AddReadOnlyFile("..foo", "subdir1/..foo");
|
||||
subdir1.AddReadOnlyFile("...foo", "subdir1/...foo");
|
||||
subdir1.AddReadOnlyFile(".foo", "subdir1/.foo");
|
||||
subdir2.AddReadOnlyFile("file3", "subdir2/file3");
|
||||
subdir2.AddReadOnlyFile("file4", "subdir2/file4");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::string files[] = {"file1", "file2", ".foo",
|
||||
"..foo", "...foo", "subdir1/file1",
|
||||
"subdir1/file2", "subdir2/file3", "subdir2/file4",
|
||||
"subdir1/.foo", "subdir1/..foo", "subdir1/...foo"};
|
||||
for (auto& file : files) {
|
||||
SCOPED_TRACE("file: " + file);
|
||||
fuchsia::io::FileSyncPtr file_ptr;
|
||||
AssertOpenPath(ptr, file, file_ptr, fuchsia::io::OPEN_RIGHT_READABLE);
|
||||
|
||||
AssertRead(file_ptr, 100, file);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenFileWithMultipleSlashesAndDotsInPath) {
|
||||
DirectoryWrapper subdir1(false);
|
||||
|
||||
dir_.AddSharedEntry("subdir1", subdir1.dir());
|
||||
subdir1.AddReadOnlyFile("file1", "file1");
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::string files[] = {"./file1",
|
||||
".//file1",
|
||||
"././/././///././file1",
|
||||
"subdir1//file1",
|
||||
"subdir1///file1",
|
||||
"subdir1////file1",
|
||||
"subdir1/./file1",
|
||||
"subdir1/.//./file1",
|
||||
"subdir1/././file1",
|
||||
"subdir1/././///file1"};
|
||||
for (auto& file : files) {
|
||||
SCOPED_TRACE("file: " + file);
|
||||
fuchsia::io::FileSyncPtr file_ptr;
|
||||
AssertOpenPath(ptr, file, file_ptr, fuchsia::io::OPEN_RIGHT_READABLE);
|
||||
|
||||
AssertRead(file_ptr, 100, "file1");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenWithInValidPaths) {
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
|
||||
DirectoryWrapper subdir1(false);
|
||||
DirectoryWrapper subdir2(false);
|
||||
|
||||
dir_.AddSharedEntry("subdir1", subdir1.dir());
|
||||
dir_.AddSharedEntry("subdir2", subdir2.dir());
|
||||
subdir1.AddReadOnlyFile("file1", "subdir1/file1");
|
||||
subdir2.AddReadOnlyFile("file3", "subdir2/file3");
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
std::vector<std::string> not_found_paths = {"file", "subdir", "subdir1/file",
|
||||
"subdir2/file1"};
|
||||
|
||||
std::string big_path(NAME_MAX + 1, 'a');
|
||||
std::vector<std::string> invalid_args_paths = {"..",
|
||||
"../",
|
||||
"subdir1/..",
|
||||
"subdir1/../",
|
||||
"subdir1/../file1",
|
||||
"file1/../file1",
|
||||
std::move(big_path)};
|
||||
|
||||
std::vector<std::string> not_dir_paths = {
|
||||
"subdir1/file1/", "subdir1/file1//", "subdir1/file1///",
|
||||
"subdir1/file1/.", "subdir1/file1/file2", "./file1/",
|
||||
"./file1/.", "./file1/file2"};
|
||||
|
||||
std::vector<zx_status_t> expected_errors = {
|
||||
ZX_ERR_NOT_FOUND, ZX_ERR_INVALID_ARGS, ZX_ERR_NOT_DIR};
|
||||
std::vector<std::vector<std::string>> invalid_paths = {
|
||||
not_found_paths, invalid_args_paths, not_dir_paths};
|
||||
|
||||
// sanity check
|
||||
ASSERT_EQ(expected_errors.size(), invalid_paths.size());
|
||||
|
||||
for (size_t i = 0; i < expected_errors.size(); i++) {
|
||||
auto expected_status = expected_errors[i];
|
||||
auto& paths = invalid_paths[i];
|
||||
for (auto& path : paths) {
|
||||
SCOPED_TRACE("path: " + path);
|
||||
fuchsia::io::NodeSyncPtr file_ptr;
|
||||
AssertOpenPath(ptr, path, file_ptr, 0, 0, expected_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, CannotOpenFileWithDirectoryFlag) {
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
auto ptr = dir_.Serve();
|
||||
fuchsia::io::FileSyncPtr file_ptr;
|
||||
AssertOpenPath(ptr, "file1", file_ptr, fuchsia::io::OPEN_FLAG_DIRECTORY, 0,
|
||||
ZX_ERR_NOT_DIR);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, CannotOpenDirectoryWithInvalidFlags) {
|
||||
uint32_t invalid_flags[] = {
|
||||
fuchsia::io::OPEN_FLAG_CREATE, fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT,
|
||||
fuchsia::io::OPEN_FLAG_TRUNCATE, fuchsia::io::OPEN_FLAG_APPEND};
|
||||
DirectoryWrapper subdir1(false);
|
||||
dir_.AddSharedEntry("subdir1", subdir1.dir());
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
std::string paths[] = {".", "subdir1"};
|
||||
|
||||
for (auto& path : paths) {
|
||||
for (auto flag : invalid_flags) {
|
||||
SCOPED_TRACE("path: " + path + ", flag: " + std::to_string(flag));
|
||||
fuchsia::io::NodeSyncPtr node_ptr;
|
||||
AssertOpenPath(ptr, path, node_ptr, flag, 0, ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenDirWithCorrectMode) {
|
||||
DirectoryWrapper subdir1(false);
|
||||
dir_.AddSharedEntry("subdir1", subdir1.dir());
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
std::string paths[] = {".", "subdir1"};
|
||||
|
||||
uint32_t modes[] = {fuchsia::io::MODE_TYPE_DIRECTORY, V_IXUSR, V_IWUSR,
|
||||
V_IRUSR};
|
||||
|
||||
for (auto& path : paths) {
|
||||
for (auto mode : modes) {
|
||||
SCOPED_TRACE("path: " + path + ", mode: " + std::to_string(mode));
|
||||
fuchsia::io::NodeSyncPtr node_ptr;
|
||||
AssertOpenPath(ptr, path, node_ptr, 0, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenDirWithInCorrectMode) {
|
||||
DirectoryWrapper subdir1(false);
|
||||
dir_.AddSharedEntry("subdir1", subdir1.dir());
|
||||
|
||||
auto ptr = dir_.Serve();
|
||||
std::string paths[] = {".", "subdir1"};
|
||||
|
||||
uint32_t modes[] = {
|
||||
fuchsia::io::MODE_TYPE_FILE, fuchsia::io::MODE_TYPE_BLOCK_DEVICE,
|
||||
fuchsia::io::MODE_TYPE_SOCKET, fuchsia::io::MODE_TYPE_SERVICE};
|
||||
|
||||
for (auto& path : paths) {
|
||||
for (auto mode : modes) {
|
||||
SCOPED_TRACE("path: " + path + ", mode: " + std::to_string(mode));
|
||||
fuchsia::io::NodeSyncPtr node_ptr;
|
||||
AssertOpenPath(ptr, path, node_ptr, 0, mode, ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenFileWithCorrectMode) {
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
uint32_t modes[] = {fuchsia::io::MODE_TYPE_FILE, V_IXUSR, V_IWUSR, V_IRUSR};
|
||||
|
||||
for (auto mode : modes) {
|
||||
SCOPED_TRACE("mode: " + std::to_string(mode));
|
||||
fuchsia::io::NodeSyncPtr node_ptr;
|
||||
AssertOpenPath(ptr, "file1", node_ptr, 0, mode);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, OpenFileWithInCorrectMode) {
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
auto ptr = dir_.Serve();
|
||||
|
||||
uint32_t modes[] = {
|
||||
fuchsia::io::MODE_TYPE_DIRECTORY, fuchsia::io::MODE_TYPE_BLOCK_DEVICE,
|
||||
fuchsia::io::MODE_TYPE_SOCKET, fuchsia::io::MODE_TYPE_SERVICE};
|
||||
|
||||
for (auto mode : modes) {
|
||||
SCOPED_TRACE("mode: " + std::to_string(mode));
|
||||
fuchsia::io::NodeSyncPtr node_ptr;
|
||||
AssertOpenPath(ptr, "file1", node_ptr, 0, mode, ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, CanCloneDirectoryConnection) {
|
||||
dir_.AddReadOnlyFile("file1", "file1");
|
||||
auto ptr = dir_.Serve();
|
||||
fuchsia::io::DirectorySyncPtr cloned_ptr;
|
||||
ptr->Clone(0, fidl::InterfaceRequest<fuchsia::io::Node>(
|
||||
cloned_ptr.NewRequest().TakeChannel()));
|
||||
|
||||
fuchsia::io::NodeSyncPtr node_ptr;
|
||||
AssertOpenPath(cloned_ptr, "file1", node_ptr, 0, 0);
|
||||
}
|
||||
|
||||
TEST_F(PseudoDirConnection, NodeReferenceIsClonedAsNodeReference) {
|
||||
fuchsia::io::DirectorySyncPtr cloned_ptr;
|
||||
{
|
||||
auto ptr = dir_.Serve(fuchsia::io::OPEN_FLAG_NODE_REFERENCE);
|
||||
|
||||
ptr->Clone(0, fidl::InterfaceRequest<fuchsia::io::Node>(
|
||||
cloned_ptr.NewRequest().TakeChannel()));
|
||||
}
|
||||
// make sure node reference was cloned
|
||||
zx_status_t status;
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
ASSERT_EQ(ZX_OK, cloned_ptr->GetAttr(&status, &attr));
|
||||
ASSERT_EQ(ZX_OK, status);
|
||||
ASSERT_NE(0u, attr.mode | fuchsia::io::MODE_TYPE_DIRECTORY);
|
||||
|
||||
std::vector<uint8_t> out_dirents;
|
||||
ASSERT_EQ(ZX_ERR_PEER_CLOSED,
|
||||
cloned_ptr->ReadDirents(100, &status, &out_dirents));
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,208 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/pseudo_file.h>
|
||||
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/vfs/cpp/flags.h>
|
||||
#include <lib/vfs/cpp/internal/file_connection.h>
|
||||
#include <zircon/assert.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
BufferedPseudoFile::BufferedPseudoFile(ReadHandler read_handler,
|
||||
WriteHandler write_handler,
|
||||
size_t buffer_capacity)
|
||||
: read_handler_(std::move(read_handler)),
|
||||
write_handler_(std::move(write_handler)),
|
||||
buffer_capacity_(buffer_capacity) {
|
||||
ZX_DEBUG_ASSERT(read_handler_ != nullptr);
|
||||
}
|
||||
|
||||
BufferedPseudoFile::~BufferedPseudoFile() = default;
|
||||
|
||||
zx_status_t BufferedPseudoFile::CreateConnection(
|
||||
uint32_t flags, std::unique_ptr<Connection>* connection) {
|
||||
std::vector<uint8_t> output;
|
||||
if (Flags::IsReadable(flags)) {
|
||||
zx_status_t status = read_handler_(&output);
|
||||
if (status != ZX_OK) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
*connection = std::make_unique<BufferedPseudoFile::Content>(
|
||||
this, flags, std::move(output));
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t BufferedPseudoFile::GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const {
|
||||
out_attributes->mode = fuchsia::io::MODE_TYPE_FILE;
|
||||
if (read_handler_ != nullptr)
|
||||
out_attributes->mode |= V_IRUSR;
|
||||
if (write_handler_)
|
||||
out_attributes->mode |= V_IWUSR;
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t BufferedPseudoFile::GetAdditionalAllowedFlags() const {
|
||||
auto allowed_flags = fuchsia::io::OPEN_RIGHT_READABLE;
|
||||
if (write_handler_ != nullptr) {
|
||||
allowed_flags |=
|
||||
fuchsia::io::OPEN_RIGHT_WRITABLE | fuchsia::io::OPEN_FLAG_TRUNCATE;
|
||||
}
|
||||
return allowed_flags;
|
||||
}
|
||||
|
||||
uint32_t BufferedPseudoFile::GetProhibitiveFlags() const {
|
||||
return fuchsia::io::OPEN_FLAG_APPEND;
|
||||
}
|
||||
|
||||
uint64_t BufferedPseudoFile::GetLength() {
|
||||
// this should never be called
|
||||
ZX_DEBUG_ASSERT(false);
|
||||
|
||||
return 0u;
|
||||
}
|
||||
|
||||
size_t BufferedPseudoFile::GetCapacity() {
|
||||
// this should never be called
|
||||
ZX_DEBUG_ASSERT(false);
|
||||
|
||||
return buffer_capacity_;
|
||||
}
|
||||
|
||||
BufferedPseudoFile::Content::Content(BufferedPseudoFile* file, uint32_t flags,
|
||||
std::vector<uint8_t> content)
|
||||
: Connection(flags),
|
||||
file_(file),
|
||||
buffer_(std::move(content)),
|
||||
flags_(flags) {
|
||||
SetInputLength(buffer_.size());
|
||||
}
|
||||
|
||||
BufferedPseudoFile::Content::~Content() {
|
||||
if (!dirty_) {
|
||||
return;
|
||||
}
|
||||
file_->write_handler_(std::move(buffer_));
|
||||
};
|
||||
|
||||
zx_status_t BufferedPseudoFile::Content::ReadAt(
|
||||
uint64_t count, uint64_t offset, std::vector<uint8_t>* out_data) {
|
||||
if (offset >= buffer_.size()) {
|
||||
return ZX_OK;
|
||||
}
|
||||
size_t actual = std::min(buffer_.size() - offset, count);
|
||||
out_data->resize(actual);
|
||||
std::copy_n(buffer_.begin() + offset, actual, out_data->begin());
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
uint32_t BufferedPseudoFile::Content::GetAdditionalAllowedFlags() const {
|
||||
return file_->GetAdditionalAllowedFlags();
|
||||
}
|
||||
|
||||
uint32_t BufferedPseudoFile::Content::GetProhibitiveFlags() const {
|
||||
return file_->GetProhibitiveFlags();
|
||||
}
|
||||
|
||||
zx_status_t BufferedPseudoFile::Content::GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const {
|
||||
return file_->GetAttr(out_attributes);
|
||||
}
|
||||
|
||||
zx_status_t BufferedPseudoFile::Content::WriteAt(std::vector<uint8_t> data,
|
||||
uint64_t offset,
|
||||
uint64_t* out_actual) {
|
||||
if (offset >= file_->buffer_capacity_) {
|
||||
*out_actual = 0u;
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
size_t actual = std::min(data.size(), file_->buffer_capacity_ - offset);
|
||||
if (actual == 0) {
|
||||
*out_actual = 0u;
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
dirty_ = true;
|
||||
if (actual + offset > buffer_.size()) {
|
||||
SetInputLength(offset + actual);
|
||||
}
|
||||
|
||||
std::copy_n(data.begin(), actual, buffer_.begin() + offset);
|
||||
*out_actual = actual;
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t BufferedPseudoFile::Content::Truncate(uint64_t length) {
|
||||
if (length > file_->buffer_capacity_) {
|
||||
return ZX_ERR_NO_SPACE;
|
||||
}
|
||||
|
||||
dirty_ = true;
|
||||
SetInputLength(length);
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
uint64_t BufferedPseudoFile::Content::GetLength() { return buffer_.size(); }
|
||||
|
||||
size_t BufferedPseudoFile::Content::GetCapacity() {
|
||||
return file_->buffer_capacity_;
|
||||
}
|
||||
|
||||
void BufferedPseudoFile::Content::SetInputLength(size_t length) {
|
||||
ZX_DEBUG_ASSERT(length <= file_->buffer_capacity_);
|
||||
|
||||
buffer_.resize(length);
|
||||
}
|
||||
|
||||
zx_status_t BufferedPseudoFile::Content::Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
std::unique_ptr<Connection> connection;
|
||||
zx_status_t status = CreateConnection(flags_, &connection);
|
||||
if (status != ZX_OK) {
|
||||
SendOnOpenEventOnError(flags_, std::move(request), status);
|
||||
return status;
|
||||
}
|
||||
status = connection->Bind(std::move(request), dispatcher);
|
||||
|
||||
AddConnection(std::move(connection));
|
||||
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
// only one connection allowed per content
|
||||
ZX_DEBUG_ASSERT(connections_.size() == 1);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
std::unique_ptr<Connection> BufferedPseudoFile::Content::Close(
|
||||
Connection* connection) {
|
||||
File::Close(connection);
|
||||
return file_->Close(this);
|
||||
}
|
||||
|
||||
void BufferedPseudoFile::Content::Clone(
|
||||
uint32_t flags, uint32_t parent_flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
file_->Clone(flags, parent_flags, std::move(object), dispatcher);
|
||||
}
|
||||
|
||||
void BufferedPseudoFile::Content::SendOnOpenEvent(zx_status_t status) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
ZX_DEBUG_ASSERT(connections_.size() == 1);
|
||||
connections_[0]->SendOnOpenEvent(status);
|
||||
}
|
||||
|
||||
} // namespace vfs
|
@ -1,123 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_PSEUDO_FILE_H_
|
||||
#define LIB_VFS_CPP_PSEUDO_FILE_H_
|
||||
|
||||
#include <lib/vfs/cpp/connection.h>
|
||||
#include <lib/vfs/cpp/file.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
// Buffered pseudo-file.
|
||||
//
|
||||
// This variant is optimized for incrementally reading and writing properties
|
||||
// which are larger than can typically be read or written by the client in
|
||||
// a single I/O transaction.
|
||||
//
|
||||
// In read mode, the pseudo-file invokes its read handler when the file is
|
||||
// opened and retains the content in a buffer which the client incrementally
|
||||
// reads from and can seek within.
|
||||
//
|
||||
// In write mode, the client incrementally writes into and seeks within the
|
||||
// buffer which the pseudo-file delivers as a whole to the write handler when
|
||||
// the file is closed(if there were any writes). Truncation is also supported.
|
||||
//
|
||||
// Instances of this class are thread-safe.
|
||||
class BufferedPseudoFile : public File {
|
||||
public:
|
||||
// Handler called to read from the pseudo-file.
|
||||
using ReadHandler = fit::function<zx_status_t(std::vector<uint8_t>* output)>;
|
||||
|
||||
// Handler called to write into the pseudo-file.
|
||||
using WriteHandler = fit::function<void(std::vector<uint8_t> input)>;
|
||||
|
||||
// Creates a buffered pseudo-file.
|
||||
//
|
||||
// |read_handler| cannot be null. If the |write_handler| is null, then the
|
||||
// pseudo-file is considered not writable. The |buffer_capacity|
|
||||
// determines the maximum number of bytes which can be written to the
|
||||
// pseudo-file's input buffer when it it opened for writing.
|
||||
BufferedPseudoFile(ReadHandler read_handler = ReadHandler(),
|
||||
WriteHandler write_handler = WriteHandler(),
|
||||
size_t buffer_capacity = 1024);
|
||||
|
||||
~BufferedPseudoFile() override;
|
||||
|
||||
// |Node| implementations:
|
||||
zx_status_t GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const override;
|
||||
|
||||
protected:
|
||||
zx_status_t CreateConnection(
|
||||
uint32_t flags, std::unique_ptr<Connection>* connection) override;
|
||||
|
||||
uint32_t GetAdditionalAllowedFlags() const override;
|
||||
|
||||
uint32_t GetProhibitiveFlags() const override;
|
||||
|
||||
private:
|
||||
class Content final : public Connection, public File {
|
||||
public:
|
||||
Content(BufferedPseudoFile* file, uint32_t flags,
|
||||
std::vector<uint8_t> content);
|
||||
~Content() override;
|
||||
|
||||
// |File| implementations:
|
||||
zx_status_t ReadAt(uint64_t count, uint64_t offset,
|
||||
std::vector<uint8_t>* out_data) override;
|
||||
zx_status_t WriteAt(std::vector<uint8_t> data, uint64_t offset,
|
||||
uint64_t* out_actual) override;
|
||||
zx_status_t Truncate(uint64_t length) override;
|
||||
|
||||
uint64_t GetLength() override;
|
||||
|
||||
size_t GetCapacity() override;
|
||||
|
||||
// Connection implementation:
|
||||
zx_status_t Bind(zx::channel request,
|
||||
async_dispatcher_t* dispatcher) override;
|
||||
|
||||
void SendOnOpenEvent(zx_status_t status) override;
|
||||
|
||||
// |Node| implementations:
|
||||
std::unique_ptr<Connection> Close(Connection* connection) override;
|
||||
|
||||
void Clone(uint32_t flags, uint32_t parent_flags,
|
||||
fidl::InterfaceRequest<fuchsia::io::Node> object,
|
||||
async_dispatcher_t* dispatcher) override;
|
||||
|
||||
zx_status_t GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const override;
|
||||
|
||||
protected:
|
||||
uint32_t GetAdditionalAllowedFlags() const override;
|
||||
|
||||
uint32_t GetProhibitiveFlags() const override;
|
||||
|
||||
private:
|
||||
void SetInputLength(size_t length);
|
||||
|
||||
BufferedPseudoFile* const file_;
|
||||
|
||||
std::vector<uint8_t> buffer_;
|
||||
uint32_t flags_;
|
||||
|
||||
// true if the file was written into
|
||||
bool dirty_ = false;
|
||||
};
|
||||
|
||||
// |File| implementations:
|
||||
uint64_t GetLength() override;
|
||||
|
||||
size_t GetCapacity() override;
|
||||
|
||||
ReadHandler const read_handler_;
|
||||
WriteHandler const write_handler_;
|
||||
const size_t buffer_capacity_;
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_PSEUDO_FILE_H_
|
@ -1,614 +0,0 @@
|
||||
// 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 <lib/async-loop/cpp/loop.h>
|
||||
#include <lib/fdio/limits.h>
|
||||
#include <lib/fdio/fd.h>
|
||||
#include <lib/fdio/fdio.h>
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <unistd.h>
|
||||
#include <zircon/processargs.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/gtest/real_loop_fixture.h"
|
||||
#include "lib/vfs/cpp/pseudo_file.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class FileWrapper {
|
||||
public:
|
||||
const std::string& buffer() { return buffer_; };
|
||||
|
||||
vfs::BufferedPseudoFile* file() { return file_.get(); };
|
||||
|
||||
static FileWrapper CreateReadWriteFile(std::string initial_str,
|
||||
size_t capacity,
|
||||
bool start_loop = true) {
|
||||
return FileWrapper(true, initial_str, capacity, start_loop);
|
||||
}
|
||||
|
||||
static FileWrapper CreateReadOnlyFile(std::string initial_str,
|
||||
bool start_loop = true) {
|
||||
return FileWrapper(false, initial_str, initial_str.length(), start_loop);
|
||||
}
|
||||
|
||||
async_dispatcher_t* dispatcher() { return loop_.dispatcher(); }
|
||||
|
||||
async::Loop& loop() { return loop_; }
|
||||
|
||||
private:
|
||||
FileWrapper(bool write_allowed, std::string initial_str, size_t capacity,
|
||||
bool start_loop)
|
||||
: buffer_(std::move(initial_str)),
|
||||
loop_(&kAsyncLoopConfigNoAttachToThread) {
|
||||
auto readFn = [this](std::vector<uint8_t>* output) {
|
||||
output->resize(buffer_.length());
|
||||
std::copy(buffer_.begin(), buffer_.end(), output->begin());
|
||||
return ZX_OK;
|
||||
};
|
||||
|
||||
vfs::BufferedPseudoFile::WriteHandler writeFn;
|
||||
if (write_allowed) {
|
||||
writeFn = [this](std::vector<uint8_t> input) {
|
||||
std::string str(input.size(), 0);
|
||||
std::copy(input.begin(), input.begin() + input.size(), str.begin());
|
||||
buffer_ = std::move(str);
|
||||
};
|
||||
}
|
||||
|
||||
file_ = std::make_unique<vfs::BufferedPseudoFile>(
|
||||
std::move(readFn), std::move(writeFn), capacity);
|
||||
if (start_loop) {
|
||||
loop_.StartThread("vfs test thread");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vfs::BufferedPseudoFile> file_;
|
||||
std::string buffer_;
|
||||
async::Loop loop_;
|
||||
};
|
||||
|
||||
class BufferedPseudoFileTest : public gtest::RealLoopFixture {
|
||||
protected:
|
||||
void AssertOpen(vfs::Node* node, async_dispatcher_t* dispatcher,
|
||||
uint32_t flags, zx_status_t expected_status,
|
||||
bool test_on_open_event = true) {
|
||||
fuchsia::io::NodePtr node_ptr;
|
||||
if (test_on_open_event) {
|
||||
flags |= fuchsia::io::OPEN_FLAG_DESCRIBE;
|
||||
}
|
||||
EXPECT_EQ(
|
||||
expected_status,
|
||||
node->Serve(flags, node_ptr.NewRequest().TakeChannel(), dispatcher));
|
||||
|
||||
if (test_on_open_event) {
|
||||
bool on_open_called = false;
|
||||
node_ptr.events().OnOpen =
|
||||
[&](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> info) {
|
||||
EXPECT_FALSE(on_open_called); // should be called only once
|
||||
on_open_called = true;
|
||||
EXPECT_EQ(expected_status, status);
|
||||
if (expected_status == ZX_OK) {
|
||||
ASSERT_NE(info.get(), nullptr);
|
||||
EXPECT_TRUE(info->is_file());
|
||||
} else {
|
||||
EXPECT_EQ(info.get(), nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
ASSERT_TRUE(RunLoopUntil([&]() { return on_open_called; }));
|
||||
}
|
||||
}
|
||||
|
||||
fuchsia::io::FileSyncPtr OpenReadWrite(vfs::Node* node,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
return OpenFile(
|
||||
node,
|
||||
fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
dispatcher);
|
||||
}
|
||||
|
||||
fuchsia::io::FileSyncPtr OpenRead(vfs::Node* node,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
return OpenFile(node, fuchsia::io::OPEN_RIGHT_READABLE, dispatcher);
|
||||
}
|
||||
|
||||
fuchsia::io::FileSyncPtr OpenFile(vfs::Node* node, uint32_t flags,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
fuchsia::io::FileSyncPtr ptr;
|
||||
node->Serve(flags, ptr.NewRequest().TakeChannel(), dispatcher);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void AssertWriteAt(fuchsia::io::FileSyncPtr& file, const std::string& str,
|
||||
int offset, zx_status_t expected_status = ZX_OK,
|
||||
int expected_actual = -1) {
|
||||
zx_status_t status;
|
||||
uint64_t actual;
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(str.length());
|
||||
std::copy(str.begin(), str.end(), buffer.begin());
|
||||
file->WriteAt(buffer, offset, &status, &actual);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
ASSERT_EQ(expected_actual == -1 ? str.length() : expected_actual, actual);
|
||||
}
|
||||
|
||||
void AssertWrite(fuchsia::io::FileSyncPtr& file, const std::string& str,
|
||||
zx_status_t expected_status = ZX_OK,
|
||||
int expected_actual = -1) {
|
||||
zx_status_t status;
|
||||
uint64_t actual;
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(str.length());
|
||||
std::copy(str.begin(), str.end(), buffer.begin());
|
||||
file->Write(buffer, &status, &actual);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
ASSERT_EQ(expected_actual == -1 ? str.length() : expected_actual, actual);
|
||||
}
|
||||
|
||||
void AssertReadAt(fuchsia::io::FileSyncPtr& file, int offset, int count,
|
||||
const std::string& expected_str,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
zx_status_t status;
|
||||
std::vector<uint8_t> buffer;
|
||||
file->ReadAt(count, offset, &status, &buffer);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
std::string str(buffer.size(), 0);
|
||||
std::copy(buffer.begin(), buffer.end(), str.begin());
|
||||
ASSERT_EQ(expected_str, str);
|
||||
}
|
||||
|
||||
void AssertRead(fuchsia::io::FileSyncPtr& file, int count,
|
||||
const std::string& expected_str,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
zx_status_t status;
|
||||
std::vector<uint8_t> buffer;
|
||||
file->Read(count, &status, &buffer);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
std::string str(buffer.size(), 0);
|
||||
std::copy(buffer.begin(), buffer.end(), str.begin());
|
||||
ASSERT_EQ(expected_str, str);
|
||||
}
|
||||
|
||||
void AssertTruncate(fuchsia::io::FileSyncPtr& file, int count,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
zx_status_t status;
|
||||
file->Truncate(count, &status);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
}
|
||||
|
||||
void AssertSeek(fuchsia::io::FileSyncPtr& file, int64_t offest,
|
||||
fuchsia::io::SeekOrigin seek, uint64_t expected_offset,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
zx_status_t status;
|
||||
uint64_t new_offset;
|
||||
file->Seek(offest, seek, &status, &new_offset);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
ASSERT_EQ(expected_offset, new_offset);
|
||||
}
|
||||
|
||||
void CloseFile(fuchsia::io::FileSyncPtr& file,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
zx_status_t status = 1;
|
||||
file->Close(&status);
|
||||
EXPECT_EQ(expected_status, status);
|
||||
}
|
||||
|
||||
void AssertFileWrapperState(FileWrapper& file_wrapper,
|
||||
const std::string& expected_str) {
|
||||
ASSERT_TRUE(RunLoopUntil([&]() {
|
||||
return file_wrapper.buffer() == expected_str;
|
||||
})) << file_wrapper.buffer();
|
||||
}
|
||||
|
||||
int OpenAsFD(vfs::Node* node, async_dispatcher_t* dispatcher) {
|
||||
zx::channel local, remote;
|
||||
EXPECT_EQ(ZX_OK, zx::channel::create(0, &local, &remote));
|
||||
EXPECT_EQ(ZX_OK, node->Serve(fuchsia::io::OPEN_RIGHT_READABLE |
|
||||
fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
std::move(remote), dispatcher));
|
||||
int fd = -1;
|
||||
EXPECT_EQ(ZX_OK, fdio_fd_create(local.release(), &fd));
|
||||
return fd;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, ServeOnInValidFlagsForReadWriteFile) {
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100, false);
|
||||
{
|
||||
SCOPED_TRACE("OPEN_FLAG_DIRECTORY");
|
||||
AssertOpen(file_wrapper.file(), dispatcher(),
|
||||
fuchsia::io::OPEN_FLAG_DIRECTORY, ZX_ERR_NOT_DIR);
|
||||
}
|
||||
uint32_t not_allowed_flags[] = {fuchsia::io::OPEN_RIGHT_ADMIN,
|
||||
fuchsia::io::OPEN_FLAG_CREATE,
|
||||
fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT,
|
||||
fuchsia::io::OPEN_FLAG_NO_REMOTE};
|
||||
for (auto not_allowed_flag : not_allowed_flags) {
|
||||
SCOPED_TRACE(std::to_string(not_allowed_flag));
|
||||
AssertOpen(file_wrapper.file(), dispatcher(), not_allowed_flag,
|
||||
ZX_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("OPEN_FLAG_APPEND");
|
||||
AssertOpen(file_wrapper.file(), dispatcher(), fuchsia::io::OPEN_FLAG_APPEND,
|
||||
ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, ServeOnInValidFlagsForReadOnlyFile) {
|
||||
auto file_wrapper = FileWrapper::CreateReadOnlyFile("test_str");
|
||||
{
|
||||
SCOPED_TRACE("OPEN_FLAG_DIRECTORY");
|
||||
AssertOpen(file_wrapper.file(), dispatcher(),
|
||||
fuchsia::io::OPEN_FLAG_DIRECTORY, ZX_ERR_NOT_DIR);
|
||||
}
|
||||
uint32_t not_allowed_flags[] = {
|
||||
fuchsia::io::OPEN_RIGHT_ADMIN, fuchsia::io::OPEN_FLAG_CREATE,
|
||||
fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT, fuchsia::io::OPEN_FLAG_NO_REMOTE,
|
||||
fuchsia::io::OPEN_RIGHT_WRITABLE, fuchsia::io::OPEN_FLAG_TRUNCATE};
|
||||
for (auto not_allowed_flag : not_allowed_flags) {
|
||||
SCOPED_TRACE(std::to_string(not_allowed_flag));
|
||||
AssertOpen(file_wrapper.file(), dispatcher(), not_allowed_flag,
|
||||
ZX_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TRACE("OPEN_FLAG_APPEND");
|
||||
AssertOpen(file_wrapper.file(), dispatcher(), fuchsia::io::OPEN_FLAG_APPEND,
|
||||
ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, ServeOnValidFlagsForReadWriteFile) {
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100, false);
|
||||
uint32_t allowed_flags[] = {
|
||||
fuchsia::io::OPEN_RIGHT_READABLE, fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
fuchsia::io::OPEN_FLAG_NODE_REFERENCE, fuchsia::io::OPEN_FLAG_TRUNCATE};
|
||||
for (auto allowed_flag : allowed_flags) {
|
||||
SCOPED_TRACE(std::to_string(allowed_flag));
|
||||
AssertOpen(file_wrapper.file(), dispatcher(), allowed_flag, ZX_OK);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, ServeOnValidFlagsForReadOnlyFile) {
|
||||
auto file_wrapper = FileWrapper::CreateReadOnlyFile("test_str", false);
|
||||
uint32_t allowed_flags[] = {fuchsia::io::OPEN_RIGHT_READABLE,
|
||||
fuchsia::io::OPEN_FLAG_NODE_REFERENCE};
|
||||
for (auto allowed_flag : allowed_flags) {
|
||||
SCOPED_TRACE(std::to_string(allowed_flag));
|
||||
AssertOpen(file_wrapper.file(), dispatcher(), allowed_flag, ZX_OK);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, Simple) {
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile("test_str", 100);
|
||||
|
||||
int fd = OpenAsFD(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
ASSERT_LE(0, fd);
|
||||
|
||||
char buffer[1024];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
ASSERT_EQ(5, pread(fd, buffer, 5, 0));
|
||||
EXPECT_STREQ("test_", buffer);
|
||||
|
||||
ASSERT_EQ(4, write(fd, "abcd", 4));
|
||||
ASSERT_EQ(5, pread(fd, buffer, 5, 0));
|
||||
EXPECT_STREQ("abcd_", buffer);
|
||||
|
||||
ASSERT_GE(0, close(fd));
|
||||
file_wrapper.loop().RunUntilIdle();
|
||||
|
||||
AssertFileWrapperState(file_wrapper, "abcd_str");
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, WriteAt) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertWriteAt(file, "was", 5);
|
||||
|
||||
const std::string updated_str = "this wasa test string";
|
||||
// confirm by reading
|
||||
AssertRead(file, str.length(), updated_str);
|
||||
|
||||
// make sure file was not updated before conenction was closed.
|
||||
ASSERT_EQ(file_wrapper.buffer(), str);
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
AssertFileWrapperState(file_wrapper, updated_str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, MultipleWriteAt) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertWriteAt(file, "was", 5);
|
||||
|
||||
AssertWriteAt(file, "tests", 10);
|
||||
|
||||
const std::string updated_str = "this wasa testsstring";
|
||||
// confirm by reading
|
||||
AssertRead(file, str.length(), updated_str);
|
||||
|
||||
// make sure file was not updated before conenction was closed.
|
||||
ASSERT_EQ(file_wrapper.buffer(), str);
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
AssertFileWrapperState(file_wrapper, updated_str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, ReadAt) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertReadAt(file, 5, 10, str.substr(5, 10));
|
||||
|
||||
// try one more
|
||||
AssertReadAt(file, 15, 5, str.substr(15, 5));
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, Read) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertRead(file, 10, str.substr(0, 10));
|
||||
|
||||
// offset should have moved
|
||||
AssertRead(file, 10, str.substr(10, 10));
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, Write) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertWrite(file, "It");
|
||||
|
||||
// offset should have moved
|
||||
AssertWrite(file, " is");
|
||||
|
||||
const std::string updated_str = "It isis a test string";
|
||||
|
||||
AssertReadAt(file, 0, 100, updated_str);
|
||||
|
||||
// make sure file was not updated before conenction was closed.
|
||||
ASSERT_EQ(file_wrapper.buffer(), str);
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
// make sure file was updated
|
||||
AssertFileWrapperState(file_wrapper, updated_str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, Truncate) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertTruncate(file, 10);
|
||||
|
||||
AssertRead(file, 100, str.substr(0, 10));
|
||||
|
||||
// make sure file was not updated before conenction was closed.
|
||||
ASSERT_EQ(file_wrapper.buffer(), str);
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
// make sure file was updated
|
||||
AssertFileWrapperState(file_wrapper, str.substr(0, 10));
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, SeekFromStart) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
|
||||
auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertSeek(file, 5, fuchsia::io::SeekOrigin::START, 5);
|
||||
|
||||
AssertRead(file, 100, str.substr(5));
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, SeekFromCurent) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
|
||||
auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertSeek(file, 5, fuchsia::io::SeekOrigin::START, 5);
|
||||
|
||||
AssertSeek(file, 10, fuchsia::io::SeekOrigin::CURRENT, 15);
|
||||
|
||||
AssertRead(file, 100, str.substr(15));
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, SeekFromEnd) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
|
||||
auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertSeek(file, -2, fuchsia::io::SeekOrigin::END, str.length() - 2);
|
||||
|
||||
AssertRead(file, 100, str.substr(str.length() - 2));
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, SeekFromEndWith0Offset) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
|
||||
auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertSeek(file, 0, fuchsia::io::SeekOrigin::END, str.length());
|
||||
|
||||
AssertRead(file, 100, "");
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, SeekFailsIfOffsetMoreThanCapacity) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadOnlyFile(str);
|
||||
auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertSeek(file, 1, fuchsia::io::SeekOrigin::END, 0, ZX_ERR_OUT_OF_RANGE);
|
||||
|
||||
// make sure offset didnot change
|
||||
AssertRead(file, 100, str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, WriteafterEndOfFile) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertSeek(file, 5, fuchsia::io::SeekOrigin::END, str.length() + 5);
|
||||
|
||||
AssertWrite(file, "is");
|
||||
|
||||
auto updated_str = str;
|
||||
updated_str.append(5, 0).append("is");
|
||||
|
||||
AssertReadAt(file, 0, 100, updated_str);
|
||||
|
||||
// make sure file was not updated before conenction was closed.
|
||||
ASSERT_EQ(file_wrapper.buffer(), str);
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
// make sure file was updated
|
||||
AssertFileWrapperState(file_wrapper, updated_str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, WriteFailsForReadOnly) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertWrite(file, "is", ZX_ERR_ACCESS_DENIED, 0);
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
// make sure file was not updated
|
||||
AssertFileWrapperState(file_wrapper, str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, WriteAtFailsForReadOnly) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertWriteAt(file, "is", 0, ZX_ERR_ACCESS_DENIED, 0);
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
// make sure file was not updated
|
||||
AssertFileWrapperState(file_wrapper, str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, TruncateFailsForReadOnly) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenRead(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
AssertTruncate(file, 10, ZX_ERR_ACCESS_DENIED);
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
// make sure file was not updated
|
||||
AssertFileWrapperState(file_wrapper, str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, ReadAtFailsForWriteOnly) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenFile(file_wrapper.file(), fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
file_wrapper.dispatcher());
|
||||
|
||||
AssertReadAt(file, 0, 10, "", ZX_ERR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, ReadFailsForWriteOnly) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenFile(file_wrapper.file(), fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
file_wrapper.dispatcher());
|
||||
|
||||
AssertRead(file, 10, "", ZX_ERR_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, CantReadNodeReferenceFile) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file =
|
||||
OpenFile(file_wrapper.file(), fuchsia::io::OPEN_FLAG_NODE_REFERENCE,
|
||||
file_wrapper.dispatcher());
|
||||
// make sure node reference was opened
|
||||
zx_status_t status;
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
ASSERT_EQ(ZX_OK, file->GetAttr(&status, &attr));
|
||||
ASSERT_EQ(ZX_OK, status);
|
||||
ASSERT_NE(0u, attr.mode | fuchsia::io::MODE_TYPE_FILE);
|
||||
|
||||
std::vector<uint8_t> buffer;
|
||||
ASSERT_EQ(ZX_ERR_PEER_CLOSED, file->Read(100, &status, &buffer));
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, CanCloneFileConnectionAndReadAndWrite) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file = OpenReadWrite(file_wrapper.file(), file_wrapper.dispatcher());
|
||||
|
||||
fuchsia::io::FileSyncPtr cloned_file;
|
||||
file->Clone(0, fidl::InterfaceRequest<fuchsia::io::Node>(
|
||||
cloned_file.NewRequest().TakeChannel()));
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
AssertWrite(cloned_file, "It");
|
||||
|
||||
const std::string updated_str = "Itis is a test string";
|
||||
|
||||
AssertReadAt(cloned_file, 0, 100, updated_str);
|
||||
|
||||
// make sure file was not updated before conenction was closed.
|
||||
ASSERT_EQ(file_wrapper.buffer(), str);
|
||||
|
||||
CloseFile(cloned_file);
|
||||
|
||||
// make sure file was updated
|
||||
AssertFileWrapperState(file_wrapper, updated_str);
|
||||
}
|
||||
|
||||
TEST_F(BufferedPseudoFileTest, NodeReferenceIsClonedAsNodeReference) {
|
||||
const std::string str = "this is a test string";
|
||||
auto file_wrapper = FileWrapper::CreateReadWriteFile(str, 100);
|
||||
auto file =
|
||||
OpenFile(file_wrapper.file(), fuchsia::io::OPEN_FLAG_NODE_REFERENCE,
|
||||
file_wrapper.dispatcher());
|
||||
|
||||
fuchsia::io::FileSyncPtr cloned_file;
|
||||
file->Clone(0, fidl::InterfaceRequest<fuchsia::io::Node>(
|
||||
cloned_file.NewRequest().TakeChannel()));
|
||||
|
||||
CloseFile(file);
|
||||
|
||||
// make sure node reference was opened
|
||||
zx_status_t status;
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
ASSERT_EQ(ZX_OK, cloned_file->GetAttr(&status, &attr));
|
||||
ASSERT_EQ(ZX_OK, status);
|
||||
ASSERT_NE(0u, attr.mode | fuchsia::io::MODE_TYPE_FILE);
|
||||
|
||||
std::vector<uint8_t> buffer;
|
||||
ASSERT_EQ(ZX_ERR_PEER_CLOSED, cloned_file->Read(100, &status, &buffer));
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,58 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/remote_dir.h>
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/zx/channel.h>
|
||||
#include <zircon/assert.h>
|
||||
#include <zircon/errors.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
RemoteDir::RemoteDir(zx::channel remote_dir, async_dispatcher_t* dispatcher) {
|
||||
ZX_ASSERT(remote_dir.is_valid());
|
||||
dir_ptr_.Bind(std::move(remote_dir), dispatcher);
|
||||
}
|
||||
|
||||
RemoteDir::RemoteDir(fidl::InterfaceHandle<fuchsia::io::Directory> dir,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
ZX_ASSERT(dir.is_valid());
|
||||
dir_ptr_.Bind(std::move(dir), dispatcher);
|
||||
}
|
||||
|
||||
RemoteDir::RemoteDir(fuchsia::io::DirectoryPtr dir_ptr)
|
||||
: dir_ptr_(std::move(dir_ptr)) {
|
||||
ZX_ASSERT(dir_ptr_.is_bound());
|
||||
}
|
||||
|
||||
RemoteDir::~RemoteDir() = default;
|
||||
|
||||
zx_status_t RemoteDir::Connect(uint32_t flags, zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
dir_ptr_->Clone(
|
||||
flags, fidl::InterfaceRequest<fuchsia ::io::Node>(std::move(request)));
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t RemoteDir::GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const {
|
||||
// Provide a default attribute set for this remote directory. This is needed
|
||||
// for cases where RemoteDir is directly read as part of ReadDir for a
|
||||
// containing directory.
|
||||
out_attributes->mode =
|
||||
fuchsia::io::MODE_TYPE_DIRECTORY | fuchsia::io::MODE_PROTECTION_MASK;
|
||||
out_attributes->id = fuchsia::io::INO_UNKNOWN;
|
||||
out_attributes->link_count = 1;
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
zx_status_t RemoteDir::Readdir(uint64_t offset, void* data, uint64_t len,
|
||||
uint64_t* out_offset, uint64_t* out_actual) {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
bool RemoteDir::IsRemote() const { return true; }
|
||||
|
||||
} // namespace vfs
|
@ -1,60 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_REMOTE_DIR_H_
|
||||
#define LIB_VFS_CPP_REMOTE_DIR_H_
|
||||
|
||||
#include <lib/vfs/cpp/directory.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
// A remote directory holds a channel to a remotely hosted directory to
|
||||
// which requests are delegated when opened.
|
||||
//
|
||||
// This class is designed to allow programs to publish remote filesystems
|
||||
// as directories without requiring a separate "mount" step. In effect,
|
||||
// a remote directory is "mounted" at creation time.
|
||||
//
|
||||
// It is not possible for the client to detach the remote directory or
|
||||
// to mount a new one in its place.
|
||||
//
|
||||
// This class is thread-safe.
|
||||
class RemoteDir final : public Directory {
|
||||
public:
|
||||
// Binds to a remotely hosted directory using the specified
|
||||
// |fuchsia.io.Directory| client channel endpoint.The channel must be valid.
|
||||
explicit RemoteDir(zx::channel remote_dir,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// Binds to a remotely hosted directory using the specified
|
||||
// InterfaceHandle. Handle must be valid.
|
||||
explicit RemoteDir(fidl::InterfaceHandle<fuchsia::io::Directory> dir,
|
||||
async_dispatcher_t* dispatcher = nullptr);
|
||||
|
||||
// Binds to a remotely hosted directory using the specified
|
||||
// |fuchsia::io::DirectoryPtr| endpoint. |dir_ptr| must be valid.
|
||||
explicit RemoteDir(fuchsia::io::DirectoryPtr dir_ptr);
|
||||
|
||||
~RemoteDir() override;
|
||||
|
||||
protected:
|
||||
// |Node| implementation
|
||||
zx_status_t Connect(uint32_t flags, zx::channel request,
|
||||
async_dispatcher_t* dispatcher) final;
|
||||
zx_status_t GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const override;
|
||||
|
||||
// |Directory| implementation
|
||||
zx_status_t Readdir(uint64_t offset, void* data, uint64_t len,
|
||||
uint64_t* out_offset, uint64_t* out_actual) final;
|
||||
|
||||
bool IsRemote() const override;
|
||||
|
||||
private:
|
||||
fuchsia::io::DirectoryPtr dir_ptr_;
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_REMOTE_DIR_H_
|
@ -1,139 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/remote_dir.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/fidl/cpp/interface_request.h>
|
||||
#include <lib/vfs/cpp/pseudo_dir.h>
|
||||
#include <lib/vfs/cpp/pseudo_file.h>
|
||||
#include <lib/vfs/cpp/testing/dir_test_util.h>
|
||||
|
||||
namespace {
|
||||
|
||||
class RemoteDirConnection : public vfs_tests::DirConnection {
|
||||
public:
|
||||
RemoteDirConnection() : loop_(&kAsyncLoopConfigNoAttachToThread) {
|
||||
AddFileToPseudoDir("file1");
|
||||
AddFileToPseudoDir("file2");
|
||||
AddFileToPseudoDir("file3");
|
||||
loop_.StartThread("vfs test thread");
|
||||
}
|
||||
|
||||
protected:
|
||||
vfs::Directory* GetDirectoryNode() override { return dir_.get(); }
|
||||
|
||||
fuchsia::io::DirectoryPtr GetPseudoDirConnection() {
|
||||
fuchsia::io::DirectoryPtr ptr;
|
||||
pseudo_dir_.Serve(fuchsia::io::OPEN_RIGHT_READABLE,
|
||||
ptr.NewRequest().TakeChannel(), loop_.dispatcher());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void ReadDir(vfs::Directory* dir, std::vector<uint8_t>* dirents,
|
||||
uint64_t buffer_size = 1024) {
|
||||
fuchsia::io::DirectorySyncPtr ptr;
|
||||
dir->Serve(fuchsia::io::OPEN_RIGHT_READABLE, ptr.NewRequest().TakeChannel(),
|
||||
loop_.dispatcher());
|
||||
zx_status_t status;
|
||||
ptr->ReadDirents(buffer_size, &status, dirents);
|
||||
ASSERT_EQ(ZX_OK, status);
|
||||
ASSERT_GT(dirents->size(), 0u);
|
||||
}
|
||||
|
||||
void CompareReadDirs() {
|
||||
std::vector<uint8_t> remote_dir_dirents;
|
||||
std::vector<uint8_t> pseudo_dir_dirents;
|
||||
ReadDir(&pseudo_dir_, &pseudo_dir_dirents);
|
||||
ReadDir(dir_.get(), &remote_dir_dirents);
|
||||
ASSERT_EQ(remote_dir_dirents, pseudo_dir_dirents);
|
||||
}
|
||||
|
||||
vfs::PseudoDir pseudo_dir_;
|
||||
std::shared_ptr<vfs::RemoteDir> dir_;
|
||||
async::Loop loop_;
|
||||
|
||||
private:
|
||||
void AddFileToPseudoDir(const std::string& name) {
|
||||
pseudo_dir_.AddEntry(name, std::make_unique<vfs::BufferedPseudoFile>(
|
||||
[name](std::vector<uint8_t>* output) {
|
||||
output->resize(name.length());
|
||||
std::copy(name.begin(), name.end(),
|
||||
output->begin());
|
||||
return ZX_OK;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RemoteDirConnection, ConstructorWithChannel) {
|
||||
auto connection = GetPseudoDirConnection();
|
||||
dir_ = std::make_shared<vfs::RemoteDir>(connection.Unbind().TakeChannel());
|
||||
CompareReadDirs();
|
||||
}
|
||||
|
||||
TEST_F(RemoteDirConnection, ConstructorWithInterfaceHandle) {
|
||||
auto connection = GetPseudoDirConnection();
|
||||
dir_ = std::make_shared<vfs::RemoteDir>(connection.Unbind());
|
||||
CompareReadDirs();
|
||||
}
|
||||
|
||||
TEST_F(RemoteDirConnection, ConstructorWithDirPtr) {
|
||||
dir_ = std::make_shared<vfs::RemoteDir>(GetPseudoDirConnection());
|
||||
CompareReadDirs();
|
||||
}
|
||||
|
||||
class RemoteDirContained : public RemoteDirConnection {
|
||||
protected:
|
||||
RemoteDirContained() {
|
||||
dir_ = std::make_shared<vfs::RemoteDir>(GetPseudoDirConnection());
|
||||
parent_pseudo_dir_.AddSharedEntry("remote_dir", dir_);
|
||||
parent_pseudo_dir_.Serve(
|
||||
fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
ptr_.NewRequest().TakeChannel(), loop_.dispatcher());
|
||||
}
|
||||
|
||||
~RemoteDirContained() { loop_.Shutdown(); }
|
||||
|
||||
vfs::PseudoDir parent_pseudo_dir_;
|
||||
fuchsia::io::DirectorySyncPtr ptr_;
|
||||
};
|
||||
|
||||
TEST_F(RemoteDirContained, RemoteDirContainedInPseudoDir) {
|
||||
std::vector<vfs_tests::Dirent> expected = {
|
||||
vfs_tests::Dirent::DirentForDot(),
|
||||
vfs_tests::Dirent::DirentForDirectory("remote_dir")};
|
||||
AssertReadDirents(ptr_, 1024, expected);
|
||||
}
|
||||
|
||||
TEST_F(RemoteDirContained, OpenAndReadFile) {
|
||||
fuchsia::io::FileSyncPtr file_ptr;
|
||||
std::string paths[] = {"remote_dir/file1", "remote_dir//file1",
|
||||
"remote_dir/./file1"};
|
||||
for (auto& path : paths) {
|
||||
SCOPED_TRACE(path);
|
||||
AssertOpenPath(ptr_, path, file_ptr, fuchsia::io::OPEN_RIGHT_READABLE);
|
||||
AssertRead(file_ptr, 1024, "file1");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RemoteDirContained, OpenRemoteDirAndRead) {
|
||||
std::string paths[] = {"remote_dir", "remote_dir/", "remote_dir/.",
|
||||
"remote_dir/./", "remote_dir//", "remote_dir//."};
|
||||
for (auto& path : paths) {
|
||||
SCOPED_TRACE(path);
|
||||
fuchsia::io::DirectorySyncPtr remote_ptr;
|
||||
AssertOpenPath(
|
||||
ptr_, path, remote_ptr,
|
||||
fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE);
|
||||
fuchsia::io::FileSyncPtr file_ptr;
|
||||
AssertOpenPath(remote_ptr, "file1", file_ptr,
|
||||
fuchsia::io::OPEN_RIGHT_READABLE);
|
||||
AssertRead(file_ptr, 1024, "file1");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
@ -1,57 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/service.h>
|
||||
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/vfs/cpp/flags.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
Service::Service(Connector connector) : connector_(std::move(connector)) {}
|
||||
|
||||
Service::~Service() = default;
|
||||
|
||||
void Service::Describe(fuchsia::io::NodeInfo* out_info) {
|
||||
out_info->set_service(fuchsia::io::Service());
|
||||
}
|
||||
|
||||
zx_status_t Service::CreateConnection(uint32_t flags,
|
||||
std::unique_ptr<Connection>* connection) {
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
uint32_t Service::GetAdditionalAllowedFlags() const {
|
||||
// TODO(ZX-3251): remove OPEN_RIGHT_WRITABLE flag
|
||||
return fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE;
|
||||
}
|
||||
|
||||
zx_status_t Service::Connect(uint32_t flags, zx::channel request,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
if (Flags::IsPathOnly(flags)) {
|
||||
return Node::Connect(flags, std::move(request), dispatcher);
|
||||
}
|
||||
if (connector_ == nullptr) {
|
||||
SendOnOpenEventOnError(flags, std::move(request), ZX_ERR_NOT_SUPPORTED);
|
||||
return ZX_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
connector_(std::move(request), dispatcher);
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
bool Service::IsDirectory() const { return false; }
|
||||
|
||||
zx_status_t Service::GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const {
|
||||
out_attributes->mode = fuchsia::io::MODE_TYPE_SERVICE | 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;
|
||||
}
|
||||
|
||||
} // namespace vfs
|
@ -1,76 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_SERVICE_H_
|
||||
#define LIB_VFS_CPP_SERVICE_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/vfs/cpp/node.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
// A |Node| which binds a channel to a service implementation when opened.
|
||||
//
|
||||
// Instances of this class are thread-safe.
|
||||
class Service : public Node {
|
||||
public:
|
||||
// Handler called to bind the provided channel to an implementation
|
||||
// of the service.
|
||||
using Connector =
|
||||
fit::function<void(zx::channel channel, async_dispatcher_t* dispatcher)>;
|
||||
|
||||
// Adds the specified interface to the set of public interfaces.
|
||||
//
|
||||
// Creates |Service| with a |connector| with the given |service_name|, using
|
||||
// the given |interface_request_handler|. |interface_request_handler| should
|
||||
// remain valid for the lifetime of this object.
|
||||
//
|
||||
// A typical usage may be:
|
||||
//
|
||||
// vfs::Service foo_service(foobar_bindings_.GetHandler(this, dispatcher));
|
||||
//
|
||||
// For now this implementation ignores |dispatcher| that we get from |Serve|
|
||||
// call, if you want to use dispatcher call |Service(Connector)|.
|
||||
template <typename Interface>
|
||||
explicit Service(fidl::InterfaceRequestHandler<Interface> handler)
|
||||
: Service([handler = std::move(handler)](zx::channel channel,
|
||||
async_dispatcher_t* dispatcher) {
|
||||
handler(fidl::InterfaceRequest<Interface>(std::move(channel)));
|
||||
}) {}
|
||||
|
||||
// Creates a service with the specified connector.
|
||||
//
|
||||
// If the |connector| is null, then incoming connection requests will be
|
||||
// dropped.
|
||||
explicit Service(Connector connector);
|
||||
|
||||
// Destroys the services and releases its connector.
|
||||
~Service() override;
|
||||
|
||||
// |Node| implementation:
|
||||
zx_status_t GetAttr(fuchsia::io::NodeAttributes* out_attributes) const final;
|
||||
|
||||
void Describe(fuchsia::io::NodeInfo* out_info) override final;
|
||||
|
||||
const Connector& connector() const { return connector_; }
|
||||
|
||||
protected:
|
||||
// |Node| implementations:
|
||||
zx_status_t CreateConnection(uint32_t flags,
|
||||
std::unique_ptr<Connection>* connection) final;
|
||||
|
||||
uint32_t GetAdditionalAllowedFlags() const override final;
|
||||
|
||||
bool IsDirectory() const override final;
|
||||
|
||||
zx_status_t Connect(uint32_t flags, zx::channel request,
|
||||
async_dispatcher_t* dispatcher) override;
|
||||
|
||||
private:
|
||||
Connector connector_;
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_SERVICE_H_
|
@ -1,153 +0,0 @@
|
||||
// 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 "lib/vfs/cpp/service.h"
|
||||
|
||||
#include <fidl/examples/echo/cpp/fidl.h>
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/fidl/cpp/binding_set.h>
|
||||
#include "lib/gtest/real_loop_fixture.h"
|
||||
#include "lib/vfs/cpp/pseudo_dir.h"
|
||||
|
||||
class ServiceTest : public gtest::RealLoopFixture,
|
||||
public fidl::examples::echo::Echo {
|
||||
void EchoString(fidl::StringPtr value, EchoStringCallback callback) override {
|
||||
callback(answer_);
|
||||
}
|
||||
|
||||
protected:
|
||||
ServiceTest()
|
||||
: answer_("my_fake_ans"),
|
||||
service_name_("echo_service"),
|
||||
second_loop_(&kAsyncLoopConfigNoAttachToThread) {
|
||||
auto service = std::make_unique<vfs::Service>(
|
||||
bindings_.GetHandler(this, second_loop_.dispatcher()));
|
||||
|
||||
dir_.Serve(0, dir_ptr_.NewRequest().TakeChannel(),
|
||||
second_loop_.dispatcher());
|
||||
dir_.AddEntry(service_name_, std::move(service));
|
||||
second_loop_.StartThread("vfs test thread");
|
||||
}
|
||||
|
||||
const std::string& answer() const { return answer_; }
|
||||
|
||||
const std::string& service_name() const { return service_name_; }
|
||||
|
||||
void AssertInValidOpen(uint32_t flag, uint32_t mode,
|
||||
zx_status_t expected_status) {
|
||||
SCOPED_TRACE("flag: " + std::to_string(flag) +
|
||||
", mode: " + std::to_string(mode));
|
||||
fuchsia::io::NodePtr node_ptr;
|
||||
dir_ptr()->Open(flag | fuchsia::io::OPEN_FLAG_DESCRIBE, mode,
|
||||
service_name(), node_ptr.NewRequest());
|
||||
|
||||
bool on_open_called = false;
|
||||
|
||||
node_ptr.events().OnOpen =
|
||||
[&](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> unused) {
|
||||
EXPECT_FALSE(on_open_called); // should be called only once
|
||||
on_open_called = true;
|
||||
EXPECT_EQ(expected_status, status);
|
||||
};
|
||||
|
||||
ASSERT_TRUE(RunLoopUntil([&]() { return on_open_called; }, zx::msec(1)));
|
||||
}
|
||||
|
||||
fuchsia::io::DirectoryPtr& dir_ptr() { return dir_ptr_; }
|
||||
|
||||
private:
|
||||
std::string answer_;
|
||||
std::string service_name_;
|
||||
fidl::BindingSet<Echo> bindings_;
|
||||
vfs::PseudoDir dir_;
|
||||
fuchsia::io::DirectoryPtr dir_ptr_;
|
||||
async::Loop second_loop_;
|
||||
};
|
||||
|
||||
TEST_F(ServiceTest, CanOpenAsNodeReferenceAndTestGetAttr) {
|
||||
fuchsia::io::NodeSyncPtr ptr;
|
||||
dir_ptr()->Open(fuchsia::io::OPEN_FLAG_NODE_REFERENCE, 0, service_name(),
|
||||
ptr.NewRequest());
|
||||
|
||||
zx_status_t s;
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
ptr->GetAttr(&s, &attr);
|
||||
EXPECT_EQ(ZX_OK, s);
|
||||
EXPECT_EQ(fuchsia::io::MODE_TYPE_SERVICE,
|
||||
attr.mode & fuchsia::io::MODE_TYPE_SERVICE);
|
||||
}
|
||||
|
||||
TEST_F(ServiceTest, CanCloneNodeReference) {
|
||||
fuchsia::io::NodeSyncPtr cloned_ptr;
|
||||
{
|
||||
fuchsia::io::NodeSyncPtr ptr;
|
||||
dir_ptr()->Open(fuchsia::io::OPEN_FLAG_NODE_REFERENCE, 0, service_name(),
|
||||
ptr.NewRequest());
|
||||
|
||||
ptr->Clone(0, cloned_ptr.NewRequest());
|
||||
}
|
||||
|
||||
zx_status_t s;
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
cloned_ptr->GetAttr(&s, &attr);
|
||||
EXPECT_EQ(ZX_OK, s);
|
||||
EXPECT_EQ(fuchsia::io::MODE_TYPE_SERVICE,
|
||||
attr.mode & fuchsia::io::MODE_TYPE_SERVICE);
|
||||
}
|
||||
|
||||
TEST_F(ServiceTest, TestDescribe) {
|
||||
fuchsia::io::NodeSyncPtr ptr;
|
||||
dir_ptr()->Open(fuchsia::io::OPEN_FLAG_NODE_REFERENCE, 0, service_name(),
|
||||
ptr.NewRequest());
|
||||
|
||||
fuchsia::io::NodeInfo info;
|
||||
ptr->Describe(&info);
|
||||
EXPECT_TRUE(info.is_service());
|
||||
}
|
||||
|
||||
TEST_F(ServiceTest, CanOpenAsAService) {
|
||||
uint32_t flags[] = {0, fuchsia::io::OPEN_RIGHT_READABLE,
|
||||
fuchsia::io::OPEN_RIGHT_WRITABLE};
|
||||
uint32_t modes[] = {
|
||||
0, fuchsia::io::MODE_TYPE_SERVICE, V_IRWXU, V_IRUSR, V_IWUSR, V_IXUSR};
|
||||
|
||||
for (uint32_t mode : modes) {
|
||||
for (uint32_t flag : flags) {
|
||||
SCOPED_TRACE("flag: " + std::to_string(flag) +
|
||||
", mode: " + std::to_string(mode));
|
||||
fidl::examples::echo::EchoSyncPtr ptr;
|
||||
dir_ptr()->Open(flag, mode, service_name(),
|
||||
fidl::InterfaceRequest<fuchsia::io::Node>(
|
||||
ptr.NewRequest().TakeChannel()));
|
||||
|
||||
fidl::StringPtr ans;
|
||||
ptr->EchoString("hello", &ans);
|
||||
EXPECT_EQ(answer(), ans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ServiceTest, CannotOpenServiceWithInvalidFlags) {
|
||||
uint32_t flags[] = {fuchsia::io::OPEN_RIGHT_ADMIN,
|
||||
fuchsia::io::OPEN_FLAG_CREATE,
|
||||
fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT,
|
||||
fuchsia::io::OPEN_FLAG_TRUNCATE,
|
||||
fuchsia::io::OPEN_FLAG_APPEND,
|
||||
fuchsia::io::OPEN_FLAG_NO_REMOTE};
|
||||
|
||||
for (uint32_t flag : flags) {
|
||||
AssertInValidOpen(flag, 0, ZX_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
AssertInValidOpen(fuchsia::io::OPEN_FLAG_DIRECTORY, 0, ZX_ERR_NOT_DIR);
|
||||
}
|
||||
|
||||
TEST_F(ServiceTest, CannotOpenServiceWithInvalidMode) {
|
||||
uint32_t modes[] = {
|
||||
fuchsia::io::MODE_TYPE_DIRECTORY, fuchsia::io::MODE_TYPE_BLOCK_DEVICE,
|
||||
fuchsia::io::MODE_TYPE_FILE, fuchsia::io::MODE_TYPE_SOCKET};
|
||||
|
||||
for (uint32_t mode : modes) {
|
||||
AssertInValidOpen(0, mode, ZX_ERR_INVALID_ARGS);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
# 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.
|
||||
|
||||
source_set("testing") {
|
||||
testonly = true
|
||||
sources = [
|
||||
"dir_test_util.cc",
|
||||
"dir_test_util.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//garnet/public/lib/gtest",
|
||||
"//sdk/lib/vfs/cpp",
|
||||
"//third_party/googletest:gtest_main",
|
||||
"//zircon/public/fidl/fuchsia-io",
|
||||
]
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/testing/dir_test_util.h>
|
||||
|
||||
namespace vfs_tests {
|
||||
|
||||
Dirent Dirent::DirentForDot() { return DirentForDirectory("."); }
|
||||
|
||||
Dirent Dirent::DirentForDirectory(const std::string& name) {
|
||||
return Dirent(fuchsia::io::INO_UNKNOWN, fuchsia::io::DIRENT_TYPE_DIRECTORY,
|
||||
name);
|
||||
}
|
||||
|
||||
Dirent Dirent::DirentForFile(const std::string& name) {
|
||||
return Dirent(fuchsia::io::INO_UNKNOWN, fuchsia::io::DIRENT_TYPE_FILE, name);
|
||||
}
|
||||
|
||||
std::string Dirent::String() {
|
||||
return "Dirent:\nino: " + std ::to_string(ino_) +
|
||||
"\ntype: " + std ::to_string(type_) +
|
||||
"\nsize: " + std ::to_string(size_) + "\nname: " + name_;
|
||||
}
|
||||
|
||||
Dirent::Dirent(uint64_t ino, uint8_t type, const std::string& name)
|
||||
: ino_(ino),
|
||||
type_(type),
|
||||
size_(static_cast<uint8_t>(name.length())),
|
||||
name_(name),
|
||||
size_in_bytes_(sizeof(vdirent_t) + size_) {
|
||||
ZX_DEBUG_ASSERT(name.length() <= static_cast<uint64_t>(NAME_MAX));
|
||||
}
|
||||
|
||||
void DirConnection::AssertOpen(async_dispatcher_t* dispatcher, uint32_t flags,
|
||||
zx_status_t expected_status,
|
||||
bool test_on_open_event) {
|
||||
fuchsia::io::NodePtr node_ptr;
|
||||
if (test_on_open_event) {
|
||||
flags |= fuchsia::io::OPEN_FLAG_DESCRIBE;
|
||||
}
|
||||
EXPECT_EQ(expected_status,
|
||||
GetDirectoryNode()->Serve(
|
||||
flags, node_ptr.NewRequest().TakeChannel(), dispatcher));
|
||||
|
||||
if (test_on_open_event) {
|
||||
bool on_open_called = false;
|
||||
node_ptr.events().OnOpen =
|
||||
[&](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> info) {
|
||||
EXPECT_FALSE(on_open_called); // should be called only once
|
||||
on_open_called = true;
|
||||
EXPECT_EQ(expected_status, status);
|
||||
if (expected_status == ZX_OK) {
|
||||
ASSERT_NE(info.get(), nullptr);
|
||||
EXPECT_TRUE(info->is_directory());
|
||||
} else {
|
||||
EXPECT_EQ(info.get(), nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
ASSERT_TRUE(RunLoopUntil([&]() { return on_open_called; }, zx::msec(1)));
|
||||
}
|
||||
}
|
||||
|
||||
void DirConnection::AssertReadDirents(fuchsia::io::DirectorySyncPtr& ptr,
|
||||
uint64_t max_bytes,
|
||||
std::vector<Dirent>& expected_dirents,
|
||||
zx_status_t expected_status) {
|
||||
std::vector<uint8_t> out_dirents;
|
||||
zx_status_t status;
|
||||
ptr->ReadDirents(max_bytes, &status, &out_dirents);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
if (status != ZX_OK) {
|
||||
return;
|
||||
}
|
||||
uint64_t expected_size = 0;
|
||||
for (auto& d : expected_dirents) {
|
||||
expected_size += d.size_in_bytes();
|
||||
}
|
||||
EXPECT_EQ(expected_size, out_dirents.size());
|
||||
uint64_t offset = 0;
|
||||
auto data_ptr = out_dirents.data();
|
||||
for (auto& d : expected_dirents) {
|
||||
SCOPED_TRACE(d.String());
|
||||
ASSERT_LE(sizeof(vdirent_t), out_dirents.size() - offset);
|
||||
vdirent_t* de = reinterpret_cast<vdirent_t*>(data_ptr + offset);
|
||||
EXPECT_EQ(d.ino(), de->ino);
|
||||
EXPECT_EQ(d.size(), de->size);
|
||||
EXPECT_EQ(d.type(), de->type);
|
||||
ASSERT_LE(d.size_in_bytes(), out_dirents.size() - offset);
|
||||
EXPECT_EQ(d.name(), std::string(de->name, de->size));
|
||||
|
||||
offset += sizeof(vdirent_t) + de->size;
|
||||
}
|
||||
}
|
||||
|
||||
void DirConnection::AssertRewind(fuchsia::io::DirectorySyncPtr& ptr,
|
||||
zx_status_t expected_status) {
|
||||
zx_status_t status;
|
||||
ptr->Rewind(&status);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
}
|
||||
|
||||
void DirConnection::AssertRead(fuchsia::io::FileSyncPtr& file, int count,
|
||||
const std::string& expected_str,
|
||||
zx_status_t expected_status) {
|
||||
zx_status_t status;
|
||||
std::vector<uint8_t> buffer;
|
||||
file->Read(count, &status, &buffer);
|
||||
ASSERT_EQ(expected_status, status);
|
||||
std::string str(buffer.size(), 0);
|
||||
std::copy(buffer.begin(), buffer.end(), str.begin());
|
||||
ASSERT_EQ(expected_str, str);
|
||||
}
|
||||
|
||||
} // namespace vfs_tests
|
@ -1,90 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_TESTING_DIR_TEST_UTIL_H_
|
||||
#define LIB_VFS_CPP_TESTING_DIR_TEST_UTIL_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/fdio/vfs.h>
|
||||
#include <lib/gtest/real_loop_fixture.h>
|
||||
#include <lib/vfs/cpp/directory.h>
|
||||
|
||||
namespace vfs_tests {
|
||||
|
||||
class Dirent {
|
||||
public:
|
||||
static Dirent DirentForDot();
|
||||
|
||||
static Dirent DirentForDirectory(const std::string& name);
|
||||
|
||||
static Dirent DirentForFile(const std::string& name);
|
||||
|
||||
std::string String();
|
||||
|
||||
uint64_t ino() const { return ino_; }
|
||||
|
||||
uint8_t type() const { return type_; }
|
||||
|
||||
uint8_t size() const { return size_; }
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
|
||||
uint64_t size_in_bytes() const { return size_in_bytes_; }
|
||||
|
||||
private:
|
||||
Dirent(uint64_t ino, uint8_t type, const std::string& name);
|
||||
|
||||
uint64_t ino_;
|
||||
uint8_t type_;
|
||||
uint8_t size_;
|
||||
std::string name_;
|
||||
|
||||
uint64_t size_in_bytes_;
|
||||
};
|
||||
|
||||
class DirConnection : public gtest::RealLoopFixture {
|
||||
protected:
|
||||
virtual vfs::Directory* GetDirectoryNode() = 0;
|
||||
|
||||
void AssertOpen(async_dispatcher_t* dispatcher, uint32_t flags,
|
||||
zx_status_t expected_status, bool test_on_open_event = true);
|
||||
|
||||
void AssertReadDirents(fuchsia::io::DirectorySyncPtr& ptr, uint64_t max_bytes,
|
||||
std::vector<Dirent>& expected_dirents,
|
||||
zx_status_t expected_status = ZX_OK);
|
||||
|
||||
void AssertRewind(fuchsia::io::DirectorySyncPtr& ptr,
|
||||
zx_status_t expected_status = ZX_OK);
|
||||
|
||||
template <typename T>
|
||||
void AssertOpenPath(fuchsia::io::DirectorySyncPtr& dir_ptr,
|
||||
const std::string& path,
|
||||
::fidl::SynchronousInterfacePtr<T>& out_sync_ptr,
|
||||
uint32_t flags = 0, uint32_t mode = 0,
|
||||
zx_status_t expected_status = ZX_OK) {
|
||||
::fidl::InterfacePtr<fuchsia::io::Node> node_ptr;
|
||||
dir_ptr->Open(flags | fuchsia::io::OPEN_FLAG_DESCRIBE, mode, path,
|
||||
node_ptr.NewRequest());
|
||||
bool on_open_called = false;
|
||||
node_ptr.events().OnOpen =
|
||||
[&](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> unused) {
|
||||
EXPECT_FALSE(on_open_called); // should be called only once
|
||||
on_open_called = true;
|
||||
EXPECT_EQ(expected_status, status);
|
||||
};
|
||||
|
||||
ASSERT_TRUE(RunLoopUntil([&]() { return on_open_called; }, zx::msec(1)));
|
||||
|
||||
// Bind channel to sync_ptr
|
||||
out_sync_ptr.Bind(node_ptr.Unbind().TakeChannel());
|
||||
}
|
||||
|
||||
void AssertRead(fuchsia::io::FileSyncPtr& file, int count,
|
||||
const std::string& expected_str,
|
||||
zx_status_t expected_status = ZX_OK);
|
||||
};
|
||||
|
||||
} // namespace vfs_tests
|
||||
|
||||
#endif // LIB_VFS_CPP_TESTING_DIR_TEST_UTIL_H_
|
@ -1,120 +0,0 @@
|
||||
// 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 <lib/vfs/cpp/vmo_file.h>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
VmoFile::VmoFile(zx::unowned_vmo unowned_vmo, size_t offset, size_t length,
|
||||
WriteOption write_option, Sharing vmo_sharing)
|
||||
: offset_(offset),
|
||||
length_(length),
|
||||
write_option_(write_option),
|
||||
vmo_sharing_(vmo_sharing) {
|
||||
unowned_vmo->duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_);
|
||||
}
|
||||
|
||||
VmoFile::VmoFile(zx::vmo vmo, size_t offset, size_t length,
|
||||
WriteOption write_option, Sharing vmo_sharing)
|
||||
: offset_(offset),
|
||||
length_(length),
|
||||
write_option_(write_option),
|
||||
vmo_sharing_(vmo_sharing),
|
||||
vmo_(std::move(vmo)) {}
|
||||
|
||||
VmoFile::~VmoFile() = default;
|
||||
|
||||
void VmoFile::Describe(fuchsia::io::NodeInfo* out_info) {
|
||||
zx::vmo temp_vmo;
|
||||
switch (vmo_sharing_) {
|
||||
case Sharing::NONE:
|
||||
out_info->set_file(fuchsia::io::FileObject());
|
||||
break;
|
||||
case Sharing::DUPLICATE:
|
||||
if (vmo_.duplicate(write_option_ == WriteOption::WRITABLE
|
||||
? ZX_RIGHTS_BASIC | ZX_RIGHT_READ | ZX_RIGHT_WRITE
|
||||
: ZX_RIGHTS_BASIC | ZX_RIGHT_READ,
|
||||
&temp_vmo) != ZX_OK) {
|
||||
return;
|
||||
}
|
||||
out_info->vmofile() = fuchsia::io::Vmofile{
|
||||
.vmo = std::move(temp_vmo), .length = length_, .offset = offset_};
|
||||
break;
|
||||
case Sharing::CLONE_COW:
|
||||
if (vmo_.create_child(ZX_VMO_CLONE_COPY_ON_WRITE, offset_, length_,
|
||||
&temp_vmo) != ZX_OK) {
|
||||
return;
|
||||
}
|
||||
out_info->vmofile() = fuchsia::io::Vmofile{
|
||||
.vmo = std::move(temp_vmo), .length = length_, .offset = offset_};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VmoFile::GetAdditionalAllowedFlags() const {
|
||||
return fuchsia::io::OPEN_RIGHT_READABLE |
|
||||
(write_option_ == WriteOption::WRITABLE
|
||||
? fuchsia::io::OPEN_RIGHT_WRITABLE
|
||||
: 0);
|
||||
}
|
||||
|
||||
zx_status_t VmoFile::ReadAt(uint64_t length, uint64_t offset,
|
||||
std::vector<uint8_t>* out_data) {
|
||||
if (length == 0u || offset >= length_) {
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
size_t remaining_length = length_ - offset;
|
||||
if (length > remaining_length) {
|
||||
length = remaining_length;
|
||||
}
|
||||
|
||||
out_data->resize(length);
|
||||
return vmo_.read(out_data->data(), offset_ + offset, length);
|
||||
}
|
||||
|
||||
zx_status_t VmoFile::WriteAt(std::vector<uint8_t> data, uint64_t offset,
|
||||
uint64_t* out_actual) {
|
||||
size_t length = data.size();
|
||||
if (length == 0u) {
|
||||
*out_actual = 0u;
|
||||
return ZX_OK;
|
||||
}
|
||||
if (offset >= length_) {
|
||||
return ZX_ERR_NO_SPACE;
|
||||
}
|
||||
|
||||
size_t remaining_length = length_ - offset;
|
||||
if (length > remaining_length) {
|
||||
length = remaining_length;
|
||||
}
|
||||
zx_status_t status = vmo_.write(data.data(), offset_ + offset, length);
|
||||
if (status == ZX_OK) {
|
||||
*out_actual = length;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
zx_status_t VmoFile::Truncate(uint64_t length) { return ZX_ERR_NOT_SUPPORTED; }
|
||||
|
||||
size_t VmoFile::GetCapacity() { return length_; };
|
||||
|
||||
size_t VmoFile::GetLength() { return length_; }
|
||||
|
||||
zx_status_t VmoFile::GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const {
|
||||
out_attributes->mode =
|
||||
fuchsia::io::MODE_TYPE_FILE | fuchsia::io::OPEN_RIGHT_READABLE |
|
||||
(write_option_ == WriteOption::WRITABLE ? fuchsia::io::OPEN_RIGHT_WRITABLE
|
||||
: 0);
|
||||
out_attributes->id = fuchsia::io::INO_UNKNOWN;
|
||||
out_attributes->content_size = length_;
|
||||
out_attributes->storage_size = length_;
|
||||
out_attributes->link_count = 1;
|
||||
out_attributes->creation_time = 0;
|
||||
out_attributes->modification_time = 0;
|
||||
return ZX_OK;
|
||||
}
|
||||
|
||||
} // namespace vfs
|
@ -1,129 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef LIB_VFS_CPP_VMO_FILE_H_
|
||||
#define LIB_VFS_CPP_VMO_FILE_H_
|
||||
|
||||
#include <fuchsia/io/cpp/fidl.h>
|
||||
#include <lib/vfs/cpp/file.h>
|
||||
#include <lib/zx/vmo.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace vfs {
|
||||
|
||||
// A file object in a file system backed by a VMO.
|
||||
//
|
||||
// Implements the |fuchsia.io.File| interface. Incoming connections are
|
||||
// owned by this object and will be destroyed when this object is destroyed.
|
||||
//
|
||||
// See also:
|
||||
//
|
||||
// * File, which represents file objects.
|
||||
class VmoFile : public File {
|
||||
public:
|
||||
// Specifies the desired behavior of writes.
|
||||
enum class WriteOption {
|
||||
// The VMO handle and file are read only.
|
||||
READ_ONLY,
|
||||
// The VMO handle and file will be writable.
|
||||
WRITABLE,
|
||||
};
|
||||
|
||||
// Specifies the desired behavior when a client asks for the file's
|
||||
// underlying VMO.
|
||||
enum class Sharing {
|
||||
// The VMO is not shared with the client.
|
||||
NONE,
|
||||
|
||||
// The VMO handle is duplicated for each client.
|
||||
//
|
||||
// This is appropriate when it is okay for clients to access the entire
|
||||
// contents of the VMO, possibly extending beyond the pages spanned by the
|
||||
// file.
|
||||
//
|
||||
// This mode is significantly more efficient than |CLONE| and |CLONE_COW|
|
||||
// and should be preferred when file spans the whole VMO or when the VMO's
|
||||
// entire content is safe for clients to read.
|
||||
DUPLICATE,
|
||||
|
||||
// The VMO range spanned by the file is cloned on demand, using
|
||||
// copy-on-write
|
||||
// semantics to isolate modifications of clients which open the file in
|
||||
// a writable mode.
|
||||
//
|
||||
// This is appropriate when clients need to be restricted from accessing
|
||||
// portions of the VMO outside of the range of the file and when file
|
||||
// modifications by clients should not be visible to each other.
|
||||
CLONE_COW,
|
||||
};
|
||||
|
||||
// Creates a file node backed an VMO owned by the creator.
|
||||
// The creator retains ownership of |unowned_vmo| which must outlive this
|
||||
// object.
|
||||
VmoFile(zx::unowned_vmo unowned_vmo, size_t offset, size_t length,
|
||||
WriteOption write_options = WriteOption::READ_ONLY,
|
||||
Sharing vmo_sharing = Sharing::DUPLICATE);
|
||||
|
||||
// Creates a file node backed by a VMO. The VmoFile takes ownership of the
|
||||
// vmo.
|
||||
VmoFile(zx::vmo vmo, size_t offset, size_t length,
|
||||
WriteOption write_options = WriteOption::READ_ONLY,
|
||||
Sharing vmo_sharing = Sharing::DUPLICATE);
|
||||
|
||||
~VmoFile();
|
||||
|
||||
// Create |count| bytes of data from the file at the given |offset|.
|
||||
//
|
||||
// The data read should be copied to |out_data|, which should be empty when
|
||||
// passed as an argument. When |ReadAt| returns, |out_data| should contain no
|
||||
// more than |count| bytes.
|
||||
zx_status_t ReadAt(uint64_t count, uint64_t offset,
|
||||
std::vector<uint8_t>* out_data) override;
|
||||
|
||||
// Write the given |data| to the file at the given |offset|.
|
||||
//
|
||||
// Data should be copied into the file starting at the beginning of |data|.
|
||||
// If |WriteAt| returns |ZX_OK|, |out_actual| should contain the number of
|
||||
// bytes actually written to the file.
|
||||
zx_status_t WriteAt(std::vector<uint8_t> data, uint64_t offset,
|
||||
uint64_t* out_actual) override;
|
||||
|
||||
// Resize the file to the given |length|.
|
||||
zx_status_t Truncate(uint64_t length) override;
|
||||
|
||||
// Override that describes this object as a vmofile.
|
||||
void Describe(fuchsia::io::NodeInfo* out_info) override;
|
||||
|
||||
// Returns current file length.
|
||||
//
|
||||
// All implementations should implement this.
|
||||
uint64_t GetLength() override;
|
||||
|
||||
// Returns file capacity.
|
||||
//
|
||||
// Seek() uses this to return ZX_ERR_OUT_OF_RANGE if new seek is more than
|
||||
// this value.
|
||||
size_t GetCapacity() override;
|
||||
|
||||
// Returns the node attributes for this VmoFile.
|
||||
zx_status_t GetAttr(
|
||||
fuchsia::io::NodeAttributes* out_attributes) const override;
|
||||
|
||||
protected:
|
||||
uint32_t GetAdditionalAllowedFlags() const override;
|
||||
|
||||
private:
|
||||
const size_t offset_;
|
||||
const size_t length_;
|
||||
const WriteOption write_option_;
|
||||
const Sharing vmo_sharing_;
|
||||
|
||||
zx::vmo vmo_;
|
||||
};
|
||||
|
||||
} // namespace vfs
|
||||
|
||||
#endif // LIB_VFS_CPP_VMO_FILE_H_
|
@ -1,438 +0,0 @@
|
||||
// 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 <lib/async-loop/cpp/loop.h>
|
||||
#include <lib/fdio/directory.h>
|
||||
#include <lib/fdio/fd.h>
|
||||
#include <lib/fdio/fdio.h>
|
||||
#include <lib/fdio/limits.h>
|
||||
#include <unistd.h>
|
||||
#include <zircon/processargs.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "lib/vfs/cpp/vmo_file.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void FillBuffer(char* buf, size_t size) {
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
buf[i] = i % 256;
|
||||
}
|
||||
}
|
||||
|
||||
zx::vmo MakeTestVmo() {
|
||||
zx::vmo ret;
|
||||
EXPECT_EQ(ZX_OK, zx::vmo::create(4096, 0, &ret));
|
||||
|
||||
char buf[4096];
|
||||
FillBuffer(buf, 4096);
|
||||
EXPECT_EQ(ZX_OK, ret.write(buf, 0, 4096));
|
||||
return ret;
|
||||
}
|
||||
|
||||
fuchsia::io::FileSyncPtr OpenAsFile(vfs::Node* node,
|
||||
async_dispatcher_t* dispatcher,
|
||||
bool writable = false) {
|
||||
zx::channel local, remote;
|
||||
EXPECT_EQ(ZX_OK, zx::channel::create(0, &local, &remote));
|
||||
EXPECT_EQ(ZX_OK,
|
||||
node->Serve(fuchsia::io::OPEN_RIGHT_READABLE |
|
||||
(writable ? fuchsia::io::OPEN_RIGHT_WRITABLE : 0),
|
||||
std::move(remote), dispatcher));
|
||||
fuchsia::io::FileSyncPtr ret;
|
||||
ret.Bind(std::move(local));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ReadVmo(const zx::vmo& vmo, size_t offset, size_t length) {
|
||||
std::vector<uint8_t> ret;
|
||||
ret.resize(length);
|
||||
EXPECT_EQ(ZX_OK, vmo.read(ret.data(), offset, length));
|
||||
return ret;
|
||||
}
|
||||
|
||||
TEST(VmoFile, ConstructTransferOwnership) {
|
||||
vfs::VmoFile file(MakeTestVmo(), 24, 1000);
|
||||
std::vector<uint8_t> output;
|
||||
EXPECT_EQ(ZX_OK, file.ReadAt(1000, 0, &output));
|
||||
EXPECT_EQ(1000u, output.size());
|
||||
}
|
||||
|
||||
TEST(VmoFile, Reading) {
|
||||
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000,
|
||||
vfs::VmoFile::WriteOption::READ_ONLY,
|
||||
vfs::VmoFile::Sharing::NONE);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Reading the VMO from offset 24 should match reading the file from offset 0.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
|
||||
zx_status_t status;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->Read(500, &status, &result));
|
||||
EXPECT_EQ(ZX_OK, status);
|
||||
EXPECT_EQ(ReadVmo(test_vmo, 24, 500), result);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->Read(500, &status, &result));
|
||||
EXPECT_EQ(ZX_OK, status);
|
||||
EXPECT_EQ(ReadVmo(test_vmo, 524, 500), result);
|
||||
}
|
||||
|
||||
TEST(VmoFile, GetAttrReadOnly) {
|
||||
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000,
|
||||
vfs::VmoFile::WriteOption::READ_ONLY,
|
||||
vfs::VmoFile::Sharing::NONE);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
zx_status_t status;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->GetAttr(&status, &attr));
|
||||
EXPECT_EQ(ZX_OK, status);
|
||||
EXPECT_EQ(1000u, attr.content_size);
|
||||
EXPECT_EQ(1000u, attr.storage_size);
|
||||
EXPECT_EQ(fuchsia::io::MODE_TYPE_FILE | fuchsia::io::OPEN_RIGHT_READABLE,
|
||||
attr.mode);
|
||||
}
|
||||
|
||||
TEST(VmoFile, GetAttrWritable) {
|
||||
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000,
|
||||
vfs::VmoFile::WriteOption::WRITABLE,
|
||||
vfs::VmoFile::Sharing::NONE);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
fuchsia::io::NodeAttributes attr;
|
||||
zx_status_t status;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->GetAttr(&status, &attr));
|
||||
EXPECT_EQ(ZX_OK, status);
|
||||
EXPECT_EQ(1000u, attr.content_size);
|
||||
EXPECT_EQ(1000u, attr.storage_size);
|
||||
EXPECT_EQ(fuchsia::io::MODE_TYPE_FILE | fuchsia::io::OPEN_RIGHT_READABLE |
|
||||
fuchsia::io::OPEN_RIGHT_WRITABLE,
|
||||
attr.mode);
|
||||
}
|
||||
|
||||
TEST(VmoFile, ReadOnlyNoSharing) {
|
||||
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000,
|
||||
vfs::VmoFile::WriteOption::READ_ONLY,
|
||||
vfs::VmoFile::Sharing::NONE);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Writes should fail, since the VMO is read-only.
|
||||
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
|
||||
zx_status_t status;
|
||||
size_t actual;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &status, &actual));
|
||||
EXPECT_NE(ZX_OK, status);
|
||||
|
||||
// Reading the VMO from offset 24 should match reading the file from offset 0.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &status, &result));
|
||||
EXPECT_EQ(vmo_result.size(), result.size());
|
||||
EXPECT_EQ(vmo_result, result);
|
||||
|
||||
// The file should appear as a regular file, the fact that a VMO is backing it
|
||||
// is hidden.
|
||||
fuchsia::io::NodeInfo info;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
|
||||
ASSERT_TRUE(info.is_file());
|
||||
}
|
||||
|
||||
TEST(VmoFile, WritableNoSharing) {
|
||||
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000,
|
||||
vfs::VmoFile::WriteOption::WRITABLE,
|
||||
vfs::VmoFile::Sharing::NONE);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Writes should succeed.
|
||||
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
|
||||
zx_status_t status;
|
||||
size_t actual;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &status, &actual));
|
||||
EXPECT_EQ(ZX_OK, status);
|
||||
EXPECT_EQ(4u, actual);
|
||||
|
||||
// Reading the VMO from offset 24 should match reading the file from offset 0.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &status, &result));
|
||||
EXPECT_EQ(vmo_result.size(), result.size());
|
||||
EXPECT_EQ(vmo_result, result);
|
||||
EXPECT_EQ('a', result[0]);
|
||||
|
||||
// The file should appear as a regular file, the fact that a VMO is backing it
|
||||
// is hidden.
|
||||
fuchsia::io::NodeInfo info;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
|
||||
ASSERT_TRUE(info.is_file());
|
||||
}
|
||||
|
||||
TEST(VmoFile, ReadOnlyDuplicate) {
|
||||
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Writes should fail, since the VMO is read-only.
|
||||
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
|
||||
zx_status_t status;
|
||||
size_t actual;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &status, &actual));
|
||||
EXPECT_NE(ZX_OK, status);
|
||||
|
||||
// Reading the VMO from offset 24 should match reading the file from offset 0.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &status, &result));
|
||||
EXPECT_EQ(vmo_result.size(), result.size());
|
||||
EXPECT_EQ(vmo_result, result);
|
||||
|
||||
// Describing the VMO duplicates the handle, and we can access the entire VMO.
|
||||
fuchsia::io::NodeInfo info;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
|
||||
ASSERT_TRUE(info.is_vmofile());
|
||||
ASSERT_EQ(1000u, info.vmofile().length);
|
||||
ASSERT_EQ(24u, info.vmofile().offset);
|
||||
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
|
||||
|
||||
// Writing should fail on the new VMO.
|
||||
EXPECT_NE(ZX_OK, info.vmofile().vmo.write("test", 0, 4));
|
||||
}
|
||||
|
||||
TEST(VmoFile, WritableDuplicate) {
|
||||
// Create a VmoFile wrapping 1000 bytes starting at offset 24 of the vmo.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000,
|
||||
vfs::VmoFile::WriteOption::WRITABLE);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Writes should succeed.
|
||||
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
|
||||
zx_status_t status;
|
||||
size_t actual;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &status, &actual));
|
||||
EXPECT_EQ(ZX_OK, status);
|
||||
EXPECT_EQ(4u, actual);
|
||||
|
||||
// Reading the VMO from offset 24 should match reading the file from offset 0.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &status, &result));
|
||||
EXPECT_EQ(vmo_result.size(), result.size());
|
||||
EXPECT_EQ(vmo_result, result);
|
||||
EXPECT_EQ('a', result[0]);
|
||||
|
||||
// Describing the VMO duplicates the handle, and we can access the entire VMO.
|
||||
fuchsia::io::NodeInfo info;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
|
||||
ASSERT_TRUE(info.is_vmofile());
|
||||
ASSERT_EQ(1000u, info.vmofile().length);
|
||||
ASSERT_EQ(24u, info.vmofile().offset);
|
||||
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
|
||||
|
||||
// Writing should succeed on the new VMO.
|
||||
EXPECT_EQ(ZX_OK, info.vmofile().vmo.write("test", 0, 4));
|
||||
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
|
||||
}
|
||||
|
||||
TEST(VmoFile, ReadOnlyCopyOnWrite) {
|
||||
// Create a VmoFile wrapping the VMO.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 0, 4096,
|
||||
vfs::VmoFile::WriteOption::READ_ONLY,
|
||||
vfs::VmoFile::Sharing::CLONE_COW);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher());
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Writes should fail, since the VMO is read-only.
|
||||
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
|
||||
zx_status_t status;
|
||||
size_t actual;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &status, &actual));
|
||||
EXPECT_NE(ZX_OK, status);
|
||||
|
||||
// Reading the VMO shuld match reading the file.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 0, 4096);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(4096, 0, &status, &result));
|
||||
EXPECT_EQ(vmo_result.size(), result.size());
|
||||
EXPECT_EQ(vmo_result, result);
|
||||
|
||||
// Describing the VMO clones the handle, and we can access the entire VMO.
|
||||
fuchsia::io::NodeInfo info;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
|
||||
ASSERT_TRUE(info.is_vmofile());
|
||||
ASSERT_EQ(4096u, info.vmofile().length);
|
||||
ASSERT_EQ(0u, info.vmofile().offset);
|
||||
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
|
||||
|
||||
// Writing should succeed on the new VMO, due to copy on write.
|
||||
EXPECT_EQ(ZX_OK, info.vmofile().vmo.write("test", 0, 4));
|
||||
EXPECT_NE(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
|
||||
}
|
||||
|
||||
TEST(VmoFile, WritableCopyOnWrite) {
|
||||
// Create a VmoFile wrapping the VMO.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 0, 4096,
|
||||
vfs::VmoFile::WriteOption::WRITABLE,
|
||||
vfs::VmoFile::Sharing::CLONE_COW);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Writes should succeed.
|
||||
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
|
||||
zx_status_t status;
|
||||
size_t actual;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &status, &actual));
|
||||
EXPECT_EQ(ZX_OK, status);
|
||||
EXPECT_EQ(4u, actual);
|
||||
|
||||
// Reading the VMO should match reading the file.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 0, 4096);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(4096, 0, &status, &result));
|
||||
EXPECT_EQ(vmo_result.size(), result.size());
|
||||
EXPECT_EQ(vmo_result, result);
|
||||
EXPECT_EQ('a', result[0]);
|
||||
|
||||
// Describing the VMO duplicates the handle, and we can access the entire VMO.
|
||||
fuchsia::io::NodeInfo info;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->Describe(&info));
|
||||
ASSERT_TRUE(info.is_vmofile());
|
||||
ASSERT_EQ(4096u, info.vmofile().length);
|
||||
ASSERT_EQ(0u, info.vmofile().offset);
|
||||
EXPECT_EQ(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
|
||||
|
||||
// Writing should succeed on the new VMO, due to copy on write.
|
||||
EXPECT_EQ(ZX_OK, info.vmofile().vmo.write("test", 0, 4));
|
||||
EXPECT_NE(ReadVmo(test_vmo, 0, 4096), ReadVmo(info.vmofile().vmo, 0, 4096));
|
||||
}
|
||||
|
||||
TEST(VmoFile, VmoWithNoRights) {
|
||||
// Create a VmoFile wrapping 1000 bytes of the VMO starting at offset 24.
|
||||
// The vmo we use has no rights, so reading, writing, and duplication will
|
||||
// fail.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
zx::vmo bad_vmo;
|
||||
ASSERT_EQ(ZX_OK, test_vmo.duplicate(0, &bad_vmo));
|
||||
vfs::VmoFile file(std::move(bad_vmo), 24, 1000,
|
||||
vfs::VmoFile::WriteOption::WRITABLE);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Writes should fail.
|
||||
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
|
||||
zx_status_t status;
|
||||
size_t actual;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &status, &actual));
|
||||
EXPECT_NE(ZX_OK, status);
|
||||
|
||||
// Reading should fail.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &status, &result));
|
||||
EXPECT_NE(ZX_OK, status);
|
||||
|
||||
// Describing the VMO should close the connection.
|
||||
fuchsia::io::NodeInfo info;
|
||||
EXPECT_EQ(ZX_ERR_PEER_CLOSED, file_ptr->Describe(&info));
|
||||
}
|
||||
|
||||
TEST(VmoFile, UnalignedCopyOnWrite) {
|
||||
// Create a VmoFile wrapping 1000 bytes of the VMO starting at offset 24.
|
||||
// This offset is not page-aligned, so cloning will fail.
|
||||
zx::vmo test_vmo = MakeTestVmo();
|
||||
vfs::VmoFile file(zx::unowned_vmo(test_vmo), 24, 1000,
|
||||
vfs::VmoFile::WriteOption::WRITABLE,
|
||||
vfs::VmoFile::Sharing::CLONE_COW);
|
||||
|
||||
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
|
||||
loop.StartThread("vfs test thread");
|
||||
|
||||
auto file_ptr = OpenAsFile(&file, loop.dispatcher(), /*writable=*/true);
|
||||
ASSERT_TRUE(file_ptr.is_bound());
|
||||
|
||||
// Writes should succeed.
|
||||
std::vector<uint8_t> value{'a', 'b', 'c', 'd'};
|
||||
zx_status_t status;
|
||||
size_t actual;
|
||||
EXPECT_EQ(ZX_OK, file_ptr->WriteAt(value, 0, &status, &actual));
|
||||
EXPECT_EQ(ZX_OK, status);
|
||||
EXPECT_EQ(4u, actual);
|
||||
|
||||
// Reading the VMO from offset 24 should match reading the file from offset 0.
|
||||
std::vector<uint8_t> result;
|
||||
std::vector<uint8_t> vmo_result = ReadVmo(test_vmo, 24, 1000);
|
||||
EXPECT_EQ(ZX_OK, file_ptr->ReadAt(1000, 0, &status, &result));
|
||||
EXPECT_EQ(vmo_result.size(), result.size());
|
||||
EXPECT_EQ(vmo_result, result);
|
||||
EXPECT_EQ('a', result[0]);
|
||||
|
||||
// Describing the VMO should close the connection.
|
||||
fuchsia::io::NodeInfo info;
|
||||
EXPECT_EQ(ZX_ERR_PEER_CLOSED, file_ptr->Describe(&info));
|
||||
}
|
||||
|
||||
} // namespace
|
Loading…
Reference in New Issue
Block a user