blob: 60f9b4e5c8a0197c439feb9e00a8953764a1519d [file] [log] [blame]
// Copyright 2020 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.
use crate::error::PowerManagerError;
use crate::message::{Message, MessageReturn};
use crate::node::Node;
use crate::system_shutdown_handler;
use crate::types::Seconds;
use crate::utils::get_current_timestamp;
use anyhow::{format_err, Context, Error};
use async_trait::async_trait;
use fidl::endpoints::Proxy;
use fidl_fuchsia_device_manager as fdevicemgr;
use fidl_fuchsia_hardware_power_statecontrol as fpowerstatecontrol;
use fidl_fuchsia_io as fio;
use fidl_fuchsia_power_manager as fpowermanager;
use fuchsia_async::{self as fasync, DurationExt, TimeoutExt};
use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
use fuchsia_inspect::{self as inspect, Property};
use fuchsia_inspect_contrib::{inspect_log, nodes::BoundedListNode};
use fuchsia_zircon as zx;
use futures::channel::mpsc;
use futures::prelude::*;
use futures::TryStreamExt;
use log::*;
use serde_derive::Deserialize;
use serde_json as json;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
/// Node: DriverManagerHandler
///
/// Summary: The primary purpose of this node is to host the
/// fuchsia.power.manager.DriverManagerRegistration service. The Driver Manager will use this
/// service to register protocol instances that Power Manager requires for normal operation. After
/// the registration protocol instances are received, the node is responsible for:
/// 1) Monitoring the provided fuchsia.device.manager.SystemStateTransition protocol instance
/// for closure.
/// 2) Vending channel connections to the underlying services in the provided
/// fuchsia.io.Directory protocol instance
/// 3) Setting the termination system state on the Driver Manager
///
/// Handles Messages:
/// - SetTerminationSystemState
///
/// Sends Messages: N/A
///
/// FIDL dependencies:
/// - fuchsia.power.manager.DriverManagerRegistration: the node hosts this protocol to allow the
/// Driver Manager to register essential protocol instances with the Power Manager
/// - fuchsia.device.manager.SystemStateTransition: a protocol of this instance is provided to
/// the Power Manager by the Driver Manager using the
/// fuchsia.power.manager.DriverManagerRegistration protocol. The SystemStateTransition is
/// then used to set the Driver Manager's termination system state.
/// - fuchsia.io.Directory: a protocol of this instance is provided to the Power Manager by the
/// Driver Manager using the fuchsia.power.manager.DriverManagerRegistration protocol. The
/// Directory is expected to represent the devfs (/dev) and is used to open driver
/// connections.
/// A builder for constructing the DriverManagerHandler node.
pub struct DriverManagerHandlerBuilder<'a, 'b> {
registration_timeout: Option<Seconds>,
driver_manager_registration: Option<DriverManagerRegistration>,
termination_channel_closed_handler: Box<dyn FnOnce() + 'static>,
service_fs: Option<&'a mut ServiceFs<ServiceObjLocal<'b, ()>>>,
inspect_root: Option<&'a inspect::Node>,
}
impl<'a, 'b> DriverManagerHandlerBuilder<'a, 'b> {
pub fn new() -> Self {
Self {
registration_timeout: None,
driver_manager_registration: None,
termination_channel_closed_handler: Box::new(termination_channel_closed_handler),
service_fs: None,
inspect_root: None,
}
}
pub fn new_from_json(
json_data: json::Value,
_nodes: &HashMap<String, Rc<dyn Node>>,
service_fs: &'a mut ServiceFs<ServiceObjLocal<'b, ()>>,
) -> Self {
#[derive(Deserialize)]
struct Config {
registration_timeout_s: Option<f64>,
}
#[derive(Deserialize)]
struct JsonData {
config: Config,
}
let data: JsonData = json::from_value(json_data).unwrap();
let mut builder = Self::new().with_service_fs(service_fs);
if let Some(timeout) = data.config.registration_timeout_s {
builder = builder.with_registration_timeout(Seconds(timeout));
}
builder
}
pub fn with_service_fs(
mut self,
service_fs: &'a mut ServiceFs<ServiceObjLocal<'b, ()>>,
) -> Self {
self.service_fs = Some(service_fs);
self
}
pub fn with_registration_timeout(mut self, timeout: Seconds) -> Self {
self.registration_timeout = Some(timeout);
self
}
#[cfg(test)]
pub fn with_termination_channel_closed_handler(
mut self,
handler: Box<impl FnOnce() + 'static>,
) -> Self {
self.termination_channel_closed_handler = handler;
self
}
#[cfg(test)]
pub fn with_driver_manager_registration(
mut self,
registration: DriverManagerRegistration,
) -> Self {
self.driver_manager_registration = Some(registration);
self
}
#[cfg(test)]
pub fn with_inspect_root(mut self, root: &'a inspect::Node) -> Self {
self.inspect_root = Some(root);
self
}
/// Constructs the DriverManagerHandler node. In order to construct a fully initialized node
/// that is able to serve driver connections for other nodes within the Power Manager, this
/// `build` function will block until it receives the required registration from the Driver
/// Manager. The function returns an error if the DriverManagerHandlerBuilder was provided with
/// a timeout value and that timeout expires before receiving the registration.
pub async fn build(self) -> Result<Rc<DriverManagerHandler>, Error> {
// Optionally use the default inspect root node
let inspect_root = self.inspect_root.unwrap_or(inspect::component::inspector().root());
// If a DriverManagerRegistration was provided, then use it. Otherwise, call the function
// that waits for the Driver Manager to provide us with the registration. If the
// registration was not received within the specified timeout, an error is returned.
let registration = if self.driver_manager_registration.is_some() {
self.driver_manager_registration.unwrap()
} else {
await_registration(
self.registration_timeout,
self.service_fs.ok_or(format_err!("ServiceFs required"))?,
)
.await?
};
// Set up Inspect and log the registration timeout configuration
let inspect = InspectData::new(inspect_root, "DriverManagerHandler".to_string());
inspect
.registration_timeout_config
.set(self.registration_timeout.unwrap_or(Seconds(0.0)).0 as u64);
inspect.registration_time.set(get_current_timestamp().0);
// Set up a signal handler to monitor `termination_state_proxy` for a PEER_CLOSED signal. By
// default, if the channel is closed then the system will be forcefully shutdown.
enable_proxy_close_handler(
registration.termination_state_proxy.clone(),
self.termination_channel_closed_handler,
);
// Bind the received Directory channel to the namespace. This lets the received drivers be
// accessed in the usual way (using `fdio::service_connect` and a /dev path).
bind_driver_directory(registration.dir).context("Failed to bind driver directory")?;
let node = Rc::new(DriverManagerHandler {
termination_state_proxy: registration.termination_state_proxy,
inspect,
});
Ok(node)
}
}
/// Default handler function that will be called if the fuchsia.device.manager.SystemStateTransition
/// protocol instance that was provided during registration is ever closed.
fn termination_channel_closed_handler() {
error!("SystemStateTransition channel closed. Forcing system shutdown");
fuchsia_trace::instant!(
"power_manager",
"DriverManagerHandler::termination_channel_closed_handler",
fuchsia_trace::Scope::Thread
);
system_shutdown_handler::force_shutdown();
}
/// Contains the required protocol instances that are received from the Driver Manager.
#[derive(Debug)]
pub struct DriverManagerRegistration {
/// Protocol instance that the Power Manager uses to set the Driver Manager's termination system
/// state.
termination_state_proxy: fdevicemgr::SystemStateTransitionProxy,
/// Directory instance that represents the devfs (/dev).
dir: fio::DirectoryProxy,
}
impl DriverManagerRegistration {
/// Determine if the contents of this registration are valid.
fn validate(&self) -> Result<(), Error> {
if self.termination_state_proxy.is_closed() {
Err(format_err!("Invalid SystemStateTransitionProxy handle"))
} else if self.dir.is_closed() {
Err(format_err!("Invalid DirectoryProxy handle"))
} else {
Ok(())
}
}
}
/// Waits to receive a successful registration from the Driver Manager. The function returns after a
/// registration was successfully received or after the timeout expires, if one was provided.
async fn await_registration<'a, 'b>(
timeout: Option<Seconds>,
service_fs: &'a mut ServiceFs<ServiceObjLocal<'b, ()>>,
) -> Result<DriverManagerRegistration, Error> {
fuchsia_trace::duration!(
"power_manager",
"DriverManagerHandler::await_registration",
"timeout" => timeout.unwrap_or(Seconds(0.0)).0
);
// Publish the fuchsia.power.manager.DriverManagerRegistration service and get the channel that
// the service will use to send the registration
let registration_receiver = setup_registration_service(service_fs);
// Wait to receive the registration on the receiver channel, or for the timeout to expire
let result = wait_for_registration(timeout, registration_receiver, service_fs).await;
fuchsia_trace::instant!(
"power_manager",
"DriverManagerHandler::await_registration_result",
fuchsia_trace::Scope::Thread,
"result" => format!("{:?}", result).as_str()
);
result
}
/// Publishes the fuchsia.power.manager.DriverManagerRegistration service so that the Driver manager
/// can send the registration that the Power Manager requires. The service is provided with the
/// sender end of a mpsc channel so that it can send the registration back once it is received. The
/// function returns the receiver end of the channel.
fn setup_registration_service<'a, 'b>(
service_fs: &'a mut ServiceFs<ServiceObjLocal<'b, ()>>,
) -> mpsc::Receiver<DriverManagerRegistration> {
let (registration_sender, registration_receiver) = mpsc::channel(1);
// Publish the fuchsia.power.manager.DriverManagerRegistration service
service_fs.dir("svc").add_fidl_service(
move |stream: fpowermanager::DriverManagerRegistrationRequestStream| {
handle_new_driver_manager_registration_stream(stream, registration_sender.clone());
},
);
registration_receiver
}
/// Waits to receive registration over the mpsc channel receiver end. If a timeout value is
/// provided, then the function will return an error after the timeout expires. The provided
/// `service_fs` is polled to allow the published fuchsia.power.manager.DriverManagerRegistration
/// service to run
async fn wait_for_registration(
timeout: Option<Seconds>,
mut registration_receiver: mpsc::Receiver<DriverManagerRegistration>,
mut service_fs: impl Stream<Item = ()> + std::marker::Unpin,
) -> Result<DriverManagerRegistration, Error> {
// Use the provided `timeout` and the current time to calculate a deadline
let timeout_time = if timeout.is_some() {
zx::Duration::from_seconds(timeout.unwrap().0 as i64).after_now()
} else {
fasync::Time::INFINITE
};
// We must poll the provided `service_fs` so that the published
// fuchsia.power.manager.DriverManagerRegistration service can run. If the stream emits an item,
// just continue in this loop until we get a registration on the channel or we hit the timeout.
loop {
match future::select(
registration_receiver.next().on_timeout(timeout_time, || None),
service_fs.next(),
)
.await
{
future::Either::Left((Some(registration), _)) => return Ok(registration),
future::Either::Left((None, _)) => {
return Err(format_err!("Timed out waiting for registration"))
}
future::Either::Right(_) => {}
}
}
}
/// Handles a new connection to the fuchsia.power.manager.DriverManagerRegistration service. The
/// provided `stream` contains the requests sent to this connection. The provided
/// `registration_sender` is used to notify the receipt of the registration.
fn handle_new_driver_manager_registration_stream(
mut stream: fpowermanager::DriverManagerRegistrationRequestStream,
mut registration_sender: mpsc::Sender<DriverManagerRegistration>,
) {
fuchsia_trace::instant!(
"power_manager",
"DriverManagerHandler::handle_new_driver_manager_registration_stream",
fuchsia_trace::Scope::Thread
);
fasync::Task::local(
async move {
while let Some(req) = stream.try_next().await? {
match req {
fpowermanager::DriverManagerRegistrationRequest::Register {
system_state_transition,
dir,
responder,
} => {
fuchsia_trace::instant!(
"power_manager",
"DriverManagerHandler::register_request",
fuchsia_trace::Scope::Thread
);
let mut result = handle_driver_manager_registration(
system_state_transition,
dir,
&mut registration_sender,
);
let _ = responder.send(&mut result);
fuchsia_trace::instant!(
"power_manager",
"DriverManagerHandler::register_request_result",
fuchsia_trace::Scope::Thread,
"result" => format!("{:?}", result).as_str()
);
// After we successfully receive the registration, we can stop processing
// any requests on this service connection
if result.is_ok() {
break;
} else {
error!("Received invalid registration");
}
}
}
}
Ok(())
}
.unwrap_or_else(|e: anyhow::Error| error!("{:?}", e)),
)
.detach();
}
/// Handles a register request sent to the fuchsia.power.manager.DriverManagerRegistration service.
/// The function determines if the registration is valid and then sends it over the mpsc channel
/// sender end.
fn handle_driver_manager_registration(
termination_state_proxy: fidl::endpoints::ClientEnd<fdevicemgr::SystemStateTransitionMarker>,
dir: fidl::endpoints::ClientEnd<fio::DirectoryMarker>,
registration_sender: &mut mpsc::Sender<DriverManagerRegistration>,
) -> Result<(), fpowermanager::RegistrationError> {
let registration = DriverManagerRegistration {
termination_state_proxy: termination_state_proxy
.into_proxy()
.map_err(|_| fpowermanager::RegistrationError::InvalidHandle)?,
dir: dir.into_proxy().map_err(|_| fpowermanager::RegistrationError::InvalidHandle)?,
};
registration.validate().map_err(|_| fpowermanager::RegistrationError::InvalidHandle)?;
registration_sender
.try_send(registration)
.map_err(|_| fpowermanager::RegistrationError::Internal)?;
Ok(())
}
/// Spawns a Future that monitors the given `proxy` for a CHANNEL_PEER_CLOSED signal. If the signal
/// is present, then the `handler` function is called.
fn enable_proxy_close_handler(
proxy: fdevicemgr::SystemStateTransitionProxy,
handler: impl FnOnce() + 'static,
) {
fasync::Task::local(async move {
let _ = proxy.on_closed().await;
handler();
})
.detach();
}
/// Creates a "/dev" directory within the namespace that is bound to the provided DirectoryProxy.
fn bind_driver_directory(dir: fio::DirectoryProxy) -> Result<(), Error> {
fdio::Namespace::installed()?
.bind(
"/dev",
dir.into_channel()
.map_err(|_| format_err!("Failed to convert DirectoryProxy into channel"))?
.into_zx_channel(),
)
.map_err(|e| e.into())
}
pub struct DriverManagerHandler {
/// Protocol instance that the Power Manager uses to set the Driver Manager's termination system
/// state.
termination_state_proxy: fdevicemgr::SystemStateTransitionProxy,
/// Struct for managing Component Inspection data
inspect: InspectData,
}
impl DriverManagerHandler {
/// Handle the SetTerminationState message. The function uses `self.termination_state_proxy` to
/// set the Driver Manager's termination state.
async fn handle_set_termination_state_message(
&self,
state: fpowerstatecontrol::SystemPowerState,
) -> Result<MessageReturn, PowerManagerError> {
// TODO(fxbug.dev/44484): This string must live for the duration of the function because the trace
// macro uses it when the function goes out of scope. Therefore, it must be bound here and
// not used anonymously at the macro callsite.
let state_str = format!("{:?}", state);
fuchsia_trace::duration!(
"power_manager",
"DriverManagerHandler::handle_set_termination_state_message",
"state" => state_str.as_str()
);
let result = self
.termination_state_proxy
.set_termination_system_state(state)
.await
.map_err(|e| format_err!("FIDL failed: {}", e))?;
let result = match result.map_err(|e| zx::Status::from_raw(e)) {
Err(zx::Status::INVALID_ARGS) => Err(PowerManagerError::InvalidArgument(format!(
"Invalid state argument: {:?}",
state
))),
Err(e) => Err(PowerManagerError::GenericError(format_err!(
"SetTerminationSystemState failed: {}",
e
))),
Ok(()) => Ok(MessageReturn::SetTerminationSystemState),
};
fuchsia_trace::instant!(
"power_manager",
"DriverManagerHandler::handle_set_termination_state_message_result",
fuchsia_trace::Scope::Thread,
"result" => format!("{:?}", result).as_str()
);
if let Err(e) = &result {
self.inspect.log_set_termination_error(format!("{:?}", state), format!("{:?}", e));
} else {
self.inspect.termination_state.set(format!("{:?}", state).as_str());
}
result
}
}
#[async_trait(?Send)]
impl Node for DriverManagerHandler {
fn name(&self) -> String {
"DriverManagerHandler".to_string()
}
async fn handle_message(&self, msg: &Message) -> Result<MessageReturn, PowerManagerError> {
match msg {
Message::SetTerminationSystemState(state) => {
self.handle_set_termination_state_message(*state).await
}
_ => Err(PowerManagerError::Unsupported),
}
}
}
struct InspectData {
// Nodes
_root_node: inspect::Node,
// Properties
registration_timeout_config: inspect::UintProperty,
registration_time: inspect::IntProperty,
termination_state: inspect::StringProperty,
set_termination_errors: RefCell<BoundedListNode>,
}
impl InspectData {
const NUM_SET_TERMINATION_ERRORS: usize = 10;
fn new(parent: &inspect::Node, name: String) -> Self {
let root_node = parent.create_child(name);
Self {
registration_timeout_config: root_node
.create_uint("registration_timeout_config (s)", 0),
registration_time: root_node.create_int("registration_time", 0),
termination_state: root_node.create_string("termination_state", "None"),
set_termination_errors: RefCell::new(BoundedListNode::new(
root_node.create_child("set_termination_errors"),
Self::NUM_SET_TERMINATION_ERRORS,
)),
_root_node: root_node,
}
}
fn log_set_termination_error(&self, state: String, error: String) {
inspect_log!(self.set_termination_errors.borrow_mut(), state: state, error: error)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use assert_matches::assert_matches;
use inspect::assert_data_tree;
use std::cell::Cell;
/// Creates a fake implementation of the SystemStateTransition protocol. Responds to the
/// SetTerminationSystemState request by calling the provided closure. The closure's return
/// value is returned to the client.
fn setup_fake_termination_state_service<T>(
mut set_termination_state: T,
) -> fdevicemgr::SystemStateTransitionProxy
where
T: FnMut(fpowerstatecontrol::SystemPowerState) -> Result<(), zx::Status> + 'static,
{
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<fdevicemgr::SystemStateTransitionMarker>()
.unwrap();
fasync::Task::local(async move {
while let Ok(req) = stream.try_next().await {
match req {
Some(fdevicemgr::SystemStateTransitionRequest::SetTerminationSystemState {
state,
responder,
}) => {
let _ = responder
.send(&mut set_termination_state(state).map_err(|e| e.into_raw()));
}
e => panic!("Unexpected request: {:?}", e),
}
}
})
.detach();
proxy
}
/// Tests that well-formed configuration JSON does not panic the `new_from_json` function.
#[fasync::run_singlethreaded(test)]
async fn test_new_from_json() {
// With a registration_timeout
let _ = DriverManagerHandlerBuilder::new_from_json(
json::json!({
"type": "DriverManagerHandler",
"name": "driver_manager_handler",
"config": {
"registration_timeout": 60.0
}
}),
&HashMap::new(),
&mut ServiceFs::new_local(),
);
// Without a registration_timeout
let _ = DriverManagerHandlerBuilder::new_from_json(
json::json!({
"type": "DriverManagerHandler",
"name": "driver_manager_handler",
"config": {
}
}),
&HashMap::new(),
&mut ServiceFs::new_local(),
);
}
/// Tests for the presence and correctness of Inspect data
#[fasync::run_singlethreaded(test)]
async fn test_inspect_data() {
let inspector = inspect::Inspector::new();
// For this test, let the server succeed for the Reboot state but give an
// error for any other state (so that we can test error paths)
let termination_state_proxy = setup_fake_termination_state_service(|state| match state {
fpowerstatecontrol::SystemPowerState::Reboot => Ok(()),
_ => Err(zx::Status::INVALID_ARGS),
});
let node = DriverManagerHandlerBuilder::new()
.with_inspect_root(inspector.root())
.with_registration_timeout(Seconds(60.0))
.with_driver_manager_registration(DriverManagerRegistration {
termination_state_proxy,
dir: fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap().0,
})
.build()
.await
.unwrap();
// Should succeed so `termination_state` will be populated
let _ = node
.handle_set_termination_state_message(fpowerstatecontrol::SystemPowerState::Reboot)
.await;
// Should fail so `set_termination_errors` will be populated
let _ = node
.handle_set_termination_state_message(fpowerstatecontrol::SystemPowerState::FullyOn)
.await;
assert_data_tree!(
inspector,
root: {
DriverManagerHandler: {
"registration_timeout_config (s)": 60u64,
"registration_time": inspect::testing::AnyProperty,
"termination_state": "Reboot",
"set_termination_errors": {
"0": {
"state": "FullyOn",
"error": "InvalidArgument(\"Invalid state argument: FullyOn\")",
"@time": inspect::testing::AnyProperty
}
}
}
}
);
}
/// Tests that the `handle_new_driver_manager_registration_stream` (service handler) and
/// `wait_for_registration` functions will work as intended by:
/// 1. the server receives the register request and returns success to the client
/// 2. the server places the received registration into the mpsc sender end
/// 3. the registration is received on the mpsc receiver end
#[fasync::run_singlethreaded(test)]
async fn test_wait_for_registration_success() {
// Immitate opening a connection with the DriverManagerRegistration service
let (sender, receiver) = mpsc::channel(1);
let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<
fpowermanager::DriverManagerRegistrationMarker,
>()
.unwrap();
handle_new_driver_manager_registration_stream(stream, sender);
// Send the register request and verify it is successful
let (transition_client, _) =
fidl::endpoints::create_endpoints::<fdevicemgr::SystemStateTransitionMarker>().unwrap();
let (dir_client, _) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>().unwrap();
assert!(proxy.register(transition_client, dir_client).await.unwrap().is_ok());
// Verify the registration was received
assert!(wait_for_registration(None, receiver, futures::stream::pending()).await.is_ok());
}
/// Tests that the timeout functionality of the `wait_for_registration` works as expected by
/// returning an error after the registration is not received after the expected time.
#[test]
fn test_wait_for_registration_timeout() {
let mut exec = fasync::TestExecutor::new_with_fake_time().unwrap();
exec.set_fake_time(fasync::Time::from_nanos(0));
let (_sender, receiver) = mpsc::channel(1);
let mut wait_future =
wait_for_registration(Some(Seconds(1.0)), receiver, futures::stream::pending()).boxed();
// Try to run the wait_future so that the timer becomes active
assert!(exec.run_until_stalled(&mut wait_future).is_pending());
// Force the timeout to expire and verify it would've fired as the expected time
assert_eq!(exec.wake_next_timer(), Some(fasync::Time::from_nanos(1e9 as i64)));
// Verify the `wait_for_registration` call returns an error
assert_matches!(
exec.run_until_stalled(&mut wait_future),
futures::task::Poll::Ready(Err(_))
);
}
/// Tests that sending a register request with invalid handles results in an error returned to
/// the client.
#[test]
fn test_registration_invalid_handles() {
let mut exec = fasync::TestExecutor::new().unwrap();
// Immitate opening a connection with the DriverManagerRegistration service
let (sender, receiver) = mpsc::channel(1);
let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<
fpowermanager::DriverManagerRegistrationMarker,
>()
.unwrap();
handle_new_driver_manager_registration_stream(stream, sender);
// Set up the fake registration using an invalid directory handle
let transition_client =
fidl::endpoints::create_endpoints::<fdevicemgr::SystemStateTransitionMarker>()
.unwrap()
.0;
let dir_client =
fidl::endpoints::ClientEnd::<fio::DirectoryMarker>::from(zx::Handle::invalid());
// Verify the register request returns an error
assert!(exec.run_singlethreaded(proxy.register(transition_client, dir_client)).is_err());
// Verify the `wait_for_registration` is still pending waiting for valid registration
assert!(exec
.run_until_stalled(
&mut wait_for_registration(None, receiver, futures::stream::pending()).boxed()
)
.is_pending());
}
/// Tests that the proxy closure monitor fires after the underlying channel is closed
#[test]
fn test_termination_channel_closure() {
let mut exec = fasync::TestExecutor::new().unwrap();
let channel_closed = Rc::new(Cell::new(false));
let channel_closed_clone = channel_closed.clone();
let (termination_state_proxy, termination_state_server) =
fidl::endpoints::create_proxy::<fdevicemgr::SystemStateTransitionMarker>().unwrap();
let registration = DriverManagerRegistration {
termination_state_proxy,
dir: fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap().0,
};
let _node = exec
.run_singlethreaded(
DriverManagerHandlerBuilder::new()
.with_driver_manager_registration(registration)
.with_termination_channel_closed_handler(Box::new(move || {
channel_closed_clone.set(true)
}))
.build(),
)
.unwrap();
// Drop the server end to close the channel
drop(termination_state_server);
// Lets the pending monitor future run
assert!(exec.run_until_stalled(&mut future::pending::<()>()).is_pending());
// Verify the channel close handler was run
assert_eq!(channel_closed.get(), true);
}
/// Tests the driver directory binding functionality by setting up the DriverManagerHandler node
/// with a Directory channel that the node will then bind to the namespace. The test connects to
/// a driver in the namespace at "/dev", which will be routed to the provided Directory channel.
/// The connection is tested to be valid by verifying a message is able to successfully be
/// passed through the channel.
#[fasync::run_singlethreaded(test)]
async fn test_connect_to_driver() {
use vfs::{directory::entry::DirectoryEntry, pseudo_directory};
// Set up a fake directory structure that contains a fake driver at class/fake.
//
// This fake driver was chosen to implement fuchsia.device.manager.SystemStateTransition and
// responds to SetTerminationSystemState requests. This protocol was chosen simply because
// the code already has a dependency on it and it can be easily used to verify the FIDL
// channel is set up properly.
let fake_devfs = pseudo_directory! {
"class" => pseudo_directory! {
"fake" => vfs::service::host(move |mut stream: fdevicemgr::SystemStateTransitionRequestStream| {
async move {
match stream.try_next().await.unwrap() {
Some(fdevicemgr::SystemStateTransitionRequest::SetTerminationSystemState {
state: _, responder
}) => {
let _ = responder.send(&mut Ok(()));
}
e => panic!("Unexpected request: {:?}", e),
}
}
})
}
};
// Connect a directory channel to the pseudo directory
let (devfs_proxy, devfs_server) = fidl::endpoints::create_proxy().unwrap();
fake_devfs.open(
vfs::execution_scope::ExecutionScope::new(),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
fio::MODE_TYPE_DIRECTORY,
vfs::path::Path::dot(),
devfs_server,
);
let registration = DriverManagerRegistration {
termination_state_proxy: setup_fake_termination_state_service(|_| Ok(())),
dir: fio::DirectoryProxy::from_channel(devfs_proxy.into_channel().unwrap()),
};
let _node = DriverManagerHandlerBuilder::new()
.with_driver_manager_registration(registration)
.build()
.await
.unwrap();
// Verify we can make a FIDL call to the fake driver and get a successful response
crate::utils::connect_to_driver::<fdevicemgr::SystemStateTransitionMarker>(
&"/dev/class/fake".to_string(),
)
.await
.expect("Failed to connect to driver")
.set_termination_system_state(fpowerstatecontrol::SystemPowerState::Reboot)
.await
.expect("set_termination_system_state FIDL failed")
.expect("set_termination_system_state returned error");
}
/// Tests that the DriverManagerHandler correctly processes the SetTerminationState message by
/// calling out to the Driver Manager using the termination state proxy.
#[fasync::run_singlethreaded(test)]
async fn test_set_termination_state() {
let termination_state = Rc::new(Cell::new(fpowerstatecontrol::SystemPowerState::FullyOn));
let termination_state_clone = termination_state.clone();
let registration = DriverManagerRegistration {
termination_state_proxy: setup_fake_termination_state_service(move |state| {
termination_state_clone.set(state);
Ok(())
}),
dir: fidl::endpoints::create_proxy_and_stream::<fio::DirectoryMarker>().unwrap().0,
};
let node = DriverManagerHandlerBuilder::new()
.with_driver_manager_registration(registration)
.build()
.await
.unwrap();
// Send the message and verify it returns successfully
assert_matches!(
node.handle_message(&Message::SetTerminationSystemState(
fpowerstatecontrol::SystemPowerState::Reboot
))
.await,
Ok(MessageReturn::SetTerminationSystemState)
);
// Verify the fake termination state service received the correct request
assert_eq!(termination_state.get(), fpowerstatecontrol::SystemPowerState::Reboot);
}
/// Tests for correct ordering of nodes within each available node config file. The
/// test verifies that if the DriverManagerHandler node is present in the config file, then it
/// is listed before any other nodes that require a driver connection (identified as a node that
/// contains a string config key called "driver_path").
#[test]
pub fn test_config_files() -> Result<(), anyhow::Error> {
crate::utils::test_each_node_config_file(|config_file| {
let driver_manager_handler_index =
config_file.iter().position(|config| config["type"] == "DriverManagerHandler");
let first_node_using_drivers_index =
config_file.iter().position(|config| config["config"].get("driver_path").is_some());
if driver_manager_handler_index.is_some()
&& first_node_using_drivers_index.is_some()
&& driver_manager_handler_index.unwrap() > first_node_using_drivers_index.unwrap()
{
return Err(format_err!(
"Must list DriverManagerHandler node before {}",
config_file[first_node_using_drivers_index.unwrap()]["name"]
));
}
Ok(())
})
}
}