shutdown_shim/
reboot_reasons.rs

1// Copyright 2020 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 fidl::marker::SourceBreaking;
6use fidl_fuchsia_hardware_power_statecontrol::{
7    self as fpower, RebootReason2, ShutdownAction, ShutdownOptions, ShutdownReason,
8};
9
10/// The action and reasons of a shutdown.
11///
12/// This type provides translation functions for supporting deprecated enums.
13// TODO(https://fxbug.dev/414413282): This type may not be necessary once `RebootReason2` is removed
14// from the API.
15#[derive(Debug, PartialEq, PartialOrd, Clone)]
16pub struct ShutdownOptionsWrapper {
17    pub action: ShutdownAction,
18    pub reasons: Vec<ShutdownReason>,
19}
20
21impl ShutdownOptionsWrapper {
22    /// Construct a new `ShutdownOptionsWrapper` with the given reason.
23    pub fn new(action: fpower::ShutdownAction, reason: ShutdownReason) -> Self {
24        Self { action, reasons: vec![reason] }
25    }
26
27    /// Construct a new `ShutdownOptionsWrapper` from the given deprecated
28    /// `RebootReason`.
29    // TODO(https://fxbug.dev/385742868): Remove this function once
30    // `RebootReason` is removed from the API.
31    pub(crate) fn from_reboot_reason_deprecated(reason: &fpower::RebootReason) -> Self {
32        let reason = match reason {
33            fpower::RebootReason::UserRequest => fpower::ShutdownReason::UserRequest,
34            fpower::RebootReason::SystemUpdate => fpower::ShutdownReason::SystemUpdate,
35            fpower::RebootReason::RetrySystemUpdate => fpower::ShutdownReason::RetrySystemUpdate,
36            fpower::RebootReason::HighTemperature => fpower::ShutdownReason::HighTemperature,
37            fpower::RebootReason::FactoryDataReset => fpower::ShutdownReason::FactoryDataReset,
38            fpower::RebootReason::SessionFailure => fpower::ShutdownReason::SessionFailure,
39            fpower::RebootReason::SysmgrFailure => {
40                // sysmgr doesn't exist anymore.
41                println!(
42                    "[shutdown-shim]: error, unexpectedly received RebootReason::SysmgrFailure"
43                );
44                fpower::ShutdownReason::unknown()
45            }
46            fpower::RebootReason::CriticalComponentFailure => {
47                fpower::ShutdownReason::CriticalComponentFailure
48            }
49            fpower::RebootReason::ZbiSwap => fpower::ShutdownReason::ZbiSwap,
50            fpower::RebootReason::OutOfMemory => fpower::ShutdownReason::OutOfMemory,
51        };
52        Self::new(fpower::ShutdownAction::Reboot, reason)
53    }
54
55    /// Construct a new `ShutdownOptionsWrapper` from the given Vec of deprecated `RebootReason2`.
56    pub(crate) fn from_reboot_reason2_deprecated(reasons: &Vec<RebootReason2>) -> Self {
57        let reasons = reasons
58            .iter()
59            .map(|reason| match reason {
60                RebootReason2::UserRequest => ShutdownReason::UserRequest,
61                RebootReason2::DeveloperRequest => ShutdownReason::DeveloperRequest,
62                RebootReason2::SystemUpdate => ShutdownReason::SystemUpdate,
63                RebootReason2::RetrySystemUpdate => ShutdownReason::RetrySystemUpdate,
64                RebootReason2::HighTemperature => ShutdownReason::HighTemperature,
65                RebootReason2::FactoryDataReset => ShutdownReason::FactoryDataReset,
66                RebootReason2::SessionFailure => ShutdownReason::SessionFailure,
67                RebootReason2::SysmgrFailure => {
68                    // sysmgr doesn't exist anymore.
69                    println!(
70                        "[shutdown-shim]: error, unexpectedly received RebootReason2::SysmgrFailure"
71                    );
72                    fpower::ShutdownReason::unknown()
73                }
74                RebootReason2::CriticalComponentFailure => ShutdownReason::CriticalComponentFailure,
75                RebootReason2::ZbiSwap => ShutdownReason::ZbiSwap,
76                RebootReason2::OutOfMemory => ShutdownReason::OutOfMemory,
77                RebootReason2::NetstackMigration => ShutdownReason::NetstackMigration,
78                RebootReason2::AndroidUnexpectedReason => ShutdownReason::AndroidUnexpectedReason,
79                RebootReason2::AndroidRescueParty => ShutdownReason::AndroidRescueParty,
80                RebootReason2::AndroidCriticalProcessFailure => {
81                    ShutdownReason::AndroidCriticalProcessFailure
82                }
83                RebootReason2::__SourceBreaking { unknown_ordinal } => {
84                    println!("[shutdown-shim]: error, unrecognized RebootReason2 ordinal: {unknown_ordinal}");
85                    ShutdownReason::unknown()
86                }
87            })
88            .collect();
89        Self { action: ShutdownAction::Reboot, reasons }
90    }
91
92    /// Convert into a deprecated `RebootReason`. It's a backwards compatible implementation.
93    /// * If multiple `ShutdownReason` are provided, prefer reasons with an
94    ///   equivalent deprecated `RebootReason` representation.
95    /// * Then, if multiple reasons are provided, prefer the first.
96    /// * Then, if the reason has no equivalent deprecated `RebootReason`, do a
97    ///   best-effort translation.
98    // TODO(https://fxbug.dev/385742868): Remove this function once
99    // `RebootReason` is removed from the API.
100    pub(crate) fn to_reboot_reason_deprecated(&self) -> fpower::RebootReason {
101        enum FoldState {
102            Direct(fpower::RebootReason),
103            Indirect(fpower::RebootReason),
104            None,
105        }
106        let state = self.reasons.iter().fold(FoldState::None, |state, reason| {
107            match (&state, &reason) {
108                // We already have a direct state; keep it.
109                (FoldState::Direct(_), _) => state,
110                // For reasons that have a direct backwards translation, use it.
111                (_, fpower::ShutdownReason::UserRequest) => {
112                    FoldState::Direct(fpower::RebootReason::UserRequest)
113                }
114                (_, fpower::ShutdownReason::SystemUpdate) => {
115                    FoldState::Direct(fpower::RebootReason::SystemUpdate)
116                }
117                (_, fpower::ShutdownReason::RetrySystemUpdate) => {
118                    FoldState::Direct(fpower::RebootReason::RetrySystemUpdate)
119                }
120                (_, fpower::ShutdownReason::HighTemperature) => {
121                    FoldState::Direct(fpower::RebootReason::HighTemperature)
122                }
123                (_, fpower::ShutdownReason::FactoryDataReset) => {
124                    FoldState::Direct(fpower::RebootReason::FactoryDataReset)
125                }
126                (_, fpower::ShutdownReason::SessionFailure) => {
127                    FoldState::Direct(fpower::RebootReason::SessionFailure)
128                }
129                (_, fpower::ShutdownReason::CriticalComponentFailure) => {
130                    FoldState::Direct(fpower::RebootReason::CriticalComponentFailure)
131                }
132                (_, fpower::ShutdownReason::ZbiSwap) => {
133                    FoldState::Direct(fpower::RebootReason::ZbiSwap)
134                }
135                (_, fpower::ShutdownReason::OutOfMemory) => {
136                    FoldState::Direct(fpower::RebootReason::OutOfMemory)
137                }
138                (_, fpower::ShutdownReason::AndroidUnexpectedReason) => {
139                    FoldState::Direct(fpower::RebootReason::UserRequest)
140                }
141                (_, fpower::ShutdownReason::AndroidRescueParty) => {
142                    FoldState::Direct(fpower::RebootReason::UserRequest)
143                }
144                (_, fpower::ShutdownReason::AndroidCriticalProcessFailure) => {
145                    FoldState::Direct(fpower::RebootReason::UserRequest)
146                }
147                (_, fpower::ShutdownReason::DeveloperRequest) => {
148                    FoldState::Direct(fpower::RebootReason::UserRequest)
149                }
150                // If we already have an indirect reason, don't overwrite it
151                // with a new indirect reason.
152                (FoldState::Indirect(_), fpower::ShutdownReason::NetstackMigration) => state,
153                // Translate `NetstackMigration` to `SystemUpdate`.
154                (FoldState::None, fpower::ShutdownReason::NetstackMigration) => {
155                    FoldState::Indirect(fpower::RebootReason::SystemUpdate)
156                }
157                (_, fpower::ShutdownReason::__SourceBreaking { unknown_ordinal: _ }) => {
158                    unreachable!()
159                }
160            }
161        });
162        match state {
163            FoldState::Direct(reason) | FoldState::Indirect(reason) => reason,
164            FoldState::None => {
165                unreachable!("Called to_reboot_reason with no reason(s) specified")
166            }
167        }
168    }
169
170    /// Convert into a vector of deprecated `RebootReason2`. It's a backwards compatible
171    /// implementation. If the reason has no equivalent deprecated `RebootReason2`, do a best-effort
172    /// translation.
173    pub(crate) fn to_reboot_reason2_deprecated(&self) -> Vec<RebootReason2> {
174        self.reasons
175            .iter()
176            .map(|item| match item {
177                ShutdownReason::UserRequest => RebootReason2::UserRequest,
178                ShutdownReason::DeveloperRequest => RebootReason2::DeveloperRequest,
179                ShutdownReason::SystemUpdate => RebootReason2::SystemUpdate,
180                ShutdownReason::RetrySystemUpdate => RebootReason2::RetrySystemUpdate,
181                ShutdownReason::HighTemperature => RebootReason2::HighTemperature,
182                ShutdownReason::FactoryDataReset => RebootReason2::FactoryDataReset,
183                ShutdownReason::SessionFailure => RebootReason2::SessionFailure,
184                ShutdownReason::CriticalComponentFailure => RebootReason2::CriticalComponentFailure,
185                ShutdownReason::ZbiSwap => RebootReason2::ZbiSwap,
186                ShutdownReason::OutOfMemory => RebootReason2::OutOfMemory,
187                ShutdownReason::NetstackMigration => RebootReason2::NetstackMigration,
188                ShutdownReason::AndroidUnexpectedReason => RebootReason2::AndroidUnexpectedReason,
189                ShutdownReason::AndroidRescueParty => RebootReason2::AndroidRescueParty,
190                ShutdownReason::AndroidCriticalProcessFailure => {
191                    RebootReason2::AndroidCriticalProcessFailure
192                }
193                ShutdownReason::__SourceBreaking { unknown_ordinal } => {
194                    println!("[shutdown-shim]: error, unrecognized ShutdownReason ordinal: {unknown_ordinal}");
195                    RebootReason2::unknown()
196                }
197            })
198            .collect()
199    }
200}
201
202impl From<ShutdownOptionsWrapper> for fpower::RebootOptions {
203    fn from(options: ShutdownOptionsWrapper) -> Self {
204        fpower::RebootOptions {
205            reasons: Some(options.to_reboot_reason2_deprecated()),
206            __source_breaking: SourceBreaking,
207        }
208    }
209}
210
211impl From<ShutdownOptionsWrapper> for ShutdownOptions {
212    fn from(options: ShutdownOptionsWrapper) -> Self {
213        ShutdownOptions {
214            action: Some(options.action),
215            reasons: Some(options.reasons),
216            __source_breaking: SourceBreaking,
217        }
218    }
219}
220
221/// The reasons a `fpower::RebootOptions` may be invalid.
222#[derive(Debug, PartialEq)]
223pub enum InvalidRebootOptions {
224    /// No reasons were provided.
225    NoReasons,
226}
227
228impl TryFrom<fpower::RebootOptions> for ShutdownOptionsWrapper {
229    type Error = InvalidRebootOptions;
230    fn try_from(options: fpower::RebootOptions) -> Result<Self, Self::Error> {
231        let fpower::RebootOptions { reasons, __source_breaking } = options;
232        if let Some(reasons) = reasons {
233            if !reasons.is_empty() {
234                return Ok(ShutdownOptionsWrapper::from_reboot_reason2_deprecated(&reasons));
235            }
236        }
237
238        Err(InvalidRebootOptions::NoReasons)
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use test_case::test_case;
246
247    #[test_case(None => Err(InvalidRebootOptions::NoReasons); "no_reasons")]
248    #[test_case(Some(vec![]) => Err(InvalidRebootOptions::NoReasons); "empty_reasons")]
249    #[test_case(Some(vec![fpower::RebootReason2::UserRequest]) => Ok(()); "success")]
250    fn reboot_reasons(
251        reasons: Option<Vec<fpower::RebootReason2>>,
252    ) -> Result<(), InvalidRebootOptions> {
253        let options = fpower::RebootOptions { reasons, __source_breaking: SourceBreaking };
254        ShutdownOptionsWrapper::try_from(options).map(|_reasons| {})
255    }
256
257    #[test_case(
258        vec![fpower::RebootReason2::UserRequest, fpower::RebootReason2::SystemUpdate] =>
259        fpower::RebootReason::UserRequest;
260        "prefer_first_a")]
261    #[test_case(
262        vec![fpower::RebootReason2::SystemUpdate, fpower::RebootReason2::UserRequest] =>
263        fpower::RebootReason::SystemUpdate;
264        "prefer_first_b")]
265    #[test_case(
266        vec![fpower::RebootReason2::NetstackMigration, fpower::RebootReason2::UserRequest] =>
267        fpower::RebootReason::UserRequest;
268        "prefer_direct")]
269    #[test_case(
270        vec![fpower::RebootReason2::NetstackMigration] =>
271        fpower::RebootReason::SystemUpdate;
272        "netstack_migration")]
273    fn reasons_to_deprecated(reasons: Vec<fpower::RebootReason2>) -> fpower::RebootReason {
274        let options =
275            fpower::RebootOptions { reasons: Some(reasons), __source_breaking: SourceBreaking };
276        ShutdownOptionsWrapper::try_from(options).unwrap().to_reboot_reason_deprecated()
277    }
278}