1use anyhow::{Context as _, Error, anyhow, ensure};
6use block_client::{BlockClient, BufferSlice, MutableBufferSlice, RemoteBlockClient};
7use fuchsia_sync::Mutex;
8use std::collections::BTreeMap;
9use std::sync::Arc;
10use zerocopy::{FromBytes as _, IntoBytes as _};
11
12pub mod format;
13
14#[derive(Clone, Default, Debug)]
17pub struct Guid(uuid::Uuid);
18
19impl From<uuid::Uuid> for Guid {
20 fn from(uuid: uuid::Uuid) -> Self {
21 Self(uuid)
22 }
23}
24
25impl Guid {
26 pub fn from_bytes(bytes: [u8; 16]) -> Self {
27 Self(uuid::Uuid::from_bytes_le(bytes))
28 }
29
30 pub fn to_bytes(&self) -> [u8; 16] {
31 self.0.to_bytes_le()
32 }
33
34 pub fn to_string(&self) -> String {
35 self.0.to_string()
36 }
37
38 pub fn nil() -> Self {
39 Self(uuid::Uuid::nil())
40 }
41
42 pub fn generate() -> Self {
43 Self(uuid::Uuid::new_v4())
44 }
45}
46
47#[derive(Clone, Debug)]
48pub struct PartitionInfo {
49 pub label: String,
50 pub type_guid: Guid,
51 pub instance_guid: Guid,
52 pub start_block: u64,
53 pub num_blocks: u64,
54 pub flags: u64,
55}
56
57impl PartitionInfo {
58 pub fn from_entry(entry: &format::PartitionTableEntry) -> Result<Self, Error> {
59 let label = String::from_utf16(entry.name.split(|v| *v == 0u16).next().unwrap())?;
60 ensure!(
61 entry.last_lba >= entry.first_lba,
62 "Partition last_lba < first_lba (first_lba: {}, last_lba: {})",
63 entry.first_lba,
64 entry.last_lba
65 );
66 let num_blocks = entry
67 .last_lba
68 .checked_add(1)
69 .ok_or_else(|| anyhow!("Partition last_lba overflow (last_lba: {})", entry.last_lba))?
70 .checked_sub(entry.first_lba)
71 .ok_or_else(|| {
72 anyhow!(
73 "Partition last_lba < first_lba (first_lba: {}, last_lba: {})",
74 entry.first_lba,
75 entry.last_lba
76 )
77 })?;
78 Ok(Self {
79 label,
80 type_guid: Guid::from_bytes(entry.type_guid),
81 instance_guid: Guid::from_bytes(entry.instance_guid),
82 start_block: entry.first_lba,
83 num_blocks,
84 flags: entry.flags,
85 })
86 }
87
88 pub fn as_entry(&self) -> format::PartitionTableEntry {
89 let mut name = [0u16; 36];
90 let raw = self.label.encode_utf16().collect::<Vec<_>>();
91 assert!(raw.len() <= name.len());
92 name[..raw.len()].copy_from_slice(&raw[..]);
93 format::PartitionTableEntry {
94 type_guid: self.type_guid.to_bytes(),
95 instance_guid: self.instance_guid.to_bytes(),
96 first_lba: self.start_block,
97 last_lba: self.start_block.saturating_add(self.num_blocks.saturating_sub(1)),
98 flags: self.flags,
99 name,
100 }
101 }
102
103 pub fn nil() -> Self {
104 Self {
105 label: String::default(),
106 type_guid: Guid::default(),
107 instance_guid: Guid::default(),
108 start_block: 0,
109 num_blocks: 0,
110 flags: 0,
111 }
112 }
113
114 pub fn is_nil(&self) -> bool {
115 self.label == ""
116 && self.type_guid.0.is_nil()
117 && self.instance_guid.0.is_nil()
118 && self.start_block == 0
119 && self.num_blocks == 0
120 && self.flags == 0
121 }
122}
123
124impl From<&PartitionInfo> for block_server::PartitionInfo {
125 fn from(info: &PartitionInfo) -> Self {
126 block_server::PartitionInfo {
127 block_count: info.num_blocks,
128 start_block_offset: Some(info.start_block),
129 type_guid: info.type_guid.to_bytes(),
130 instance_guid: info.instance_guid.to_bytes(),
131 name: info.label.clone(),
132 flags: Some(info.flags),
133 ..Default::default()
134 }
135 }
136}
137
138impl From<PartitionInfo> for block_server::PartitionInfo {
139 fn from(info: PartitionInfo) -> Self {
140 block_server::PartitionInfo::from(&info)
141 }
142}
143
144enum WhichHeader {
145 Primary,
146 Backup,
147}
148
149impl WhichHeader {
150 fn offset(&self, block_size: u64, block_count: u64) -> u64 {
151 match self {
152 Self::Primary => block_size,
153 Self::Backup => (block_count - 1) * block_size,
154 }
155 }
156}
157
158async fn load_metadata(
159 client: &RemoteBlockClient,
160 which: WhichHeader,
161) -> Result<(format::Header, BTreeMap<u32, PartitionInfo>), Error> {
162 let bs = client.block_size() as usize;
163 let mut header_block = vec![0u8; client.block_size() as usize];
164 client
165 .read_at(
166 MutableBufferSlice::Memory(&mut header_block[..]),
167 which.offset(bs as u64, client.block_count() as u64),
168 )
169 .await
170 .context("Read header")?;
171 let (header, _) = format::Header::ref_from_prefix(&header_block[..])
172 .map_err(|_| anyhow!("Header has invalid size"))?;
173 header.ensure_integrity(client.block_count(), client.block_size() as u64)?;
174 let partition_table_offset = header.part_start * bs as u64;
175 let partition_table_size = (header.num_parts * header.part_size) as usize;
176 let partition_table_size_rounded =
177 partition_table_size.checked_next_multiple_of(bs).ok_or_else(|| {
178 anyhow!(
179 "Overflow when rounding up partition table size \
180 (partition_table_size: {partition_table_size}, block_size: {bs})"
181 )
182 })?;
183 let mut partition_table = BTreeMap::new();
184 if header.num_parts > 0 {
185 let mut partition_table_blocks = vec![0u8; partition_table_size_rounded];
186 client
187 .read_at(
188 MutableBufferSlice::Memory(&mut partition_table_blocks[..]),
189 partition_table_offset,
190 )
191 .await
192 .with_context(|| {
193 format!(
194 "Failed to read partition table (sz {}) from offset {}",
195 partition_table_size, partition_table_offset
196 )
197 })?;
198 let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC)
199 .checksum(&partition_table_blocks[..partition_table_size]);
200 ensure!(header.crc32_parts == crc, "Invalid partition table checksum");
201
202 let mut used_ranges = Vec::new();
203 for i in 0..header.num_parts as usize {
204 let entry_raw = &partition_table_blocks
205 [i * header.part_size as usize..(i + 1) * header.part_size as usize];
206 let (entry, _) = format::PartitionTableEntry::ref_from_prefix(entry_raw)
207 .map_err(|_| anyhow!("Failed to parse partition {i}"))?;
208 if entry.is_empty() {
209 continue;
210 }
211 entry
212 .ensure_integrity(header.first_usable, header.last_usable)
213 .context("GPT partition table entry invalid!")?;
214 let end = entry.last_lba.checked_add(1).ok_or_else(|| {
215 anyhow!(
216 "Partition {i} last_lba overflow (first_lba: {}, last_lba: {})",
217 entry.first_lba,
218 entry.last_lba
219 )
220 })?;
221 used_ranges.push(entry.first_lba..end);
222
223 partition_table.insert(i as u32, PartitionInfo::from_entry(entry)?);
224 }
225 used_ranges.sort_by_key(|r| r.start);
226 for pairs in used_ranges.windows(2) {
227 ensure!(pairs[0].end <= pairs[1].start, "Overlapping partitions");
228 }
229 }
230 Ok((header.clone(), partition_table))
231}
232
233struct TransactionState {
234 pending_id: u64,
235 next_id: u64,
236}
237
238impl Default for TransactionState {
239 fn default() -> Self {
240 Self { pending_id: u64::MAX, next_id: 0 }
241 }
242}
243
244pub struct Gpt {
246 client: Arc<RemoteBlockClient>,
247 header: format::Header,
248 partitions: BTreeMap<u32, PartitionInfo>,
249 transaction_state: Arc<Mutex<TransactionState>>,
250}
251
252impl std::fmt::Debug for Gpt {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
254 f.debug_struct("Gpt")
255 .field("header", &self.header)
256 .field("partitions", &self.partitions)
257 .finish()
258 }
259}
260
261#[derive(Eq, thiserror::Error, Clone, Debug, PartialEq)]
262pub enum TransactionCommitError {
263 #[error("I/O error")]
264 Io,
265 #[error("Invalid arguments")]
266 InvalidArguments,
267 #[error("No space")]
268 NoSpace,
269}
270
271impl From<format::FormatError> for TransactionCommitError {
272 fn from(error: format::FormatError) -> Self {
273 match error {
274 format::FormatError::InvalidArguments => Self::InvalidArguments,
275 format::FormatError::NoSpace => Self::NoSpace,
276 }
277 }
278}
279
280impl From<TransactionCommitError> for zx::Status {
281 fn from(error: TransactionCommitError) -> zx::Status {
282 match error {
283 TransactionCommitError::Io => zx::Status::IO,
284 TransactionCommitError::InvalidArguments => zx::Status::INVALID_ARGS,
285 TransactionCommitError::NoSpace => zx::Status::NO_SPACE,
286 }
287 }
288}
289
290#[derive(Eq, thiserror::Error, Clone, Debug, PartialEq)]
291pub enum AddPartitionError {
292 #[error("Invalid arguments")]
293 InvalidArguments,
294 #[error("No space")]
295 NoSpace,
296}
297
298impl From<AddPartitionError> for zx::Status {
299 fn from(error: AddPartitionError) -> zx::Status {
300 match error {
301 AddPartitionError::InvalidArguments => zx::Status::INVALID_ARGS,
302 AddPartitionError::NoSpace => zx::Status::NO_SPACE,
303 }
304 }
305}
306
307impl Gpt {
308 pub async fn open(client: Arc<RemoteBlockClient>) -> Result<Self, Error> {
310 let mut restore_primary = false;
311 let (header, partitions) = match load_metadata(&client, WhichHeader::Primary).await {
312 Ok(v) => v,
313 Err(error) => {
314 log::warn!(error:?; "Failed to load primary metadata; falling back to backup");
315 restore_primary = true;
316 load_metadata(&client, WhichHeader::Backup)
317 .await
318 .context("Failed to load backup metadata")?
319 }
320 };
321 let mut this = Self {
322 client,
323 header,
324 partitions,
325 transaction_state: Arc::new(Mutex::new(TransactionState::default())),
326 };
327 if restore_primary {
328 log::info!("Restoring primary metadata from backup!");
329 this.header.backup_lba = this.header.current_lba;
330 this.header.current_lba = 1;
331 this.header.part_start = 2;
332 this.header.crc32 = this.header.compute_checksum();
333 let partition_table =
334 this.flattened_partitions().into_iter().map(|v| v.as_entry()).collect::<Vec<_>>();
335 let partition_table_raw = format::serialize_partition_table(
336 &mut this.header,
337 this.client.block_size() as usize,
338 this.client.block_count(),
339 &partition_table[..],
340 )
341 .context("Failed to serialize existing partition table")?;
342 this.write_metadata(&this.header, &partition_table_raw[..])
343 .await
344 .context("Failed to restore primary metadata")?;
345 }
346 Ok(this)
347 }
348
349 pub async fn format(
352 client: Arc<RemoteBlockClient>,
353 partitions: Vec<PartitionInfo>,
354 ) -> Result<Self, Error> {
355 let header = format::Header::new(
356 client.block_count(),
357 client.block_size(),
358 partitions.len() as u32,
359 )?;
360 let mut this = Self {
361 client,
362 header,
363 partitions: BTreeMap::new(),
364 transaction_state: Arc::new(Mutex::new(TransactionState::default())),
365 };
366 let mut transaction = this.create_transaction().unwrap();
367 transaction.partitions = partitions;
368 this.commit_transaction(transaction).await?;
369 Ok(this)
370 }
371
372 pub fn client(&self) -> &Arc<RemoteBlockClient> {
373 &self.client
374 }
375
376 pub fn set_client(&mut self, client: Arc<RemoteBlockClient>) {
379 self.client = client;
380 }
381
382 #[cfg(test)]
383 fn take_client(self) -> Arc<RemoteBlockClient> {
384 self.client
385 }
386
387 pub fn header(&self) -> &format::Header {
388 &self.header
389 }
390
391 pub fn partitions(&self) -> &BTreeMap<u32, PartitionInfo> {
392 &self.partitions
393 }
394
395 fn flattened_partitions(&self) -> Vec<PartitionInfo> {
398 let mut partitions = vec![PartitionInfo::nil(); self.header.num_parts as usize];
399 for (idx, partition) in &self.partitions {
400 partitions[*idx as usize] = partition.clone();
401 }
402 partitions
403 }
404
405 pub fn create_transaction(&self) -> Option<Transaction> {
407 {
408 let mut state = self.transaction_state.lock();
409 if state.pending_id != u64::MAX {
410 return None;
411 } else {
412 state.pending_id = state.next_id;
413 state.next_id += 1;
414 }
415 }
416 Some(Transaction {
417 partitions: self.flattened_partitions(),
418 transaction_state: self.transaction_state.clone(),
419 })
420 }
421
422 pub async fn commit_transaction(
423 &mut self,
424 mut transaction: Transaction,
425 ) -> Result<(), TransactionCommitError> {
426 let mut new_header = self.header.clone();
427 let entries =
428 transaction.partitions.iter().map(|entry| entry.as_entry()).collect::<Vec<_>>();
429 let partition_table_raw = format::serialize_partition_table(
430 &mut new_header,
431 self.client.block_size() as usize,
432 self.client.block_count(),
433 &entries[..],
434 )?;
435
436 let mut backup_header = new_header.clone();
437 backup_header.current_lba = backup_header.backup_lba;
438 backup_header.backup_lba = 1;
439 backup_header.part_start = backup_header.last_usable + 1;
440 backup_header.crc32 = backup_header.compute_checksum();
441
442 self.write_metadata(&backup_header, &partition_table_raw[..]).await.map_err(|err| {
445 log::warn!(err:?; "Failed to write metadata");
446 TransactionCommitError::Io
447 })?;
448 self.client.flush().await.map_err(|err| {
452 log::warn!(err:?; "Failed to flush metadata writes");
453 TransactionCommitError::Io
454 })?;
455 self.write_metadata(&new_header, &partition_table_raw[..]).await.map_err(|err| {
456 log::warn!(err:?; "Failed to write metadata");
457 TransactionCommitError::Io
458 })?;
459 self.client.flush().await.map_err(|err| {
460 log::warn!(err:?; "Failed to flush metadata writes");
461 TransactionCommitError::Io
462 })?;
463
464 self.header = new_header;
465 self.partitions = BTreeMap::new();
466 let mut idx = 0;
467 for partition in std::mem::take(&mut transaction.partitions) {
468 if !partition.is_nil() {
469 self.partitions.insert(idx, partition);
470 }
471 idx += 1;
472 }
473 Ok(())
474 }
475
476 pub fn add_partition(
480 &mut self,
481 transaction: &mut Transaction,
482 mut info: PartitionInfo,
483 ) -> Result<usize, AddPartitionError> {
484 assert_eq!(info.start_block, 0);
485
486 if info.label.is_empty()
487 || info.type_guid.0.is_nil()
488 || info.instance_guid.0.is_nil()
489 || info.num_blocks == 0
490 {
491 return Err(AddPartitionError::InvalidArguments);
492 }
493
494 let last_usable_end =
495 self.header.last_usable.checked_add(1).ok_or(AddPartitionError::InvalidArguments)?;
496 let mut allocated_ranges =
497 vec![0..self.header.first_usable, last_usable_end..self.client.block_count()];
498 let mut slot_idx = None;
499 for i in 0..transaction.partitions.len() {
500 let partition = &transaction.partitions[i];
501 if slot_idx.is_none() && partition.is_nil() {
502 slot_idx = Some(i);
503 }
504 if !partition.is_nil() {
505 let end = partition
506 .start_block
507 .checked_add(partition.num_blocks)
508 .ok_or(AddPartitionError::InvalidArguments)?;
509 allocated_ranges.push(partition.start_block..end);
510 }
511 }
512 let slot_idx = slot_idx.ok_or(AddPartitionError::NoSpace)?;
513 allocated_ranges.sort_by_key(|range| range.start);
514
515 let alignment = (4096 / self.client.block_size() as u64).max(1);
516 let mut start_block = None;
517 for [a, b] in allocated_ranges.array_windows() {
518 let candidate = a.end.next_multiple_of(alignment);
519 if candidate <= b.start && b.start - candidate >= info.num_blocks {
520 start_block = Some(candidate);
521 break;
522 }
523 }
524 info.start_block = start_block.ok_or(AddPartitionError::NoSpace)?;
525
526 transaction.partitions[slot_idx] = info;
527 Ok(slot_idx)
528 }
529
530 async fn write_metadata(
531 &self,
532 header: &format::Header,
533 partition_table: &[u8],
534 ) -> Result<(), Error> {
535 let bs = self.client.block_size() as usize;
536 let mut header_block = vec![0u8; bs];
537 header.write_to_prefix(&mut header_block[..]).unwrap();
538 self.client
539 .write_at(BufferSlice::Memory(&header_block[..]), header.current_lba * bs as u64)
540 .await
541 .context("Failed to write header")?;
542 if !partition_table.is_empty() {
543 self.client
544 .write_at(BufferSlice::Memory(partition_table), header.part_start * bs as u64)
545 .await
546 .context("Failed to write partition table")?;
547 }
548 Ok(())
549 }
550}
551
552pub struct Transaction {
554 pub partitions: Vec<PartitionInfo>,
555 transaction_state: Arc<Mutex<TransactionState>>,
556}
557
558impl std::fmt::Debug for Transaction {
559 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
560 f.debug_struct("Transaction").field("partitions", &self.partitions).finish()
561 }
562}
563
564impl Drop for Transaction {
565 fn drop(&mut self) {
566 let mut state = self.transaction_state.lock();
567 debug_assert!(state.pending_id != u64::MAX);
568 state.pending_id = u64::MAX;
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use crate::{AddPartitionError, Gpt, Guid, PartitionInfo, format};
575 use anyhow::Error;
576 use block_client::{BlockClient as _, BufferSlice, MutableBufferSlice, RemoteBlockClient};
577 use fidl_fuchsia_storage_block as fblock;
578 use fuchsia_async as fasync;
579 use std::ops::Range;
580 use std::sync::Arc;
581 use std::sync::atomic::{AtomicBool, Ordering};
582 use test_vmo_backed_block_server::{
583 InitialContents, Observer, VmoBackedServer, VmoBackedServerOptions, WriteAction, WriteCache,
584 };
585 use zerocopy::IntoBytes as _;
586
587 async fn connect_to_server(
588 server: VmoBackedServer,
589 ) -> (Arc<RemoteBlockClient>, fasync::Task<()>) {
590 let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
591 let task =
592 fasync::Task::spawn(
593 async move { server.serve(server_end.into_stream()).await.unwrap() },
594 );
595 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
596 (client, task)
597 }
598
599 #[fuchsia::test]
600 async fn load_unformatted_gpt() {
601 let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
602 let (client, _task) = connect_to_server(server).await;
603 Gpt::open(client).await.expect_err("load should fail");
604 }
605
606 #[fuchsia::test]
607 async fn load_formatted_empty_gpt() {
608 let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
609 let (client, _task) = connect_to_server(server).await;
610 Gpt::format(client.clone(), vec![]).await.expect("format failed");
611 Gpt::open(client).await.expect("load should succeed");
612 }
613
614 #[fuchsia::test]
615 async fn load_formatted_gpt_with_minimal_size() {
616 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
617 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
618 const PART_NAME: &str = "part";
619
620 let server = VmoBackedServer::new(6, 4096, &[]).expect("Failed to create VmoBackedServer");
621 let (client, _task) = connect_to_server(server).await;
622 Gpt::format(
623 client.clone(),
624 vec![PartitionInfo {
625 label: PART_NAME.to_string(),
626 type_guid: Guid::from_bytes(PART_TYPE_GUID),
627 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
628 start_block: 3,
629 num_blocks: 1,
630 flags: 0,
631 }],
632 )
633 .await
634 .expect("format failed");
635 let manager = Gpt::open(client).await.expect("load should succeed");
636 assert_eq!(manager.header.first_usable, 3);
637 assert_eq!(manager.header.last_usable, 3);
638 let partition = manager.partitions().get(&0).expect("No entry found");
639 assert_eq!(partition.start_block, 3);
640 assert_eq!(partition.num_blocks, 1);
641 assert!(manager.partitions().get(&1).is_none());
642 }
643
644 #[fuchsia::test]
645 async fn load_formatted_gpt_with_one_partition() {
646 const PART_TYPE_GUID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
647 const PART_INSTANCE_GUID: [u8; 16] =
648 [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
649 const PART_NAME: &str = "part";
650
651 let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
652 let (client, _task) = connect_to_server(server).await;
653 Gpt::format(
654 client.clone(),
655 vec![PartitionInfo {
656 label: PART_NAME.to_string(),
657 type_guid: Guid::from_bytes(PART_TYPE_GUID),
658 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
659 start_block: 4,
660 num_blocks: 1,
661 flags: 0,
662 }],
663 )
664 .await
665 .expect("format failed");
666 let manager = Gpt::open(client).await.expect("load should succeed");
667 let partition = manager.partitions().get(&0).expect("No entry found");
668 assert_eq!(partition.label, "part");
669 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
670 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
671 assert_eq!(partition.start_block, 4);
672 assert_eq!(partition.num_blocks, 1);
673 assert!(manager.partitions().get(&1).is_none());
674 }
675
676 #[fuchsia::test]
677 async fn load_formatted_gpt_with_two_partitions() {
678 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
679 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
680 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
681 const PART_1_NAME: &str = "part1";
682 const PART_2_NAME: &str = "part2";
683
684 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
685 let (client, _task) = connect_to_server(server).await;
686 Gpt::format(
687 client.clone(),
688 vec![
689 PartitionInfo {
690 label: PART_1_NAME.to_string(),
691 type_guid: Guid::from_bytes(PART_TYPE_GUID),
692 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
693 start_block: 4,
694 num_blocks: 1,
695 flags: 0,
696 },
697 PartitionInfo {
698 label: PART_2_NAME.to_string(),
699 type_guid: Guid::from_bytes(PART_TYPE_GUID),
700 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
701 start_block: 7,
702 num_blocks: 1,
703 flags: 0,
704 },
705 ],
706 )
707 .await
708 .expect("format failed");
709 let manager = Gpt::open(client).await.expect("load should succeed");
710 let partition = manager.partitions().get(&0).expect("No entry found");
711 assert_eq!(partition.label, PART_1_NAME);
712 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
713 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
714 assert_eq!(partition.start_block, 4);
715 assert_eq!(partition.num_blocks, 1);
716 let partition = manager.partitions().get(&1).expect("No entry found");
717 assert_eq!(partition.label, PART_2_NAME);
718 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
719 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
720 assert_eq!(partition.start_block, 7);
721 assert_eq!(partition.num_blocks, 1);
722 assert!(manager.partitions().get(&2).is_none());
723 }
724
725 #[fuchsia::test]
726 async fn load_formatted_gpt_with_extra_bytes_in_partition_name() {
727 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
728 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
729 const PART_NAME: &str = "part\0extrastuff";
730
731 let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
732 let (client, _task) = connect_to_server(server).await;
733 Gpt::format(
734 client.clone(),
735 vec![PartitionInfo {
736 label: PART_NAME.to_string(),
737 type_guid: Guid::from_bytes(PART_TYPE_GUID),
738 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
739 start_block: 4,
740 num_blocks: 1,
741 flags: 0,
742 }],
743 )
744 .await
745 .expect("format failed");
746 let manager = Gpt::open(client).await.expect("load should succeed");
747 let partition = manager.partitions().get(&0).expect("No entry found");
748 assert_eq!(partition.label, "part");
750 }
751
752 #[fuchsia::test]
753 async fn load_formatted_gpt_with_empty_partition_name() {
754 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
755 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
756 const PART_NAME: &str = "";
757
758 let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
759 let (client, _task) = connect_to_server(server).await;
760 Gpt::format(
761 client.clone(),
762 vec![PartitionInfo {
763 label: PART_NAME.to_string(),
764 type_guid: Guid::from_bytes(PART_TYPE_GUID),
765 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
766 start_block: 4,
767 num_blocks: 1,
768 flags: 0,
769 }],
770 )
771 .await
772 .expect("format failed");
773 let manager = Gpt::open(client).await.expect("load should succeed");
774 let partition = manager.partitions().get(&0).expect("No entry found");
775 assert_eq!(partition.label, "");
776 }
777
778 #[fuchsia::test]
779 async fn load_formatted_gpt_with_invalid_primary_header() {
780 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
781 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
782 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
783 const PART_1_NAME: &str = "part1";
784 const PART_2_NAME: &str = "part2";
785
786 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
787 let (client, _task) = connect_to_server(server).await;
788 Gpt::format(
789 client.clone(),
790 vec![
791 PartitionInfo {
792 label: PART_1_NAME.to_string(),
793 type_guid: Guid::from_bytes(PART_TYPE_GUID),
794 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
795 start_block: 4,
796 num_blocks: 1,
797 flags: 0,
798 },
799 PartitionInfo {
800 label: PART_2_NAME.to_string(),
801 type_guid: Guid::from_bytes(PART_TYPE_GUID),
802 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
803 start_block: 7,
804 num_blocks: 1,
805 flags: 0,
806 },
807 ],
808 )
809 .await
810 .expect("format failed");
811 client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 512).await.unwrap();
813 let manager = Gpt::open(client).await.expect("load should succeed");
814 let partition = manager.partitions().get(&0).expect("No entry found");
815 assert_eq!(partition.label, PART_1_NAME);
816 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
817 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
818 assert_eq!(partition.start_block, 4);
819 assert_eq!(partition.num_blocks, 1);
820 let partition = manager.partitions().get(&1).expect("No entry found");
821 assert_eq!(partition.label, PART_2_NAME);
822 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
823 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
824 assert_eq!(partition.start_block, 7);
825 assert_eq!(partition.num_blocks, 1);
826 assert!(manager.partitions().get(&2).is_none());
827 }
828
829 #[fuchsia::test]
830 async fn load_formatted_gpt_with_invalid_primary_partition_table() {
831 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
832 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
833 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
834 const PART_1_NAME: &str = "part1";
835 const PART_2_NAME: &str = "part2";
836
837 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
838 let (client, _task) = connect_to_server(server).await;
839 Gpt::format(
840 client.clone(),
841 vec![
842 PartitionInfo {
843 label: PART_1_NAME.to_string(),
844 type_guid: Guid::from_bytes(PART_TYPE_GUID),
845 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
846 start_block: 4,
847 num_blocks: 1,
848 flags: 0,
849 },
850 PartitionInfo {
851 label: PART_2_NAME.to_string(),
852 type_guid: Guid::from_bytes(PART_TYPE_GUID),
853 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
854 start_block: 7,
855 num_blocks: 1,
856 flags: 0,
857 },
858 ],
859 )
860 .await
861 .expect("format failed");
862 client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 1024).await.unwrap();
864 let manager = Gpt::open(client).await.expect("load should succeed");
865 let partition = manager.partitions().get(&0).expect("No entry found");
866 assert_eq!(partition.label, PART_1_NAME);
867 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
868 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
869 assert_eq!(partition.start_block, 4);
870 assert_eq!(partition.num_blocks, 1);
871 let partition = manager.partitions().get(&1).expect("No entry found");
872 assert_eq!(partition.label, PART_2_NAME);
873 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
874 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
875 assert_eq!(partition.start_block, 7);
876 assert_eq!(partition.num_blocks, 1);
877 assert!(manager.partitions().get(&2).is_none());
878 }
879
880 #[fuchsia::test]
881 async fn drop_transaction() {
882 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
883 let (client, _task) = connect_to_server(server).await;
884 Gpt::format(client.clone(), vec![]).await.expect("format failed");
885 let manager = Gpt::open(client).await.expect("load should succeed");
886 {
887 let _transaction = manager.create_transaction().unwrap();
888 assert!(manager.create_transaction().is_none());
889 }
890 let _transaction =
891 manager.create_transaction().expect("Transaction dropped but not available");
892 }
893
894 #[fuchsia::test]
895 async fn commit_empty_transaction() {
896 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
897 let (client, _task) = connect_to_server(server).await;
898 Gpt::format(client.clone(), vec![]).await.expect("format failed");
899 let mut manager = Gpt::open(client).await.expect("load should succeed");
900 let transaction = manager.create_transaction().unwrap();
901 manager.commit_transaction(transaction).await.expect("Commit failed");
902
903 assert_eq!(manager.header().num_parts, 0);
906 assert!(manager.partitions().is_empty());
907 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
908 assert_eq!(manager.header().num_parts, 0);
909 assert!(manager.partitions().is_empty());
910 }
911
912 #[fuchsia::test]
913 async fn add_partition_in_transaction() {
914 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
915 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
916 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
917 const PART_1_NAME: &str = "part1";
918 const PART_2_NAME: &str = "part2";
919
920 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
921 let (client, _task) = connect_to_server(server).await;
922 Gpt::format(
923 client.clone(),
924 vec![PartitionInfo {
925 label: PART_1_NAME.to_string(),
926 type_guid: Guid::from_bytes(PART_TYPE_GUID),
927 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
928 start_block: 4,
929 num_blocks: 1,
930 flags: 0,
931 }],
932 )
933 .await
934 .expect("format failed");
935 let mut manager = Gpt::open(client).await.expect("load should succeed");
936 let mut transaction = manager.create_transaction().unwrap();
937 assert_eq!(transaction.partitions.len(), 1);
938 transaction.partitions.push(crate::PartitionInfo {
939 label: PART_2_NAME.to_string(),
940 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
941 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
942 start_block: 7,
943 num_blocks: 1,
944 flags: 0,
945 });
946 manager.commit_transaction(transaction).await.expect("Commit failed");
947
948 assert_eq!(manager.header().num_parts, 2);
951 assert!(manager.partitions().get(&2).is_none());
952 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
953 assert_eq!(manager.header().num_parts, 2);
954 let partition = manager.partitions().get(&0).expect("No entry found");
955 assert_eq!(partition.label, PART_1_NAME);
956 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
957 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
958 assert_eq!(partition.start_block, 4);
959 assert_eq!(partition.num_blocks, 1);
960 let partition = manager.partitions().get(&1).expect("No entry found");
961 assert_eq!(partition.label, PART_2_NAME);
962 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
963 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
964 assert_eq!(partition.start_block, 7);
965 assert_eq!(partition.num_blocks, 1);
966 assert!(manager.partitions().get(&2).is_none());
967 }
968
969 #[fuchsia::test]
970 async fn remove_partition_in_transaction() {
971 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
972 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
973 const PART_NAME: &str = "part1";
974
975 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
976 let (client, _task) = connect_to_server(server).await;
977 Gpt::format(
978 client.clone(),
979 vec![PartitionInfo {
980 label: PART_NAME.to_string(),
981 type_guid: Guid::from_bytes(PART_TYPE_GUID),
982 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
983 start_block: 4,
984 num_blocks: 1,
985 flags: 0,
986 }],
987 )
988 .await
989 .expect("format failed");
990 let mut manager = Gpt::open(client).await.expect("load should succeed");
991 let mut transaction = manager.create_transaction().unwrap();
992 assert_eq!(transaction.partitions.len(), 1);
993 transaction.partitions.clear();
994 manager.commit_transaction(transaction).await.expect("Commit failed");
995
996 assert_eq!(manager.header().num_parts, 0);
999 assert!(manager.partitions().get(&0).is_none());
1000 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1001 assert_eq!(manager.header().num_parts, 0);
1002 assert!(manager.partitions().get(&0).is_none());
1003 }
1004
1005 #[fuchsia::test]
1006 async fn modify_partition_in_transaction() {
1007 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1008 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1009 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1010 const PART_1_NAME: &str = "part1";
1011 const PART_2_NAME: &str = "part2";
1012
1013 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
1014 let (client, _task) = connect_to_server(server).await;
1015 Gpt::format(
1016 client.clone(),
1017 vec![PartitionInfo {
1018 label: PART_1_NAME.to_string(),
1019 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1020 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1021 start_block: 4,
1022 num_blocks: 1,
1023 flags: 0,
1024 }],
1025 )
1026 .await
1027 .expect("format failed");
1028 let mut manager = Gpt::open(client).await.expect("load should succeed");
1029 let mut transaction = manager.create_transaction().unwrap();
1030 assert_eq!(transaction.partitions.len(), 1);
1031 transaction.partitions[0] = crate::PartitionInfo {
1032 label: PART_2_NAME.to_string(),
1033 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1034 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1035 start_block: 7,
1036 num_blocks: 1,
1037 flags: 0,
1038 };
1039 manager.commit_transaction(transaction).await.expect("Commit failed");
1040
1041 assert_eq!(manager.header().num_parts, 1);
1044 let partition = manager.partitions().get(&0).expect("No entry found");
1045 assert_eq!(partition.label, PART_2_NAME);
1046 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1047 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1048 assert_eq!(partition.start_block, 7);
1049 assert_eq!(partition.num_blocks, 1);
1050 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1051 assert_eq!(manager.header().num_parts, 1);
1052 let partition = manager.partitions().get(&0).expect("No entry found");
1053 assert_eq!(partition.label, PART_2_NAME);
1054 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1055 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1056 assert_eq!(partition.start_block, 7);
1057 assert_eq!(partition.num_blocks, 1);
1058 assert!(manager.partitions().get(&1).is_none());
1059 }
1060
1061 #[fuchsia::test]
1062 async fn grow_partition_table_in_transaction() {
1063 let server =
1064 VmoBackedServer::new(2048, 512, &[]).expect("Failed to create VmoBackedServer");
1065 let (client, _task) = connect_to_server(server).await;
1066 Gpt::format(
1067 client.clone(),
1068 vec![PartitionInfo {
1069 label: "part".to_string(),
1070 type_guid: Guid::from_bytes([1u8; 16]),
1071 instance_guid: Guid::from_bytes([1u8; 16]),
1072 start_block: 34,
1073 num_blocks: 1,
1074 flags: 0,
1075 }],
1076 )
1077 .await
1078 .expect("format failed");
1079 let mut manager = Gpt::open(client).await.expect("load should succeed");
1080 assert_eq!(manager.header().num_parts, 1);
1081 assert_eq!(manager.header().first_usable, 3);
1082 let mut transaction = manager.create_transaction().unwrap();
1083 transaction.partitions.resize(128, crate::PartitionInfo::nil());
1084 manager.commit_transaction(transaction).await.expect("Commit failed");
1085
1086 assert_eq!(manager.header().num_parts, 128);
1089 assert_eq!(manager.header().first_usable, 34);
1090 let partition = manager.partitions().get(&0).expect("No entry found");
1091 assert_eq!(partition.label, "part");
1092 assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1093 assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1094 assert_eq!(partition.start_block, 34);
1095 assert_eq!(partition.num_blocks, 1);
1096 assert!(manager.partitions().get(&1).is_none());
1097 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1098 assert_eq!(manager.header().num_parts, 128);
1099 assert_eq!(manager.header().first_usable, 34);
1100 let partition = manager.partitions().get(&0).expect("No entry found");
1101 assert_eq!(partition.label, "part");
1102 assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1103 assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1104 assert_eq!(partition.start_block, 34);
1105 assert_eq!(partition.num_blocks, 1);
1106 assert!(manager.partitions().get(&1).is_none());
1107 }
1108
1109 #[fuchsia::test]
1110 async fn shrink_partition_table_in_transaction() {
1111 let mut partitions = vec![];
1112 for i in 0..128 {
1113 partitions.push(PartitionInfo {
1114 label: format!("part-{i}"),
1115 type_guid: Guid::from_bytes([i as u8 + 1; 16]),
1116 instance_guid: Guid::from_bytes([i as u8 + 1; 16]),
1117 start_block: 34 + i,
1118 num_blocks: 1,
1119 flags: 0,
1120 });
1121 }
1122 let server =
1123 VmoBackedServer::new(2048, 512, &[]).expect("Failed to create VmoBackedServer");
1124 let (client, _task) = connect_to_server(server).await;
1125 Gpt::format(client.clone(), partitions).await.expect("format failed");
1126 let mut manager = Gpt::open(client).await.expect("load should succeed");
1127 assert_eq!(manager.header().num_parts, 128);
1128 assert_eq!(manager.header().first_usable, 34);
1129 let mut transaction = manager.create_transaction().unwrap();
1130 transaction.partitions.clear();
1131 manager.commit_transaction(transaction).await.expect("Commit failed");
1132
1133 assert_eq!(manager.header().num_parts, 0);
1136 assert_eq!(manager.header().first_usable, 2);
1137 assert!(manager.partitions().get(&0).is_none());
1138 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1139 assert_eq!(manager.header().num_parts, 0);
1140 assert_eq!(manager.header().first_usable, 2);
1141 assert!(manager.partitions().get(&0).is_none());
1142 }
1143
1144 #[fuchsia::test]
1145 async fn invalid_transaction_rejected() {
1146 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1147 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1148 const PART_NAME: &str = "part1";
1149
1150 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
1151 let (client, _task) = connect_to_server(server).await;
1152 Gpt::format(
1153 client.clone(),
1154 vec![PartitionInfo {
1155 label: PART_NAME.to_string(),
1156 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1157 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1158 start_block: 4,
1159 num_blocks: 1,
1160 flags: 0,
1161 }],
1162 )
1163 .await
1164 .expect("format failed");
1165 let mut manager = Gpt::open(client).await.expect("load should succeed");
1166 let mut transaction = manager.create_transaction().unwrap();
1167 assert_eq!(transaction.partitions.len(), 1);
1168 transaction.partitions[0].start_block = 0;
1170 manager.commit_transaction(transaction).await.expect_err("Commit should have failed");
1171
1172 assert_eq!(manager.header().num_parts, 1);
1175 let partition = manager.partitions().get(&0).expect("No entry found");
1176 assert_eq!(partition.label, PART_NAME);
1177 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1178 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1179 assert_eq!(partition.start_block, 4);
1180 assert_eq!(partition.num_blocks, 1);
1181 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1182 assert_eq!(manager.header().num_parts, 1);
1183 let partition = manager.partitions().get(&0).expect("No entry found");
1184 assert_eq!(partition.label, PART_NAME);
1185 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1186 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1187 assert_eq!(partition.start_block, 4);
1188 assert_eq!(partition.num_blocks, 1);
1189 }
1190
1191 struct DiscardingObserver {
1193 block_size: u64,
1194 discard_range: Range<u64>,
1195 }
1196
1197 impl Observer for DiscardingObserver {
1198 fn write(
1199 &self,
1200 device_block_offset: u64,
1201 block_count: u32,
1202 _vmo: &Arc<zx::Vmo>,
1203 _vmo_offset: u64,
1204 _opts: block_server::WriteOptions,
1205 ) -> WriteAction {
1206 let write_range = (device_block_offset * self.block_size)
1207 ..(device_block_offset + block_count as u64) * self.block_size;
1208 if write_range.end <= self.discard_range.start
1209 || write_range.start >= self.discard_range.end
1210 {
1211 WriteAction::Write
1212 } else {
1213 WriteAction::Discard
1214 }
1215 }
1216 }
1217
1218 #[fuchsia::test]
1219 async fn transaction_applied_if_primary_metadata_partially_written() {
1220 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1221 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1222 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1223 const PART_1_NAME: &str = "part1";
1224 const PART_2_NAME: &str = "part2";
1225
1226 let vmo = zx::Vmo::create(8192).unwrap();
1227 let server = VmoBackedServerOptions {
1228 initial_contents: InitialContents::FromVmo(vmo),
1229 block_size: 512,
1230 observer: Some(Box::new(DiscardingObserver {
1231 discard_range: 1024..1536,
1232 block_size: 512,
1233 })),
1234 ..Default::default()
1235 }
1236 .build()
1237 .unwrap();
1238 let (client, _task) = connect_to_server(server).await;
1239 Gpt::format(
1240 client.clone(),
1241 vec![PartitionInfo {
1242 label: PART_1_NAME.to_string(),
1243 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1244 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1245 start_block: 4,
1246 num_blocks: 1,
1247 flags: 0,
1248 }],
1249 )
1250 .await
1251 .expect("format failed");
1252 let mut manager = Gpt::open(client).await.expect("load should succeed");
1253 let mut transaction = manager.create_transaction().unwrap();
1254 transaction.partitions.push(crate::PartitionInfo {
1255 label: PART_2_NAME.to_string(),
1256 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1257 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1258 start_block: 7,
1259 num_blocks: 1,
1260 flags: 0,
1261 });
1262 manager.commit_transaction(transaction).await.expect("Commit failed");
1263
1264 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1265 assert_eq!(manager.header().num_parts, 2);
1266 let partition = manager.partitions().get(&0).expect("No entry found");
1267 assert_eq!(partition.label, PART_1_NAME);
1268 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1269 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1270 assert_eq!(partition.start_block, 4);
1271 assert_eq!(partition.num_blocks, 1);
1272 let partition = manager.partitions().get(&1).expect("No entry found");
1273 assert_eq!(partition.label, PART_2_NAME);
1274 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1275 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1276 assert_eq!(partition.start_block, 7);
1277 assert_eq!(partition.num_blocks, 1);
1278 }
1279
1280 #[fuchsia::test]
1281 async fn transaction_not_applied_if_primary_metadata_not_written() {
1282 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1283 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1284 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1285 const PART_1_NAME: &str = "part1";
1286 const PART_2_NAME: &str = "part2";
1287
1288 let vmo = zx::Vmo::create(8192).unwrap();
1289 let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1290 {
1291 let server =
1292 VmoBackedServer::from_vmo(512, vmo_dup).expect("Failed to create VmoBackedServer");
1293 let (client, _task) = connect_to_server(server).await;
1294 Gpt::format(
1295 client.clone(),
1296 vec![PartitionInfo {
1297 label: PART_1_NAME.to_string(),
1298 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1299 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1300 start_block: 4,
1301 num_blocks: 1,
1302 flags: 0,
1303 }],
1304 )
1305 .await
1306 .expect("format failed");
1307 }
1308 let server = VmoBackedServerOptions {
1309 initial_contents: InitialContents::FromVmo(vmo),
1310 block_size: 512,
1311 observer: Some(Box::new(DiscardingObserver {
1312 discard_range: 0..2048,
1313 block_size: 512,
1314 })),
1315 ..Default::default()
1316 }
1317 .build()
1318 .unwrap();
1319 let (client, _task) = connect_to_server(server).await;
1320
1321 let mut manager = Gpt::open(client).await.expect("load should succeed");
1322 let mut transaction = manager.create_transaction().unwrap();
1323 transaction.partitions.push(crate::PartitionInfo {
1324 label: PART_2_NAME.to_string(),
1325 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1326 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1327 start_block: 7,
1328 num_blocks: 1,
1329 flags: 0,
1330 });
1331 manager.commit_transaction(transaction).await.expect("Commit failed");
1332
1333 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1334 assert_eq!(manager.header().num_parts, 1);
1335 let partition = manager.partitions().get(&0).expect("No entry found");
1336 assert_eq!(partition.label, PART_1_NAME);
1337 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1338 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1339 assert_eq!(partition.start_block, 4);
1340 assert_eq!(partition.num_blocks, 1);
1341 assert!(manager.partitions().get(&1).is_none());
1342 }
1343
1344 #[fuchsia::test]
1345 async fn transaction_not_applied_if_backup_metadata_partially_written() {
1346 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1347 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1348 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1349 const PART_1_NAME: &str = "part1";
1350 const PART_2_NAME: &str = "part2";
1351
1352 let vmo = zx::Vmo::create(8192).unwrap();
1353 let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1354 {
1355 let server =
1356 VmoBackedServer::from_vmo(512, vmo_dup).expect("Failed to create VmoBackedServer");
1357 let (client, _task) = connect_to_server(server).await;
1358 Gpt::format(
1359 client.clone(),
1360 vec![PartitionInfo {
1361 label: PART_1_NAME.to_string(),
1362 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1363 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1364 start_block: 4,
1365 num_blocks: 1,
1366 flags: 0,
1367 }],
1368 )
1369 .await
1370 .expect("format failed");
1371 }
1372 let server = VmoBackedServerOptions {
1373 initial_contents: InitialContents::FromVmo(vmo),
1374 block_size: 512,
1375 observer: Some(Box::new(DiscardingObserver {
1376 discard_range: 0..7680,
1377 block_size: 512,
1378 })),
1379 ..Default::default()
1380 }
1381 .build()
1382 .unwrap();
1383 let (client, _task) = connect_to_server(server).await;
1384
1385 let mut manager = Gpt::open(client).await.expect("load should succeed");
1386 let mut transaction = manager.create_transaction().unwrap();
1387 transaction.partitions.push(crate::PartitionInfo {
1388 label: PART_2_NAME.to_string(),
1389 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1390 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1391 start_block: 7,
1392 num_blocks: 1,
1393 flags: 0,
1394 });
1395 manager.commit_transaction(transaction).await.expect("Commit failed");
1396
1397 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1398 assert_eq!(manager.header().num_parts, 1);
1399 let partition = manager.partitions().get(&0).expect("No entry found");
1400 assert_eq!(partition.label, PART_1_NAME);
1401 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1402 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1403 assert_eq!(partition.start_block, 4);
1404 assert_eq!(partition.num_blocks, 1);
1405 assert!(manager.partitions().get(&1).is_none());
1406 }
1407
1408 #[fuchsia::test]
1409 async fn restore_primary_from_backup() {
1410 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1411 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1412 const PART_NAME: &str = "part1";
1413
1414 let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
1415 let (client, _task) = connect_to_server(server).await;
1416 Gpt::format(
1417 client.clone(),
1418 vec![PartitionInfo {
1419 label: PART_NAME.to_string(),
1420 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1421 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1422 start_block: 4,
1423 num_blocks: 1,
1424 flags: 0,
1425 }],
1426 )
1427 .await
1428 .expect("format failed");
1429 let mut old_metadata = vec![0u8; 2048];
1430 client.read_at(MutableBufferSlice::Memory(&mut old_metadata[..]), 0).await.unwrap();
1431 let mut buffer = vec![0u8; 2048];
1432 client.write_at(BufferSlice::Memory(&buffer[..]), 0).await.unwrap();
1433
1434 let manager = Gpt::open(client).await.expect("load should succeed");
1435 let client = manager.take_client();
1436
1437 client.read_at(MutableBufferSlice::Memory(&mut buffer[..]), 0).await.unwrap();
1438 assert_eq!(old_metadata, buffer);
1439 }
1440
1441 #[fuchsia::test]
1442 async fn load_golden_gpt_linux() {
1443 let server = VmoBackedServer::from_file(512, "/pkg/data/gpt_golden/gpt.linux.blk");
1444 let (client, _task) = connect_to_server(server).await;
1445 let manager = Gpt::open(client).await.expect("load should succeed");
1446 let partition = manager.partitions().get(&0).expect("No entry found");
1447 assert_eq!(partition.label, "ext");
1448 assert_eq!(partition.type_guid.to_string(), "0fc63daf-8483-4772-8e79-3d69d8477de4");
1449 assert_eq!(partition.start_block, 8);
1450 assert_eq!(partition.num_blocks, 1);
1451 assert!(manager.partitions().get(&1).is_none());
1452 }
1453
1454 #[fuchsia::test]
1455 async fn load_golden_gpt_fuchsia() {
1456 let server = VmoBackedServer::from_file(512, "/pkg/data/gpt_golden/gpt.fuchsia.blk");
1457 let (client, _task) = connect_to_server(server).await;
1458
1459 struct ExpectedPartition {
1460 label: &'static str,
1461 type_guid: &'static str,
1462 blocks: Range<u64>,
1463 }
1464 const EXPECTED_PARTITIONS: [ExpectedPartition; 8] = [
1465 ExpectedPartition {
1466 label: "bootloader",
1467 type_guid: "5ece94fe-4c86-11e8-a15b-480fcf35f8e6",
1468 blocks: 11..12,
1469 },
1470 ExpectedPartition {
1471 label: "zircon_a",
1472 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1473 blocks: 12..13,
1474 },
1475 ExpectedPartition {
1476 label: "zircon_b",
1477 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1478 blocks: 13..14,
1479 },
1480 ExpectedPartition {
1481 label: "zircon_r",
1482 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1483 blocks: 14..15,
1484 },
1485 ExpectedPartition {
1486 label: "vbmeta_a",
1487 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1488 blocks: 15..16,
1489 },
1490 ExpectedPartition {
1491 label: "vbmeta_b",
1492 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1493 blocks: 16..17,
1494 },
1495 ExpectedPartition {
1496 label: "vbmeta_r",
1497 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1498 blocks: 17..18,
1499 },
1500 ExpectedPartition {
1501 label: "durable_boot",
1502 type_guid: "a409e16b-78aa-4acc-995c-302352621a41",
1503 blocks: 18..19,
1504 },
1505 ];
1506
1507 let manager = Gpt::open(client).await.expect("load should succeed");
1508 for i in 0..EXPECTED_PARTITIONS.len() as u32 {
1509 let partition = manager.partitions().get(&i).expect("No entry found");
1510 let expected = &EXPECTED_PARTITIONS[i as usize];
1511 assert_eq!(partition.label, expected.label);
1512 assert_eq!(partition.type_guid.to_string(), expected.type_guid);
1513 assert_eq!(partition.start_block, expected.blocks.start);
1514 assert_eq!(partition.num_blocks, expected.blocks.end - expected.blocks.start);
1515 }
1516 }
1517
1518 #[fuchsia::test]
1519 async fn add_partitions_till_no_blocks_left() {
1520 let server = VmoBackedServer::new(128, 512, &[]).expect("Failed to create VmoBackedServer");
1521 let (client, _task) = connect_to_server(server).await;
1522 Gpt::format(client.clone(), vec![PartitionInfo::nil(); 32]).await.expect("format failed");
1523 let mut manager = Gpt::open(client).await.expect("load should succeed");
1524 let mut transaction = manager.create_transaction().unwrap();
1525 assert_eq!(transaction.partitions.len(), 32);
1526 let mut num = 0;
1527 loop {
1528 match manager.add_partition(
1529 &mut transaction,
1530 crate::PartitionInfo {
1531 label: format!("part-{num}"),
1532 type_guid: crate::Guid::generate(),
1533 instance_guid: crate::Guid::generate(),
1534 start_block: 0,
1535 num_blocks: 1,
1536 flags: 0,
1537 },
1538 ) {
1539 Ok(_) => {
1540 num += 1;
1541 }
1542 Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1543 Err(AddPartitionError::NoSpace) => break,
1544 };
1545 }
1546 assert!(num <= 32);
1547 manager.commit_transaction(transaction).await.expect("Commit failed");
1548
1549 assert_eq!(manager.header().num_parts, 32);
1552 assert_eq!(manager.partitions().len(), num);
1553
1554 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1555 assert_eq!(manager.header().num_parts, 32);
1556 assert_eq!(manager.partitions().len(), num);
1557 }
1558
1559 #[fuchsia::test]
1560 async fn add_partitions_till_no_slots_left() {
1561 let server = VmoBackedServer::new(128, 512, &[]).expect("Failed to create VmoBackedServer");
1562 let (client, _task) = connect_to_server(server).await;
1563 Gpt::format(client.clone(), vec![PartitionInfo::nil(); 4]).await.expect("format failed");
1564 let mut manager = Gpt::open(client).await.expect("load should succeed");
1565 let mut transaction = manager.create_transaction().unwrap();
1566 assert_eq!(transaction.partitions.len(), 4);
1567 let mut num = 0;
1568 loop {
1569 match manager.add_partition(
1570 &mut transaction,
1571 crate::PartitionInfo {
1572 label: format!("part-{num}"),
1573 type_guid: crate::Guid::generate(),
1574 instance_guid: crate::Guid::generate(),
1575 start_block: 0,
1576 num_blocks: 1,
1577 flags: 0,
1578 },
1579 ) {
1580 Ok(_) => {
1581 num += 1;
1582 }
1583 Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1584 Err(AddPartitionError::NoSpace) => break,
1585 };
1586 }
1587 assert!(num <= 4);
1588 manager.commit_transaction(transaction).await.expect("Commit failed");
1589
1590 assert_eq!(manager.header().num_parts, 4);
1593 assert_eq!(manager.partitions().len(), num);
1594
1595 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1596 assert_eq!(manager.header().num_parts, 4);
1597 assert_eq!(manager.partitions().len(), num);
1598 }
1599
1600 struct ShufflingObserver {
1602 start: Arc<AtomicBool>,
1604 shuffle_if_contains_offset: u64,
1606 }
1607
1608 impl Observer for ShufflingObserver {
1609 fn flush(&self, writes: Option<&mut WriteCache>) {
1610 if self.start.load(Ordering::Relaxed) {
1611 let Some(writes) = writes else { unreachable!() };
1612 if writes
1613 .iter()
1614 .filter(|(offset, _)| **offset == self.shuffle_if_contains_offset)
1615 .next()
1616 .is_some()
1617 {
1618 writes.shuffle();
1619 writes.discard_some();
1620 }
1621 }
1622 }
1623
1624 fn close(&self, writes: Option<&mut WriteCache>) {
1625 if self.start.load(Ordering::Relaxed) {
1627 let Some(writes) = writes else { unreachable!() };
1628 writes.shuffle();
1629 }
1630 }
1631 }
1632
1633 #[fuchsia::test]
1634 async fn metadata_update_is_atomic() {
1635 const BLOCK_SIZE: u64 = 512;
1636 const BLOCK_COUNT: u64 = 128;
1637 for shuffle_if_contains_offset in [1, BLOCK_COUNT - 1] {
1642 let vmo = zx::Vmo::create(BLOCK_SIZE * BLOCK_COUNT).unwrap();
1643 let start_shuffling = Arc::new(AtomicBool::new(false));
1644 let server = VmoBackedServerOptions {
1645 initial_contents: InitialContents::FromVmo(vmo),
1646 block_size: BLOCK_SIZE as u32,
1647 observer: Some(Box::new(ShufflingObserver {
1648 start: start_shuffling.clone(),
1649 shuffle_if_contains_offset,
1650 })),
1651 write_tracking: true,
1652 ..Default::default()
1653 }
1654 .build()
1655 .unwrap();
1656 let (client, _task) = connect_to_server(server).await;
1657 Gpt::format(client.clone(), vec![PartitionInfo::nil(); 80])
1658 .await
1659 .expect("format failed");
1660
1661 start_shuffling.store(true, Ordering::Relaxed);
1662
1663 let mut manager = Gpt::open(client).await.expect("load should succeed");
1664 let mut transaction = manager.create_transaction().unwrap();
1665 transaction.partitions.truncate(40);
1666 let mut num = 0;
1667 loop {
1668 match manager.add_partition(
1669 &mut transaction,
1670 crate::PartitionInfo {
1671 label: format!("part-{num}"),
1672 type_guid: crate::Guid::generate(),
1673 instance_guid: crate::Guid::generate(),
1674 start_block: 0,
1675 num_blocks: 1,
1676 flags: 0,
1677 },
1678 ) {
1679 Ok(_) => {
1680 num += 1;
1681 }
1682 Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1683 Err(AddPartitionError::NoSpace) => break,
1684 };
1685 }
1686 assert!(num <= 40);
1687 manager.commit_transaction(transaction).await.expect("Commit failed");
1688
1689 assert_eq!(manager.header().num_parts, 40);
1691 assert_eq!(manager.partitions().len(), num);
1692
1693 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1697 let len = manager.partitions().len();
1698 assert!(len == 0 || len == num);
1699 }
1700 }
1701
1702 async fn try_load_invalid_gpt(
1703 block_count: u64,
1704 block_size: u32,
1705 mut header: format::Header,
1706 entries: Vec<format::PartitionTableEntry>,
1707 ) -> Result<Gpt, Error> {
1708 let vmo = zx::Vmo::create(block_count * block_size as u64).unwrap();
1709
1710 let part_size = std::mem::size_of::<format::PartitionTableEntry>();
1711 let mut part_table_bytes = vec![0u8; entries.len() * part_size];
1712 for (i, entry) in entries.iter().enumerate() {
1713 part_table_bytes[i * part_size..(i + 1) * part_size].copy_from_slice(entry.as_bytes());
1714 }
1715
1716 let crc_parts = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC).checksum(&part_table_bytes);
1717 header.crc32_parts = crc_parts;
1718 header.crc32 = header.compute_checksum();
1719
1720 vmo.write(header.as_bytes(), block_size as u64).unwrap();
1722 vmo.write(&part_table_bytes, 2 * block_size as u64).unwrap();
1723
1724 let mut backup_header = header.clone();
1726 backup_header.current_lba = block_count - 1;
1727 backup_header.backup_lba = 1;
1728 backup_header.part_start = backup_header.last_usable.saturating_add(1);
1729 backup_header.crc32 = backup_header.compute_checksum();
1730
1731 vmo.write(backup_header.as_bytes(), (block_count - 1) * block_size as u64).unwrap();
1732
1733 let partition_table_len = header.part_size as u64 * header.num_parts as u64;
1734 let partition_table_blocks =
1735 partition_table_len.checked_next_multiple_of(block_size as u64).unwrap()
1736 / block_size as u64;
1737
1738 if let Some(end) = backup_header.part_start.checked_add(partition_table_blocks) {
1739 if end <= backup_header.current_lba {
1740 if let Some(offset) = backup_header.part_start.checked_mul(block_size as u64) {
1741 vmo.write(&part_table_bytes, offset).unwrap();
1742 }
1743 }
1744 }
1745
1746 let vmo_clone = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1747 let server = VmoBackedServer::from_vmo(block_size, vmo_clone).unwrap();
1748 let (client, _task) = connect_to_server(server).await;
1749
1750 Gpt::open(client).await
1751 }
1752
1753 #[fuchsia::test]
1754 async fn test_partition_before_first_usable() {
1755 let block_count = 128;
1756 let block_size = 512;
1757 let header = format::Header::new(block_count, block_size, 128).unwrap();
1758 let mut entries = vec![format::PartitionTableEntry::empty(); 128];
1759 entries[0] = format::PartitionTableEntry {
1760 type_guid: [1; 16],
1761 instance_guid: [1; 16],
1762 first_lba: header.first_usable - 1,
1763 last_lba: header.first_usable + 10,
1764 ..format::PartitionTableEntry::empty()
1765 };
1766 let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1767 assert!(res.is_err());
1768 let err_msg = format!("{:?}", res.err().unwrap());
1769 assert!(
1770 err_msg.contains("GPT partition table entry invalid"),
1771 "Unexpected error: {}",
1772 err_msg
1773 );
1774 }
1775
1776 #[fuchsia::test]
1777 async fn test_partition_after_last_usable() {
1778 let block_count = 128;
1779 let block_size = 512;
1780 let header = format::Header::new(block_count, block_size, 128).unwrap();
1781 let mut entries = vec![format::PartitionTableEntry::empty(); 128];
1782 entries[0] = format::PartitionTableEntry {
1783 type_guid: [1; 16],
1784 instance_guid: [1; 16],
1785 first_lba: header.first_usable,
1786 last_lba: header.last_usable + 1,
1787 ..format::PartitionTableEntry::empty()
1788 };
1789 let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1790 assert!(res.is_err());
1791 let err_msg = format!("{:?}", res.err().unwrap());
1792 assert!(
1793 err_msg.contains("GPT partition table entry invalid"),
1794 "Unexpected error: {}",
1795 err_msg
1796 );
1797 }
1798
1799 #[fuchsia::test]
1800 async fn test_overlapping_partitions() {
1801 let block_count = 128;
1802 let block_size = 512;
1803 let header = format::Header::new(block_count, block_size, 128).unwrap();
1804 let mut entries = vec![format::PartitionTableEntry::empty(); 128];
1805 entries[0] = format::PartitionTableEntry {
1806 type_guid: [1; 16],
1807 instance_guid: [1; 16],
1808 first_lba: header.first_usable,
1809 last_lba: header.first_usable + 10,
1810 ..format::PartitionTableEntry::empty()
1811 };
1812 entries[1] = format::PartitionTableEntry {
1813 type_guid: [1; 16],
1814 instance_guid: [2; 16],
1815 first_lba: header.first_usable + 5, last_lba: header.first_usable + 15,
1817 ..format::PartitionTableEntry::empty()
1818 };
1819 let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1820 assert!(res.is_err());
1821 let err_msg = format!("{:?}", res.err().unwrap());
1822 assert!(err_msg.contains("Overlapping partitions"), "Unexpected error: {}", err_msg);
1823 }
1824
1825 #[fuchsia::test]
1826 async fn test_header_first_usable_too_small() {
1827 let block_count = 128;
1828 let block_size = 512;
1829 let mut header = format::Header::new(block_count, block_size, 128).unwrap();
1830 header.first_usable = 33;
1836 let entries = vec![format::PartitionTableEntry::empty(); 128];
1837 let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1838 assert!(res.is_err());
1839 let err_msg = format!("{:?}", res.err().unwrap());
1840 assert!(err_msg.contains("Invalid first_usable"), "Unexpected error: {}", err_msg);
1841 }
1842
1843 #[fuchsia::test]
1844 async fn test_header_last_usable_too_large() {
1845 let block_count = 128;
1846 let block_size = 512;
1847 let mut header = format::Header::new(block_count, block_size, 128).unwrap();
1848 header.last_usable = 95;
1853 let entries = vec![format::PartitionTableEntry::empty(); 128];
1854 let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1855 assert!(res.is_err());
1856 let err_msg = format!("{:?}", res.err().unwrap());
1857 assert!(err_msg.contains("Invalid last_usable"), "Unexpected error: {}", err_msg);
1858 }
1859
1860 #[fuchsia::test]
1861 fn test_from_entry_last_lba_overflow() {
1862 let entry = format::PartitionTableEntry {
1863 type_guid: [1; 16],
1864 instance_guid: [1; 16],
1865 first_lba: 34,
1866 last_lba: u64::MAX,
1867 flags: 0,
1868 name: [0; 36],
1869 };
1870 assert!(PartitionInfo::from_entry(&entry).is_err());
1871 }
1872
1873 #[fuchsia::test]
1874 fn test_from_entry_last_lba_less_than_first_lba() {
1875 let entry = format::PartitionTableEntry {
1876 type_guid: [1; 16],
1877 instance_guid: [1; 16],
1878 first_lba: 34,
1879 last_lba: 33,
1880 flags: 0,
1881 name: [0; 36],
1882 };
1883 assert!(PartitionInfo::from_entry(&entry).is_err());
1884 }
1885
1886 #[fuchsia::test]
1887 async fn test_header_last_usable_overflow() {
1888 let block_count = 128;
1889 let block_size = 512;
1890 let mut header = format::Header::new(block_count, block_size, 128).unwrap();
1891 header.last_usable = u64::MAX;
1892 let entries = vec![format::PartitionTableEntry::empty(); 128];
1893 let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1894 assert!(res.is_err());
1895 }
1896}