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::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 =
109 part.get_info().await.context("Get info failed")?.map_err(zx::Status::from_raw)?;
110 let block_size = info.block_size.into();
111 let size = info.block_count * block_size;
112
113 Ok(Some(Partition { pave_type, src, size, block_size }))
114 } else {
115 Ok(None)
116 }
117 }
118
119 fn get_efi_pave_type(label: &str) -> Option<PartitionPaveType> {
120 if label.starts_with("zircon_") && label.len() == "zircon_x".len() {
121 let configuration = Partition::letter_to_configuration(label.chars().last().unwrap());
122 Some(PartitionPaveType::Asset { r#type: Asset::Kernel, config: configuration })
123 } else if label.starts_with("vbmeta_") && label.len() == "vbmeta_x".len() {
124 let configuration = Partition::letter_to_configuration(label.chars().last().unwrap());
125 Some(PartitionPaveType::Asset {
126 r#type: Asset::VerifiedBootMetadata,
127 config: configuration,
128 })
129 } else if label.starts_with("efi")
130 || label.starts_with("fuchsia.esp")
131 || label.starts_with("bootloader")
132 {
133 Some(PartitionPaveType::Bootloader)
134 } else {
135 None
136 }
137 }
138
139 fn get_coreboot_pave_type(label: &str) -> Option<PartitionPaveType> {
140 if let Ok(re) = regex_lite::Regex::new(r"^zircon_(.)\.signed$") {
141 if let Some(captures) = re.captures(label) {
142 let config = Partition::letter_to_configuration(
143 captures.get(1).unwrap().as_str().chars().last().unwrap(),
144 );
145 Some(PartitionPaveType::Asset { r#type: Asset::Kernel, config: config })
146 } else {
147 None
148 }
149 } else {
150 None
151 }
152 }
153
154 pub async fn get_partitions(
162 block_device: &BlockDevice,
163 all_devices: &Vec<BlockDevice>,
164 bootloader: BootloaderType,
165 ) -> Result<Vec<Self>, Error> {
166 let mut partitions = Vec::new();
167
168 for entry in all_devices {
169 if !entry.topo_path.starts_with(&block_device.topo_path) || entry == block_device {
170 continue;
173 }
174 let (local, remote) = zx::Channel::create();
175 fdio::service_connect(&entry.class_path, remote).context("Connecting to partition")?;
176 let local = fidl::AsyncChannel::from_channel(local);
177
178 let proxy = BlockProxy::from_channel(local);
179 if let Some(partition) = Partition::new(entry.class_path.clone(), proxy, bootloader)
180 .await
181 .context(format!(
182 "Creating partition for block device at {} ({})",
183 entry.topo_path, entry.class_path
184 ))?
185 {
186 partitions.push(partition);
187 }
188 }
189 Ok(partitions)
190 }
191
192 pub async fn pave<F>(
194 &self,
195 data_sink: &DynamicDataSinkProxy,
196 progress_callback: &F,
197 ) -> Result<(), Error>
198 where
199 F: Send + Sync + Fn(usize, usize) -> (),
200 {
201 match self.pave_type {
202 PartitionPaveType::Asset { r#type: asset, config } => {
203 let fidl_buf = self.read_data().await?;
204 data_sink.write_asset(config, asset, fidl_buf).await?;
205 }
206 PartitionPaveType::Bootloader => {
207 let fidl_buf = self.read_data().await?;
208 data_sink.write_firmware(Configuration::A, "", fidl_buf).await?;
210 }
211 PartitionPaveType::Volume => {
212 self.pave_volume(data_sink, progress_callback).await?;
213 }
214 };
215 Ok(())
216 }
217
218 async fn pave_volume<F>(
219 &self,
220 _data_sink: &DynamicDataSinkProxy,
221 _progress_callback: &F,
222 ) -> Result<(), Error>
223 where
224 F: Send + Sync + Fn(usize, usize) -> (),
225 {
226 Err(Error::from(zx::Status::NOT_SUPPORTED))
227 }
228
229 pub async fn pave_b(&self, data_sink: &DynamicDataSinkProxy) -> Result<(), Error> {
232 if !self.is_ab() {
233 return Err(Error::from(zx::Status::NOT_SUPPORTED));
234 }
235
236 let fidl_buf = self.read_data().await?;
237 match self.pave_type {
238 PartitionPaveType::Asset { r#type: asset, config: _ } => {
239 data_sink.write_asset(Configuration::B, asset, fidl_buf).await?;
243 Ok(())
244 }
245 _ => Err(Error::from(zx::Status::NOT_SUPPORTED)),
246 }
247 }
248
249 pub fn is_ab(&self) -> bool {
251 if let PartitionPaveType::Asset { r#type: _, config } = self.pave_type {
252 return config == Configuration::A;
255 }
256 return false;
257 }
258
259 async fn read_data(&self) -> Result<Buffer, Error> {
261 let mut rounded_size = self.size;
262 let page_size = u64::from(zx::system_get_page_size());
263 if rounded_size % page_size != 0 {
264 rounded_size += page_size;
265 rounded_size -= rounded_size % page_size;
266 }
267
268 let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, rounded_size)?;
269
270 let proxy =
271 fuchsia_component::client::connect_to_protocol_at_path::<BlockMarker>(&self.src)
272 .with_context(|| format!("Connecting to block device {}", self.src))?;
273 let block_device = RemoteBlockClient::new(proxy).await?;
274 let vmo_id = unsafe { block_device.attach_vmo(&vmo) }.await?;
277
278 let max_read_length: u64 = self.block_size * 100;
280 let mut read: u64 = 0;
281 while read < self.size {
282 let read_size = min(self.size - read, max_read_length);
283 if let Err(e) = block_device
284 .read_at(MutableBufferSlice::new_with_vmo_id(&vmo_id, read, read_size), read)
285 .await
286 .context("Reading from partition to VMO")
287 {
288 block_device.detach_vmo(vmo_id).await?;
290 return Err(e);
291 }
292
293 read += read_size;
294 }
295
296 block_device.detach_vmo(vmo_id).await?;
297
298 return Ok(Buffer { vmo: fidl::Vmo::from(vmo), size: self.size });
299 }
300
301 fn letter_to_configuration(letter: char) -> Configuration {
305 match letter {
308 'A' | 'a' => Configuration::A,
309 'B' | 'b' => Configuration::A,
310 'R' | 'r' => Configuration::Recovery,
311 _ => Configuration::A,
312 }
313 }
314}
315
316impl fmt::Debug for Partition {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 match self.pave_type {
319 PartitionPaveType::Asset { r#type, config } => write!(
320 f,
321 "Partition[src={}, pave_type={:?}, asset={:?}, config={:?}]",
322 self.src, self.pave_type, r#type, config
323 ),
324 _ => write!(f, "Partition[src={}, pave_type={:?}]", self.src, self.pave_type),
325 }
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use fidl_fuchsia_storage_block::{
333 BlockInfo, BlockMarker, BlockRequest, BlockRequestStream, DeviceFlag, Guid,
334 };
335 use fuchsia_async as fasync;
336 use futures::{TryFutureExt, TryStreamExt};
337
338 async fn serve_partition(
339 label: &str,
340 block_size: u32,
341 block_count: u64,
342 guid: [u8; 16],
343 mut stream: BlockRequestStream,
344 ) -> Result<(), Error> {
345 while let Some(req) = stream.try_next().await? {
346 match req {
347 BlockRequest::GetName { responder } => responder.send(0, Some(label))?,
348 BlockRequest::GetInfo { responder } => responder.send(Ok(&BlockInfo {
349 block_count,
350 block_size,
351 max_transfer_size: 0,
352 flags: DeviceFlag::empty(),
353 }))?,
354 BlockRequest::GetTypeGuid { responder } => {
355 responder.send(0, Some(&Guid { value: guid }))?
356 }
357 _ => panic!("Expected a GetInfo/GetName request, but did not get one."),
358 }
359 }
360 Ok(())
361 }
362
363 fn mock_partition(
364 label: &'static str,
365 block_size: usize,
366 block_count: usize,
367 guid: [u8; 16],
368 ) -> Result<BlockProxy, Error> {
369 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<BlockMarker>();
370 fasync::Task::local(
371 serve_partition(
372 label,
373 block_size.try_into().unwrap(),
374 block_count.try_into().unwrap(),
375 guid,
376 stream,
377 )
378 .unwrap_or_else(|e| panic!("Error while serving fake block device: {}", e)),
379 )
380 .detach();
381 Ok(proxy)
382 }
383
384 #[fasync::run_singlethreaded(test)]
385 async fn test_new_partition_bad_guid() -> Result<(), Error> {
386 let proxy = mock_partition("zircon_a", 512, 1000, [0xaa; 16])?;
387 let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Efi).await?;
388 assert!(part.is_none());
389 Ok(())
390 }
391
392 #[fasync::run_singlethreaded(test)]
393 async fn test_new_partition_zircona() -> Result<(), Error> {
394 let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
395 let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Efi).await?;
396 assert!(part.is_some());
397 let part = part.unwrap();
398 assert_eq!(
399 part.pave_type,
400 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
401 );
402 assert_eq!(part.size, 512 * 1000);
403 assert_eq!(part.src, "zircon_a");
404 assert!(part.is_ab());
405 Ok(())
406 }
407
408 #[fasync::run_singlethreaded(test)]
409 async fn test_new_partition_zirconb() -> Result<(), Error> {
410 let proxy = mock_partition("zircon_b", 20, 1000, WORKSTATION_INSTALLER_GPT)?;
411 let part = Partition::new("zircon_b".to_string(), proxy, BootloaderType::Efi).await?;
412 assert!(part.is_some());
413 let part = part.unwrap();
414 assert_eq!(
415 part.pave_type,
416 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
417 );
418 assert_eq!(part.size, 20 * 1000);
419 assert_eq!(part.src, "zircon_b");
420 assert!(part.is_ab());
421 Ok(())
422 }
423
424 #[fasync::run_singlethreaded(test)]
425 async fn test_new_partition_zirconr() -> Result<(), Error> {
426 let proxy = mock_partition("zircon_r", 40, 200, WORKSTATION_INSTALLER_GPT)?;
427 let part = Partition::new("zircon_r".to_string(), proxy, BootloaderType::Efi).await?;
428 assert!(part.is_some());
429 let part = part.unwrap();
430 assert_eq!(
431 part.pave_type,
432 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::Recovery }
433 );
434 assert_eq!(part.size, 40 * 200);
435 assert_eq!(part.src, "zircon_r");
436 assert!(!part.is_ab());
437 Ok(())
438 }
439
440 async fn new_partition_vbmetax_test_helper(
441 name: &'static str,
442 expected_config: Configuration,
443 ) -> Result<(), Error> {
444 let proxy = mock_partition(name, 40, 200, WORKSTATION_INSTALLER_GPT)?;
445 let part = Partition::new(name.to_string(), proxy, BootloaderType::Efi).await?;
446 assert!(part.is_some());
447 let part = part.unwrap();
448 assert_eq!(
449 part.pave_type,
450 PartitionPaveType::Asset {
451 r#type: Asset::VerifiedBootMetadata,
452 config: expected_config
453 }
454 );
455 assert_eq!(part.size, 40 * 200);
456 assert_eq!(part.src, name);
457 Ok(())
458 }
459
460 #[fasync::run_singlethreaded(test)]
461 async fn test_new_partition_vbmetaa() -> Result<(), Error> {
462 new_partition_vbmetax_test_helper("vbmeta_a", Configuration::A).await
463 }
464
465 #[fasync::run_singlethreaded(test)]
466 async fn test_new_partition_vbmetab() -> Result<(), Error> {
467 new_partition_vbmetax_test_helper("vbmeta_b", Configuration::A).await
470 }
471
472 #[fasync::run_singlethreaded(test)]
473 async fn test_new_partition_vbmetar() -> Result<(), Error> {
474 new_partition_vbmetax_test_helper("vbmeta_r", Configuration::Recovery).await
475 }
476
477 #[fasync::run_singlethreaded(test)]
478 async fn test_new_partition_efi() -> Result<(), Error> {
479 let proxy = mock_partition("efi", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
480 let part = Partition::new("efi".to_string(), proxy, BootloaderType::Efi).await?;
481 assert!(part.is_some());
482 let part = part.unwrap();
483 assert_eq!(part.pave_type, PartitionPaveType::Bootloader);
484 assert_eq!(part.size, 512 * 1000);
485 assert_eq!(part.src, "efi");
486 assert!(!part.is_ab());
487 Ok(())
488 }
489
490 #[fasync::run_singlethreaded(test)]
491 async fn test_new_partition_fvm() -> Result<(), Error> {
492 let proxy = mock_partition("storage-sparse", 2048, 4097, WORKSTATION_INSTALLER_GPT)?;
493 let part = Partition::new("storage-sparse".to_string(), proxy, BootloaderType::Efi).await?;
494 assert!(part.is_some());
495 let part = part.unwrap();
496 assert_eq!(part.pave_type, PartitionPaveType::Volume);
497 assert_eq!(part.size, 2048 * 4097);
498 assert_eq!(part.src, "storage-sparse");
499 assert!(!part.is_ab());
500 Ok(())
501 }
502
503 #[fasync::run_singlethreaded(test)]
504 async fn test_zircona_unsigned_coreboot() -> Result<(), Error> {
505 let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
506 let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Coreboot).await?;
507 assert!(part.is_none());
508 Ok(())
509 }
510
511 #[fasync::run_singlethreaded(test)]
512 async fn test_zircona_signed_coreboot() -> Result<(), Error> {
513 let proxy = mock_partition("zircon_a.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
514 let part =
515 Partition::new("zircon_a.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
516 assert!(part.is_some());
517 let part = part.unwrap();
518 assert_eq!(
519 part.pave_type,
520 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
521 );
522 assert_eq!(part.size, 512 * 1000);
523 assert_eq!(part.src, "zircon_a.signed");
524 assert!(part.is_ab());
525 Ok(())
526 }
527
528 #[fasync::run_singlethreaded(test)]
529 async fn test_new_partition_unknown() -> Result<(), Error> {
530 let proxy = mock_partition("unknown-label", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
531 let part = Partition::new("unknown-label".to_string(), proxy, BootloaderType::Efi).await?;
532 assert!(part.is_none());
533 Ok(())
534 }
535
536 #[fasync::run_singlethreaded(test)]
537 async fn test_new_partition_zedboot_efi() -> Result<(), Error> {
538 let proxy = mock_partition("zedboot-efi", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
539 let part = Partition::new("zedboot-efi".to_string(), proxy, BootloaderType::Efi).await?;
540 assert!(part.is_none());
541 Ok(())
542 }
543
544 #[fasync::run_singlethreaded(test)]
545 async fn test_invalid_partitions_coreboot() -> Result<(), Error> {
546 let proxy = mock_partition("zircon_.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
547 let part =
548 Partition::new("zircon_.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
549 assert!(part.is_none());
550
551 let proxy = mock_partition("zircon_aa.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
552 let part =
553 Partition::new("zircon_aa.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
554 assert!(part.is_none());
555
556 Ok(())
557 }
558
559 #[fasync::run_singlethreaded(test)]
560 async fn test_invalid_partitions_efi() -> Result<(), Error> {
561 let proxy = mock_partition("zircon_", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
562 let part = Partition::new("zircon_".to_string(), proxy, BootloaderType::Efi).await?;
563 assert!(part.is_none());
564
565 let proxy = mock_partition("zircon_aa", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
566 let part = Partition::new("zircon_aa".to_string(), proxy, BootloaderType::Efi).await?;
567 assert!(part.is_none());
568
569 let proxy = mock_partition("zircon_a.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
570 let part =
571 Partition::new("zircon_a.signed".to_string(), proxy, BootloaderType::Efi).await?;
572 assert!(part.is_none());
573 Ok(())
574 }
575
576 #[fasync::run_singlethreaded(test)]
577 async fn test_new_partition_usb_bad_guid() -> Result<(), Error> {
578 let proxy = mock_partition("zircon_a", 512, 1000, [0xaa; 16])?;
579 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
580 assert!(part.is_none());
581 Ok(())
582 }
583
584 #[fasync::run_singlethreaded(test)]
585 async fn test_new_partition_usb_zircona() -> Result<(), Error> {
586 let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_PARTITION_GPTS[2])?;
587 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
588 assert!(part.is_some());
589 let part = part.unwrap();
590 assert_eq!(
591 part.pave_type,
592 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
593 );
594 assert_eq!(part.size, 512 * 1000);
595 assert_eq!(part.src, "/dev/usb-bus");
596 assert!(part.is_ab());
597 Ok(())
598 }
599
600 #[fasync::run_singlethreaded(test)]
601 async fn test_new_partition_usb_zirconb() -> Result<(), Error> {
602 let proxy = mock_partition("zircon_b", 20, 1000, WORKSTATION_PARTITION_GPTS[3])?;
603 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
604 assert!(part.is_some());
605 let part = part.unwrap();
606 assert_eq!(
607 part.pave_type,
608 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
609 );
610 assert_eq!(part.size, 20 * 1000);
611 assert_eq!(part.src, "/dev/usb-bus");
612 assert!(part.is_ab());
613 Ok(())
614 }
615
616 #[fasync::run_singlethreaded(test)]
617 async fn test_new_partition_usb_zirconr() -> Result<(), Error> {
618 let proxy = mock_partition("zircon_r", 40, 200, WORKSTATION_PARTITION_GPTS[4])?;
619 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
620 assert!(part.is_some());
621 let part = part.unwrap();
622 assert_eq!(
623 part.pave_type,
624 PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::Recovery }
625 );
626 assert_eq!(part.size, 40 * 200);
627 assert_eq!(part.src, "/dev/usb-bus");
628 assert!(!part.is_ab());
629 Ok(())
630 }
631
632 #[fasync::run_singlethreaded(test)]
633 async fn test_new_partition_usb_efi() -> Result<(), Error> {
634 let proxy = mock_partition("efi-system", 512, 1000, WORKSTATION_PARTITION_GPTS[0])?;
635 let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
636 assert!(part.is_some());
637 let part = part.unwrap();
638 assert_eq!(part.pave_type, PartitionPaveType::Bootloader);
639 assert_eq!(part.size, 512 * 1000);
640 assert_eq!(part.src, "/dev/usb-bus");
641 assert!(!part.is_ab());
642 Ok(())
643 }
644}