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