Refer to SDK rather than locally checked out packages (#314)

This commit is contained in:
Dan Field 2019-09-18 12:40:49 -07:00 committed by GitHub
parent 9c762bea4e
commit 25b164b5f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 9 additions and 9351 deletions

View File

@ -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 = [ "../../../" ]
}

View File

@ -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" ]
}

View File

@ -1,3 +0,0 @@
abarth@google.com
anmittal@google.com
jeffbrown@google.com

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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",
]
}

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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",
]
}

View File

@ -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;
}

View File

@ -1,10 +0,0 @@
{
"program": {
"binary": "bin/environment_delegating_runner"
},
"sandbox": {
"services": [
"fuchsia.sys.Runner"
]
}
}

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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
},
]
}

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -1,11 +0,0 @@
{
"program": {
"binary": "test/component_cpp_testing_tests"
},
"sandbox": {
"services": [
"fuchsia.sys.Environment",
"fuchsia.sys.Loader"
]
}
}

View File

@ -1,5 +0,0 @@
{
"program": {
"binary": "test/component_cpp_testing_unittests"
}
}

View File

@ -1,5 +0,0 @@
{
"program": {
"binary": "test/component_cpp_unittests"
}
}

View File

@ -1,5 +0,0 @@
{
"program": {
"binary": "bin/helper_proc"
}
}

View File

@ -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

View File

@ -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

View File

@ -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()));
}

View File

@ -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",
]
}

View File

@ -1,2 +0,0 @@
abarth@google.com
anmittal@google.com

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -1,5 +0,0 @@
{
"program": {
"binary": "test/vfs_cpp_unittests"
}
}

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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_

View File

@ -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);
}
}

View File

@ -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",
]
}

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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