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