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}