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 fake_block_server::{FakeServer, FakeServerOptions};
502 use std::ops::Range;
503 use std::sync::Arc;
504 use zx::HandleBased;
505 use {fidl_fuchsia_hardware_block_volume as fvolume, fuchsia_async as fasync};
506
507 #[fuchsia::test]
508 async fn load_unformatted_gpt() {
509 let vmo = zx::Vmo::create(4096).unwrap();
510 let server = Arc::new(FakeServer::from_vmo(512, vmo));
511 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
512
513 let _task =
514 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
515 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
516 Gpt::open(client).await.expect_err("load should fail");
517 }
518
519 #[fuchsia::test]
520 async fn load_formatted_empty_gpt() {
521 let vmo = zx::Vmo::create(4096).unwrap();
522 let server = Arc::new(FakeServer::from_vmo(512, vmo));
523 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
524
525 let _task =
526 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
527 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
528 Gpt::format(client.clone(), vec![]).await.expect("format failed");
529 Gpt::open(client).await.expect("load should succeed");
530 }
531
532 #[fuchsia::test]
533 async fn load_formatted_gpt_with_minimal_size() {
534 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
535 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
536 const PART_NAME: &str = "part";
537
538 let vmo = zx::Vmo::create(6 * 4096).unwrap();
539 let server = Arc::new(FakeServer::from_vmo(4096, vmo));
540 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
541
542 let _task =
543 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
544 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
545 Gpt::format(
546 client.clone(),
547 vec![PartitionInfo {
548 label: PART_NAME.to_string(),
549 type_guid: Guid::from_bytes(PART_TYPE_GUID),
550 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
551 start_block: 3,
552 num_blocks: 1,
553 flags: 0,
554 }],
555 )
556 .await
557 .expect("format failed");
558 let manager = Gpt::open(client).await.expect("load should succeed");
559 assert_eq!(manager.header.first_usable, 3);
560 assert_eq!(manager.header.last_usable, 3);
561 let partition = manager.partitions().get(&0).expect("No entry found");
562 assert_eq!(partition.start_block, 3);
563 assert_eq!(partition.num_blocks, 1);
564 assert!(manager.partitions().get(&1).is_none());
565 }
566
567 #[fuchsia::test]
568 async fn load_formatted_gpt_with_one_partition() {
569 const PART_TYPE_GUID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
570 const PART_INSTANCE_GUID: [u8; 16] =
571 [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
572 const PART_NAME: &str = "part";
573
574 let vmo = zx::Vmo::create(4096).unwrap();
575 let server = Arc::new(FakeServer::from_vmo(512, vmo));
576 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
577
578 let _task =
579 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
580 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
581 Gpt::format(
582 client.clone(),
583 vec![PartitionInfo {
584 label: PART_NAME.to_string(),
585 type_guid: Guid::from_bytes(PART_TYPE_GUID),
586 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
587 start_block: 4,
588 num_blocks: 1,
589 flags: 0,
590 }],
591 )
592 .await
593 .expect("format failed");
594 let manager = Gpt::open(client).await.expect("load should succeed");
595 let partition = manager.partitions().get(&0).expect("No entry found");
596 assert_eq!(partition.label, "part");
597 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
598 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
599 assert_eq!(partition.start_block, 4);
600 assert_eq!(partition.num_blocks, 1);
601 assert!(manager.partitions().get(&1).is_none());
602 }
603
604 #[fuchsia::test]
605 async fn load_formatted_gpt_with_two_partitions() {
606 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
607 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
608 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
609 const PART_1_NAME: &str = "part1";
610 const PART_2_NAME: &str = "part2";
611
612 let vmo = zx::Vmo::create(8192).unwrap();
613 let server = Arc::new(FakeServer::from_vmo(512, vmo));
614 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
615
616 let _task =
617 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
618 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
619 Gpt::format(
620 client.clone(),
621 vec![
622 PartitionInfo {
623 label: PART_1_NAME.to_string(),
624 type_guid: Guid::from_bytes(PART_TYPE_GUID),
625 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
626 start_block: 4,
627 num_blocks: 1,
628 flags: 0,
629 },
630 PartitionInfo {
631 label: PART_2_NAME.to_string(),
632 type_guid: Guid::from_bytes(PART_TYPE_GUID),
633 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
634 start_block: 7,
635 num_blocks: 1,
636 flags: 0,
637 },
638 ],
639 )
640 .await
641 .expect("format failed");
642 let manager = Gpt::open(client).await.expect("load should succeed");
643 let partition = manager.partitions().get(&0).expect("No entry found");
644 assert_eq!(partition.label, PART_1_NAME);
645 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
646 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
647 assert_eq!(partition.start_block, 4);
648 assert_eq!(partition.num_blocks, 1);
649 let partition = manager.partitions().get(&1).expect("No entry found");
650 assert_eq!(partition.label, PART_2_NAME);
651 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
652 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
653 assert_eq!(partition.start_block, 7);
654 assert_eq!(partition.num_blocks, 1);
655 assert!(manager.partitions().get(&2).is_none());
656 }
657
658 #[fuchsia::test]
659 async fn load_formatted_gpt_with_extra_bytes_in_partition_name() {
660 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
661 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
662 const PART_NAME: &str = "part\0extrastuff";
663
664 let vmo = zx::Vmo::create(4096).unwrap();
665 let server = Arc::new(FakeServer::from_vmo(512, vmo));
666 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
667
668 let _task =
669 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
670 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
671 Gpt::format(
672 client.clone(),
673 vec![PartitionInfo {
674 label: PART_NAME.to_string(),
675 type_guid: Guid::from_bytes(PART_TYPE_GUID),
676 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
677 start_block: 4,
678 num_blocks: 1,
679 flags: 0,
680 }],
681 )
682 .await
683 .expect("format failed");
684 let manager = Gpt::open(client).await.expect("load should succeed");
685 let partition = manager.partitions().get(&0).expect("No entry found");
686 assert_eq!(partition.label, "part");
688 }
689
690 #[fuchsia::test]
691 async fn load_formatted_gpt_with_empty_partition_name() {
692 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
693 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
694 const PART_NAME: &str = "";
695
696 let vmo = zx::Vmo::create(4096).unwrap();
697 let server = Arc::new(FakeServer::from_vmo(512, vmo));
698 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
699
700 let _task =
701 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
702 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
703 Gpt::format(
704 client.clone(),
705 vec![PartitionInfo {
706 label: PART_NAME.to_string(),
707 type_guid: Guid::from_bytes(PART_TYPE_GUID),
708 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
709 start_block: 4,
710 num_blocks: 1,
711 flags: 0,
712 }],
713 )
714 .await
715 .expect("format failed");
716 let manager = Gpt::open(client).await.expect("load should succeed");
717 let partition = manager.partitions().get(&0).expect("No entry found");
718 assert_eq!(partition.label, "");
719 }
720
721 #[fuchsia::test]
722 async fn load_formatted_gpt_with_invalid_primary_header() {
723 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
724 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
725 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
726 const PART_1_NAME: &str = "part1";
727 const PART_2_NAME: &str = "part2";
728
729 let vmo = zx::Vmo::create(8192).unwrap();
730
731 let server = Arc::new(FakeServer::from_vmo(512, vmo));
732 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
733
734 let _task =
735 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
736 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
737 Gpt::format(
738 client.clone(),
739 vec![
740 PartitionInfo {
741 label: PART_1_NAME.to_string(),
742 type_guid: Guid::from_bytes(PART_TYPE_GUID),
743 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
744 start_block: 4,
745 num_blocks: 1,
746 flags: 0,
747 },
748 PartitionInfo {
749 label: PART_2_NAME.to_string(),
750 type_guid: Guid::from_bytes(PART_TYPE_GUID),
751 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
752 start_block: 7,
753 num_blocks: 1,
754 flags: 0,
755 },
756 ],
757 )
758 .await
759 .expect("format failed");
760 client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 512).await.unwrap();
762 let manager = Gpt::open(client).await.expect("load should succeed");
763 let partition = manager.partitions().get(&0).expect("No entry found");
764 assert_eq!(partition.label, PART_1_NAME);
765 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
766 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
767 assert_eq!(partition.start_block, 4);
768 assert_eq!(partition.num_blocks, 1);
769 let partition = manager.partitions().get(&1).expect("No entry found");
770 assert_eq!(partition.label, PART_2_NAME);
771 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
772 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
773 assert_eq!(partition.start_block, 7);
774 assert_eq!(partition.num_blocks, 1);
775 assert!(manager.partitions().get(&2).is_none());
776 }
777
778 #[fuchsia::test]
779 async fn load_formatted_gpt_with_invalid_primary_partition_table() {
780 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
781 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
782 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
783 const PART_1_NAME: &str = "part1";
784 const PART_2_NAME: &str = "part2";
785
786 let vmo = zx::Vmo::create(8192).unwrap();
787
788 let server = Arc::new(FakeServer::from_vmo(512, vmo));
789 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
790
791 let _task =
792 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
793 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
794 Gpt::format(
795 client.clone(),
796 vec![
797 PartitionInfo {
798 label: PART_1_NAME.to_string(),
799 type_guid: Guid::from_bytes(PART_TYPE_GUID),
800 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
801 start_block: 4,
802 num_blocks: 1,
803 flags: 0,
804 },
805 PartitionInfo {
806 label: PART_2_NAME.to_string(),
807 type_guid: Guid::from_bytes(PART_TYPE_GUID),
808 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
809 start_block: 7,
810 num_blocks: 1,
811 flags: 0,
812 },
813 ],
814 )
815 .await
816 .expect("format failed");
817 client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 1024).await.unwrap();
819 let manager = Gpt::open(client).await.expect("load should succeed");
820 let partition = manager.partitions().get(&0).expect("No entry found");
821 assert_eq!(partition.label, PART_1_NAME);
822 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
823 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
824 assert_eq!(partition.start_block, 4);
825 assert_eq!(partition.num_blocks, 1);
826 let partition = manager.partitions().get(&1).expect("No entry found");
827 assert_eq!(partition.label, PART_2_NAME);
828 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
829 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
830 assert_eq!(partition.start_block, 7);
831 assert_eq!(partition.num_blocks, 1);
832 assert!(manager.partitions().get(&2).is_none());
833 }
834
835 #[fuchsia::test]
836 async fn drop_transaction() {
837 let vmo = zx::Vmo::create(8192).unwrap();
838 let server = Arc::new(FakeServer::from_vmo(512, vmo));
839 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
840
841 let _task =
842 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
843 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
844 Gpt::format(client.clone(), vec![]).await.expect("format failed");
845 let manager = Gpt::open(client).await.expect("load should succeed");
846 {
847 let _transaction = manager.create_transaction().unwrap();
848 assert!(manager.create_transaction().is_none());
849 }
850 let _transaction =
851 manager.create_transaction().expect("Transaction dropped but not available");
852 }
853
854 #[fuchsia::test]
855 async fn commit_empty_transaction() {
856 let vmo = zx::Vmo::create(8192).unwrap();
857 let server = Arc::new(FakeServer::from_vmo(512, vmo));
858 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
859
860 let _task =
861 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
862 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
863 Gpt::format(client.clone(), vec![]).await.expect("format failed");
864 let mut manager = Gpt::open(client).await.expect("load should succeed");
865 let transaction = manager.create_transaction().unwrap();
866 manager.commit_transaction(transaction).await.expect("Commit failed");
867
868 assert_eq!(manager.header().num_parts, 0);
871 assert!(manager.partitions().is_empty());
872 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
873 assert_eq!(manager.header().num_parts, 0);
874 assert!(manager.partitions().is_empty());
875 }
876
877 #[fuchsia::test]
878 async fn add_partition_in_transaction() {
879 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
880 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
881 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
882 const PART_1_NAME: &str = "part1";
883 const PART_2_NAME: &str = "part2";
884
885 let vmo = zx::Vmo::create(8192).unwrap();
886 let server = Arc::new(FakeServer::from_vmo(512, vmo));
887 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
888
889 let _task =
890 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
891 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
892 Gpt::format(
893 client.clone(),
894 vec![PartitionInfo {
895 label: PART_1_NAME.to_string(),
896 type_guid: Guid::from_bytes(PART_TYPE_GUID),
897 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
898 start_block: 4,
899 num_blocks: 1,
900 flags: 0,
901 }],
902 )
903 .await
904 .expect("format failed");
905 let mut manager = Gpt::open(client).await.expect("load should succeed");
906 let mut transaction = manager.create_transaction().unwrap();
907 assert_eq!(transaction.partitions.len(), 1);
908 transaction.partitions.push(crate::PartitionInfo {
909 label: PART_2_NAME.to_string(),
910 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
911 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
912 start_block: 7,
913 num_blocks: 1,
914 flags: 0,
915 });
916 manager.commit_transaction(transaction).await.expect("Commit failed");
917
918 assert_eq!(manager.header().num_parts, 2);
921 assert!(manager.partitions().get(&2).is_none());
922 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
923 assert_eq!(manager.header().num_parts, 2);
924 let partition = manager.partitions().get(&0).expect("No entry found");
925 assert_eq!(partition.label, PART_1_NAME);
926 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
927 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
928 assert_eq!(partition.start_block, 4);
929 assert_eq!(partition.num_blocks, 1);
930 let partition = manager.partitions().get(&1).expect("No entry found");
931 assert_eq!(partition.label, PART_2_NAME);
932 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
933 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
934 assert_eq!(partition.start_block, 7);
935 assert_eq!(partition.num_blocks, 1);
936 assert!(manager.partitions().get(&2).is_none());
937 }
938
939 #[fuchsia::test]
940 async fn remove_partition_in_transaction() {
941 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
942 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
943 const PART_NAME: &str = "part1";
944
945 let vmo = zx::Vmo::create(8192).unwrap();
946 let server = Arc::new(FakeServer::from_vmo(512, vmo));
947 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
948
949 let _task =
950 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
951 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
952 Gpt::format(
953 client.clone(),
954 vec![PartitionInfo {
955 label: PART_NAME.to_string(),
956 type_guid: Guid::from_bytes(PART_TYPE_GUID),
957 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
958 start_block: 4,
959 num_blocks: 1,
960 flags: 0,
961 }],
962 )
963 .await
964 .expect("format failed");
965 let mut manager = Gpt::open(client).await.expect("load should succeed");
966 let mut transaction = manager.create_transaction().unwrap();
967 assert_eq!(transaction.partitions.len(), 1);
968 transaction.partitions.clear();
969 manager.commit_transaction(transaction).await.expect("Commit failed");
970
971 assert_eq!(manager.header().num_parts, 0);
974 assert!(manager.partitions().get(&0).is_none());
975 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
976 assert_eq!(manager.header().num_parts, 0);
977 assert!(manager.partitions().get(&0).is_none());
978 }
979
980 #[fuchsia::test]
981 async fn modify_partition_in_transaction() {
982 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
983 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
984 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
985 const PART_1_NAME: &str = "part1";
986 const PART_2_NAME: &str = "part2";
987
988 let vmo = zx::Vmo::create(8192).unwrap();
989 let server = Arc::new(FakeServer::from_vmo(512, vmo));
990 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
991
992 let _task =
993 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
994 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
995 Gpt::format(
996 client.clone(),
997 vec![PartitionInfo {
998 label: PART_1_NAME.to_string(),
999 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1000 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1001 start_block: 4,
1002 num_blocks: 1,
1003 flags: 0,
1004 }],
1005 )
1006 .await
1007 .expect("format failed");
1008 let mut manager = Gpt::open(client).await.expect("load should succeed");
1009 let mut transaction = manager.create_transaction().unwrap();
1010 assert_eq!(transaction.partitions.len(), 1);
1011 transaction.partitions[0] = crate::PartitionInfo {
1012 label: PART_2_NAME.to_string(),
1013 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1014 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1015 start_block: 7,
1016 num_blocks: 1,
1017 flags: 0,
1018 };
1019 manager.commit_transaction(transaction).await.expect("Commit failed");
1020
1021 assert_eq!(manager.header().num_parts, 1);
1024 let partition = manager.partitions().get(&0).expect("No entry found");
1025 assert_eq!(partition.label, PART_2_NAME);
1026 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1027 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1028 assert_eq!(partition.start_block, 7);
1029 assert_eq!(partition.num_blocks, 1);
1030 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1031 assert_eq!(manager.header().num_parts, 1);
1032 let partition = manager.partitions().get(&0).expect("No entry found");
1033 assert_eq!(partition.label, PART_2_NAME);
1034 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1035 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1036 assert_eq!(partition.start_block, 7);
1037 assert_eq!(partition.num_blocks, 1);
1038 assert!(manager.partitions().get(&1).is_none());
1039 }
1040
1041 #[fuchsia::test]
1042 async fn grow_partition_table_in_transaction() {
1043 let vmo = zx::Vmo::create(1024 * 1024).unwrap();
1044 let server = Arc::new(FakeServer::from_vmo(512, vmo));
1045 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1046
1047 let _task =
1048 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1049 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1050 Gpt::format(
1051 client.clone(),
1052 vec![PartitionInfo {
1053 label: "part".to_string(),
1054 type_guid: Guid::from_bytes([1u8; 16]),
1055 instance_guid: Guid::from_bytes([1u8; 16]),
1056 start_block: 34,
1057 num_blocks: 1,
1058 flags: 0,
1059 }],
1060 )
1061 .await
1062 .expect("format failed");
1063 let mut manager = Gpt::open(client).await.expect("load should succeed");
1064 assert_eq!(manager.header().num_parts, 1);
1065 assert_eq!(manager.header().first_usable, 3);
1066 let mut transaction = manager.create_transaction().unwrap();
1067 transaction.partitions.resize(128, crate::PartitionInfo::nil());
1068 manager.commit_transaction(transaction).await.expect("Commit failed");
1069
1070 assert_eq!(manager.header().num_parts, 128);
1073 assert_eq!(manager.header().first_usable, 34);
1074 let partition = manager.partitions().get(&0).expect("No entry found");
1075 assert_eq!(partition.label, "part");
1076 assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1077 assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1078 assert_eq!(partition.start_block, 34);
1079 assert_eq!(partition.num_blocks, 1);
1080 assert!(manager.partitions().get(&1).is_none());
1081 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1082 assert_eq!(manager.header().num_parts, 128);
1083 assert_eq!(manager.header().first_usable, 34);
1084 let partition = manager.partitions().get(&0).expect("No entry found");
1085 assert_eq!(partition.label, "part");
1086 assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1087 assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1088 assert_eq!(partition.start_block, 34);
1089 assert_eq!(partition.num_blocks, 1);
1090 assert!(manager.partitions().get(&1).is_none());
1091 }
1092
1093 #[fuchsia::test]
1094 async fn shrink_partition_table_in_transaction() {
1095 let vmo = zx::Vmo::create(1024 * 1024).unwrap();
1096 let mut partitions = vec![];
1097 for i in 0..128 {
1098 partitions.push(PartitionInfo {
1099 label: format!("part-{i}"),
1100 type_guid: Guid::from_bytes([i as u8 + 1; 16]),
1101 instance_guid: Guid::from_bytes([i as u8 + 1; 16]),
1102 start_block: 34 + i,
1103 num_blocks: 1,
1104 flags: 0,
1105 });
1106 }
1107 let server = Arc::new(FakeServer::from_vmo(512, vmo));
1108 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1109
1110 let _task =
1111 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1112 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1113 Gpt::format(client.clone(), partitions).await.expect("format failed");
1114 let mut manager = Gpt::open(client).await.expect("load should succeed");
1115 assert_eq!(manager.header().num_parts, 128);
1116 assert_eq!(manager.header().first_usable, 34);
1117 let mut transaction = manager.create_transaction().unwrap();
1118 transaction.partitions.clear();
1119 manager.commit_transaction(transaction).await.expect("Commit failed");
1120
1121 assert_eq!(manager.header().num_parts, 0);
1124 assert_eq!(manager.header().first_usable, 2);
1125 assert!(manager.partitions().get(&0).is_none());
1126 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1127 assert_eq!(manager.header().num_parts, 0);
1128 assert_eq!(manager.header().first_usable, 2);
1129 assert!(manager.partitions().get(&0).is_none());
1130 }
1131
1132 #[fuchsia::test]
1133 async fn invalid_transaction_rejected() {
1134 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1135 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1136 const PART_NAME: &str = "part1";
1137
1138 let vmo = zx::Vmo::create(8192).unwrap();
1139 let server = Arc::new(FakeServer::from_vmo(512, vmo));
1140 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1141
1142 let _task =
1143 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1144 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1145 Gpt::format(
1146 client.clone(),
1147 vec![PartitionInfo {
1148 label: PART_NAME.to_string(),
1149 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1150 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1151 start_block: 4,
1152 num_blocks: 1,
1153 flags: 0,
1154 }],
1155 )
1156 .await
1157 .expect("format failed");
1158 let mut manager = Gpt::open(client).await.expect("load should succeed");
1159 let mut transaction = manager.create_transaction().unwrap();
1160 assert_eq!(transaction.partitions.len(), 1);
1161 transaction.partitions[0].start_block = 0;
1163 manager.commit_transaction(transaction).await.expect_err("Commit should have failed");
1164
1165 assert_eq!(manager.header().num_parts, 1);
1168 let partition = manager.partitions().get(&0).expect("No entry found");
1169 assert_eq!(partition.label, PART_NAME);
1170 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1171 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1172 assert_eq!(partition.start_block, 4);
1173 assert_eq!(partition.num_blocks, 1);
1174 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1175 assert_eq!(manager.header().num_parts, 1);
1176 let partition = manager.partitions().get(&0).expect("No entry found");
1177 assert_eq!(partition.label, PART_NAME);
1178 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1179 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1180 assert_eq!(partition.start_block, 4);
1181 assert_eq!(partition.num_blocks, 1);
1182 }
1183
1184 struct DiscardingObserver {
1186 block_size: u64,
1187 discard_range: Range<u64>,
1188 }
1189
1190 impl fake_block_server::Observer for DiscardingObserver {
1191 fn write(
1192 &self,
1193 device_block_offset: u64,
1194 block_count: u32,
1195 _vmo: &Arc<zx::Vmo>,
1196 _vmo_offset: u64,
1197 _opts: block_server::WriteOptions,
1198 ) -> fake_block_server::WriteAction {
1199 let write_range = (device_block_offset * self.block_size)
1200 ..(device_block_offset + block_count as u64) * self.block_size;
1201 if write_range.end <= self.discard_range.start
1202 || write_range.start >= self.discard_range.end
1203 {
1204 fake_block_server::WriteAction::Write
1205 } else {
1206 fake_block_server::WriteAction::Discard
1207 }
1208 }
1209 }
1210
1211 #[fuchsia::test]
1212 async fn transaction_applied_if_primary_metadata_partially_written() {
1213 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1214 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1215 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1216 const PART_1_NAME: &str = "part1";
1217 const PART_2_NAME: &str = "part2";
1218
1219 let vmo = zx::Vmo::create(8192).unwrap();
1220 let server = Arc::new(FakeServer::from(FakeServerOptions {
1221 vmo: Some(vmo),
1222 block_size: 512,
1223 observer: Some(Box::new(DiscardingObserver {
1224 discard_range: 1024..1536,
1225 block_size: 512,
1226 })),
1227 ..Default::default()
1228 }));
1229 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1230
1231 let _task =
1232 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1233 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1234 Gpt::format(
1235 client.clone(),
1236 vec![PartitionInfo {
1237 label: PART_1_NAME.to_string(),
1238 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1239 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1240 start_block: 4,
1241 num_blocks: 1,
1242 flags: 0,
1243 }],
1244 )
1245 .await
1246 .expect("format failed");
1247 let mut manager = Gpt::open(client).await.expect("load should succeed");
1248 let mut transaction = manager.create_transaction().unwrap();
1249 transaction.partitions.push(crate::PartitionInfo {
1250 label: PART_2_NAME.to_string(),
1251 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1252 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1253 start_block: 7,
1254 num_blocks: 1,
1255 flags: 0,
1256 });
1257 manager.commit_transaction(transaction).await.expect("Commit failed");
1258
1259 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1260 assert_eq!(manager.header().num_parts, 2);
1261 let partition = manager.partitions().get(&0).expect("No entry found");
1262 assert_eq!(partition.label, PART_1_NAME);
1263 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1264 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1265 assert_eq!(partition.start_block, 4);
1266 assert_eq!(partition.num_blocks, 1);
1267 let partition = manager.partitions().get(&1).expect("No entry found");
1268 assert_eq!(partition.label, PART_2_NAME);
1269 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1270 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1271 assert_eq!(partition.start_block, 7);
1272 assert_eq!(partition.num_blocks, 1);
1273 }
1274
1275 #[fuchsia::test]
1276 async fn transaction_not_applied_if_primary_metadata_not_written() {
1277 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1278 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1279 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1280 const PART_1_NAME: &str = "part1";
1281 const PART_2_NAME: &str = "part2";
1282
1283 let vmo = zx::Vmo::create(8192).unwrap();
1284 let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1285 {
1286 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1287 let server = Arc::new(FakeServer::from_vmo(512, vmo_dup));
1288 let _task =
1289 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1290 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1291 Gpt::format(
1292 client.clone(),
1293 vec![PartitionInfo {
1294 label: PART_1_NAME.to_string(),
1295 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1296 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1297 start_block: 4,
1298 num_blocks: 1,
1299 flags: 0,
1300 }],
1301 )
1302 .await
1303 .expect("format failed");
1304 }
1305 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1306 let server = Arc::new(FakeServer::from(FakeServerOptions {
1307 vmo: Some(vmo),
1308 block_size: 512,
1309 observer: Some(Box::new(DiscardingObserver {
1310 discard_range: 0..2048,
1311 block_size: 512,
1312 })),
1313 ..Default::default()
1314 }));
1315 let _task =
1316 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1317 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1318
1319 let mut manager = Gpt::open(client).await.expect("load should succeed");
1320 let mut transaction = manager.create_transaction().unwrap();
1321 transaction.partitions.push(crate::PartitionInfo {
1322 label: PART_2_NAME.to_string(),
1323 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1324 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1325 start_block: 7,
1326 num_blocks: 1,
1327 flags: 0,
1328 });
1329 manager.commit_transaction(transaction).await.expect("Commit failed");
1330
1331 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1332 assert_eq!(manager.header().num_parts, 1);
1333 let partition = manager.partitions().get(&0).expect("No entry found");
1334 assert_eq!(partition.label, PART_1_NAME);
1335 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1336 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1337 assert_eq!(partition.start_block, 4);
1338 assert_eq!(partition.num_blocks, 1);
1339 assert!(manager.partitions().get(&1).is_none());
1340 }
1341
1342 #[fuchsia::test]
1343 async fn transaction_not_applied_if_backup_metadata_partially_written() {
1344 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1345 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1346 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1347 const PART_1_NAME: &str = "part1";
1348 const PART_2_NAME: &str = "part2";
1349
1350 let vmo = zx::Vmo::create(8192).unwrap();
1351 let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1352 {
1353 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1354 let server = Arc::new(FakeServer::from_vmo(512, vmo_dup));
1355 let _task =
1356 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1357 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1358 Gpt::format(
1359 client.clone(),
1360 vec![PartitionInfo {
1361 label: PART_1_NAME.to_string(),
1362 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1363 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1364 start_block: 4,
1365 num_blocks: 1,
1366 flags: 0,
1367 }],
1368 )
1369 .await
1370 .expect("format failed");
1371 }
1372 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1373 let server = Arc::new(FakeServer::from(FakeServerOptions {
1374 vmo: Some(vmo),
1375 block_size: 512,
1376 observer: Some(Box::new(DiscardingObserver {
1377 discard_range: 0..7680,
1378 block_size: 512,
1379 })),
1380 ..Default::default()
1381 }));
1382 let _task =
1383 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1384 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1385
1386 let mut manager = Gpt::open(client).await.expect("load should succeed");
1387 let mut transaction = manager.create_transaction().unwrap();
1388 transaction.partitions.push(crate::PartitionInfo {
1389 label: PART_2_NAME.to_string(),
1390 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1391 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1392 start_block: 7,
1393 num_blocks: 1,
1394 flags: 0,
1395 });
1396 manager.commit_transaction(transaction).await.expect("Commit failed");
1397
1398 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1399 assert_eq!(manager.header().num_parts, 1);
1400 let partition = manager.partitions().get(&0).expect("No entry found");
1401 assert_eq!(partition.label, PART_1_NAME);
1402 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1403 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1404 assert_eq!(partition.start_block, 4);
1405 assert_eq!(partition.num_blocks, 1);
1406 assert!(manager.partitions().get(&1).is_none());
1407 }
1408
1409 #[fuchsia::test]
1410 async fn restore_primary_from_backup() {
1411 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1412 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1413 const PART_NAME: &str = "part1";
1414
1415 let vmo = zx::Vmo::create(8192).unwrap();
1416 let server = Arc::new(FakeServer::from_vmo(512, vmo));
1417 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1418
1419 let _task =
1420 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1421 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1422 Gpt::format(
1423 client.clone(),
1424 vec![PartitionInfo {
1425 label: PART_NAME.to_string(),
1426 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1427 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1428 start_block: 4,
1429 num_blocks: 1,
1430 flags: 0,
1431 }],
1432 )
1433 .await
1434 .expect("format failed");
1435 let mut old_metadata = vec![0u8; 2048];
1436 client.read_at(MutableBufferSlice::Memory(&mut old_metadata[..]), 0).await.unwrap();
1437 let mut buffer = vec![0u8; 2048];
1438 client.write_at(BufferSlice::Memory(&buffer[..]), 0).await.unwrap();
1439
1440 let manager = Gpt::open(client).await.expect("load should succeed");
1441 let client = manager.take_client();
1442
1443 client.read_at(MutableBufferSlice::Memory(&mut buffer[..]), 0).await.unwrap();
1444 assert_eq!(old_metadata, buffer);
1445 }
1446
1447 #[fuchsia::test]
1448 async fn load_golden_gpt_linux() {
1449 let contents = std::fs::read("/pkg/data/gpt_golden/gpt.linux.blk").unwrap();
1450 let server = Arc::new(FakeServer::new(contents.len() as u64 / 512, 512, &contents));
1451 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1452
1453 let _task =
1454 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1455 let manager = Gpt::open(Arc::new(RemoteBlockClient::new(client).await.unwrap()))
1456 .await
1457 .expect("load should succeed");
1458 let partition = manager.partitions().get(&0).expect("No entry found");
1459 assert_eq!(partition.label, "ext");
1460 assert_eq!(partition.type_guid.to_string(), "0fc63daf-8483-4772-8e79-3d69d8477de4");
1461 assert_eq!(partition.start_block, 8);
1462 assert_eq!(partition.num_blocks, 1);
1463 assert!(manager.partitions().get(&1).is_none());
1464 }
1465
1466 #[fuchsia::test]
1467 async fn load_golden_gpt_fuchsia() {
1468 let contents = std::fs::read("/pkg/data/gpt_golden/gpt.fuchsia.blk").unwrap();
1469 let server = Arc::new(FakeServer::new(contents.len() as u64 / 512, 512, &contents));
1470 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1471
1472 struct ExpectedPartition {
1473 label: &'static str,
1474 type_guid: &'static str,
1475 blocks: Range<u64>,
1476 }
1477 const EXPECTED_PARTITIONS: [ExpectedPartition; 8] = [
1478 ExpectedPartition {
1479 label: "bootloader",
1480 type_guid: "5ece94fe-4c86-11e8-a15b-480fcf35f8e6",
1481 blocks: 11..12,
1482 },
1483 ExpectedPartition {
1484 label: "zircon_a",
1485 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1486 blocks: 12..13,
1487 },
1488 ExpectedPartition {
1489 label: "zircon_b",
1490 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1491 blocks: 13..14,
1492 },
1493 ExpectedPartition {
1494 label: "zircon_r",
1495 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1496 blocks: 14..15,
1497 },
1498 ExpectedPartition {
1499 label: "vbmeta_a",
1500 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1501 blocks: 15..16,
1502 },
1503 ExpectedPartition {
1504 label: "vbmeta_b",
1505 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1506 blocks: 16..17,
1507 },
1508 ExpectedPartition {
1509 label: "vbmeta_r",
1510 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1511 blocks: 17..18,
1512 },
1513 ExpectedPartition {
1514 label: "durable_boot",
1515 type_guid: "a409e16b-78aa-4acc-995c-302352621a41",
1516 blocks: 18..19,
1517 },
1518 ];
1519
1520 let _task =
1521 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1522 let manager = Gpt::open(Arc::new(RemoteBlockClient::new(client).await.unwrap()))
1523 .await
1524 .expect("load should succeed");
1525 for i in 0..EXPECTED_PARTITIONS.len() as u32 {
1526 let partition = manager.partitions().get(&i).expect("No entry found");
1527 let expected = &EXPECTED_PARTITIONS[i as usize];
1528 assert_eq!(partition.label, expected.label);
1529 assert_eq!(partition.type_guid.to_string(), expected.type_guid);
1530 assert_eq!(partition.start_block, expected.blocks.start);
1531 assert_eq!(partition.num_blocks, expected.blocks.end - expected.blocks.start);
1532 }
1533 }
1534
1535 #[fuchsia::test]
1536 async fn add_partitions_till_no_blocks_left() {
1537 let vmo = zx::Vmo::create(65536).unwrap();
1538 let server = Arc::new(FakeServer::from_vmo(512, vmo));
1539 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1540
1541 let _task =
1542 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1543 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1544 Gpt::format(client.clone(), vec![PartitionInfo::nil(); 32]).await.expect("format failed");
1545 let mut manager = Gpt::open(client).await.expect("load should succeed");
1546 let mut transaction = manager.create_transaction().unwrap();
1547 assert_eq!(transaction.partitions.len(), 32);
1548 let mut num = 0;
1549 loop {
1550 match manager.add_partition(
1551 &mut transaction,
1552 crate::PartitionInfo {
1553 label: format!("part-{num}"),
1554 type_guid: crate::Guid::generate(),
1555 instance_guid: crate::Guid::generate(),
1556 start_block: 0,
1557 num_blocks: 1,
1558 flags: 0,
1559 },
1560 ) {
1561 Ok(_) => {
1562 num += 1;
1563 }
1564 Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1565 Err(AddPartitionError::NoSpace) => break,
1566 };
1567 }
1568 assert!(num <= 32);
1569 manager.commit_transaction(transaction).await.expect("Commit failed");
1570
1571 assert_eq!(manager.header().num_parts, 32);
1574 assert_eq!(manager.partitions().len(), num);
1575
1576 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1577 assert_eq!(manager.header().num_parts, 32);
1578 assert_eq!(manager.partitions().len(), num);
1579 }
1580
1581 #[fuchsia::test]
1582 async fn add_partitions_till_no_slots_left() {
1583 let vmo = zx::Vmo::create(65536).unwrap();
1584 let server = Arc::new(FakeServer::from_vmo(512, vmo));
1585 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1586
1587 let _task =
1588 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1589 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1590 Gpt::format(client.clone(), vec![PartitionInfo::nil(); 4]).await.expect("format failed");
1591 let mut manager = Gpt::open(client).await.expect("load should succeed");
1592 let mut transaction = manager.create_transaction().unwrap();
1593 assert_eq!(transaction.partitions.len(), 4);
1594 let mut num = 0;
1595 loop {
1596 match manager.add_partition(
1597 &mut transaction,
1598 crate::PartitionInfo {
1599 label: format!("part-{num}"),
1600 type_guid: crate::Guid::generate(),
1601 instance_guid: crate::Guid::generate(),
1602 start_block: 0,
1603 num_blocks: 1,
1604 flags: 0,
1605 },
1606 ) {
1607 Ok(_) => {
1608 num += 1;
1609 }
1610 Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1611 Err(AddPartitionError::NoSpace) => break,
1612 };
1613 }
1614 assert!(num <= 4);
1615 manager.commit_transaction(transaction).await.expect("Commit failed");
1616
1617 assert_eq!(manager.header().num_parts, 4);
1620 assert_eq!(manager.partitions().len(), num);
1621
1622 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1623 assert_eq!(manager.header().num_parts, 4);
1624 assert_eq!(manager.partitions().len(), num);
1625 }
1626}