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