Skip to main content

installer/
partition.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
24/// Represents a partition that will be paved to the disk.
25pub struct Partition {
26    pave_type: PartitionPaveType,
27    src: String,
28    size: u64,
29    block_size: u64,
30}
31
32/// This GUID is used by the installer to identify partitions that contain
33/// data that will be installed to disk. The `fx mkinstaller` tool generates
34/// images containing partitions with this GUID.
35static WORKSTATION_INSTALLER_GPT: [u8; 16] = [
36    0xce, 0x98, 0xce, 0x4d, 0x7e, 0xe7, 0xc1, 0x45, 0xa8, 0x63, 0xca, 0xf9, 0x2f, 0x13, 0x30, 0xc1,
37];
38
39/// These GUIDs are used by the installer to identify partitions that contain
40/// data that will be installed to disk from a usb disk. The `fx make-fuchsia-vol`
41/// tool generates images containing partitions with these GUIDs.
42static 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    ], // bootloader
47    [
48        0x6b, 0xe1, 0x09, 0xa4, 0xaa, 0x78, 0xcc, 0x4a, 0x5c, 0x99, 0x41, 0x1a, 0x62, 0x52, 0x23,
49        0x30,
50    ], // durable_boot
51    [
52        0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
53        0xf7,
54    ], // zircon_a
55    [
56        0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
57        0xf7,
58    ], // zircon_b
59    [
60        0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
61        0xf7,
62    ], // zircon_r
63];
64
65impl Partition {
66    /// Creates a new partition. Returns `None` if the partition is not
67    /// a partition that should be paved to the disk.
68    ///
69    /// # Arguments
70    /// * `src` - path to a block device that represents this partition.
71    /// * `part` - a |BlockProxy| that is connected to this partition.
72    /// * `bootloader` - the |BootloaderType| of this device.
73    ///
74    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            // TODO(https://fxbug.dev/42121026) support any other partitions that might be needed
94            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    /// Gather all partitions that are children of the given block device,
155    /// and return them.
156    ///
157    /// # Arguments
158    /// * `block_device` - the |BlockDevice| to get partitions from.
159    /// * `all_devices` - All known block devices in the system.
160    /// * `bootloader` - the |BootloaderType| of this device.
161    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                // Skip partitions that are not children of this block device, and skip the block
171                // device itself.
172                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    /// Pave this partition to disk, using the given |DynamicDataSinkProxy|.
193    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                // Currently we only store the bootloader in slot A, we don't use an A/B/R scheme.
209                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    /// Pave this A/B partition to its 'B' slot.
230    /// Will return an error if the partition is not an A/B partition.
231    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                // pave() will always pave to A, so this always paves to B.
240                // The A/B config from the partition is not respected because on a fresh
241                // install we want A/B to be identical, so we install the same thing to both.
242                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    /// Returns true if this partition has A/B variants when installed.
250    pub fn is_ab(&self) -> bool {
251        if let PartitionPaveType::Asset { r#type: _, config } = self.pave_type {
252            // We only check against the A configuration because |letter_to_configuration|
253            // returns A for 'A' and 'B' configurations.
254            return config == Configuration::A;
255        }
256        return false;
257    }
258
259    /// Read this partition into a FIDL buffer.
260    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        // SAFETY: We only attach this VMO once, and we do not hold any Rust references to its
275        // memory (in fact, we do not even map it).
276        let vmo_id = unsafe { block_device.attach_vmo(&vmo) }.await?;
277
278        // Reading too much at a time causes the UMS driver to return an error.
279        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                // Need to detach before returning.
289                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    /// Return the |Configuration| that is represented by the given
302    /// character. Returns 'Recovery' for the letters 'R' and 'r', and 'A' for
303    /// anything else.
304    fn letter_to_configuration(letter: char) -> Configuration {
305        // Note that we treat 'A' and 'B' the same, as the installer will install
306        // the same image to both A and B.
307        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        // 'A' and 'B' are treated the same, as the installer will install
468        // the same image to both A and B.
469        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}