1use crate::BootloaderType;
6use anyhow::{Context as _, Error};
7use block_client::{BlockClient, MutableBufferSlice, RemoteBlockClient};
8use fidl::endpoints::Proxy;
9use fidl_fuchsia_mem::Buffer;
10use fidl_fuchsia_paver::{Asset, Configuration, DynamicDataSinkProxy};
11use fidl_fuchsia_storage_block::{BlockMarker, BlockProxy};
12
13use recovery_util_block::BlockDevice;
14use std::cmp::min;
15use std::fmt;
16
17#[derive(Debug, PartialEq)]
18pub enum PartitionPaveType {
19 Asset { r#type: Asset, config: Configuration },
20 Volume,
21 Bootloader,
22}
23
24pub struct Partition {
26 pave_type: PartitionPaveType,
27 src: String,
28 size: u64,
29 block_size: u64,
30}
31
32static WORKSTATION_INSTALLER_GPT: [u8; 16] = [
36 0xce, 0x98, 0xce, 0x4d, 0x7e, 0xe7, 0xc1, 0x45, 0xa8, 0x63, 0xca, 0xf9, 0x2f, 0x13, 0x30, 0xc1,
37];
38
39static WORKSTATION_PARTITION_GPTS: [[u8; 16]; 5] = [
43 [
44 0xfe, 0x94, 0xce, 0x5e, 0x86, 0x4c, 0xe8, 0x11, 0xa1, 0x5b, 0x48, 0x0f, 0xcf, 0x35, 0xf8,
45 0xe6,
46 ], [
48 0x6b, 0xe1, 0x09, 0xa4, 0xaa, 0x78, 0xcc, 0x4a, 0x5c, 0x99, 0x41, 0x1a, 0x62, 0x52, 0x23,
49 0x30,
50 ], [
52 0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
53 0xf7,
54 ], [
56 0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
57 0xf7,
58 ], [
60 0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
61 0xf7,
62 ], ];
64
65impl Partition {
66 async fn new(
75 src: String,
76 part: BlockProxy,
77 bootloader: BootloaderType,
78 ) -> Result<Option<Self>, Error> {
79 let (status, guid) = part.get_type_guid().await.context("Get type guid failed")?;
80 if let None = guid {
81 return Err(Error::new(zx::Status::err_from_raw(status)));
82 }
83
84 let (_status, name) = part.get_name().await.context("Get name failed")?;
85 let pave_type;
86 if let Some(string) = name {
87 let guid = guid.unwrap();
88 if guid.value != WORKSTATION_INSTALLER_GPT
89 && !(src.contains("usb-bus") && WORKSTATION_PARTITION_GPTS.contains(&guid.value))
90 {
91 return Ok(None);
92 }
93 if string == "storage-sparse" {
95 pave_type = Some(PartitionPaveType::Volume);
96 } else if bootloader == BootloaderType::Efi {
97 pave_type = Partition::get_efi_pave_type(&string.to_lowercase());
98 } else if bootloader == BootloaderType::Coreboot {
99 pave_type = Partition::get_coreboot_pave_type(&string);
100 } else {
101 pave_type = None;
102 }
103 } else {
104 return Ok(None);
105 }
106
107 if let Some(pave_type) = pave_type {
108 let info = part
109 .get_info()
110 .await
111 .context("Get info failed")?
112 .map_err(zx::Status::err_from_raw)?;
113 let block_size = info.block_size.into();
114 let size = info.block_count * block_size;
115
116 Ok(Some(Partition { pave_type, src, size, block_size }))
117 } else {
118 Ok(None)
119 }
120 }
121
122 fn get_efi_pave_type(label: &str) -> Option<PartitionPaveType> {
123 if label.starts_with("zircon_") && label.len() == "zircon_x".len() {
124 let configuration = Partition::letter_to_configuration(label.chars().last().unwrap());
125 Some(PartitionPaveType::Asset { r#type: Asset::Kernel, config: configuration })
126 } else if label.starts_with("vbmeta_") && label.len() == "vbmeta_x".len() {
127 let configuration = Partition::letter_to_configuration(label.chars().last().unwrap());
128 Some(PartitionPaveType::Asset {
129 r#type: Asset::VerifiedBootMetadata,
130 config: configuration,
131 })
132 } else if label.starts_with("efi")
133 || label.starts_with("fuchsia.esp")
134 || label.starts_with("bootloader")
135 {
136 Some(PartitionPaveType::Bootloader)
137 } else {
138 None
139 }
140 }
141
142 fn get_coreboot_pave_type(label: &str) -> Option<PartitionPaveType> {
143 if let Ok(re) = regex_lite::Regex::new(r"^zircon_(.)\.signed$") {
144 if let Some(captures) = re.captures(label) {
145 let config = Partition::letter_to_configuration(
146 captures.get(1).unwrap().as_str().chars().last().unwrap(),
147 );
148 Some(PartitionPaveType::Asset { r#type: Asset::Kernel, config: config })
149 } else {
150 None
151 }
152 } else {
153 None
154 }
155 }
156
157 pub async fn get_partitions(
165 block_device: &BlockDevice,
166 all_devices: &Vec<BlockDevice>,
167 bootloader: BootloaderType,
168 ) -> Result<Vec<Self>, Error> {
169 let mut partitions = Vec::new();
170
171 for entry in all_devices {
172 if !entry.topo_path.starts_with(&block_device.topo_path) || entry == block_device {
173 continue;
176 }
177 let (local, remote) = zx::Channel::create();
178 fdio::service_connect(&entry.class_path, remote).context("Connecting to partition")?;
179 let local = fidl::AsyncChannel::from_channel(local);
180
181 let proxy = BlockProxy::from_channel(local);
182 if let Some(partition) = Partition::new(entry.class_path.clone(), proxy, bootloader)
183 .await
184 .context(format!(
185 "Creating partition for block device at {} ({})",
186 entry.topo_path, entry.class_path
187 ))?
188 {
189 partitions.push(partition);
190 }
191 }
192 Ok(partitions)
193 }
194
195 pub async fn pave<F>(
197 &self,
198 data_sink: &DynamicDataSinkProxy,
199 progress_callback: &F,
200 ) -> Result<(), Error>
201 where
202 F: Send + Sync + Fn(usize, usize) -> (),
203 {
204 match self.pave_type {
205 PartitionPaveType::Asset { r#type: asset, config } => {
206 let fidl_buf = self.read_data().await?;
207 data_sink.write_asset(config, asset, fidl_buf).await?;
208 }
209 PartitionPaveType::Bootloader => {
210 let fidl_buf = self.read_data().await?;
211 data_sink.write_firmware(Configuration::A, "", fidl_buf).await?;
213 }
214 PartitionPaveType::Volume => {
215 self.pave_volume(data_sink, progress_callback).await?;
216 }
217 };
218 Ok(())
219 }
220
221 async fn pave_volume<F>(
222 &self,
223 _data_sink: &DynamicDataSinkProxy,
224 _progress_callback: &F,
225 ) -> Result<(), Error>
226 where
227 F: Send + Sync + Fn(usize, usize) -> (),
228 {
229 Err(Error::from(zx::Status::NOT_SUPPORTED))
230 }
231
232 pub async fn pave_b(&self, data_sink: &DynamicDataSinkProxy) -> Result<(), Error> {
235 if !self.is_ab() {
236 return Err(Error::from(zx::Status::NOT_SUPPORTED));
237 }
238
239 let fidl_buf = self.read_data().await?;
240 match self.pave_type {
241 PartitionPaveType::Asset { r#type: asset, config: _ } => {
242 data_sink.write_asset(Configuration::B, asset, fidl_buf).await?;
246 Ok(())
247 }
248 _ => Err(Error::from(zx::Status::NOT_SUPPORTED)),
249 }
250 }
251
252 pub fn is_ab(&self) -> bool {
254 if let PartitionPaveType::Asset { r#type: _, config } = self.pave_type {
255 return config == Configuration::A;
258 }
259 return false;
260 }
261
262 async fn read_data(&self) -> Result<Buffer, Error> {
264 let mut rounded_size = self.size;
265 let page_size = u64::from(zx::system_get_page_size());
266 if rounded_size % page_size != 0 {
267 rounded_size += page_size;
268 rounded_size -= rounded_size % page_size;
269 }
270
271 let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, rounded_size)?;
272
273 let proxy =
274 fuchsia_component::client::connect_to_protocol_at_path::<BlockMarker>(&self.src)
275 .with_context(|| format!("Connecting to block device {}", self.src))?;
276 let block_device = RemoteBlockClient::new(proxy).await?;
277 let vmo_id = unsafe { block_device.attach_vmo(&vmo) }.await?;
280
281 let max_read_length: u64 = self.block_size * 100;
283 let mut read: u64 = 0;
284 while read < self.size {
285 let read_size = min(self.size - read, max_read_length);
286 if let Err(e) = block_device
287 .read_at(MutableBufferSlice::new_with_vmo_id(&vmo_id, read, read_size), read)
288 .await
289 .context("Reading from partition to VMO")
290 {
291 block_device.detach_vmo(vmo_id).await?;
293 return Err(e);
294 }
295
296 read += read_size;
297 }
298
299 block_device.detach_vmo(vmo_id).await?;
300
301 return Ok(Buffer { vmo: fidl::Vmo::from(vmo), size: self.size });
302 }
303
304 fn letter_to_configuration(letter: char) -> Configuration {
308 match letter {
311 'A' | 'a' => Configuration::A,
312 'B' | 'b' => Configuration::A,
313 'R' | 'r' => Configuration::Recovery,
314 _ => Configuration::A,
315 }
316 }
317}
318
319impl fmt::Debug for Partition {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 match self.pave_type {
322 PartitionPaveType::Asset { r#type, config } => write!(
323 f,
324 "Partition[src={}, pave_type={:?}, asset={:?}, config={:?}]",
325 self.src, self.pave_type, r#type, config
326 ),
327 _ => write!(f, "Partition[src={}, pave_type={:?}]", self.src, self.pave_type),
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use fidl_fuchsia_storage_block::{
336 BlockInfo, BlockMarker, BlockRequest, BlockRequestStream, DeviceFlag, Guid,
337 };
338 use fuchsia_async as fasync;
339 use futures::{TryFutureExt, TryStreamExt};
340
341 async fn serve_partition(
342 label: &str,
343 block_size: u32,
344 block_count: u64,
345 guid: [u8; 16],
346 mut stream: BlockRequestStream,
347 ) -> Result<(), Error> {
348 while let Some(req) = stream.try_next().await? {
349 match req {
350 BlockRequest::GetName { responder } => responder.send(0, Some(label))?,
351 BlockRequest::GetInfo { responder } => responder.send(Ok(&BlockInfo {
352 block_count,
353 block_size,
354 max_transfer_size: 0,
355 flags: DeviceFlag::empty(),
356 }))?,
357 BlockRequest::GetTypeGuid { responder } => {
358 responder.send(0, Some(&Guid { value: guid }))?
359 }
360 _ => panic!("Expected a GetInfo/GetName request, but did not get one."),
361 }
362 }
363 Ok(())
364 }
365
366 fn mock_partition(
367 label: &'static str,
368 block_size: usize,
369 block_count: usize,
370 guid: [u8; 16],
371 ) -> Result<BlockProxy, Error> {
372 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<BlockMarker>();
373 fasync::Task::local(
374 serve_partition(
375 label,
376 block_size.try_into().unwrap(),
377 block_count.try_into().unwrap(),
378 guid,
379 stream,
380 )
381 .unwrap_or_else(|e| panic!("Error while serving fake block device: {}", e)),
382 )
383 .detach();
384 Ok(proxy)
385 }
386
387 #[fuchsia::test]
388 async fn test_new_partition_bad_guid() -> Result<(), Error> {
389 let proxy = mock_partition("zircon_a", 512, 1000, [0xaa; 16])?;
390 let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Efi).await?;
391 assert!(part.is_none());
392 Ok(())
393 }
394
395 #[fuchsia::test]
396 async fn test_new_partition_zircona() -> Result<(), Error> {
397 let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
398 let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Efi).await?;
399 assert!(part.is_some());
400 let part = part.unwrap();
401 assert_eq!(
402 part.pave_type,
403 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
404 );
405 assert_eq!(part.size, 512 * 1000);
406 assert_eq!(part.src, "zircon_a");
407 assert!(part.is_ab());
408 Ok(())
409 }
410
411 #[fuchsia::test]
412 async fn test_new_partition_zirconb() -> Result<(), Error> {
413 let proxy = mock_partition("zircon_b", 20, 1000, WORKSTATION_INSTALLER_GPT)?;
414 let part = Partition::new("zircon_b".to_string(), proxy, BootloaderType::Efi).await?;
415 assert!(part.is_some());
416 let part = part.unwrap();
417 assert_eq!(
418 part.pave_type,
419 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
420 );
421 assert_eq!(part.size, 20 * 1000);
422 assert_eq!(part.src, "zircon_b");
423 assert!(part.is_ab());
424 Ok(())
425 }
426
427 #[fuchsia::test]
428 async fn test_new_partition_zirconr() -> Result<(), Error> {
429 let proxy = mock_partition("zircon_r", 40, 200, WORKSTATION_INSTALLER_GPT)?;
430 let part = Partition::new("zircon_r".to_string(), proxy, BootloaderType::Efi).await?;
431 assert!(part.is_some());
432 let part = part.unwrap();
433 assert_eq!(
434 part.pave_type,
435 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::Recovery }
436 );
437 assert_eq!(part.size, 40 * 200);
438 assert_eq!(part.src, "zircon_r");
439 assert!(!part.is_ab());
440 Ok(())
441 }
442
443 async fn new_partition_vbmetax_test_helper(
444 name: &'static str,
445 expected_config: Configuration,
446 ) -> Result<(), Error> {
447 let proxy = mock_partition(name, 40, 200, WORKSTATION_INSTALLER_GPT)?;
448 let part = Partition::new(name.to_string(), proxy, BootloaderType::Efi).await?;
449 assert!(part.is_some());
450 let part = part.unwrap();
451 assert_eq!(
452 part.pave_type,
453 PartitionPaveType::Asset {
454 r#type: Asset::VerifiedBootMetadata,
455 config: expected_config
456 }
457 );
458 assert_eq!(part.size, 40 * 200);
459 assert_eq!(part.src, name);
460 Ok(())
461 }
462
463 #[fuchsia::test]
464 async fn test_new_partition_vbmetaa() -> Result<(), Error> {
465 new_partition_vbmetax_test_helper("vbmeta_a", Configuration::A).await
466 }
467
468 #[fuchsia::test]
469 async fn test_new_partition_vbmetab() -> Result<(), Error> {
470 new_partition_vbmetax_test_helper("vbmeta_b", Configuration::A).await
473 }
474
475 #[fuchsia::test]
476 async fn test_new_partition_vbmetar() -> Result<(), Error> {
477 new_partition_vbmetax_test_helper("vbmeta_r", Configuration::Recovery).await
478 }
479
480 #[fuchsia::test]
481 async fn test_new_partition_efi() -> Result<(), Error> {
482 let proxy = mock_partition("efi", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
483 let part = Partition::new("efi".to_string(), proxy, BootloaderType::Efi).await?;
484 assert!(part.is_some());
485 let part = part.unwrap();
486 assert_eq!(part.pave_type, PartitionPaveType::Bootloader);
487 assert_eq!(part.size, 512 * 1000);
488 assert_eq!(part.src, "efi");
489 assert!(!part.is_ab());
490 Ok(())
491 }
492
493 #[fuchsia::test]
494 async fn test_new_partition_fvm() -> Result<(), Error> {
495 let proxy = mock_partition("storage-sparse", 2048, 4097, WORKSTATION_INSTALLER_GPT)?;
496 let part = Partition::new("storage-sparse".to_string(), proxy, BootloaderType::Efi).await?;
497 assert!(part.is_some());
498 let part = part.unwrap();
499 assert_eq!(part.pave_type, PartitionPaveType::Volume);
500 assert_eq!(part.size, 2048 * 4097);
501 assert_eq!(part.src, "storage-sparse");
502 assert!(!part.is_ab());
503 Ok(())
504 }
505
506 #[fuchsia::test]
507 async fn test_zircona_unsigned_coreboot() -> Result<(), Error> {
508 let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
509 let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Coreboot).await?;
510 assert!(part.is_none());
511 Ok(())
512 }
513
514 #[fuchsia::test]
515 async fn test_zircona_signed_coreboot() -> Result<(), Error> {
516 let proxy = mock_partition("zircon_a.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
517 let part =
518 Partition::new("zircon_a.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
519 assert!(part.is_some());
520 let part = part.unwrap();
521 assert_eq!(
522 part.pave_type,
523 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
524 );
525 assert_eq!(part.size, 512 * 1000);
526 assert_eq!(part.src, "zircon_a.signed");
527 assert!(part.is_ab());
528 Ok(())
529 }
530
531 #[fuchsia::test]
532 async fn test_new_partition_unknown() -> Result<(), Error> {
533 let proxy = mock_partition("unknown-label", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
534 let part = Partition::new("unknown-label".to_string(), proxy, BootloaderType::Efi).await?;
535 assert!(part.is_none());
536 Ok(())
537 }
538
539 #[fuchsia::test]
540 async fn test_new_partition_zedboot_efi() -> Result<(), Error> {
541 let proxy = mock_partition("zedboot-efi", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
542 let part = Partition::new("zedboot-efi".to_string(), proxy, BootloaderType::Efi).await?;
543 assert!(part.is_none());
544 Ok(())
545 }
546
547 #[fuchsia::test]
548 async fn test_invalid_partitions_coreboot() -> Result<(), Error> {
549 let proxy = mock_partition("zircon_.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
550 let part =
551 Partition::new("zircon_.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
552 assert!(part.is_none());
553
554 let proxy = mock_partition("zircon_aa.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
555 let part =
556 Partition::new("zircon_aa.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
557 assert!(part.is_none());
558
559 Ok(())
560 }
561
562 #[fuchsia::test]
563 async fn test_invalid_partitions_efi() -> Result<(), Error> {
564 let proxy = mock_partition("zircon_", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
565 let part = Partition::new("zircon_".to_string(), proxy, BootloaderType::Efi).await?;
566 assert!(part.is_none());
567
568 let proxy = mock_partition("zircon_aa", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
569 let part = Partition::new("zircon_aa".to_string(), proxy, BootloaderType::Efi).await?;
570 assert!(part.is_none());
571
572 let proxy = mock_partition("zircon_a.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
573 let part =
574 Partition::new("zircon_a.signed".to_string(), proxy, BootloaderType::Efi).await?;
575 assert!(part.is_none());
576 Ok(())
577 }
578
579 #[fuchsia::test]
580 async fn test_new_partition_usb_bad_guid() -> Result<(), Error> {
581 let proxy = mock_partition("zircon_a", 512, 1000, [0xaa; 16])?;
582 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
583 assert!(part.is_none());
584 Ok(())
585 }
586
587 #[fuchsia::test]
588 async fn test_new_partition_usb_zircona() -> Result<(), Error> {
589 let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_PARTITION_GPTS[2])?;
590 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
591 assert!(part.is_some());
592 let part = part.unwrap();
593 assert_eq!(
594 part.pave_type,
595 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
596 );
597 assert_eq!(part.size, 512 * 1000);
598 assert_eq!(part.src, "/dev/usb-bus");
599 assert!(part.is_ab());
600 Ok(())
601 }
602
603 #[fuchsia::test]
604 async fn test_new_partition_usb_zirconb() -> Result<(), Error> {
605 let proxy = mock_partition("zircon_b", 20, 1000, WORKSTATION_PARTITION_GPTS[3])?;
606 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
607 assert!(part.is_some());
608 let part = part.unwrap();
609 assert_eq!(
610 part.pave_type,
611 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
612 );
613 assert_eq!(part.size, 20 * 1000);
614 assert_eq!(part.src, "/dev/usb-bus");
615 assert!(part.is_ab());
616 Ok(())
617 }
618
619 #[fuchsia::test]
620 async fn test_new_partition_usb_zirconr() -> Result<(), Error> {
621 let proxy = mock_partition("zircon_r", 40, 200, WORKSTATION_PARTITION_GPTS[4])?;
622 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
623 assert!(part.is_some());
624 let part = part.unwrap();
625 assert_eq!(
626 part.pave_type,
627 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::Recovery }
628 );
629 assert_eq!(part.size, 40 * 200);
630 assert_eq!(part.src, "/dev/usb-bus");
631 assert!(!part.is_ab());
632 Ok(())
633 }
634
635 #[fuchsia::test]
636 async fn test_new_partition_usb_efi() -> Result<(), Error> {
637 let proxy = mock_partition("efi-system", 512, 1000, WORKSTATION_PARTITION_GPTS[0])?;
638 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
639 assert!(part.is_some());
640 let part = part.unwrap();
641 assert_eq!(part.pave_type, PartitionPaveType::Bootloader);
642 assert_eq!(part.size, 512 * 1000);
643 assert_eq!(part.src, "/dev/usb-bus");
644 assert!(!part.is_ab());
645 Ok(())
646 }
647}