1 // Copyright (C) 2024  Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use super::BootToken;
16 use zerocopy::{AsBytes, ByteSlice, FromBytes, FromZeroes, Ref};
17 
18 /// Tracks whether slot metadata differs from on-disk representation.
19 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
20 pub enum CacheStatus {
21     /// Slot metadata is the same as on disk
22     Clean,
23     /// Slot metadata has been modified
24     Dirty,
25 }
26 
27 /// Custom error type
28 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
29 pub enum MetadataParseError {
30     /// The magic number field was corrupted
31     BadMagic,
32     /// The version of the structure is unsupported
33     BadVersion,
34     /// The struct checksum check failed
35     BadChecksum,
36     /// The deserialization buffer is too small
37     BufferTooSmall,
38 }
39 
40 /// Trait that describes the operations all slot metadata implementations must support
41 /// to be used as the backing store in a SlotBlock.
42 pub trait MetadataBytes: Copy + AsBytes + FromBytes + FromZeroes + Default {
43     /// Returns a zerocopy reference to Self if buffer
44     /// represents a valid serialization of Self.
45     /// Implementors should check for invariants,
46     /// e.g. checksums, magic numbers, and version numbers.
47     ///
48     /// Returns Err if the buffer does not represent a valid structure.
validate<B: ByteSlice>(buffer: B) -> Result<Ref<B, Self>, MetadataParseError>49     fn validate<B: ByteSlice>(buffer: B) -> Result<Ref<B, Self>, MetadataParseError>;
50 
51     /// Called right before writing metadata back to disk.
52     /// Implementors should restore invariants,
53     /// update checksums, or take other appropriate actions.
prepare_for_sync(&mut self)54     fn prepare_for_sync(&mut self);
55 }
56 
57 /// Generalized description of a partition-backed ABR metadata structure.
58 pub struct SlotBlock<'a, MB: MetadataBytes> {
59     /// The partition the metadata was read from and will be written back to.
60     pub partition: &'a str,
61     /// The offset from the beginning of the partition in bytes.
62     pub partition_offset: u64,
63 
64     // Internally tracked cache clean/dirty info
65     cache_status: CacheStatus,
66     // SlotBlock holds the boot token until mark_boot_attempt gets called.
67     boot_token: Option<BootToken>,
68     // Serialized slot metadata
69     data: MB,
70 }
71 
72 impl<'a, MB: MetadataBytes> SlotBlock<'a, MB> {
73     /// Note to those implementing Manager for SlotBlock<'_, CustomType>:
74     /// Be very, very careful with custody of the boot token.
75     /// If you release it outside of the implementation of Manager::mark_boot_attempt,
76     /// mark_boot_attempt will fail and the kernel may boot without tracking the attempt.
77     /// If you lose the token, the only way to get it back is to reboot the device.
take_boot_token(&mut self) -> Option<BootToken>78     pub fn take_boot_token(&mut self) -> Option<BootToken> {
79         self.boot_token.take()
80     }
81 
82     /// Returns a mutable reference to the slot metadata and marks the cache as dirty.
get_mut_data(&mut self) -> &mut MB83     pub fn get_mut_data(&mut self) -> &mut MB {
84         self.cache_status = CacheStatus::Dirty;
85         &mut self.data
86     }
87 
88     /// Returns an immutable reference to the slot metadata
get_data(&self) -> &MB89     pub fn get_data(&self) -> &MB {
90         &self.data
91     }
92 
93     #[cfg(test)]
94     /// Returns the cache status
cache_status(&self) -> CacheStatus95     pub fn cache_status(&self) -> CacheStatus {
96         self.cache_status
97     }
98 
99     /// Attempt to deserialize a slot control block
100     ///
101     /// # Returns
102     /// * `SlotBlock` - returns either the deserialized
103     ///                 representation of the slot control block
104     ///                 OR a fresh, default valued slot control block
105     ///                 if there was an internal error.
106     ///
107     ///                 TODO(b/329116902): errors are logged
deserialize<B: ByteSlice>( buffer: B, partition: &'a str, partition_offset: u64, boot_token: BootToken, ) -> Self108     pub fn deserialize<B: ByteSlice>(
109         buffer: B,
110         partition: &'a str,
111         partition_offset: u64,
112         boot_token: BootToken,
113     ) -> Self {
114         // TODO(b/329116902): log failures
115         // validate(buffer)
116         // .inspect_err(|e| {
117         //     eprintln!("ABR metadata failed verification, using metadata defaults: {e}")
118         // })
119         let (data, cache_status) = match MB::validate(buffer) {
120             Ok(data) => (*data, CacheStatus::Clean),
121             Err(_) => (Default::default(), CacheStatus::Dirty),
122         };
123 
124         SlotBlock { cache_status, boot_token: Some(boot_token), data, partition, partition_offset }
125     }
126 
127     /// Write back slot metadata to disk.
128     /// The MetadataBytes type should reestablish any invariants when
129     /// `prepare_for_sync` is called, e.g. recalculating checksums.
130     ///
131     /// Does NOT write back to disk if no changes have been made and the cache is clean.
132     /// Panics if the write attempt fails.
sync_to_disk<BlockDev: gbl_storage::AsBlockDevice>(&mut self, block_dev: &mut BlockDev)133     pub fn sync_to_disk<BlockDev: gbl_storage::AsBlockDevice>(&mut self, block_dev: &mut BlockDev) {
134         if self.cache_status == CacheStatus::Clean {
135             return;
136         }
137 
138         self.data.prepare_for_sync();
139 
140         match block_dev.write_gpt_partition(
141             self.partition,
142             self.partition_offset,
143             self.get_mut_data().as_bytes_mut(),
144         ) {
145             Ok(_) => self.cache_status = CacheStatus::Clean,
146             Err(e) => panic!("{}", e),
147         };
148     }
149 }
150 
151 #[cfg(test)]
152 impl<MB: MetadataBytes> Default for SlotBlock<'_, MB> {
153     /// Returns a default valued SlotBlock.
154     /// Only used in tests because BootToken cannot be constructed out of crate.
default() -> Self155     fn default() -> Self {
156         Self {
157             partition: "",
158             partition_offset: 0,
159             cache_status: CacheStatus::Clean,
160             boot_token: Some(BootToken(())),
161             data: Default::default(),
162         }
163     }
164 }
165