watchdog_vmgs_format/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! The watchdog state VMGS data format, as used in Hyper-V.
5//!
6//! At the moment, the format is dead simple: it's just a single byte - either a
7//! 1 or a 0 - tha represents if the previous boot failed.
8
9#![forbid(unsafe_code)]
10
11use thiserror::Error;
12use vmcore::non_volatile_store::NonVolatileStore;
13
14/// Watchdog VMGS formatted buffer contained invalid data.
15#[derive(Debug, Error)]
16#[error("expected single byte (0 or 1), got {0} bytes, starting with {1:?}")]
17pub struct InvalidFormatError(usize, Option<u8>);
18
19/// Data format used to persist watchdog state to VMGS.
20struct WatchdogVmgsFormat {
21    boot_failure: bool,
22}
23
24impl WatchdogVmgsFormat {
25    /// Return a new instance [`WatchdogVmgsFormat`].
26    fn new() -> Self {
27        Self {
28            boot_failure: false,
29        }
30    }
31
32    /// Update existing existing instance of [`WatchdogVmgsFormat`] with the data
33    /// stored in the provided buffer.
34    fn update_from_slice(&mut self, buf: &[u8]) -> Result<(), InvalidFormatError> {
35        let boot_status = match buf {
36            [] => return Err(InvalidFormatError(0, None)),
37            [0] => false,
38            [1] => true,
39            [other, ..] => return Err(InvalidFormatError(buf.len(), Some(*other))),
40        };
41
42        self.boot_failure = boot_status;
43
44        Ok(())
45    }
46
47    /// Return a slice to persist to VMGS.
48    fn as_slice(&self) -> &[u8] {
49        if self.boot_failure { &[1] } else { &[0] }
50    }
51}
52
53/// Errors which may occur as part of [`WatchdogVmgsFormatStore`]
54/// operations.
55#[derive(Debug, Error)]
56#[expect(missing_docs)] // self-explanatory variants
57pub enum WatchdogVmgsFormatStoreError {
58    #[error("could not access non-volatile store")]
59    NonVolatileStoreAccessError(#[source] vmcore::non_volatile_store::NonVolatileStoreError),
60    #[error("invalid data pull from non-volatile store")]
61    InvalidFormat(#[source] InvalidFormatError),
62}
63
64/// Persist and restore watchdog data into a [`NonVolatileStore`] using the VMGS
65/// watchdog data format.
66pub struct WatchdogVmgsFormatStore {
67    store: Box<dyn NonVolatileStore>,
68    state: WatchdogVmgsFormat,
69}
70
71impl WatchdogVmgsFormatStore {
72    /// Construct a new instance of [`WatchdogVmgsFormatStore`], populated
73    /// with data from the provided store.
74    pub async fn new(
75        mut store: Box<dyn NonVolatileStore>,
76    ) -> Result<Self, WatchdogVmgsFormatStoreError> {
77        use WatchdogVmgsFormatStoreError as Error;
78
79        let buf = store
80            .restore()
81            .await
82            .map_err(Error::NonVolatileStoreAccessError)?;
83
84        let mut state = WatchdogVmgsFormat::new();
85
86        if let Some(buf) = buf {
87            state
88                .update_from_slice(&buf)
89                .map_err(Error::InvalidFormat)?;
90        }
91
92        Ok(Self { store, state })
93    }
94
95    async fn flush(&mut self) -> Result<(), WatchdogVmgsFormatStoreError> {
96        use WatchdogVmgsFormatStoreError as Error;
97
98        self.store
99            .persist(self.state.as_slice().to_vec())
100            .await
101            .map_err(Error::NonVolatileStoreAccessError)?;
102
103        Ok(())
104    }
105
106    /// Enable the boot status flag.
107    pub async fn set_boot_failure(&mut self) -> Result<(), WatchdogVmgsFormatStoreError> {
108        let prev = self.state.boot_failure;
109        self.state.boot_failure = true;
110
111        // only flush if data has changed
112        if !prev {
113            self.flush().await?;
114        }
115
116        Ok(())
117    }
118
119    /// Read the boot status flag, returning it's value, while simultaneously
120    /// resetting it.
121    pub async fn read_and_clear_boot_status(
122        &mut self,
123    ) -> Result<bool, WatchdogVmgsFormatStoreError> {
124        let prev = self.state.boot_failure;
125        self.state.boot_failure = false;
126
127        // only flush if data has changed
128        if prev {
129            self.flush().await?;
130        }
131
132        Ok(prev)
133    }
134}