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