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