blob: faa33cc31f84f000a410b721226fc7e3ce8d869e [file] [log] [blame]
// 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.
#ifndef SRC_DEVICES_BIN_DRIVER_MANAGER_DEVFS_DEVFS_H_
#define SRC_DEVICES_BIN_DRIVER_MANAGER_DEVFS_DEVFS_H_
#include <fidl/fuchsia.component.runner/cpp/fidl.h>
#include <fidl/fuchsia.device.fs/cpp/wire.h>
#include <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/async/dispatcher.h>
#include <lib/component/incoming/cpp/clone.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <random>
#include <fbl/ref_ptr.h>
#include <fbl/string.h>
#include "src/devices/bin/driver_manager/component_owner.h"
#include "src/storage/lib/vfs/cpp/pseudo_dir.h"
#include "src/storage/lib/vfs/cpp/vnode.h"
namespace driver_manager {
class Devfs;
class PseudoDir;
class DevfsDevice;
// PathServer acts as a contained TopologicalPath server, allowing clients
// to connect and vending the topological path of the devnode.
// Pathserver checks the variable kClassesThatAllowTopologicalPath when clients
// attempt to bind to the service, and prohibits clients from connecting to drivers
// whose class name is not in the allowlist.
class PathServer : public fidl::WireServer<fuchsia_device_fs::TopologicalPath> {
public:
explicit PathServer(std::string path, async_dispatcher_t* dispatcher)
: path_(path), dispatcher_(dispatcher) {}
// TopologicalPath protocol:
void GetTopologicalPath(GetTopologicalPathCompleter::Sync& completer) override {
completer.ReplySuccess(fidl::StringView::FromExternal(GetPath()));
}
void Bind(zx::channel channel, const std::string& class_name);
fidl::ProtocolHandler<fuchsia_device_fs::TopologicalPath> GetHandler() {
return bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure);
}
std::string GetPath() { return path_; }
private:
std::string path_;
async_dispatcher_t* dispatcher_;
fidl::ServerBindingGroup<fuchsia_device_fs::TopologicalPath> bindings_;
};
class Devnode {
public:
// This class represents a device in devfs. It is called "passthrough" because it sends
// the channel and the connection type to a callback function.
struct PassThrough {
// The Device connect callback is for accessing the /dev/class/xxx protocol for the device
using DeviceConnectCallback = fit::function<zx_status_t(zx::channel)>;
// The controller callback is for accessing the fuchsia.device/Controller
// interface associated with the device.
using ControllerConnectCallback =
fit::function<zx_status_t(fidl::ServerEnd<fuchsia_device::Controller>)>;
// Create a Passthrough class. The client must make sure that any captures in the callback
// live as long as the passthrough class (for this reason it's strongly recommended to use
// owned captures).
explicit PassThrough(DeviceConnectCallback device_callback,
ControllerConnectCallback controller_callback)
: device_connect(std::make_shared<DeviceConnectCallback>(std::move(device_callback))),
controller_connect(
std::make_shared<ControllerConnectCallback>(std::move(controller_callback))) {}
std::shared_ptr<DeviceConnectCallback> device_connect;
std::shared_ptr<ControllerConnectCallback> controller_connect;
};
using Target = std::optional<PassThrough>;
// Constructs a root node.
explicit Devnode(Devfs& devfs);
// `parent` must outlive `this`.
Devnode(Devfs& devfs, PseudoDir& parent, Target target, fbl::String name, const std::string& path,
const std::string& class_name = "none");
~Devnode();
Devnode(const Devnode&) = delete;
Devnode& operator=(const Devnode&) = delete;
Devnode(Devnode&&) = delete;
Devnode& operator=(Devnode&&) = delete;
// Add a child to this Devnode. The child will be added to both the topological path and under the
// given `class_name`.
zx_status_t add_child(std::string_view name, std::optional<std::string_view> class_name,
Target target, DevfsDevice& out_child);
// Exports `target`.
//
// If `topological_path` is provided, then `target` will be exported at that path under `this`.
//
// If `class_path` is provided, then `target` will be exported under that class path.
zx_status_t export_dir(Devnode::Target target, std::optional<std::string_view> topological_path,
std::optional<std::string_view> class_path,
std::vector<std::unique_ptr<Devnode>>& out);
std::string_view name() const;
PseudoDir& children() const { return node().children(); }
void advertise_modified();
// Publishes the node to devfs. Asserts if called more than once.
void publish();
// The actual vnode implementation. This is distinct from the outer class
// because `fs::Vnode` imposes reference-counted semantics, and we want to
// preserve owned semantics on the outer class.
//
// This is exposed for use in tests.
class VnodeImpl : public fs::Vnode {
public:
fuchsia_io::NodeProtocolKinds GetProtocols() const final;
zx::result<fs::VnodeAttributes> GetAttributes() const final;
zx_status_t Lookup(std::string_view name, fbl::RefPtr<fs::Vnode>* out) final;
zx_status_t WatchDir(fs::FuchsiaVfs* vfs, fuchsia_io::wire::WatchMask mask, uint32_t options,
fidl::ServerEnd<fuchsia_io::DirectoryWatcher> watcher) final;
zx_status_t Readdir(fs::VdirCookie* cookie, void* dirents, size_t len,
size_t* out_actual) final;
zx_status_t ConnectService(zx::channel channel) final;
PseudoDir& children() const { return *children_; }
Devnode& holder_;
const Target target_;
private:
friend fbl::internal::MakeRefCountedHelper<VnodeImpl>;
VnodeImpl(Devnode& holder, Target target);
bool IsDirectory() const;
fbl::RefPtr<PseudoDir> children_ = fbl::MakeRefCounted<PseudoDir>();
};
private:
// Advertises a service that corresponds to the class name
zx_status_t TryAddService(std::string_view class_name, Target target,
std::string_view instance_name);
zx_status_t export_class(Devnode::Target target, std::string_view class_path,
std::vector<std::unique_ptr<Devnode>>& out);
zx_status_t export_topological_path(Devnode::Target target, std::string_view topological_path,
std::vector<std::unique_ptr<Devnode>>& out);
VnodeImpl& node() const { return *node_; }
const Target& target() const { return node_->target_; }
friend class Devfs;
friend class PseudoDir;
Devfs& devfs_;
fbl::RefPtr<PseudoDir> parent_;
const fbl::RefPtr<VnodeImpl> node_;
const std::optional<fbl::String> name_;
PathServer path_server_;
// If service_name_ is valid, it means that there is a service advertised, and should be removed
// upon destruction of this devnode.
std::optional<std::string> service_path_;
std::optional<std::string> service_name_;
};
class PseudoDir : public fs::PseudoDir {
public:
std::unordered_map<fbl::String, std::reference_wrapper<Devnode>, std::hash<std::string_view>>
unpublished;
};
class DevfsDevice {
public:
void advertise_modified();
void publish();
void unpublish();
std::optional<Devnode>& topological_node() { return topological_; }
private:
friend Devnode;
std::unique_ptr<Devnode>& protocol_node() { return protocol_; }
std::optional<Devnode> topological_;
// TODO(https://fxbug.dev/42062564): These protocol nodes are currently always empty directories.
// Change this to a pure `RemoteNode` that doesn't expose a directory.
std::unique_ptr<Devnode> protocol_;
};
// Manages the root functionality of devfs.
// Also acts a a proxy driver.
// Mounts as a boot driver and adversises services that are registered under
// a recognized class name. See class_names.h for more info.
class Devfs : public fidl::Server<fuchsia_component_runner::ComponentController>,
public fidl::WireAsyncEventHandler<fuchsia_component::Controller>,
public ComponentOwner {
public:
// `root` must outlive `this`.
explicit Devfs(std::optional<Devnode>& root, async_dispatcher_t* dispatcher);
zx::result<fidl::ClientEnd<fuchsia_io::Directory>> Connect(fs::FuchsiaVfs& vfs);
zx::result<std::string> MakeInstanceName(std::string_view class_name);
// ComponentOwner
void SetController(fidl::ClientEnd<fuchsia_component::Controller> component_controller) override;
void OnComponentStarted(const std::weak_ptr<BootupTracker>& bootup_tracker,
const std::string& moniker,
zx::result<StartedComponent> component) override;
void RequestStartComponent(fuchsia_process::wire::HandleInfo startup_handle,
const std::string& moniker,
const std::weak_ptr<BootupTracker>& bootup_tracker) override;
bool SkipInjectedOffers() const override { return true; }
fbl::RefPtr<PseudoDir> get_class_entry(std::string_view class_name) {
ZX_ASSERT(class_entries_.contains(std::string(class_name)));
return class_entries_[std::string(class_name)];
}
async_dispatcher_t* dispatcher() { return dispatcher_; }
component::OutgoingDirectory& outgoing() { return outgoing_; }
// Called by the Driver Runner when the special devfs driver component is
// created.
void AttachComponent(fuchsia_component_runner::ComponentStartInfo info,
fidl::ServerEnd<fuchsia_component_runner::ComponentController> controller);
// fuchsia_component_runner::ComponentController
void Stop(StopCompleter::Sync& completer) override { CloseComponent(); }
void Kill(KillCompleter::Sync& completer) override { CloseComponent(); }
void handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_component_runner::ComponentController> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override {}
// fidl::WireAsyncEventHandler<fuchsia_component::Controller>
void on_fidl_error(fidl::UnbindInfo info) override;
void handle_unknown_event(
fidl::UnknownEventMetadata<fuchsia_component::Controller> metadata) override {}
private:
friend class Devnode;
static std::optional<std::reference_wrapper<fs::Vnode>> Lookup(PseudoDir& parent,
std::string_view name);
// close the fake driver component
void CloseComponent();
Devnode& root_;
component::OutgoingDirectory outgoing_;
async_dispatcher_t* dispatcher_;
std::optional<fidl::ServerBinding<fuchsia_component_runner::ComponentController>> binding_;
fidl::WireClient<fuchsia_component::Controller> component_controller_;
std::default_random_engine device_number_generator_;
fbl::RefPtr<PseudoDir> class_ = fbl::MakeRefCounted<PseudoDir>();
std::unordered_map<std::string, fbl::RefPtr<PseudoDir>> class_entries_;
};
} // namespace driver_manager
#endif // SRC_DEVICES_BIN_DRIVER_MANAGER_DEVFS_DEVFS_H_