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