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 as fpower;
7
8/// The reasons of a reboot.
9///
10/// This type acts as a witness that the provided reasons are valid (i.e. at
11/// least one reason was provided).
12#[derive(Debug, PartialEq, PartialOrd, Clone)]
13pub struct RebootReasons(pub Vec<fpower::RebootReason2>);
14
15impl RebootReasons {
16    /// Construct a new `RebootReasons` with the given reason.
17    pub fn new(reason: fpower::RebootReason2) -> Self {
18        Self(vec![reason])
19    }
20
21    /// Construct a new `RebootReasons` from the given deprecated
22    /// `RebootReason`.
23    // TODO(https://fxbug.dev/385742868): Remove this function once
24    // `RebootReason` is removed from the API.
25    pub(crate) fn from_deprecated(reason: &fpower::RebootReason) -> Self {
26        let reason = match reason {
27            fpower::RebootReason::UserRequest => fpower::RebootReason2::UserRequest,
28            fpower::RebootReason::SystemUpdate => fpower::RebootReason2::SystemUpdate,
29            fpower::RebootReason::RetrySystemUpdate => fpower::RebootReason2::RetrySystemUpdate,
30            fpower::RebootReason::HighTemperature => fpower::RebootReason2::HighTemperature,
31            fpower::RebootReason::FactoryDataReset => fpower::RebootReason2::FactoryDataReset,
32            fpower::RebootReason::SessionFailure => fpower::RebootReason2::SessionFailure,
33            fpower::RebootReason::SysmgrFailure => fpower::RebootReason2::SysmgrFailure,
34            fpower::RebootReason::CriticalComponentFailure => {
35                fpower::RebootReason2::CriticalComponentFailure
36            }
37            fpower::RebootReason::ZbiSwap => fpower::RebootReason2::ZbiSwap,
38            fpower::RebootReason::OutOfMemory => fpower::RebootReason2::OutOfMemory,
39        };
40        Self::new(reason)
41    }
42
43    /// Convert this set of `RebootReasons` into a deprecated `RebootReason`.
44    /// It's a backwards compatible implementation.
45    /// * If multiple `RebootReason2` are provided, prefer reasons with an
46    ///   equivalent deprecated `RebootReason` representation.
47    /// * Then, if multiple reasons are provided, prefer the first.
48    /// * Then, if the reason has no equivalent deprecated `RebootReason`, do a
49    ///   best-effort translation.
50    // TODO(https://fxbug.dev/385742868): Remove this function once
51    // `RebootReason` is removed from the API.
52    pub(crate) fn to_deprecated(&self) -> fpower::RebootReason {
53        enum FoldState {
54            Direct(fpower::RebootReason),
55            Indirect(fpower::RebootReason),
56            None,
57        }
58        let state = self.0.iter().fold(FoldState::None, |state, reason| {
59            match (&state, &reason) {
60                // We already have a direct state; keep it.
61                (FoldState::Direct(_), _) => state,
62                // For reasons that have a direct backwards translation, use it.
63                (_, fpower::RebootReason2::UserRequest) => {
64                    FoldState::Direct(fpower::RebootReason::UserRequest)
65                }
66                (_, fpower::RebootReason2::SystemUpdate) => {
67                    FoldState::Direct(fpower::RebootReason::SystemUpdate)
68                }
69                (_, fpower::RebootReason2::RetrySystemUpdate) => {
70                    FoldState::Direct(fpower::RebootReason::RetrySystemUpdate)
71                }
72                (_, fpower::RebootReason2::HighTemperature) => {
73                    FoldState::Direct(fpower::RebootReason::HighTemperature)
74                }
75                (_, fpower::RebootReason2::FactoryDataReset) => {
76                    FoldState::Direct(fpower::RebootReason::FactoryDataReset)
77                }
78                (_, fpower::RebootReason2::SessionFailure) => {
79                    FoldState::Direct(fpower::RebootReason::SessionFailure)
80                }
81                (_, fpower::RebootReason2::SysmgrFailure) => {
82                    FoldState::Direct(fpower::RebootReason::SysmgrFailure)
83                }
84                (_, fpower::RebootReason2::CriticalComponentFailure) => {
85                    FoldState::Direct(fpower::RebootReason::CriticalComponentFailure)
86                }
87                (_, fpower::RebootReason2::ZbiSwap) => {
88                    FoldState::Direct(fpower::RebootReason::ZbiSwap)
89                }
90                (_, fpower::RebootReason2::OutOfMemory) => {
91                    FoldState::Direct(fpower::RebootReason::OutOfMemory)
92                }
93                (_, fpower::RebootReason2::AndroidUnexpectedReason) => {
94                    FoldState::Direct(fpower::RebootReason::UserRequest)
95                }
96                (_, fpower::RebootReason2::DeveloperRequest) => {
97                    FoldState::Direct(fpower::RebootReason::UserRequest)
98                }
99                // If we already have an indirect reason, don't overwrite it
100                // with a new indirect reason.
101                (FoldState::Indirect(_), fpower::RebootReason2::NetstackMigration) => state,
102                // Translate `NetstackMigration` to `SystemUpdate`.
103                (FoldState::None, fpower::RebootReason2::NetstackMigration) => {
104                    FoldState::Indirect(fpower::RebootReason::SystemUpdate)
105                }
106                (_, fpower::RebootReason2::__SourceBreaking { unknown_ordinal: _ }) => {
107                    unreachable!()
108                }
109            }
110        });
111        match state {
112            FoldState::Direct(reason) | FoldState::Indirect(reason) => reason,
113            FoldState::None => {
114                unreachable!("RebootReasons is guaranteed to have at least 1 reason.")
115            }
116        }
117    }
118}
119
120impl AsRef<Vec<fpower::RebootReason2>> for RebootReasons {
121    fn as_ref(&self) -> &Vec<fpower::RebootReason2> {
122        &self.0
123    }
124}
125
126impl From<RebootReasons> for fpower::RebootOptions {
127    fn from(RebootReasons(reasons): RebootReasons) -> Self {
128        fpower::RebootOptions { reasons: Some(reasons), __source_breaking: SourceBreaking }
129    }
130}
131
132/// The reasons a `fpower::RebootOptions` may be invalid.
133#[derive(Debug, PartialEq)]
134pub enum InvalidRebootOptions {
135    /// No reasons were provided.
136    NoReasons,
137}
138
139impl TryFrom<fpower::RebootOptions> for RebootReasons {
140    type Error = InvalidRebootOptions;
141    fn try_from(options: fpower::RebootOptions) -> Result<Self, Self::Error> {
142        let fpower::RebootOptions { reasons, __source_breaking } = options;
143        if let Some(reasons) = reasons {
144            if !reasons.is_empty() {
145                return Ok(RebootReasons(reasons));
146            }
147        }
148
149        Err(InvalidRebootOptions::NoReasons)
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use test_case::test_case;
157
158    #[test_case(None => Err(InvalidRebootOptions::NoReasons); "no_reasons")]
159    #[test_case(Some(vec![]) => Err(InvalidRebootOptions::NoReasons); "empty_reasons")]
160    #[test_case(Some(vec![fpower::RebootReason2::UserRequest]) => Ok(()); "success")]
161    fn reboot_reasons(
162        reasons: Option<Vec<fpower::RebootReason2>>,
163    ) -> Result<(), InvalidRebootOptions> {
164        let options = fpower::RebootOptions { reasons, __source_breaking: SourceBreaking };
165        RebootReasons::try_from(options).map(|_reasons| {})
166    }
167
168    #[test_case(
169        vec![fpower::RebootReason2::UserRequest, fpower::RebootReason2::SystemUpdate] =>
170        fpower::RebootReason::UserRequest;
171        "prefer_first_a")]
172    #[test_case(
173        vec![fpower::RebootReason2::SystemUpdate, fpower::RebootReason2::UserRequest] =>
174        fpower::RebootReason::SystemUpdate;
175        "prefer_first_b")]
176    #[test_case(
177        vec![fpower::RebootReason2::NetstackMigration, fpower::RebootReason2::UserRequest] =>
178        fpower::RebootReason::UserRequest;
179        "prefer_direct")]
180    #[test_case(
181        vec![fpower::RebootReason2::NetstackMigration] =>
182        fpower::RebootReason::SystemUpdate;
183        "netstack_migration")]
184    fn reasons_to_deprecated(reasons: Vec<fpower::RebootReason2>) -> fpower::RebootReason {
185        let options =
186            fpower::RebootOptions { reasons: Some(reasons), __source_breaking: SourceBreaking };
187        RebootReasons::try_from(options).unwrap().to_deprecated()
188    }
189}