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