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                // If we already have an indirect reason, don't overwrite it
97                // with a new indirect reason.
98                (FoldState::Indirect(_), fpower::RebootReason2::NetstackMigration) => state,
99                // Translate `NetstackMigration` to `SystemUpdate`.
100                (FoldState::None, fpower::RebootReason2::NetstackMigration) => {
101                    FoldState::Indirect(fpower::RebootReason::SystemUpdate)
102                }
103                (_, fpower::RebootReason2::__SourceBreaking { unknown_ordinal: _ }) => {
104                    unreachable!()
105                }
106            }
107        });
108        match state {
109            FoldState::Direct(reason) | FoldState::Indirect(reason) => reason,
110            FoldState::None => {
111                unreachable!("RebootReasons is guaranteed to have at least 1 reason.")
112            }
113        }
114    }
115}
116
117impl AsRef<Vec<fpower::RebootReason2>> for RebootReasons {
118    fn as_ref(&self) -> &Vec<fpower::RebootReason2> {
119        &self.0
120    }
121}
122
123impl From<RebootReasons> for fpower::RebootOptions {
124    fn from(RebootReasons(reasons): RebootReasons) -> Self {
125        fpower::RebootOptions { reasons: Some(reasons), __source_breaking: SourceBreaking }
126    }
127}
128
129/// The reasons a `fpower::RebootOptions` may be invalid.
130#[derive(Debug, PartialEq)]
131pub enum InvalidRebootOptions {
132    /// No reasons were provided.
133    NoReasons,
134}
135
136impl TryFrom<fpower::RebootOptions> for RebootReasons {
137    type Error = InvalidRebootOptions;
138    fn try_from(options: fpower::RebootOptions) -> Result<Self, Self::Error> {
139        let fpower::RebootOptions { reasons, __source_breaking } = options;
140        if let Some(reasons) = reasons {
141            if !reasons.is_empty() {
142                return Ok(RebootReasons(reasons));
143            }
144        }
145
146        Err(InvalidRebootOptions::NoReasons)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use test_case::test_case;
154
155    #[test_case(None => Err(InvalidRebootOptions::NoReasons); "no_reasons")]
156    #[test_case(Some(vec![]) => Err(InvalidRebootOptions::NoReasons); "empty_reasons")]
157    #[test_case(Some(vec![fpower::RebootReason2::UserRequest]) => Ok(()); "success")]
158    fn reboot_reasons(
159        reasons: Option<Vec<fpower::RebootReason2>>,
160    ) -> Result<(), InvalidRebootOptions> {
161        let options = fpower::RebootOptions { reasons, __source_breaking: SourceBreaking };
162        RebootReasons::try_from(options).map(|_reasons| {})
163    }
164
165    #[test_case(
166        vec![fpower::RebootReason2::UserRequest, fpower::RebootReason2::SystemUpdate] =>
167        fpower::RebootReason::UserRequest;
168        "prefer_first_a")]
169    #[test_case(
170        vec![fpower::RebootReason2::SystemUpdate, fpower::RebootReason2::UserRequest] =>
171        fpower::RebootReason::SystemUpdate;
172        "prefer_first_b")]
173    #[test_case(
174        vec![fpower::RebootReason2::NetstackMigration, fpower::RebootReason2::UserRequest] =>
175        fpower::RebootReason::UserRequest;
176        "prefer_direct")]
177    #[test_case(
178        vec![fpower::RebootReason2::NetstackMigration] =>
179        fpower::RebootReason::SystemUpdate;
180        "netstack_migration")]
181    fn reasons_to_deprecated(reasons: Vec<fpower::RebootReason2>) -> fpower::RebootReason {
182        let options =
183            fpower::RebootOptions { reasons: Some(reasons), __source_breaking: SourceBreaking };
184        RebootReasons::try_from(options).unwrap().to_deprecated()
185    }
186}