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