1use anyhow::{Context as _, Error, anyhow};
6use block_client::{BlockClient, BufferSlice, MutableBufferSlice, RemoteBlockClient};
7use fuchsia_sync::Mutex;
8use std::collections::BTreeMap;
9use std::sync::Arc;
10use zerocopy::{FromBytes as _, IntoBytes as _};
11
12pub mod format;
13
14#[derive(Clone, Default, Debug)]
17pub struct Guid(uuid::Uuid);
18
19impl From<uuid::Uuid> for Guid {
20 fn from(uuid: uuid::Uuid) -> Self {
21 Self(uuid)
22 }
23}
24
25impl Guid {
26 pub fn from_bytes(bytes: [u8; 16]) -> Self {
27 Self(uuid::Uuid::from_bytes_le(bytes))
28 }
29
30 pub fn to_bytes(&self) -> [u8; 16] {
31 self.0.to_bytes_le()
32 }
33
34 pub fn to_string(&self) -> String {
35 self.0.to_string()
36 }
37
38 pub fn nil() -> Self {
39 Self(uuid::Uuid::nil())
40 }
41
42 pub fn generate() -> Self {
43 Self(uuid::Uuid::new_v4())
44 }
45}
46
47#[derive(Clone, Debug)]
48pub struct PartitionInfo {
49 pub label: String,
50 pub type_guid: Guid,
51 pub instance_guid: Guid,
52 pub start_block: u64,
53 pub num_blocks: u64,
54 pub flags: u64,
55}
56
57impl PartitionInfo {
58 pub fn from_entry(entry: &format::PartitionTableEntry) -> Result<Self, Error> {
59 let label = String::from_utf16(entry.name.split(|v| *v == 0u16).next().unwrap())?;
60 Ok(Self {
61 label,
62 type_guid: Guid::from_bytes(entry.type_guid),
63 instance_guid: Guid::from_bytes(entry.instance_guid),
64 start_block: entry.first_lba,
65 num_blocks: entry
66 .last_lba
67 .checked_add(1)
68 .unwrap()
69 .checked_sub(entry.first_lba)
70 .unwrap(),
71 flags: entry.flags,
72 })
73 }
74
75 pub fn as_entry(&self) -> format::PartitionTableEntry {
76 let mut name = [0u16; 36];
77 let raw = self.label.encode_utf16().collect::<Vec<_>>();
78 assert!(raw.len() <= name.len());
79 name[..raw.len()].copy_from_slice(&raw[..]);
80 format::PartitionTableEntry {
81 type_guid: self.type_guid.to_bytes(),
82 instance_guid: self.instance_guid.to_bytes(),
83 first_lba: self.start_block,
84 last_lba: self.start_block + self.num_blocks.saturating_sub(1),
85 flags: self.flags,
86 name,
87 }
88 }
89
90 pub fn nil() -> Self {
91 Self {
92 label: String::default(),
93 type_guid: Guid::default(),
94 instance_guid: Guid::default(),
95 start_block: 0,
96 num_blocks: 0,
97 flags: 0,
98 }
99 }
100
101 pub fn is_nil(&self) -> bool {
102 self.label == ""
103 && self.type_guid.0.is_nil()
104 && self.instance_guid.0.is_nil()
105 && self.start_block == 0
106 && self.num_blocks == 0
107 && self.flags == 0
108 }
109}
110
111enum WhichHeader {
112 Primary,
113 Backup,
114}
115
116impl WhichHeader {
117 fn offset(&self, block_size: u64, block_count: u64) -> u64 {
118 match self {
119 Self::Primary => block_size,
120 Self::Backup => (block_count - 1) * block_size,
121 }
122 }
123}
124
125async fn load_metadata(
126 client: &RemoteBlockClient,
127 which: WhichHeader,
128) -> Result<(format::Header, BTreeMap<u32, PartitionInfo>), Error> {
129 let bs = client.block_size() as usize;
130 let mut header_block = vec![0u8; client.block_size() as usize];
131 client
132 .read_at(
133 MutableBufferSlice::Memory(&mut header_block[..]),
134 which.offset(bs as u64, client.block_count() as u64),
135 )
136 .await
137 .context("Read header")?;
138 let (header, _) = format::Header::ref_from_prefix(&header_block[..])
139 .map_err(|_| anyhow!("Header has invalid size"))?;
140 header.ensure_integrity(client.block_count(), client.block_size() as u64)?;
141 let partition_table_offset = header.part_start * bs as u64;
142 let partition_table_size = (header.num_parts * header.part_size) as usize;
143 let partition_table_size_rounded = partition_table_size
144 .checked_next_multiple_of(bs)
145 .ok_or_else(|| anyhow!("Overflow when rounding up partition table size "))?;
146 let mut partition_table = BTreeMap::new();
147 if header.num_parts > 0 {
148 let mut partition_table_blocks = vec![0u8; partition_table_size_rounded];
149 client
150 .read_at(
151 MutableBufferSlice::Memory(&mut partition_table_blocks[..]),
152 partition_table_offset,
153 )
154 .await
155 .with_context(|| {
156 format!(
157 "Failed to read partition table (sz {}) from offset {}",
158 partition_table_size, partition_table_offset
159 )
160 })?;
161 let crc = crc::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::warn!(err:?; "Failed to write metadata");
387 TransactionCommitError::Io
388 })?;
389 self.client.flush().await.map_err(|err| {
393 log::warn!(err:?; "Failed to flush metadata writes");
394 TransactionCommitError::Io
395 })?;
396 self.write_metadata(&new_header, &partition_table_raw[..]).await.map_err(|err| {
397 log::warn!(err:?; "Failed to write metadata");
398 TransactionCommitError::Io
399 })?;
400 self.client.flush().await.map_err(|err| {
401 log::warn!(err:?; "Failed to flush metadata writes");
402 TransactionCommitError::Io
403 })?;
404
405 self.header = new_header;
406 self.partitions = BTreeMap::new();
407 let mut idx = 0;
408 for partition in std::mem::take(&mut transaction.partitions) {
409 if !partition.is_nil() {
410 self.partitions.insert(idx, partition);
411 }
412 idx += 1;
413 }
414 Ok(())
415 }
416
417 pub fn add_partition(
421 &mut self,
422 transaction: &mut Transaction,
423 mut info: PartitionInfo,
424 ) -> Result<usize, AddPartitionError> {
425 assert_eq!(info.start_block, 0);
426
427 if info.label.is_empty()
428 || info.type_guid.0.is_nil()
429 || info.instance_guid.0.is_nil()
430 || info.num_blocks == 0
431 {
432 return Err(AddPartitionError::InvalidArguments);
433 }
434
435 let mut allocated_ranges = vec![
436 0..self.header.first_usable,
437 self.header.last_usable + 1..self.client.block_count(),
438 ];
439 let mut slot_idx = None;
440 for i in 0..transaction.partitions.len() {
441 let partition = &transaction.partitions[i];
442 if slot_idx.is_none() && partition.is_nil() {
443 slot_idx = Some(i);
444 }
445 if !partition.is_nil() {
446 allocated_ranges
447 .push(partition.start_block..partition.start_block + partition.num_blocks);
448 }
449 }
450 let slot_idx = slot_idx.ok_or(AddPartitionError::NoSpace)?;
451 allocated_ranges.sort_by_key(|range| range.start);
452
453 let mut start_block = None;
454 for window in allocated_ranges.windows(2) {
455 if window[1].start - window[0].end >= info.num_blocks {
456 start_block = Some(window[0].end);
457 break;
458 }
459 }
460 info.start_block = start_block.ok_or(AddPartitionError::NoSpace)?;
461
462 transaction.partitions[slot_idx] = info;
463 Ok(slot_idx)
464 }
465
466 async fn write_metadata(
467 &self,
468 header: &format::Header,
469 partition_table: &[u8],
470 ) -> Result<(), Error> {
471 let bs = self.client.block_size() as usize;
472 let mut header_block = vec![0u8; bs];
473 header.write_to_prefix(&mut header_block[..]).unwrap();
474 self.client
475 .write_at(BufferSlice::Memory(&header_block[..]), header.current_lba * bs as u64)
476 .await
477 .context("Failed to write header")?;
478 if !partition_table.is_empty() {
479 self.client
480 .write_at(BufferSlice::Memory(partition_table), header.part_start * bs as u64)
481 .await
482 .context("Failed to write partition table")?;
483 }
484 Ok(())
485 }
486}
487
488pub struct Transaction {
490 pub partitions: Vec<PartitionInfo>,
491 transaction_state: Arc<Mutex<TransactionState>>,
492}
493
494impl std::fmt::Debug for Transaction {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
496 f.debug_struct("Transaction").field("partitions", &self.partitions).finish()
497 }
498}
499
500impl Drop for Transaction {
501 fn drop(&mut self) {
502 let mut state = self.transaction_state.lock();
503 debug_assert!(state.pending_id != u64::MAX);
504 state.pending_id = u64::MAX;
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use crate::{AddPartitionError, Gpt, Guid, PartitionInfo};
511 use block_client::{BlockClient as _, BufferSlice, MutableBufferSlice, RemoteBlockClient};
512 use std::ops::Range;
513 use std::sync::Arc;
514 use std::sync::atomic::{AtomicBool, Ordering};
515 use vmo_backed_block_server::{
516 InitialContents, VmoBackedServer, VmoBackedServerOptions, VmoBackedServerTestingExt as _,
517 };
518 use zx::HandleBased;
519 use {fidl_fuchsia_hardware_block_volume as fvolume, fuchsia_async as fasync};
520
521 #[fuchsia::test]
522 async fn load_unformatted_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::open(client).await.expect_err("load should fail");
531 }
532
533 #[fuchsia::test]
534 async fn load_formatted_empty_gpt() {
535 let vmo = zx::Vmo::create(4096).unwrap();
536 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
537 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
538
539 let _task =
540 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
541 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
542 Gpt::format(client.clone(), vec![]).await.expect("format failed");
543 Gpt::open(client).await.expect("load should succeed");
544 }
545
546 #[fuchsia::test]
547 async fn load_formatted_gpt_with_minimal_size() {
548 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
549 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
550 const PART_NAME: &str = "part";
551
552 let vmo = zx::Vmo::create(6 * 4096).unwrap();
553 let server = Arc::new(VmoBackedServer::from_vmo(4096, vmo));
554 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
555
556 let _task =
557 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
558 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
559 Gpt::format(
560 client.clone(),
561 vec![PartitionInfo {
562 label: PART_NAME.to_string(),
563 type_guid: Guid::from_bytes(PART_TYPE_GUID),
564 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
565 start_block: 3,
566 num_blocks: 1,
567 flags: 0,
568 }],
569 )
570 .await
571 .expect("format failed");
572 let manager = Gpt::open(client).await.expect("load should succeed");
573 assert_eq!(manager.header.first_usable, 3);
574 assert_eq!(manager.header.last_usable, 3);
575 let partition = manager.partitions().get(&0).expect("No entry found");
576 assert_eq!(partition.start_block, 3);
577 assert_eq!(partition.num_blocks, 1);
578 assert!(manager.partitions().get(&1).is_none());
579 }
580
581 #[fuchsia::test]
582 async fn load_formatted_gpt_with_one_partition() {
583 const PART_TYPE_GUID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
584 const PART_INSTANCE_GUID: [u8; 16] =
585 [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
586 const PART_NAME: &str = "part";
587
588 let vmo = zx::Vmo::create(4096).unwrap();
589 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
590 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
591
592 let _task =
593 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
594 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
595 Gpt::format(
596 client.clone(),
597 vec![PartitionInfo {
598 label: PART_NAME.to_string(),
599 type_guid: Guid::from_bytes(PART_TYPE_GUID),
600 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
601 start_block: 4,
602 num_blocks: 1,
603 flags: 0,
604 }],
605 )
606 .await
607 .expect("format failed");
608 let manager = Gpt::open(client).await.expect("load should succeed");
609 let partition = manager.partitions().get(&0).expect("No entry found");
610 assert_eq!(partition.label, "part");
611 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
612 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
613 assert_eq!(partition.start_block, 4);
614 assert_eq!(partition.num_blocks, 1);
615 assert!(manager.partitions().get(&1).is_none());
616 }
617
618 #[fuchsia::test]
619 async fn load_formatted_gpt_with_two_partitions() {
620 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
621 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
622 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
623 const PART_1_NAME: &str = "part1";
624 const PART_2_NAME: &str = "part2";
625
626 let vmo = zx::Vmo::create(8192).unwrap();
627 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
628 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
629
630 let _task =
631 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
632 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
633 Gpt::format(
634 client.clone(),
635 vec![
636 PartitionInfo {
637 label: PART_1_NAME.to_string(),
638 type_guid: Guid::from_bytes(PART_TYPE_GUID),
639 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
640 start_block: 4,
641 num_blocks: 1,
642 flags: 0,
643 },
644 PartitionInfo {
645 label: PART_2_NAME.to_string(),
646 type_guid: Guid::from_bytes(PART_TYPE_GUID),
647 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
648 start_block: 7,
649 num_blocks: 1,
650 flags: 0,
651 },
652 ],
653 )
654 .await
655 .expect("format failed");
656 let manager = Gpt::open(client).await.expect("load should succeed");
657 let partition = manager.partitions().get(&0).expect("No entry found");
658 assert_eq!(partition.label, PART_1_NAME);
659 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
660 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
661 assert_eq!(partition.start_block, 4);
662 assert_eq!(partition.num_blocks, 1);
663 let partition = manager.partitions().get(&1).expect("No entry found");
664 assert_eq!(partition.label, PART_2_NAME);
665 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
666 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
667 assert_eq!(partition.start_block, 7);
668 assert_eq!(partition.num_blocks, 1);
669 assert!(manager.partitions().get(&2).is_none());
670 }
671
672 #[fuchsia::test]
673 async fn load_formatted_gpt_with_extra_bytes_in_partition_name() {
674 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
675 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
676 const PART_NAME: &str = "part\0extrastuff";
677
678 let vmo = zx::Vmo::create(4096).unwrap();
679 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
680 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
681
682 let _task =
683 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
684 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
685 Gpt::format(
686 client.clone(),
687 vec![PartitionInfo {
688 label: PART_NAME.to_string(),
689 type_guid: Guid::from_bytes(PART_TYPE_GUID),
690 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
691 start_block: 4,
692 num_blocks: 1,
693 flags: 0,
694 }],
695 )
696 .await
697 .expect("format failed");
698 let manager = Gpt::open(client).await.expect("load should succeed");
699 let partition = manager.partitions().get(&0).expect("No entry found");
700 assert_eq!(partition.label, "part");
702 }
703
704 #[fuchsia::test]
705 async fn load_formatted_gpt_with_empty_partition_name() {
706 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
707 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
708 const PART_NAME: &str = "";
709
710 let vmo = zx::Vmo::create(4096).unwrap();
711 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
712 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
713
714 let _task =
715 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
716 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
717 Gpt::format(
718 client.clone(),
719 vec![PartitionInfo {
720 label: PART_NAME.to_string(),
721 type_guid: Guid::from_bytes(PART_TYPE_GUID),
722 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
723 start_block: 4,
724 num_blocks: 1,
725 flags: 0,
726 }],
727 )
728 .await
729 .expect("format failed");
730 let manager = Gpt::open(client).await.expect("load should succeed");
731 let partition = manager.partitions().get(&0).expect("No entry found");
732 assert_eq!(partition.label, "");
733 }
734
735 #[fuchsia::test]
736 async fn load_formatted_gpt_with_invalid_primary_header() {
737 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
738 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
739 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
740 const PART_1_NAME: &str = "part1";
741 const PART_2_NAME: &str = "part2";
742
743 let vmo = zx::Vmo::create(8192).unwrap();
744
745 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
746 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
747
748 let _task =
749 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
750 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
751 Gpt::format(
752 client.clone(),
753 vec![
754 PartitionInfo {
755 label: PART_1_NAME.to_string(),
756 type_guid: Guid::from_bytes(PART_TYPE_GUID),
757 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
758 start_block: 4,
759 num_blocks: 1,
760 flags: 0,
761 },
762 PartitionInfo {
763 label: PART_2_NAME.to_string(),
764 type_guid: Guid::from_bytes(PART_TYPE_GUID),
765 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
766 start_block: 7,
767 num_blocks: 1,
768 flags: 0,
769 },
770 ],
771 )
772 .await
773 .expect("format failed");
774 client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 512).await.unwrap();
776 let manager = Gpt::open(client).await.expect("load should succeed");
777 let partition = manager.partitions().get(&0).expect("No entry found");
778 assert_eq!(partition.label, PART_1_NAME);
779 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
780 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
781 assert_eq!(partition.start_block, 4);
782 assert_eq!(partition.num_blocks, 1);
783 let partition = manager.partitions().get(&1).expect("No entry found");
784 assert_eq!(partition.label, PART_2_NAME);
785 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
786 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
787 assert_eq!(partition.start_block, 7);
788 assert_eq!(partition.num_blocks, 1);
789 assert!(manager.partitions().get(&2).is_none());
790 }
791
792 #[fuchsia::test]
793 async fn load_formatted_gpt_with_invalid_primary_partition_table() {
794 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
795 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
796 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
797 const PART_1_NAME: &str = "part1";
798 const PART_2_NAME: &str = "part2";
799
800 let vmo = zx::Vmo::create(8192).unwrap();
801
802 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
803 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
804
805 let _task =
806 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
807 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
808 Gpt::format(
809 client.clone(),
810 vec![
811 PartitionInfo {
812 label: PART_1_NAME.to_string(),
813 type_guid: Guid::from_bytes(PART_TYPE_GUID),
814 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
815 start_block: 4,
816 num_blocks: 1,
817 flags: 0,
818 },
819 PartitionInfo {
820 label: PART_2_NAME.to_string(),
821 type_guid: Guid::from_bytes(PART_TYPE_GUID),
822 instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
823 start_block: 7,
824 num_blocks: 1,
825 flags: 0,
826 },
827 ],
828 )
829 .await
830 .expect("format failed");
831 client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 1024).await.unwrap();
833 let manager = Gpt::open(client).await.expect("load should succeed");
834 let partition = manager.partitions().get(&0).expect("No entry found");
835 assert_eq!(partition.label, PART_1_NAME);
836 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
837 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
838 assert_eq!(partition.start_block, 4);
839 assert_eq!(partition.num_blocks, 1);
840 let partition = manager.partitions().get(&1).expect("No entry found");
841 assert_eq!(partition.label, PART_2_NAME);
842 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
843 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
844 assert_eq!(partition.start_block, 7);
845 assert_eq!(partition.num_blocks, 1);
846 assert!(manager.partitions().get(&2).is_none());
847 }
848
849 #[fuchsia::test]
850 async fn drop_transaction() {
851 let vmo = zx::Vmo::create(8192).unwrap();
852 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
853 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
854
855 let _task =
856 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
857 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
858 Gpt::format(client.clone(), vec![]).await.expect("format failed");
859 let manager = Gpt::open(client).await.expect("load should succeed");
860 {
861 let _transaction = manager.create_transaction().unwrap();
862 assert!(manager.create_transaction().is_none());
863 }
864 let _transaction =
865 manager.create_transaction().expect("Transaction dropped but not available");
866 }
867
868 #[fuchsia::test]
869 async fn commit_empty_transaction() {
870 let vmo = zx::Vmo::create(8192).unwrap();
871 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
872 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
873
874 let _task =
875 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
876 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
877 Gpt::format(client.clone(), vec![]).await.expect("format failed");
878 let mut manager = Gpt::open(client).await.expect("load should succeed");
879 let transaction = manager.create_transaction().unwrap();
880 manager.commit_transaction(transaction).await.expect("Commit failed");
881
882 assert_eq!(manager.header().num_parts, 0);
885 assert!(manager.partitions().is_empty());
886 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
887 assert_eq!(manager.header().num_parts, 0);
888 assert!(manager.partitions().is_empty());
889 }
890
891 #[fuchsia::test]
892 async fn add_partition_in_transaction() {
893 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
894 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
895 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
896 const PART_1_NAME: &str = "part1";
897 const PART_2_NAME: &str = "part2";
898
899 let vmo = zx::Vmo::create(8192).unwrap();
900 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
901 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
902
903 let _task =
904 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
905 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
906 Gpt::format(
907 client.clone(),
908 vec![PartitionInfo {
909 label: PART_1_NAME.to_string(),
910 type_guid: Guid::from_bytes(PART_TYPE_GUID),
911 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
912 start_block: 4,
913 num_blocks: 1,
914 flags: 0,
915 }],
916 )
917 .await
918 .expect("format failed");
919 let mut manager = Gpt::open(client).await.expect("load should succeed");
920 let mut transaction = manager.create_transaction().unwrap();
921 assert_eq!(transaction.partitions.len(), 1);
922 transaction.partitions.push(crate::PartitionInfo {
923 label: PART_2_NAME.to_string(),
924 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
925 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
926 start_block: 7,
927 num_blocks: 1,
928 flags: 0,
929 });
930 manager.commit_transaction(transaction).await.expect("Commit failed");
931
932 assert_eq!(manager.header().num_parts, 2);
935 assert!(manager.partitions().get(&2).is_none());
936 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
937 assert_eq!(manager.header().num_parts, 2);
938 let partition = manager.partitions().get(&0).expect("No entry found");
939 assert_eq!(partition.label, PART_1_NAME);
940 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
941 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
942 assert_eq!(partition.start_block, 4);
943 assert_eq!(partition.num_blocks, 1);
944 let partition = manager.partitions().get(&1).expect("No entry found");
945 assert_eq!(partition.label, PART_2_NAME);
946 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
947 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
948 assert_eq!(partition.start_block, 7);
949 assert_eq!(partition.num_blocks, 1);
950 assert!(manager.partitions().get(&2).is_none());
951 }
952
953 #[fuchsia::test]
954 async fn remove_partition_in_transaction() {
955 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
956 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
957 const PART_NAME: &str = "part1";
958
959 let vmo = zx::Vmo::create(8192).unwrap();
960 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
961 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
962
963 let _task =
964 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
965 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
966 Gpt::format(
967 client.clone(),
968 vec![PartitionInfo {
969 label: PART_NAME.to_string(),
970 type_guid: Guid::from_bytes(PART_TYPE_GUID),
971 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
972 start_block: 4,
973 num_blocks: 1,
974 flags: 0,
975 }],
976 )
977 .await
978 .expect("format failed");
979 let mut manager = Gpt::open(client).await.expect("load should succeed");
980 let mut transaction = manager.create_transaction().unwrap();
981 assert_eq!(transaction.partitions.len(), 1);
982 transaction.partitions.clear();
983 manager.commit_transaction(transaction).await.expect("Commit failed");
984
985 assert_eq!(manager.header().num_parts, 0);
988 assert!(manager.partitions().get(&0).is_none());
989 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
990 assert_eq!(manager.header().num_parts, 0);
991 assert!(manager.partitions().get(&0).is_none());
992 }
993
994 #[fuchsia::test]
995 async fn modify_partition_in_transaction() {
996 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
997 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
998 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
999 const PART_1_NAME: &str = "part1";
1000 const PART_2_NAME: &str = "part2";
1001
1002 let vmo = zx::Vmo::create(8192).unwrap();
1003 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1004 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1005
1006 let _task =
1007 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1008 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1009 Gpt::format(
1010 client.clone(),
1011 vec![PartitionInfo {
1012 label: PART_1_NAME.to_string(),
1013 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1014 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1015 start_block: 4,
1016 num_blocks: 1,
1017 flags: 0,
1018 }],
1019 )
1020 .await
1021 .expect("format failed");
1022 let mut manager = Gpt::open(client).await.expect("load should succeed");
1023 let mut transaction = manager.create_transaction().unwrap();
1024 assert_eq!(transaction.partitions.len(), 1);
1025 transaction.partitions[0] = crate::PartitionInfo {
1026 label: PART_2_NAME.to_string(),
1027 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1028 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1029 start_block: 7,
1030 num_blocks: 1,
1031 flags: 0,
1032 };
1033 manager.commit_transaction(transaction).await.expect("Commit failed");
1034
1035 assert_eq!(manager.header().num_parts, 1);
1038 let partition = manager.partitions().get(&0).expect("No entry found");
1039 assert_eq!(partition.label, PART_2_NAME);
1040 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1041 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1042 assert_eq!(partition.start_block, 7);
1043 assert_eq!(partition.num_blocks, 1);
1044 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1045 assert_eq!(manager.header().num_parts, 1);
1046 let partition = manager.partitions().get(&0).expect("No entry found");
1047 assert_eq!(partition.label, PART_2_NAME);
1048 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1049 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1050 assert_eq!(partition.start_block, 7);
1051 assert_eq!(partition.num_blocks, 1);
1052 assert!(manager.partitions().get(&1).is_none());
1053 }
1054
1055 #[fuchsia::test]
1056 async fn grow_partition_table_in_transaction() {
1057 let vmo = zx::Vmo::create(1024 * 1024).unwrap();
1058 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1059 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1060
1061 let _task =
1062 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1063 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1064 Gpt::format(
1065 client.clone(),
1066 vec![PartitionInfo {
1067 label: "part".to_string(),
1068 type_guid: Guid::from_bytes([1u8; 16]),
1069 instance_guid: Guid::from_bytes([1u8; 16]),
1070 start_block: 34,
1071 num_blocks: 1,
1072 flags: 0,
1073 }],
1074 )
1075 .await
1076 .expect("format failed");
1077 let mut manager = Gpt::open(client).await.expect("load should succeed");
1078 assert_eq!(manager.header().num_parts, 1);
1079 assert_eq!(manager.header().first_usable, 3);
1080 let mut transaction = manager.create_transaction().unwrap();
1081 transaction.partitions.resize(128, crate::PartitionInfo::nil());
1082 manager.commit_transaction(transaction).await.expect("Commit failed");
1083
1084 assert_eq!(manager.header().num_parts, 128);
1087 assert_eq!(manager.header().first_usable, 34);
1088 let partition = manager.partitions().get(&0).expect("No entry found");
1089 assert_eq!(partition.label, "part");
1090 assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1091 assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1092 assert_eq!(partition.start_block, 34);
1093 assert_eq!(partition.num_blocks, 1);
1094 assert!(manager.partitions().get(&1).is_none());
1095 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1096 assert_eq!(manager.header().num_parts, 128);
1097 assert_eq!(manager.header().first_usable, 34);
1098 let partition = manager.partitions().get(&0).expect("No entry found");
1099 assert_eq!(partition.label, "part");
1100 assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1101 assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1102 assert_eq!(partition.start_block, 34);
1103 assert_eq!(partition.num_blocks, 1);
1104 assert!(manager.partitions().get(&1).is_none());
1105 }
1106
1107 #[fuchsia::test]
1108 async fn shrink_partition_table_in_transaction() {
1109 let vmo = zx::Vmo::create(1024 * 1024).unwrap();
1110 let mut partitions = vec![];
1111 for i in 0..128 {
1112 partitions.push(PartitionInfo {
1113 label: format!("part-{i}"),
1114 type_guid: Guid::from_bytes([i as u8 + 1; 16]),
1115 instance_guid: Guid::from_bytes([i as u8 + 1; 16]),
1116 start_block: 34 + i,
1117 num_blocks: 1,
1118 flags: 0,
1119 });
1120 }
1121 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1122 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1123
1124 let _task =
1125 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1126 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1127 Gpt::format(client.clone(), partitions).await.expect("format failed");
1128 let mut manager = Gpt::open(client).await.expect("load should succeed");
1129 assert_eq!(manager.header().num_parts, 128);
1130 assert_eq!(manager.header().first_usable, 34);
1131 let mut transaction = manager.create_transaction().unwrap();
1132 transaction.partitions.clear();
1133 manager.commit_transaction(transaction).await.expect("Commit failed");
1134
1135 assert_eq!(manager.header().num_parts, 0);
1138 assert_eq!(manager.header().first_usable, 2);
1139 assert!(manager.partitions().get(&0).is_none());
1140 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1141 assert_eq!(manager.header().num_parts, 0);
1142 assert_eq!(manager.header().first_usable, 2);
1143 assert!(manager.partitions().get(&0).is_none());
1144 }
1145
1146 #[fuchsia::test]
1147 async fn invalid_transaction_rejected() {
1148 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1149 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1150 const PART_NAME: &str = "part1";
1151
1152 let vmo = zx::Vmo::create(8192).unwrap();
1153 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1154 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1155
1156 let _task =
1157 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1158 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1159 Gpt::format(
1160 client.clone(),
1161 vec![PartitionInfo {
1162 label: PART_NAME.to_string(),
1163 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1164 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1165 start_block: 4,
1166 num_blocks: 1,
1167 flags: 0,
1168 }],
1169 )
1170 .await
1171 .expect("format failed");
1172 let mut manager = Gpt::open(client).await.expect("load should succeed");
1173 let mut transaction = manager.create_transaction().unwrap();
1174 assert_eq!(transaction.partitions.len(), 1);
1175 transaction.partitions[0].start_block = 0;
1177 manager.commit_transaction(transaction).await.expect_err("Commit should have failed");
1178
1179 assert_eq!(manager.header().num_parts, 1);
1182 let partition = manager.partitions().get(&0).expect("No entry found");
1183 assert_eq!(partition.label, PART_NAME);
1184 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1185 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1186 assert_eq!(partition.start_block, 4);
1187 assert_eq!(partition.num_blocks, 1);
1188 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1189 assert_eq!(manager.header().num_parts, 1);
1190 let partition = manager.partitions().get(&0).expect("No entry found");
1191 assert_eq!(partition.label, PART_NAME);
1192 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1193 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1194 assert_eq!(partition.start_block, 4);
1195 assert_eq!(partition.num_blocks, 1);
1196 }
1197
1198 struct DiscardingObserver {
1200 block_size: u64,
1201 discard_range: Range<u64>,
1202 }
1203
1204 impl vmo_backed_block_server::Observer for DiscardingObserver {
1205 fn write(
1206 &self,
1207 device_block_offset: u64,
1208 block_count: u32,
1209 _vmo: &Arc<zx::Vmo>,
1210 _vmo_offset: u64,
1211 _opts: block_server::WriteOptions,
1212 ) -> vmo_backed_block_server::WriteAction {
1213 let write_range = (device_block_offset * self.block_size)
1214 ..(device_block_offset + block_count as u64) * self.block_size;
1215 if write_range.end <= self.discard_range.start
1216 || write_range.start >= self.discard_range.end
1217 {
1218 vmo_backed_block_server::WriteAction::Write
1219 } else {
1220 vmo_backed_block_server::WriteAction::Discard
1221 }
1222 }
1223 }
1224
1225 #[fuchsia::test]
1226 async fn transaction_applied_if_primary_metadata_partially_written() {
1227 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1228 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1229 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1230 const PART_1_NAME: &str = "part1";
1231 const PART_2_NAME: &str = "part2";
1232
1233 let vmo = zx::Vmo::create(8192).unwrap();
1234 let server = Arc::new(
1235 VmoBackedServerOptions {
1236 initial_contents: InitialContents::FromVmo(vmo),
1237 block_size: 512,
1238 observer: Some(Box::new(DiscardingObserver {
1239 discard_range: 1024..1536,
1240 block_size: 512,
1241 })),
1242 ..Default::default()
1243 }
1244 .build()
1245 .unwrap(),
1246 );
1247 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1248
1249 let _task =
1250 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1251 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1252 Gpt::format(
1253 client.clone(),
1254 vec![PartitionInfo {
1255 label: PART_1_NAME.to_string(),
1256 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1257 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1258 start_block: 4,
1259 num_blocks: 1,
1260 flags: 0,
1261 }],
1262 )
1263 .await
1264 .expect("format failed");
1265 let mut manager = Gpt::open(client).await.expect("load should succeed");
1266 let mut transaction = manager.create_transaction().unwrap();
1267 transaction.partitions.push(crate::PartitionInfo {
1268 label: PART_2_NAME.to_string(),
1269 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1270 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1271 start_block: 7,
1272 num_blocks: 1,
1273 flags: 0,
1274 });
1275 manager.commit_transaction(transaction).await.expect("Commit failed");
1276
1277 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1278 assert_eq!(manager.header().num_parts, 2);
1279 let partition = manager.partitions().get(&0).expect("No entry found");
1280 assert_eq!(partition.label, PART_1_NAME);
1281 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1282 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1283 assert_eq!(partition.start_block, 4);
1284 assert_eq!(partition.num_blocks, 1);
1285 let partition = manager.partitions().get(&1).expect("No entry found");
1286 assert_eq!(partition.label, PART_2_NAME);
1287 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1288 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1289 assert_eq!(partition.start_block, 7);
1290 assert_eq!(partition.num_blocks, 1);
1291 }
1292
1293 #[fuchsia::test]
1294 async fn transaction_not_applied_if_primary_metadata_not_written() {
1295 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1296 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1297 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1298 const PART_1_NAME: &str = "part1";
1299 const PART_2_NAME: &str = "part2";
1300
1301 let vmo = zx::Vmo::create(8192).unwrap();
1302 let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1303 {
1304 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1305 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo_dup));
1306 let _task =
1307 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1308 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1309 Gpt::format(
1310 client.clone(),
1311 vec![PartitionInfo {
1312 label: PART_1_NAME.to_string(),
1313 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1314 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1315 start_block: 4,
1316 num_blocks: 1,
1317 flags: 0,
1318 }],
1319 )
1320 .await
1321 .expect("format failed");
1322 }
1323 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1324 let server = Arc::new(
1325 VmoBackedServerOptions {
1326 initial_contents: InitialContents::FromVmo(vmo),
1327 block_size: 512,
1328 observer: Some(Box::new(DiscardingObserver {
1329 discard_range: 0..2048,
1330 block_size: 512,
1331 })),
1332 ..Default::default()
1333 }
1334 .build()
1335 .unwrap(),
1336 );
1337 let _task =
1338 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1339 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1340
1341 let mut manager = Gpt::open(client).await.expect("load should succeed");
1342 let mut transaction = manager.create_transaction().unwrap();
1343 transaction.partitions.push(crate::PartitionInfo {
1344 label: PART_2_NAME.to_string(),
1345 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1346 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1347 start_block: 7,
1348 num_blocks: 1,
1349 flags: 0,
1350 });
1351 manager.commit_transaction(transaction).await.expect("Commit failed");
1352
1353 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1354 assert_eq!(manager.header().num_parts, 1);
1355 let partition = manager.partitions().get(&0).expect("No entry found");
1356 assert_eq!(partition.label, PART_1_NAME);
1357 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1358 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1359 assert_eq!(partition.start_block, 4);
1360 assert_eq!(partition.num_blocks, 1);
1361 assert!(manager.partitions().get(&1).is_none());
1362 }
1363
1364 #[fuchsia::test]
1365 async fn transaction_not_applied_if_backup_metadata_partially_written() {
1366 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1367 const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1368 const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1369 const PART_1_NAME: &str = "part1";
1370 const PART_2_NAME: &str = "part2";
1371
1372 let vmo = zx::Vmo::create(8192).unwrap();
1373 let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1374 {
1375 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1376 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo_dup));
1377 let _task =
1378 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1379 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1380 Gpt::format(
1381 client.clone(),
1382 vec![PartitionInfo {
1383 label: PART_1_NAME.to_string(),
1384 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1385 instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1386 start_block: 4,
1387 num_blocks: 1,
1388 flags: 0,
1389 }],
1390 )
1391 .await
1392 .expect("format failed");
1393 }
1394 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1395 let server = Arc::new(
1396 VmoBackedServerOptions {
1397 initial_contents: InitialContents::FromVmo(vmo),
1398 block_size: 512,
1399 observer: Some(Box::new(DiscardingObserver {
1400 discard_range: 0..7680,
1401 block_size: 512,
1402 })),
1403 ..Default::default()
1404 }
1405 .build()
1406 .unwrap(),
1407 );
1408 let _task =
1409 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1410 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1411
1412 let mut manager = Gpt::open(client).await.expect("load should succeed");
1413 let mut transaction = manager.create_transaction().unwrap();
1414 transaction.partitions.push(crate::PartitionInfo {
1415 label: PART_2_NAME.to_string(),
1416 type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1417 instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1418 start_block: 7,
1419 num_blocks: 1,
1420 flags: 0,
1421 });
1422 manager.commit_transaction(transaction).await.expect("Commit failed");
1423
1424 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1425 assert_eq!(manager.header().num_parts, 1);
1426 let partition = manager.partitions().get(&0).expect("No entry found");
1427 assert_eq!(partition.label, PART_1_NAME);
1428 assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1429 assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1430 assert_eq!(partition.start_block, 4);
1431 assert_eq!(partition.num_blocks, 1);
1432 assert!(manager.partitions().get(&1).is_none());
1433 }
1434
1435 #[fuchsia::test]
1436 async fn restore_primary_from_backup() {
1437 const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1438 const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1439 const PART_NAME: &str = "part1";
1440
1441 let vmo = zx::Vmo::create(8192).unwrap();
1442 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1443 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1444
1445 let _task =
1446 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1447 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1448 Gpt::format(
1449 client.clone(),
1450 vec![PartitionInfo {
1451 label: PART_NAME.to_string(),
1452 type_guid: Guid::from_bytes(PART_TYPE_GUID),
1453 instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1454 start_block: 4,
1455 num_blocks: 1,
1456 flags: 0,
1457 }],
1458 )
1459 .await
1460 .expect("format failed");
1461 let mut old_metadata = vec![0u8; 2048];
1462 client.read_at(MutableBufferSlice::Memory(&mut old_metadata[..]), 0).await.unwrap();
1463 let mut buffer = vec![0u8; 2048];
1464 client.write_at(BufferSlice::Memory(&buffer[..]), 0).await.unwrap();
1465
1466 let manager = Gpt::open(client).await.expect("load should succeed");
1467 let client = manager.take_client();
1468
1469 client.read_at(MutableBufferSlice::Memory(&mut buffer[..]), 0).await.unwrap();
1470 assert_eq!(old_metadata, buffer);
1471 }
1472
1473 #[fuchsia::test]
1474 async fn load_golden_gpt_linux() {
1475 let contents = std::fs::read("/pkg/data/gpt_golden/gpt.linux.blk").unwrap();
1476 let server = Arc::new(VmoBackedServer::new(contents.len() as u64 / 512, 512, &contents));
1477 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1478
1479 let _task =
1480 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1481 let manager = Gpt::open(Arc::new(RemoteBlockClient::new(client).await.unwrap()))
1482 .await
1483 .expect("load should succeed");
1484 let partition = manager.partitions().get(&0).expect("No entry found");
1485 assert_eq!(partition.label, "ext");
1486 assert_eq!(partition.type_guid.to_string(), "0fc63daf-8483-4772-8e79-3d69d8477de4");
1487 assert_eq!(partition.start_block, 8);
1488 assert_eq!(partition.num_blocks, 1);
1489 assert!(manager.partitions().get(&1).is_none());
1490 }
1491
1492 #[fuchsia::test]
1493 async fn load_golden_gpt_fuchsia() {
1494 let contents = std::fs::read("/pkg/data/gpt_golden/gpt.fuchsia.blk").unwrap();
1495 let server = Arc::new(VmoBackedServer::new(contents.len() as u64 / 512, 512, &contents));
1496 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1497
1498 struct ExpectedPartition {
1499 label: &'static str,
1500 type_guid: &'static str,
1501 blocks: Range<u64>,
1502 }
1503 const EXPECTED_PARTITIONS: [ExpectedPartition; 8] = [
1504 ExpectedPartition {
1505 label: "bootloader",
1506 type_guid: "5ece94fe-4c86-11e8-a15b-480fcf35f8e6",
1507 blocks: 11..12,
1508 },
1509 ExpectedPartition {
1510 label: "zircon_a",
1511 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1512 blocks: 12..13,
1513 },
1514 ExpectedPartition {
1515 label: "zircon_b",
1516 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1517 blocks: 13..14,
1518 },
1519 ExpectedPartition {
1520 label: "zircon_r",
1521 type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1522 blocks: 14..15,
1523 },
1524 ExpectedPartition {
1525 label: "vbmeta_a",
1526 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1527 blocks: 15..16,
1528 },
1529 ExpectedPartition {
1530 label: "vbmeta_b",
1531 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1532 blocks: 16..17,
1533 },
1534 ExpectedPartition {
1535 label: "vbmeta_r",
1536 type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1537 blocks: 17..18,
1538 },
1539 ExpectedPartition {
1540 label: "durable_boot",
1541 type_guid: "a409e16b-78aa-4acc-995c-302352621a41",
1542 blocks: 18..19,
1543 },
1544 ];
1545
1546 let _task =
1547 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1548 let manager = Gpt::open(Arc::new(RemoteBlockClient::new(client).await.unwrap()))
1549 .await
1550 .expect("load should succeed");
1551 for i in 0..EXPECTED_PARTITIONS.len() as u32 {
1552 let partition = manager.partitions().get(&i).expect("No entry found");
1553 let expected = &EXPECTED_PARTITIONS[i as usize];
1554 assert_eq!(partition.label, expected.label);
1555 assert_eq!(partition.type_guid.to_string(), expected.type_guid);
1556 assert_eq!(partition.start_block, expected.blocks.start);
1557 assert_eq!(partition.num_blocks, expected.blocks.end - expected.blocks.start);
1558 }
1559 }
1560
1561 #[fuchsia::test]
1562 async fn add_partitions_till_no_blocks_left() {
1563 let vmo = zx::Vmo::create(65536).unwrap();
1564 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1565 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1566
1567 let _task =
1568 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1569 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1570 Gpt::format(client.clone(), vec![PartitionInfo::nil(); 32]).await.expect("format failed");
1571 let mut manager = Gpt::open(client).await.expect("load should succeed");
1572 let mut transaction = manager.create_transaction().unwrap();
1573 assert_eq!(transaction.partitions.len(), 32);
1574 let mut num = 0;
1575 loop {
1576 match manager.add_partition(
1577 &mut transaction,
1578 crate::PartitionInfo {
1579 label: format!("part-{num}"),
1580 type_guid: crate::Guid::generate(),
1581 instance_guid: crate::Guid::generate(),
1582 start_block: 0,
1583 num_blocks: 1,
1584 flags: 0,
1585 },
1586 ) {
1587 Ok(_) => {
1588 num += 1;
1589 }
1590 Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1591 Err(AddPartitionError::NoSpace) => break,
1592 };
1593 }
1594 assert!(num <= 32);
1595 manager.commit_transaction(transaction).await.expect("Commit failed");
1596
1597 assert_eq!(manager.header().num_parts, 32);
1600 assert_eq!(manager.partitions().len(), num);
1601
1602 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1603 assert_eq!(manager.header().num_parts, 32);
1604 assert_eq!(manager.partitions().len(), num);
1605 }
1606
1607 #[fuchsia::test]
1608 async fn add_partitions_till_no_slots_left() {
1609 let vmo = zx::Vmo::create(65536).unwrap();
1610 let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1611 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1612
1613 let _task =
1614 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1615 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1616 Gpt::format(client.clone(), vec![PartitionInfo::nil(); 4]).await.expect("format failed");
1617 let mut manager = Gpt::open(client).await.expect("load should succeed");
1618 let mut transaction = manager.create_transaction().unwrap();
1619 assert_eq!(transaction.partitions.len(), 4);
1620 let mut num = 0;
1621 loop {
1622 match manager.add_partition(
1623 &mut transaction,
1624 crate::PartitionInfo {
1625 label: format!("part-{num}"),
1626 type_guid: crate::Guid::generate(),
1627 instance_guid: crate::Guid::generate(),
1628 start_block: 0,
1629 num_blocks: 1,
1630 flags: 0,
1631 },
1632 ) {
1633 Ok(_) => {
1634 num += 1;
1635 }
1636 Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1637 Err(AddPartitionError::NoSpace) => break,
1638 };
1639 }
1640 assert!(num <= 4);
1641 manager.commit_transaction(transaction).await.expect("Commit failed");
1642
1643 assert_eq!(manager.header().num_parts, 4);
1646 assert_eq!(manager.partitions().len(), num);
1647
1648 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1649 assert_eq!(manager.header().num_parts, 4);
1650 assert_eq!(manager.partitions().len(), num);
1651 }
1652
1653 struct ShufflingObserver {
1655 start: Arc<AtomicBool>,
1657 shuffle_if_contains_offset: u64,
1659 }
1660
1661 impl vmo_backed_block_server::Observer for ShufflingObserver {
1662 fn flush(&self, writes: Option<&mut vmo_backed_block_server::Writes>) {
1663 if self.start.load(Ordering::Relaxed) {
1664 let Some(writes) = writes else { unreachable!() };
1665 if writes
1666 .iter()
1667 .filter(|(offset, _)| **offset == self.shuffle_if_contains_offset)
1668 .next()
1669 .is_some()
1670 {
1671 writes.shuffle();
1672 writes.discard_some();
1673 }
1674 }
1675 }
1676
1677 fn close(&self, writes: Option<&mut vmo_backed_block_server::Writes>) {
1678 if self.start.load(Ordering::Relaxed) {
1680 let Some(writes) = writes else { unreachable!() };
1681 writes.shuffle();
1682 }
1683 }
1684 }
1685
1686 #[fuchsia::test]
1687 async fn metadata_update_is_atomic() {
1688 const BLOCK_SIZE: u64 = 512;
1689 const BLOCK_COUNT: u64 = 128;
1690 for shuffle_if_contains_offset in [1, BLOCK_COUNT - 1] {
1695 let vmo = zx::Vmo::create(BLOCK_SIZE * BLOCK_COUNT).unwrap();
1696 let start_shuffling = Arc::new(AtomicBool::new(false));
1697 let server = Arc::new(
1698 VmoBackedServerOptions {
1699 initial_contents: InitialContents::FromVmo(vmo),
1700 block_size: BLOCK_SIZE as u32,
1701 observer: Some(Box::new(ShufflingObserver {
1702 start: start_shuffling.clone(),
1703 shuffle_if_contains_offset,
1704 })),
1705 write_tracking: true,
1706 ..Default::default()
1707 }
1708 .build()
1709 .unwrap(),
1710 );
1711 let (client, server_end) = fidl::endpoints::create_proxy::<fvolume::VolumeMarker>();
1712 let _task =
1713 fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1714 let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1715 Gpt::format(client.clone(), vec![PartitionInfo::nil(); 80])
1716 .await
1717 .expect("format failed");
1718
1719 start_shuffling.store(true, Ordering::Relaxed);
1720
1721 let mut manager = Gpt::open(client).await.expect("load should succeed");
1722 let mut transaction = manager.create_transaction().unwrap();
1723 transaction.partitions.truncate(40);
1724 let mut num = 0;
1725 loop {
1726 match manager.add_partition(
1727 &mut transaction,
1728 crate::PartitionInfo {
1729 label: format!("part-{num}"),
1730 type_guid: crate::Guid::generate(),
1731 instance_guid: crate::Guid::generate(),
1732 start_block: 0,
1733 num_blocks: 1,
1734 flags: 0,
1735 },
1736 ) {
1737 Ok(_) => {
1738 num += 1;
1739 }
1740 Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1741 Err(AddPartitionError::NoSpace) => break,
1742 };
1743 }
1744 assert!(num <= 40);
1745 manager.commit_transaction(transaction).await.expect("Commit failed");
1746
1747 assert_eq!(manager.header().num_parts, 40);
1749 assert_eq!(manager.partitions().len(), num);
1750
1751 let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1755 let len = manager.partitions().len();
1756 assert!(len == 0 || len == num);
1757 }
1758 }
1759}