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