Skip to main content

recovery_util/
reboot.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::Error;
6use async_trait::async_trait;
7use fidl_fuchsia_hardware_power_statecontrol as powercontrol;
8use fuchsia_async as fasync;
9use fuchsia_component::client::connect_to_protocol;
10#[cfg(test)]
11use mockall::automock;
12use zx::{MonotonicDuration, Status as zx_status};
13
14#[cfg_attr(test, automock)]
15#[async_trait(?Send)]
16pub trait RebootHandler {
17    /// Request a reboot with optional delay in seconds. This is currently not cancellable and does not return an error result.
18    /// The caller will be responsible for handling which thread to schedule this request on.
19    async fn reboot(&self, delay_seconds: Option<u64>) -> Result<(), Error>;
20}
21
22#[derive(Default)]
23pub struct RebootImpl;
24
25impl RebootImpl {
26    async fn request_reboot_with_proxy(
27        &self,
28        delay_seconds: Option<u64>,
29        proxy: powercontrol::AdminProxy,
30    ) -> Result<(), Error> {
31        println!("Rebooting after {:?} seconds...", delay_seconds.unwrap_or(0));
32
33        if let Some(delay) = delay_seconds {
34            fasync::Timer::new(fasync::MonotonicInstant::after(MonotonicDuration::from_seconds(
35                delay.try_into()?,
36            )))
37            .await;
38        }
39
40        // TODO(b/239569913): Update with a recovery-specific reboot reason.
41        proxy
42            .shutdown(&powercontrol::ShutdownOptions {
43                action: Some(powercontrol::ShutdownAction::Reboot),
44                reasons: Some(vec![powercontrol::ShutdownReason::FactoryDataReset]),
45                ..Default::default()
46            })
47            .await?
48            .map_err(zx_status::err_from_raw)?;
49        Ok(())
50    }
51}
52
53#[async_trait(?Send)]
54impl RebootHandler for RebootImpl {
55    async fn reboot(&self, delay_seconds: Option<u64>) -> Result<(), Error> {
56        let proxy = connect_to_protocol::<powercontrol::AdminMarker>()?;
57        self.request_reboot_with_proxy(delay_seconds, proxy).await
58    }
59}
60
61#[cfg(test)]
62mod test {
63    use super::*;
64    use fidl_fuchsia_hardware_power_statecontrol as powercontrol;
65    use fidl_fuchsia_hardware_power_statecontrol::{
66        ShutdownAction, ShutdownOptions, ShutdownReason,
67    };
68    use fuchsia_async as fasync;
69    use fuchsia_async::TimeoutExt;
70    use futures::channel::mpsc;
71    use futures::{StreamExt, TryStreamExt};
72
73    // Reboot tests - this functionality is only exercised in recovery OTA flows.
74    fn create_mock_powercontrol_server()
75    -> Result<(powercontrol::AdminProxy, mpsc::Receiver<powercontrol::ShutdownOptions>), Error>
76    {
77        let (mut sender, receiver) = mpsc::channel(1);
78        let (proxy, mut request_stream) =
79            fidl::endpoints::create_proxy_and_stream::<powercontrol::AdminMarker>();
80
81        fasync::Task::local(async move {
82            while let Some(request) =
83                request_stream.try_next().await.expect("failed to read mock request")
84            {
85                match request {
86                    powercontrol::AdminRequest::Shutdown { options, responder } => {
87                        sender.start_send(options).unwrap();
88                        let result: powercontrol::AdminShutdownResult = { Ok(()) };
89                        responder.send(result).ok();
90                    }
91                    _ => {
92                        panic!("Mock server not configured to handle request");
93                    }
94                }
95            }
96        })
97        .detach();
98
99        Ok((proxy, receiver))
100    }
101
102    #[fuchsia::test]
103    async fn test_reboot_reason_no_delay() {
104        let (proxy, mut receiver) = create_mock_powercontrol_server().unwrap();
105
106        let reboot = RebootImpl::default();
107        reboot.request_reboot_with_proxy(None, proxy).await.unwrap();
108
109        let options =
110            receiver.next().on_timeout(MonotonicDuration::from_seconds(5), || None).await.unwrap();
111
112        assert_eq!(
113            options,
114            ShutdownOptions {
115                action: Some(ShutdownAction::Reboot),
116                reasons: Some(vec![ShutdownReason::FactoryDataReset]),
117                ..Default::default()
118            }
119        );
120    }
121
122    #[fuchsia::test]
123    async fn test_reboot_with_delay() {
124        let delay_seconds = 1;
125        let (proxy, mut receiver) = create_mock_powercontrol_server().unwrap();
126
127        let start_time = fasync::MonotonicInstant::now();
128        let reboot = RebootImpl::default();
129        reboot.request_reboot_with_proxy(Some(delay_seconds), proxy).await.unwrap();
130
131        let options =
132            receiver.next().on_timeout(MonotonicDuration::from_seconds(5), || None).await.unwrap();
133
134        let end_time = fasync::MonotonicInstant::now();
135
136        assert!((end_time - start_time).into_seconds() >= delay_seconds.try_into().unwrap());
137        assert_eq!(
138            options,
139            ShutdownOptions {
140                action: Some(ShutdownAction::Reboot),
141                reasons: Some(vec![ShutdownReason::FactoryDataReset]),
142                ..Default::default()
143            }
144        );
145    }
146}