diff --git a/tools/fidlcat/lib/BUILD.gn b/tools/fidlcat/lib/BUILD.gn index a5faccdffc7d1bbcd7df484012dfb8b8e1471c2d..32d907ec9c2d7281ed563957f53403878996ba8c 100644 --- a/tools/fidlcat/lib/BUILD.gn +++ b/tools/fidlcat/lib/BUILD.gn @@ -89,6 +89,8 @@ source_set("tests") { action("json_for_test") { deps = [ ":fidl($fidl_toolchain)", + ":fidl-sys($fidl_toolchain)", + "//sdk/fidl/fuchsia.sys", "//sdk/lib/fidl/cpp/test:frobinator", "//zircon/public/fidl/fuchsia-io", ] @@ -104,6 +106,8 @@ action("json_for_test") { "/fidling/gen/zircon/public/fidl/fuchsia-io/fuchsia-io.fidl.json", rebase_path(root_build_dir) + "/fidling/gen/tools/fidlcat/lib/fidl.fidl.json", + rebase_path(root_build_dir) + + "/fidling/gen/tools/fidlcat/lib/fidl-sys.fidl.json", ] } @@ -115,3 +119,11 @@ fidl("fidl") { "testdata/types.test.fidl", ] } + +fidl("fidl-sys") { + name = "test.fidlcat.sys" + + sources = [ + "testdata/sys.test.fidl", + ] +} diff --git a/tools/fidlcat/lib/generate_test_includes.sh b/tools/fidlcat/lib/generate_test_includes.sh index 3081f57bcd9f4c07cf2ad2773c9ac1010a72cd74..9e5ed7712f231072ad35f7f823335cc528773dd7 100755 --- a/tools/fidlcat/lib/generate_test_includes.sh +++ b/tools/fidlcat/lib/generate_test_includes.sh @@ -25,11 +25,17 @@ class ExampleMap { ExampleMap() { map_ = { EOF -for i in "$@"; do \ + +for i in "$@"; do + if [[ ! -f "${i}" ]]; then + echo "file ${i} not found" + exit 1 + fi; cat >> "${FILENAME}" << EOF {"${i}", R"FIDL($(cat "${i}"))FIDL"}, EOF done + cat >> "${FILENAME}" << EOF }; } diff --git a/tools/fidlcat/lib/library_loader.cc b/tools/fidlcat/lib/library_loader.cc index 2da97a45d8492e50238d0b01257ab4d3473ba7d0..cf0b6dd54c93fd77241dd18924bfa8b7be68f45d 100644 --- a/tools/fidlcat/lib/library_loader.cc +++ b/tools/fidlcat/lib/library_loader.cc @@ -51,6 +51,17 @@ std::unique_ptr<Type> Library::TypeFromIdentifier( return Type::get_illegal(); } +bool Library::GetInterfaceByName(const std::string& name, + const Interface** ptr) const { + for (const auto& interface : interfaces()) { + if (interface.name() == name) { + *ptr = &interface; + return true; + } + } + return false; +} + const std::unique_ptr<Type> Enum::GetType() const { // TODO Consider caching this. return Type::ScalarTypeFromName(value_["type"].GetString()); @@ -145,7 +156,9 @@ InterfaceMethod::InterfaceMethod(const Interface& interface, if (value_["has_request"].GetBool()) { request_params_ = std::make_optional<std::vector<InterfaceMethodParameter>>(); - for (auto& request : value["maybe_request"].GetArray()) { + auto request_arr = value["maybe_request"].GetArray(); + request_params_->reserve(request_arr.Size()); + for (auto& request : request_arr) { request_params_->emplace_back(*this, request); } } else { @@ -155,7 +168,9 @@ InterfaceMethod::InterfaceMethod(const Interface& interface, if (value_["has_response"].GetBool()) { response_params_ = std::make_optional<std::vector<InterfaceMethodParameter>>(); - for (auto& response : value["maybe_response"].GetArray()) { + auto response_arr = value["maybe_response"].GetArray(); + response_params_->reserve(response_arr.Size()); + for (auto& response : response_arr) { response_params_->emplace_back(*this, response); } } else { @@ -190,4 +205,15 @@ std::string InterfaceMethod::fully_qualified_name() const { return enclosing_interface_.name() + "." + name(); } +bool Interface::GetMethodByFullName(const std::string& name, + const InterfaceMethod** method_ptr) const { + for (const auto& method : methods()) { + if (method.fully_qualified_name() == name) { + *method_ptr = &method; + return true; + } + } + return false; +} + } // namespace fidlcat diff --git a/tools/fidlcat/lib/library_loader.h b/tools/fidlcat/lib/library_loader.h index cbcc41fafd05dc5c71e164deb1aacc6e9d6d48ed..75f95b0c53f1b2fcd356fe6addc8d16e84c3bc08 100644 --- a/tools/fidlcat/lib/library_loader.h +++ b/tools/fidlcat/lib/library_loader.h @@ -89,10 +89,11 @@ class InterfaceMethodParameter { class InterfaceMethod { public: + friend class Interface; InterfaceMethod(const Interface& interface, const rapidjson::Value& value); InterfaceMethod(InterfaceMethod&& other); - int32_t get_ordinal() const { + Ordinal get_ordinal() const { return std::strtoll(value_["ordinal"].GetString(), nullptr, 10); } @@ -139,6 +140,16 @@ class Interface { } } + Interface(Interface&& other) + : value_(other.value_), enclosing_library_(other.enclosing_library_) { + for (auto& method : other.interface_methods_) { + interface_methods_.emplace_back(*this, method.value_); + } + } + + Interface(const Interface& other) = delete; + Interface& operator=(const Interface&) = delete; + std::string name() const { return value_["name"].GetString(); } void AddMethodsToIndex(std::map<Ordinal, const InterfaceMethod*>& index) { @@ -149,6 +160,10 @@ class Interface { } } + // Sets *|method| to the fully qualified |name|'s InterfaceMethod + bool GetMethodByFullName(const std::string& name, + const InterfaceMethod** method) const; + const Library& enclosing_library() const { return enclosing_library_; } const std::vector<InterfaceMethod>& methods() const { @@ -224,7 +239,9 @@ class Union { : enclosing_library_(enclosing_library), value_(value), illegal_(nullptr) { - for (auto& member : value["members"].GetArray()) { + auto member_arr = value["members"].GetArray(); + members_.reserve(member_arr.Size()); + for (auto& member : member_arr) { members_.emplace_back(*this, member); } } @@ -286,7 +303,9 @@ class Struct { public: Struct(const Library& enclosing_library, const rapidjson::Value& value) : enclosing_library_(enclosing_library), value_(value) { - for (auto& member : value["members"].GetArray()) { + auto member_arr = value["members"].GetArray(); + members_.reserve(member_arr.Size()); + for (auto& member : member_arr) { members_.emplace_back(*this, member); } } @@ -311,7 +330,11 @@ class Library { public: Library(const LibraryLoader& enclosing, rapidjson::Document& document) : enclosing_loader_(enclosing), backing_document_(std::move(document)) { - for (auto& decl : backing_document_["interface_declarations"].GetArray()) { + auto interfaces_array = + backing_document_["interface_declarations"].GetArray(); + interfaces_.reserve(interfaces_array.Size()); + + for (auto& decl : interfaces_array) { interfaces_.emplace_back(*this, decl); } for (auto& enu : backing_document_["enum_declarations"].GetArray()) { @@ -352,6 +375,9 @@ class Library { const std::vector<Interface>& interfaces() const { return interfaces_; } + // Set *ptr to the Interface called |name| + bool GetInterfaceByName(const std::string& name, const Interface** ptr) const; + Library& operator=(const Library&) = delete; Library(const Library&) = delete; diff --git a/tools/fidlcat/lib/library_loader_test.cc b/tools/fidlcat/lib/library_loader_test.cc index 50e09e259c5b7286cb2dfce8e61141652caa189e..6fd0060571056c135dd2928ad5a6e4f33044631e 100644 --- a/tools/fidlcat/lib/library_loader_test.cc +++ b/tools/fidlcat/lib/library_loader_test.cc @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "library_loader.h" + #include <iostream> #include <sstream> #include <string> #include <vector> #include "gtest/gtest.h" -#include "library_loader.h" #include "tools/fidlcat/lib/library_loader_test_data.h" namespace fidlcat { @@ -28,29 +29,54 @@ TEST(LibraryLoader, LoadSimple) { const Library* library_ptr; loader.GetLibraryFromName("fidl.test.frobinator", &library_ptr); - const std::vector<Interface>& interfaces = library_ptr->interfaces(); std::string kDesiredInterfaceName = "fidl.test.frobinator/Frobinator"; const Interface* found_interface = nullptr; - for (const auto& interface : interfaces) { - if (interface.name() == kDesiredInterfaceName) { - found_interface = &interface; - break; - } - } + ASSERT_TRUE( + library_ptr->GetInterfaceByName(kDesiredInterfaceName, &found_interface)); + ASSERT_NE(found_interface, nullptr) << "Could not find interface " << kDesiredInterfaceName; - const InterfaceMethod* found_method = nullptr; std::string kDesiredFullMethodName = "fidl.test.frobinator/Frobinator.Frob"; - for (const auto& method : found_interface->methods()) { - if (method.fully_qualified_name() == kDesiredFullMethodName) { - found_method = &method; - break; - } - } + const InterfaceMethod* found_method = nullptr; + found_interface->GetMethodByFullName(kDesiredFullMethodName, &found_method); + ASSERT_NE(found_method, nullptr) << "Could not find method " << kDesiredFullMethodName; } +TEST(LibraryLoader, LoadFromOrdinal) { + fidlcat_test::ExampleMap examples; + std::vector<std::unique_ptr<std::istream>> library_files; + for (auto element : examples.map()) { + std::unique_ptr<std::istream> file = std::make_unique<std::istringstream>( + std::istringstream(element.second)); + + library_files.push_back(std::move(file)); + } + LibraryReadError err; + LibraryLoader loader = LibraryLoader(library_files, &err); + ASSERT_EQ(LibraryReadError::kOk, err.value); + + const Library* library_ptr = nullptr; + ASSERT_TRUE(loader.GetLibraryFromName("test.fidlcat.sys", &library_ptr)); + + std::string kDesiredInterfaceName = "test.fidlcat.sys/ComponentController"; + const Interface* found_interface = nullptr; + ASSERT_TRUE( + library_ptr->GetInterfaceByName(kDesiredInterfaceName, &found_interface)); + + const InterfaceMethod* found_method = nullptr; + found_interface->GetMethodByFullName( + "test.fidlcat.sys/ComponentController.OnDirectoryReady", &found_method); + + Ordinal correct_ordinal = found_method->get_ordinal(); + const InterfaceMethod* ordinal_method; + ASSERT_TRUE(loader.GetByOrdinal(correct_ordinal, &ordinal_method)); + ASSERT_STREQ(kDesiredInterfaceName.c_str(), + ordinal_method->enclosing_interface().name().c_str()); + ASSERT_STREQ("OnDirectoryReady", ordinal_method->name().c_str()); +} + } // namespace fidlcat diff --git a/tools/fidlcat/lib/testdata/sys.test.fidl b/tools/fidlcat/lib/testdata/sys.test.fidl new file mode 100644 index 0000000000000000000000000000000000000000..3e7957559190b4bc7319292383ec0ec25fb422ee --- /dev/null +++ b/tools/fidlcat/lib/testdata/sys.test.fidl @@ -0,0 +1,380 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library test.fidlcat.sys; + +// The goal of this file is to get a pretty big library. It's a copy +// of fuchsia.sys and fuchsia.mem as of 26 April 2019. + +/// A Buffer for data whose size is not necessarily a multiple of the page +/// size. +/// +/// VMO objects have a physical size that is always a multiple of the page +/// size. As such, VMO alone cannot serve as a buffer for arbitrarly sized +/// data. |fuchsia.mem.Buffer| is a standard struct that aggregate the VMO +/// and its size. +struct Buffer { + /// The vmo. + handle<vmo> vmo; + + /// The size of the data in the vmo in bytes. This size must be smaller + /// than the physical size of the vmo. + uint64 size; +}; + +enum TerminationReason { + // The channel closed without giving a termination reason. + UNKNOWN = 0; + // Component ran and exited with a given return_code. + EXITED = 1; + // The given URL given to launch was invalid. + URL_INVALID = 2; + // The requested package could not be found. + PACKAGE_NOT_FOUND = 3; + // An internal error happened during the launch process. + INTERNAL_ERROR = 4; + // Process creation failed. + PROCESS_CREATION_ERROR = 5; + // A Runner failed to start. + RUNNER_FAILED = 6; + // A Runner terminated while attempting to run a component. + RUNNER_TERMINATED = 7; + // Attempted to use an unsupported feature. + UNSUPPORTED = 8; +}; + +// An interface for controlling components. +// +// Closing this interface implicitly kills the controlled component unless +// the |Detach| method has been called. +// +// If the component exits, this interface will be closed. +// +// Typically obtained via |Launcher.CreateComponent|. +protocol ComponentController { + // Terminates the component. + // + // This ComponentController connection is closed when the component has + // terminated. + Kill(); + + // Decouples the lifetime of the component from this controller. + // + // After calling |Detach|, the component will not be implicitly killed when + // this interface is closed. + Detach(); + + // DEPRECATED: Use OnTerminated instead of Wait(). + // 3: Wait() + + // Event that is triggered when the component is terminated. + // + // This event provides the return code of the process and reason for + // its termination. The return_code is only valid if the termination + // reason is EXITED. If the termination reason is not EXITED, the + // return code is guaranteed not to be 0. + -> OnTerminated(int64 return_code, TerminationReason termination_reason); + + // Event that is triggered when the component's output directory is mounted. + // + // This event will not be triggered for every component, only those that + // serve a directory over their PA_DIRECTORY_REQUEST handle. + -> OnDirectoryReady(); +}; + +// An interface for controlling an environment. +// +// Closing this interface implicitly kills the controlled environment unless +// the |Detach| method has been called. +// +// If the environment is destroyed, this interface will be closed. +// +// Typically obtained via |Environment.CreateNestedEnvironment|. +protocol EnvironmentController { + // Terminates the environment. + // + // When an |Environment| is terminated, all applications launched + // in the environment (and in all transitively nested environments) are also + // killed. + Kill() -> (); + + // Decouples the lifetime of the environment from this controller. + // + // After calling |Detach|, the environment will not be implicitly killed when + // this interface is closed. + Detach(); + + // Event that is triggered when the environment is created. + -> OnCreated(); +}; + +// Maximum length for an environment label. +const uint32 kLabelMaxLength = 32; + +struct EnvironmentOptions { + // True if this environment should inherit services provided by the + // parent environment. + bool inherit_parent_services; + // True if components in this environment can share a runner provided + // by the parent environment. If false, a new runner will be started + // in this environment for components. + bool allow_parent_runners; + // True if this environment should be killed first in out of memory + // situations by setting the ZX_PROP_JOB_KILL_ON_OOM property on this + // environment's job. + bool kill_on_oom; + // True if "persistent" storage requested by components in this environment should not actually + // be persistent, and instead be deleted when this environment is killed. + bool delete_storage_on_death; +}; + +// An interface for managing a set of applications. +// +// Applications run inside environments, which provide ambient services and +// support for their lifecycle. +[Discoverable] +protocol Environment { + // Creates a new environment nested inside this environment. + // + // When applications are created inside the nested environment using the + // environment's |Launcher|, the environment requests the + // environment services from |host_directory| before passing those services to + // the newly created application in its |StartupInfo|. + // + // The |controller| can be used to control the lifecycle of the created + // environment. Note that by default the environment will be killed + // automatically when the |EnvironmentController|'s interface is closed. You + // can use |EnvironmentController.Detach| to disable this behavior. + // + // |label| defines the new environment's label/name. It must be unique within + // the parent environment (though not globally) and is used for isolating + // separate environments. It can also be used for diagnostic purposes. The + // label will be truncated if it is longer than |kLabelMaxLength|. + // + // |additional_services|, which may be empty, contains a list of services + // that the environment provides, which are hosted by + // |additional_services.host_directory|. If |options.inherit_parent_services| + // is false, |host_directory| must provide a |Loader| service if it wishes to + // allow new components to be loaded in the new environment. + // + // |options| provides additional options, see |EnvironmentOptions| for + // details. + CreateNestedEnvironment(request<Environment> environment, + request<EnvironmentController> controller, + string label, + ServiceList? additional_services, + EnvironmentOptions options); + + // Gets the Launcher associated with this environment. + // + // Applications created using this application launcher will be given the + // environment services provided by this environment's |host_directory|. + GetLauncher(request<Launcher> launcher); + + // Gets a superset of services provided by this environment's + // |host_directory|. + GetServices(request<ServiceProvider> services); + + // Gets a superset of services provided by this environment's + // |host_directory|. + GetDirectory(handle<channel> directory_request); +}; + +struct FlatNamespace { + // The mount point for each of the directories below. + // + // For example, ["/pkg", "/svc"]. + vector<string> paths; + + // The directories mounted at each path in the namespace. + vector<handle<channel>> directories; +}; + +// An interface for providing a job handle. Instances of this interface are +// created in the context of an already-identified realm, so there is no need +// to explicitly identify the realm below. +[Discoverable] +protocol JobProvider { + // Gets the root job associated with the realm. + GetJob() -> (handle<job> job); +}; + +// An FDIO file descriptor. +// TODO(abarth): Use the real FDIO declaration once FDIO converts to FIDL2. +struct FileDescriptor { + // The FDIO types of the handle (e.g., FA_FDIO_REMOTE). + int32 type0; + int32 type1; + int32 type2; + + // The handles for the file descriptor (e.g., a channel). + handle? handle0; + handle? handle1; + handle? handle2; +}; + +// Information used to create an instance of a component and obtain +// services from it. +struct LaunchInfo { + // The location from which to retrieve this component. + // + // This field will probably be replaced with a stronger notion of identity, + // such as an unforgeable token. This field is included in this iteration to + // ease the transition from the previous component interfaces. + string url; + + // The arguments to be provided to the component. + vector<string>? arguments; + + // The file descriptor to use for stdout. + // + // If null, the component will use the default stdout for the environment. + FileDescriptor? out; + + // The file descriptor to use for stderr. + // + // If null, the component will use the default stderr for the environment. + FileDescriptor? err; + + // The interface request for a Directory that is passed through to the + // component and arrives in the component as its |directory_request| + // interface request. + handle<channel>? directory_request; + + // A custom namespace that can be appended to the namespace generated by + // appmgr and provided to this component. + // Adding a mount point at standard paths like 'pkg' or 'svc' will be ignored. + // HACK(alhaad): Adding mount points for deprecated default directories like + // '/data' will override the default. + FlatNamespace? flat_namespace; + + // A list of services to be added to this component's svc namespace. These + // services are in addition to those coming from Environment. + ServiceList? additional_services; +}; + +struct ServiceList { + // A list of services that can be requested from |provider|. + vector<string> names; + + // A service provider to get the services listed in |names| from. + ServiceProvider? provider; + + // A channel to the directory hosting the services in |names|. + // TODO(CP-124): Support |host_directory| for CreateComponent and deprecate + // |provider|. + handle<channel>? host_directory; +}; + +// An interface for creating component instances. +// +// Typically obtained via |Environment.GetLauncher|. +[Discoverable] +protocol Launcher { + // Creates a new instance of the component described by |launch_info|. + // + // The component instance is created in the |Environment| + // associated with this |Launcher|. When creating the component, + // the environment requests the environment services for this component from + // its |EnvironmentHost|. + // + // The |controller| can be used to control the lifecycle of the created + // component instance. If an |ComponentController|'s interface is + // requested, the component instance is killed when the interface is closed. + CreateComponent(LaunchInfo launch_info, + request<ComponentController>? controller); +}; + +// An interface for loading from pacakges. +[Discoverable] +protocol Loader { + // LoadUrl a package by url. + LoadUrl(string url) -> (Package? package); +}; + +// Information given to components at startup. +// +// For ELF binaries, this information is provided in the initialization message +// given to libc by fuchsia.process.Launcher. +struct StartupInfo { + // The launch info for the to start. + LaunchInfo launch_info; + + // The namespace in which to run the component. + FlatNamespace flat_namespace; + + // Key string value string map of the component's program metadata, obtained + // from its component manifest. + vector<ProgramMetadata>? program_metadata; + + // TODO(abarth): Add more fields to this struct relating to component and + // environment identity. +}; + +// Program information about a component. +struct ProgramMetadata { + // Key for program metadata pair. E.g. "binary" for an ELF binary component, + // or "data" for a flutter/dart component. + string key; + + // Value for program metadata pair. E.g. "bin/app" for a "binary" key, or + // "data/foo" for a flutter/dart component. + string value; +}; + +// A binary representation of a component. +// +// Typically provided to |Runner.StartComponent| when starting a +// component. +struct Package { + // A read-only binary representation of the component. For example, if the + // component is intended to run in the Dart virtual machine, this data might + // contain a dartx package. + Buffer? data; + + // A directory containing the contents of the package. For example, if the + // component is stored in pkgfs, this directory will be the pkgfs directory + // containing the package. + handle<channel>? directory; + + // Resolved URL of the component. This is the url specified in startup_info + // after following redirects and resolving relative paths. + string resolved_url; +}; + +// An interface for running components. +// +// Typically exposed by components that provide execution environments for +// particular classes of programs. For example, the Dart virtual machine exposes +// this interface to run Dart programs. +[Discoverable] +protocol Runner { + // Execute the given component. + // + // Upon startup, the component is to be given the information in + // |startup_info|, but the mechanism by which the component receives that + // information is up to the component runner. + // + // The |controller| interface request typically originates from the + // |Launcher.CreateComponent| message that caused this + // component to be started. + StartComponent(Package package, + StartupInfo startup_info, + request<ComponentController>? controller); +}; + +// An interface through which a client may request services from a host. +// Instances of this interface are created within the context of an +// already-identified client and host pair, so there is no need to explicitly +// identify the client or host in the methods below. +// +// This interface is deprecated. Services should be published as directory +// entries instead, just like files. +// TODO(ZX-1358): Point to the FIDL interface for file I/O once RIO is migrated. +protocol ServiceProvider { + // Asks the host to provide the service identified by |service_name| through + // the |channel| endpoint supplied by the caller. If the host is not willing + // or able to provide the requested service, it should close the |channel|. + ConnectToService(string service_name, handle<channel> channel); +};