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 use core::cell::RefCell;
15 use crc32fast::Hasher;
16 pub use gbl_storage::{
17     alignment_scratch_size, is_aligned, is_buffer_aligned, required_scratch_size, AsBlockDevice,
18     AsMultiBlockDevices, BlockDeviceEx, BlockInfo, BlockIo, BlockIoError, GptEntry, GptHeader,
19     IoStatus, NonBlockingBlockIo, GPT_MAGIC, GPT_NAME_LEN_U16,
20 };
21 use safemath::SafeNum;
22 use std::collections::BTreeMap;
23 use zerocopy::AsBytes;
24 
25 // Declares a per-thread global instance of timestamp. The timestamp is used to control the
26 // execution of non-blocking IO.
27 thread_local! {
28     static TIMESTAMP: RefCell<u64> = RefCell::new(Default::default());
29     /// Number of `TimestampPauser` in effect.
30     static NUM_TIMESTAMP_PAUSER: RefCell<u64> = RefCell::new(Default::default());
31 }
32 
33 /// Increases the value of timestamp.
advance_timestamp()34 pub fn advance_timestamp() {
35     TIMESTAMP.with(|ts| (*ts.borrow_mut()) += 1);
36 }
37 
38 /// Queries the current value of timestamp.
query_timestamp() -> u6439 pub fn query_timestamp() -> u64 {
40     NUM_TIMESTAMP_PAUSER.with(|v| (*v.borrow() == 0).then(|| advance_timestamp()));
41     TIMESTAMP.with(|ts| *ts.borrow())
42 }
43 
44 /// When a `TimestampPauser` is in scope, timestamp will not be increased by query.
45 pub struct TimestampPauser {}
46 
47 impl Drop for TimestampPauser {
drop(&mut self)48     fn drop(&mut self) {
49         NUM_TIMESTAMP_PAUSER.with(|v| *v.borrow_mut() -= 1);
50     }
51 }
52 
53 impl TimestampPauser {
54     /// Creates a new instance to pause the timestamp.
new() -> Self55     pub fn new() -> Self {
56         NUM_TIMESTAMP_PAUSER.with(|v| *v.borrow_mut() += 1);
57         Self {}
58     }
59 
60     /// Consumes the pauser, causing it to go out of scope. When all pausers go out of scope,
61     /// timestamp resumes.
resume(self)62     pub fn resume(self) {}
63 }
64 
65 /// `NonBlockingIoState` tracks the non-blocking IO state.
66 enum NonBlockingIoState {
67     // (timestamp when initiated, blk offset, buffer, is read)
68     Pending(u64, u64, &'static mut [u8], bool),
69     Ready(IoStatus),
70 }
71 
72 /// Helper `gbl_storage::BlockIo` struct for TestBlockDevice.
73 pub struct TestBlockIo {
74     /// The storage block size in bytes.
75     pub block_size: u64,
76     /// The storage access alignment in bytes.
77     pub alignment: u64,
78     /// The backing storage data.
79     pub storage: Vec<u8>,
80     /// The number of successful write calls.
81     pub num_writes: usize,
82     /// The number of successful read calls.
83     pub num_reads: usize,
84     /// Pending non-blocking IO
85     io: Option<NonBlockingIoState>,
86 }
87 
88 impl TestBlockIo {
new(block_size: u64, alignment: u64, data: Vec<u8>) -> Self89     pub fn new(block_size: u64, alignment: u64, data: Vec<u8>) -> Self {
90         Self { block_size, alignment, storage: data, num_writes: 0, num_reads: 0, io: None }
91     }
92 
check_alignment(&mut self, buffer: &[u8]) -> bool93     fn check_alignment(&mut self, buffer: &[u8]) -> bool {
94         matches!(is_buffer_aligned(buffer, self.alignment()), Ok(true))
95             && matches!(is_aligned(buffer.len().into(), self.block_size().into()), Ok(true))
96     }
97 }
98 
99 impl BlockIo for TestBlockIo {
info(&mut self) -> BlockInfo100     fn info(&mut self) -> BlockInfo {
101         NonBlockingBlockIo::info(self)
102     }
103 
read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> Result<(), BlockIoError>104     fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> Result<(), BlockIoError> {
105         // `BlockIo` is implemented for `&mut dyn NonBlockingBlockIo`
106         BlockIo::read_blocks(&mut (self as &mut dyn NonBlockingBlockIo), blk_offset, out)
107     }
108 
write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<(), BlockIoError>109     fn write_blocks(&mut self, blk_offset: u64, data: &mut [u8]) -> Result<(), BlockIoError> {
110         BlockIo::write_blocks(&mut (self as &mut dyn NonBlockingBlockIo), blk_offset, data)
111     }
112 }
113 
114 // SAFETY:
115 // * When `TestBlockIo::io` is `Some(NonBlockingIoState(Pending(_, _, buffer, _)))`,
116 //   `check_status()` always returns `IoStatus::Pending`. `check_status()` returns other `IoStatus`
117 //   values if and only if `TestBlockIo::io` is not `Some(NonBlockingIoState(Pending())`, in which
118 //   case the buffer is not tracked anymore and thus will not be retained again.
119 // * `Self::check_status()` does not dereference the input pointer.
120 // * `TestBlockIo::io` is set to `Some(NonBlockingIoState(Pending(_, _, buffer, _)))` and retains
121 //   the buffer only on success (returning Ok(())).
122 unsafe impl NonBlockingBlockIo for TestBlockIo {
123     /// Returns a `BlockInfo` for the block device.
info(&mut self) -> BlockInfo124     fn info(&mut self) -> BlockInfo {
125         BlockInfo {
126             block_size: self.block_size,
127             num_blocks: u64::try_from(self.storage.len()).unwrap() / self.block_size,
128             alignment: self.alignment,
129         }
130     }
131 
write_blocks( &mut self, blk_offset: u64, buffer: *mut [u8], ) -> core::result::Result<(), BlockIoError>132     unsafe fn write_blocks(
133         &mut self,
134         blk_offset: u64,
135         buffer: *mut [u8],
136     ) -> core::result::Result<(), BlockIoError> {
137         match self.io {
138             Some(_) => Err(BlockIoError::MediaBusy),
139             _ => {
140                 self.num_writes += 1;
141                 // SAFETY: By safety requirement, trait implementation can retain the buffer until
142                 // it no longer returns `IoStatus::Pending` in `Self::check_status()`.
143                 let buffer = unsafe { &mut *buffer };
144                 assert!(self.check_alignment(buffer));
145                 self.io =
146                     Some(NonBlockingIoState::Pending(query_timestamp(), blk_offset, buffer, false));
147                 Ok(())
148             }
149         }
150     }
151 
read_blocks( &mut self, blk_offset: u64, buffer: *mut [u8], ) -> core::result::Result<(), BlockIoError>152     unsafe fn read_blocks(
153         &mut self,
154         blk_offset: u64,
155         buffer: *mut [u8],
156     ) -> core::result::Result<(), BlockIoError> {
157         match self.io {
158             Some(_) => Err(BlockIoError::MediaBusy),
159             _ => {
160                 self.num_reads += 1;
161                 // SAFETY: By safety requirement, trait implementation can retain the buffer until
162                 // it no longer returns `IoStatus::Pending` in `Self::check_status()`.
163                 let buffer = unsafe { &mut *buffer };
164                 assert!(self.check_alignment(buffer));
165                 self.io =
166                     Some(NonBlockingIoState::Pending(query_timestamp(), blk_offset, buffer, true));
167                 Ok(())
168             }
169         }
170     }
171 
check_status(&mut self, buf: *mut [u8]) -> IoStatus172     fn check_status(&mut self, buf: *mut [u8]) -> IoStatus {
173         match self.io.as_mut() {
174             Some(NonBlockingIoState::Pending(ts, blk_offset, ref mut buffer, is_read))
175                 if std::ptr::eq(*buffer as *const [u8], buf as _) =>
176             {
177                 // Executes the IO if current timestamp is newer.
178                 if query_timestamp() > *ts {
179                     let offset = (SafeNum::from(*blk_offset) * self.block_size).try_into().unwrap();
180                     match is_read {
181                         true => buffer.clone_from_slice(&self.storage[offset..][..buffer.len()]),
182                         _ => self.storage[offset..][..buffer.len()].clone_from_slice(buffer),
183                     }
184                     self.io = Some(NonBlockingIoState::Ready(IoStatus::Completed));
185                 }
186                 IoStatus::Pending
187             }
188             Some(NonBlockingIoState::Ready(v)) => {
189                 let res = *v;
190                 self.io.take();
191                 res
192             }
193             _ => IoStatus::NotFound,
194         }
195     }
196 
abort(&mut self) -> core::result::Result<(), BlockIoError>197     fn abort(&mut self) -> core::result::Result<(), BlockIoError> {
198         match self.io {
199             Some(NonBlockingIoState::Pending(_, _, _, _)) => {
200                 self.io = Some(NonBlockingIoState::Ready(IoStatus::Aborted));
201             }
202             _ => {}
203         }
204         Ok(())
205     }
206 }
207 
208 /// Simple RAM based block device used by unit tests.
209 pub struct TestBlockDevice {
210     /// The BlockIo helper struct.
211     pub io: TestBlockIo,
212     /// In-memory backing store.
213     pub scratch: Vec<u8>,
214     max_gpt_entries: u64,
215 }
216 
217 impl TestBlockDevice {
as_block_device_ex(&mut self) -> BlockDeviceEx218     pub fn as_block_device_ex(&mut self) -> BlockDeviceEx {
219         BlockDeviceEx::new((&mut self.io as &mut dyn NonBlockingBlockIo).into())
220     }
221 }
222 
223 impl From<&[u8]> for TestBlockDevice {
from(data: &[u8]) -> Self224     fn from(data: &[u8]) -> Self {
225         TestBlockDeviceBuilder::new().set_data(data).build()
226     }
227 }
228 
229 impl AsBlockDevice for TestBlockDevice {
with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIo, &mut [u8], u64))230     fn with(&mut self, f: &mut dyn FnMut(&mut dyn BlockIo, &mut [u8], u64)) {
231         f(&mut self.io, &mut self.scratch[..], self.max_gpt_entries)
232     }
233 }
234 
235 impl Default for TestBlockDevice {
default() -> Self236     fn default() -> Self {
237         TestBlockDeviceBuilder::new().build()
238     }
239 }
240 
241 /// A description of the backing data store for a block device or partition.
242 /// Can either describe explicit data the device or partition is initialized with
243 /// OR a size in bytes if the device or partition can be initialized in a blank state.
244 #[derive(Copy, Clone)]
245 pub enum BackingStore<'a> {
246     Data(&'a [u8]),
247     Size(usize),
248 }
249 
250 impl<'a> BackingStore<'a> {
size(&self) -> usize251     fn size(&self) -> usize {
252         match self {
253             Self::Data(slice) => slice.len(),
254             Self::Size(size) => *size,
255         }
256     }
257 }
258 
259 enum DiskDescription<'a> {
260     Disk(BackingStore<'a>),
261     Partitions(BTreeMap<&'static str, BackingStore<'a>>),
262 }
263 
264 /// Builder struct for TestBlockDevice.
265 /// Most tests will want either:
266 /// 1) A blank device of a reasonable size OR
267 /// 2) A device with specific initial data.
268 /// Other customizations include block size,
269 /// the maximum number of GPT entries,
270 /// the alignment requirements,
271 /// and the size of the scratch buffer.
272 ///
273 /// Note: setting the storage size or storage data is generally safe,
274 ///       as long as the backing store is large enough,
275 ///       but customizing other attributes may generate a block device
276 ///       that cannot successfully complete any operations.
277 ///       This may be exactly the intention, but be warned that it can be tricky
278 ///       to customize the device and generate something that works without errors.
279 pub struct TestBlockDeviceBuilder<'a> {
280     block_size: u64,
281     max_gpt_entries: u64,
282     alignment: u64,
283     disk_description: DiskDescription<'a>,
284     scratch_size: Option<usize>,
285 }
286 
287 impl<'a> TestBlockDeviceBuilder<'a> {
288     /// The default access alignment in bytes.
289     pub const DEFAULT_ALIGNMENT: u64 = 64;
290     /// The default block size in bytes.
291     pub const DEFAULT_BLOCK_SIZE: u64 = 512;
292     /// The default maximum number of GPT entries.
293     pub const MAX_GPT_ENTRIES: u64 = 128;
294 
295     /// Creates a new TestBlockDeviceBuilder with defaults for all attributes.
new() -> Self296     pub fn new() -> Self {
297         Self {
298             block_size: Self::DEFAULT_BLOCK_SIZE,
299             max_gpt_entries: Self::MAX_GPT_ENTRIES,
300             alignment: Self::DEFAULT_ALIGNMENT,
301             disk_description: DiskDescription::Disk(BackingStore::Size(
302                 (Self::DEFAULT_BLOCK_SIZE * 32) as usize,
303             )),
304             scratch_size: None,
305         }
306     }
307 
308     /// Set the block size of the block device in bytes.
309     /// The default is `DEFAULT_BLOCK_SIZE`.
set_block_size(mut self, block_size: u64) -> Self310     pub fn set_block_size(mut self, block_size: u64) -> Self {
311         self.block_size = block_size;
312         self
313     }
314 
315     /// Set the maximum number of GPT entries for the GPT header.
316     /// The default is `MAX_GPT_ENTRIES`.
317     /// Note: setting too large a number of entries will make a device
318     ///       that fails to sync its GPT.
set_max_gpt_entries(mut self, max_gpt_entries: u64) -> Self319     pub fn set_max_gpt_entries(mut self, max_gpt_entries: u64) -> Self {
320         self.max_gpt_entries = max_gpt_entries;
321         self
322     }
323 
324     /// Set the required alignment for the TestBlockDevice.
325     /// An alignment of `0` means there are no alignment requirements.
326     /// The default is `DEFAULT_ALIGNMENT`.
set_alignment(mut self, alignment: u64) -> Self327     pub fn set_alignment(mut self, alignment: u64) -> Self {
328         self.alignment = alignment;
329         self
330     }
331 
332     /// Set the size of TestBlockDevice in bytes.
333     /// When built, the TestBlockDevice will have a blank backing store of size `size`.
334     /// The default is `DEFAULT_BLOCK_SIZE` * 32.
335     ///
336     /// Note: This option is mutually exclusive with `set_data` and `add_partition`.
337     ///       If `set_data` or `add_partition` have been called, `set_size` overrides
338     ///       those customizations.
set_size(mut self, size: usize) -> Self339     pub fn set_size(mut self, size: usize) -> Self {
340         self.disk_description = DiskDescription::Disk(BackingStore::Size(size));
341         self
342     }
343 
344     /// Sets the block device's backing data to the provided slice.
345     ///
346     /// Note: This option is mutually exclusive with `set_size` and `add_partition`.
347     ///       If `set_size` or `add_partition` have been called, `set_data` overrides
348     ///       those customizations.
set_data(mut self, data: &'a [u8]) -> Self349     pub fn set_data(mut self, data: &'a [u8]) -> Self {
350         self.disk_description = DiskDescription::Disk(BackingStore::Data(data));
351         self
352     }
353 
354     /// Adds a partition description.
355     /// Partitions can be defined either with a specific backing store
356     /// from a slice OR from a specific size in bytes.
357     /// Partition sizes are rounded up to full blocks.
358     /// If the same partition name is added multiple times,
359     /// the last definition is used.
360     ///
361     /// Note: explicitly added partitions are mutually exclusive with
362     ///       `set_size` and `set_data`.
363     ///       If either have been called, `add_partition` overrides that customization.
add_partition(mut self, name: &'static str, backing: BackingStore<'a>) -> Self364     pub fn add_partition(mut self, name: &'static str, backing: BackingStore<'a>) -> Self {
365         match self.disk_description {
366             DiskDescription::Disk(_) => {
367                 let mut map = BTreeMap::new();
368                 map.insert(name, backing);
369                 self.disk_description = DiskDescription::Partitions(map);
370             }
371             DiskDescription::Partitions(ref mut map) => {
372                 map.insert(name, backing);
373             }
374         };
375         self
376     }
377 
378     /// Customize the size of the block device's scratch buffer.
379     /// The default size is a known safe minimum calculated when `build()` is called.
380     ///
381     /// Note: Too small a scratch buffer will generate errors.
382     ///       Unless a test is specifically interested in a non-default
383     ///       scratch size, it's better to rely on the default size.
set_scratch_size(mut self, scratch_size: usize) -> Self384     pub fn set_scratch_size(mut self, scratch_size: usize) -> Self {
385         self.scratch_size = Some(scratch_size);
386         self
387     }
388 
389     /// Consumes the builder and generates a TestBlockDevice
390     /// with the desired customizations.
build(self) -> TestBlockDevice391     pub fn build(self) -> TestBlockDevice {
392         let storage = match self.disk_description {
393             DiskDescription::Disk(BackingStore::Data(slice)) => Vec::from(slice),
394             DiskDescription::Disk(BackingStore::Size(size)) => vec![0u8; size],
395             DiskDescription::Partitions(partitions) => {
396                 partitions_to_disk_data(&partitions, self.block_size as usize)
397             }
398         };
399         assert!(storage.len() % (self.block_size as usize) == 0);
400         let mut io = TestBlockIo::new(self.block_size, self.alignment, storage);
401         let scratch_size = match self.scratch_size {
402             Some(s) => s,
403             None => required_scratch_size(&mut io, self.max_gpt_entries).unwrap(),
404         };
405         TestBlockDevice {
406             io,
407             scratch: vec![0u8; scratch_size],
408             max_gpt_entries: self.max_gpt_entries,
409         }
410     }
411 }
412 
str_to_utf16_entry_name(name: &str) -> [u16; GPT_NAME_LEN_U16]413 fn str_to_utf16_entry_name(name: &str) -> [u16; GPT_NAME_LEN_U16] {
414     assert!(name.len() < GPT_NAME_LEN_U16);
415     let mut data = [0; GPT_NAME_LEN_U16];
416     let tmp: Vec<u16> = name.encode_utf16().collect();
417     for (d, t) in std::iter::zip(data.iter_mut(), tmp) {
418         *d = t;
419     }
420     data
421 }
422 
pad_to_block_size(store: &mut Vec<u8>, block_size: usize)423 fn pad_to_block_size(store: &mut Vec<u8>, block_size: usize) {
424     let delta = (block_size - store.len() % block_size) % block_size;
425     for _ in 0..delta {
426         store.push(0);
427     }
428 }
429 
add_blocks(store: &mut Vec<u8>, data: &[u8], block_size: usize)430 fn add_blocks(store: &mut Vec<u8>, data: &[u8], block_size: usize) {
431     store.extend(data.iter());
432     pad_to_block_size(store, block_size);
433 }
434 
pad_bytes(store: &mut Vec<u8>, size: usize, block_size: usize)435 fn pad_bytes(store: &mut Vec<u8>, size: usize, block_size: usize) {
436     for _ in 0..size {
437         store.push(0);
438     }
439     pad_to_block_size(store, block_size);
440 }
441 
partitions_to_disk_data( partitions: &BTreeMap<&'static str, BackingStore>, block_size: usize, ) -> Vec<u8>442 fn partitions_to_disk_data(
443     partitions: &BTreeMap<&'static str, BackingStore>,
444     block_size: usize,
445 ) -> Vec<u8> {
446     let gpt_max_entries = 128;
447     assert!(partitions.len() <= gpt_max_entries);
448     let entry_blocks: u64 = ((SafeNum::from(partitions.len()) * std::mem::size_of::<GptEntry>())
449         .round_up(block_size)
450         / block_size)
451         .try_into()
452         .unwrap();
453     let mut block = entry_blocks
454         + 1  // Protective MBR
455         + 1 // Primary GPT header
456         ;
457     // Leading mbr
458     let mut store = vec![0; block_size];
459     let mut header = GptHeader {
460         magic: GPT_MAGIC,
461         current: 1,
462         size: std::mem::size_of::<GptHeader>() as u32,
463         first: block,
464         entries: 2,
465         entries_count: std::cmp::min(partitions.len(), gpt_max_entries) as u32,
466         entries_size: std::mem::size_of::<GptEntry>() as u32,
467         ..Default::default()
468     };
469 
470     // Define gpt entry structures
471     let entries: Vec<GptEntry> = partitions
472         .iter()
473         .take(gpt_max_entries)
474         .map(|(k, v)| {
475             let last = (SafeNum::from(v.size()).round_up(block_size) / block_size + block - 1)
476                 .try_into()
477                 .unwrap();
478             let mut entry = GptEntry {
479                 part_type: Default::default(),
480                 guid: Default::default(),
481                 first: block,
482                 last,
483                 flags: 0,
484                 name: str_to_utf16_entry_name(k),
485             };
486             entry.guid[0] = block as u8;
487             block = last + 1;
488             entry
489         })
490         .collect();
491 
492     // Patch last fields of header
493     header.last = block - 1;
494     header.backup = block + entry_blocks;
495     header.entries_crc = entries
496         .iter()
497         .fold(Hasher::new(), |mut h, e| {
498             h.update(e.as_bytes());
499             h
500         })
501         .finalize();
502     header.update_crc();
503 
504     // Primary header
505     add_blocks(&mut store, header.as_bytes(), block_size);
506 
507     // Primary entries
508     for e in &entries {
509         store.extend(e.as_bytes());
510     }
511     pad_to_block_size(&mut store, block_size);
512 
513     // Partition store
514     for p in partitions.values() {
515         match p {
516             BackingStore::Data(d) => add_blocks(&mut store, d, block_size),
517             BackingStore::Size(s) => pad_bytes(&mut store, *s, block_size),
518         };
519     }
520 
521     // Backup entries
522     let backup_entries_block = store.len() / block_size;
523     for e in entries {
524         store.extend(e.as_bytes());
525     }
526     pad_to_block_size(&mut store, block_size);
527     // Tweak header to make it the backup.
528     header.current = header.backup;
529     header.backup = 1;
530     header.entries = backup_entries_block.try_into().unwrap();
531     header.update_crc();
532     add_blocks(&mut store, header.as_bytes(), block_size);
533 
534     store
535 }
536 
537 /// Simple RAM based multi-block device used for unit tests.
538 pub struct TestMultiBlockDevices(pub Vec<TestBlockDevice>);
539 
540 impl AsMultiBlockDevices for TestMultiBlockDevices {
for_each( &mut self, f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64), ) -> core::result::Result<(), Option<&'static str>>541     fn for_each(
542         &mut self,
543         f: &mut dyn FnMut(&mut dyn AsBlockDevice, u64),
544     ) -> core::result::Result<(), Option<&'static str>> {
545         let _ = self
546             .0
547             .iter_mut()
548             .enumerate()
549             .for_each(|(idx, ele)| f(ele, u64::try_from(idx).unwrap()));
550         Ok(())
551     }
552 }
553 
554 #[cfg(test)]
555 mod test {
556     use super::*;
557 
558     #[test]
test_builder_partitions()559     fn test_builder_partitions() {
560         let data: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
561         let mut actual: [u8; 8] = Default::default();
562         let mut block_dev = TestBlockDeviceBuilder::new()
563             .add_partition("squid", BackingStore::Data(&data))
564             .add_partition("clam", BackingStore::Size(28))
565             .build();
566 
567         assert!(block_dev.sync_gpt().is_ok());
568         assert!(block_dev.read_gpt_partition("squid", 0, actual.as_mut_slice()).is_ok());
569         assert_eq!(actual, data);
570 
571         assert!(block_dev.read_gpt_partition("clam", 0, actual.as_mut_slice()).is_ok());
572         assert_eq!(actual, [0u8; 8]);
573     }
574 }
575