vmcore/
save_restore.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Traits and types for save/restore support.
5//!
6//! To implement save/restore for your device or state unit, you must do these
7//! things:
8//!
9//! 1. You define a saved state type. This type needs to be stable across
10//!    releases, so you should not share types with your implementation even if
11//!    they are currently identical. By decoupling your runtime types and your
12//!    saved state types, you are much less likely to accidentally break saved
13//!    state compatibility.
14//!
15//! 2. `derive` some traits on your type. Specifically, you must derive
16//!    [`Protobuf`] so that your type can be encoded into protobuf format, and
17//!    any fields in your type must also implement [`Protobuf`].
18//!
19//!    So that your saved state type can show up in generated `.proto` files,
20//!    you must also set an attribute `#[mesh(package = "my.package.name")]`.
21//!    This specifies the protobuf package, as well as the file name of your
22//!    protobuf file. For the root object of your device's saved state, this
23//!    package name becomes part of the serialized state, so you cannot change
24//!    it later.
25//!
26//!    Finally, in the root object of your saved state, you must additionally
27//!    derive [`SavedStateRoot`]. This provides some additional metadata so that
28//!    we can find your saved state type and generate a `.proto` file from it
29//!    for analysis. You only need to put this on the root types or types that
30//!    will be converted to [`SavedStateBlob`]; the infrastructure will find any
31//!    dependent types. But it doesn't hurt to put it on other types, too.
32//!
33//! 3. Typically, you implement the [`SaveRestore`] trait. This trait allows you
34//!    to specify your associated saved state type and to implement `save` and
35//!    `restore` methods that act on this type.
36//!
37//!    For some device types (such as vmbus devices), you may need to use a
38//!    device-specific trait that provides additional parameters. But the
39//!    pattern should be the same.
40
41#![warn(missing_docs)]
42
43/// Derives [`SavedStateRoot`] for a type.
44///
45/// This ensures that a saved state blob's metadata can be found, so that it can
46/// be used to generate .proto files and perform offline analysis of saved state
47/// compatibility.
48///
49/// To use this, you must also derive [`Protobuf`] and set a protobuf package
50/// for your type. The package name should be defined to group related types
51/// together; typically the same package should be used for all types defined in
52/// a module.
53///
54/// For example:
55///
56/// ```rust
57/// # use vmcore::save_restore::{SavedStateRoot, SavedStateBlob};
58/// # use mesh::payload::Protobuf;
59/// #[derive(Protobuf, SavedStateRoot)]
60/// #[mesh(package = "test.my_device")]
61/// struct MySavedState {
62///     #[mesh(1)]
63///     active: bool,
64/// }
65///
66/// // This will now compile.
67/// let _blob = SavedStateBlob::new(MySavedState { active: true });
68/// ```
69pub use save_restore_derive::SavedStateRoot;
70
71use mesh::payload;
72use mesh::payload::DefaultEncoding;
73use mesh::payload::DescribedProtobuf;
74use mesh::payload::Protobuf;
75use mesh::payload::encoding::ImpossibleField;
76use mesh::payload::message::ProtobufAny;
77use mesh::payload::protofile::MessageDescription;
78
79/// Implemented by objects which can be saved/restored
80pub trait SaveRestore {
81    /// The concrete saved state type.
82    type SavedState;
83
84    /// Saves the object's state.
85    fn save(&mut self) -> Result<Self::SavedState, SaveError>;
86    /// Restores the object's state.
87    fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError>;
88}
89
90/// Convenience type for objects that do not support being saved.
91pub enum SavedStateNotSupported {}
92
93impl DefaultEncoding for SavedStateNotSupported {
94    type Encoding = ImpossibleField;
95}
96
97impl SavedStateRoot for SavedStateNotSupported {
98    // This type should not be included in the .proto output.
99    fn do_not_impl_this_manually(&self) {}
100}
101
102/// Convenience type for objects that have no saved state.
103#[derive(Protobuf, SavedStateRoot)]
104#[mesh(package = "save_restore")]
105pub struct NoSavedState;
106
107/// Trait implemented by objects that implement `SaveRestore` with an associated
108/// type that can be serialized as a protobuf message.
109pub trait ProtobufSaveRestore {
110    /// Save the object.
111    fn save(&mut self) -> Result<SavedStateBlob, SaveError>;
112    /// Restore the object.
113    fn restore(&mut self, state: SavedStateBlob) -> Result<(), RestoreError>;
114}
115
116/// An opaque saved state blob, encoded as a protobuf message.
117#[derive(Debug, Protobuf)]
118#[mesh(transparent)]
119pub struct SavedStateBlob(ProtobufAny);
120
121/// Trait implemented by "root" saved state blobs, which are ones that either
122/// form the root of a saved state tree, or at points in the tree where the type
123/// is not known at compile time.
124///
125/// **Do not implement this trait manually.** Derive it with
126/// `#[derive(SavedStateRoot)]`. This emits extra code to ensure that the saved
127/// state type metadata is included in the binary.
128pub trait SavedStateRoot: DescribedProtobuf {
129    #[doc(hidden)]
130    fn do_not_impl_this_manually(&self);
131}
132
133impl SavedStateBlob {
134    /// Encodes `data` as a protobuf message.
135    pub fn new<T: SavedStateRoot>(data: T) -> Self {
136        Self(ProtobufAny::new(data))
137    }
138
139    /// Decodes the protobuf message into `T`.
140    pub fn parse<T: SavedStateRoot>(&self) -> Result<T, payload::Error> {
141        self.0.parse()
142    }
143}
144
145impl<T: SaveRestore> ProtobufSaveRestore for T
146where
147    T::SavedState: 'static + Send + SavedStateRoot,
148{
149    fn save(&mut self) -> Result<SavedStateBlob, SaveError> {
150        self.save().map(SavedStateBlob::new)
151    }
152
153    fn restore(&mut self, state: SavedStateBlob) -> Result<(), RestoreError> {
154        self.restore(state.parse()?)
155    }
156}
157
158/// A restore error.
159#[derive(Debug, thiserror::Error)]
160pub enum RestoreError {
161    /// unknown entry ID
162    #[error("unknown entry id: {0}")]
163    UnknownEntryId(String),
164    /// restore failure in a child object
165    #[error("failed to restore child device {0}")]
166    ChildError(String, #[source] Box<RestoreError>),
167    /// failure to decode a protobuf object
168    #[error("failed to decode protobuf")]
169    ProtobufDecode(#[from] payload::Error),
170    /// this object does not support save state
171    #[error("unexpected saved state")]
172    SavedStateNotSupported,
173    /// custom saved state corruption error
174    #[error("saved state is invalid")]
175    InvalidSavedState(#[source] anyhow::Error),
176    /// non-state-related restore failure
177    #[error(transparent)]
178    Other(anyhow::Error),
179}
180
181/// A save error.
182#[derive(Debug, thiserror::Error)]
183pub enum SaveError {
184    /// This object does not support saved state.
185    #[error("save state not supported")]
186    NotSupported,
187    /// Save failed in child object.
188    #[error("failed to save child device {0}")]
189    ChildError(String, #[source] Box<SaveError>),
190    /// Save failed due to some other error.
191    #[error(transparent)]
192    Other(anyhow::Error),
193    /// The child saved state is invalid.
194    #[error("child saved state is invalid")]
195    InvalidChildSavedState(#[source] anyhow::Error),
196}
197
198/// A save operation error.
199#[derive(Debug, thiserror::Error)]
200pub enum CollectError {
201    /// some save results are missing
202    #[error("failed to receive all save results")]
203    MissingResults,
204    /// got more save results than expected
205    #[error("received more results than expected")]
206    TooManyResults,
207    /// a save payload is corrupted
208    #[error("received bad payload")]
209    BadPayload(#[source] payload::Error),
210}
211
212/// Gets the message descriptions for all types deriving [`SavedStateRoot`].
213///
214/// This can be used with
215/// [`DescriptorWriter`](mesh::payload::protofile::DescriptorWriter) to write
216/// `.proto` files for the saved states.
217pub fn saved_state_roots() -> impl Iterator<Item = &'static MessageDescription<'static>> {
218    private::SAVED_STATE_ROOTS.iter().flatten().copied()
219}
220
221// For `save_restore_derive`
222#[doc(hidden)]
223pub mod private {
224    pub use linkme;
225    pub use mesh::payload::protofile;
226
227    // Use Option<&X> in case the linker inserts some stray nulls, as we think
228    // it might on Windows.
229    //
230    // See <https://devblogs.microsoft.com/oldnewthing/20181108-00/?p=100165>.
231    #[linkme::distributed_slice]
232    pub static SAVED_STATE_ROOTS: [Option<&'static protofile::MessageDescription<'static>>] = [..];
233
234    // Always have at least one entry to work around linker bugs.
235    //
236    // See <https://github.com/llvm/llvm-project/issues/65855>.
237    #[linkme::distributed_slice(SAVED_STATE_ROOTS)]
238    static WORKAROUND: Option<&'static protofile::MessageDescription<'static>> = None;
239
240    #[doc(hidden)]
241    #[macro_export]
242    macro_rules! declare_saved_state_root {
243        ($ident:ty) => {
244            impl $crate::save_restore::SavedStateRoot for $ident {
245                fn do_not_impl_this_manually(&self) {}
246            }
247            const _: () = {
248                use $crate::save_restore::private::SAVED_STATE_ROOTS;
249                use $crate::save_restore::private::linkme;
250                use $crate::save_restore::private::protofile;
251
252                #[linkme::distributed_slice(SAVED_STATE_ROOTS)]
253                #[linkme(crate = linkme)]
254                static DESCRIPTION: Option<&'static protofile::MessageDescription<'static>> =
255                    Some(&protofile::message_description::<$ident>());
256            };
257        };
258    }
259}