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 fuchsia_sync::Mutex;
14use futures::TryFutureExt;
15use futures::future::try_join;
16use payload_streamer::{BlockDevicePayloadStreamer, PayloadStreamer};
17use recovery_util_block::BlockDevice;
18use std::cmp::min;
19use std::fmt;
20
21/// Number of nanoseconds in a second.
22const NS_PER_S: i64 = 1_000_000_000;
23
24#[derive(Debug, PartialEq)]
25pub enum PartitionPaveType {
26    Asset { r#type: Asset, config: Configuration },
27    Volume,
28    Bootloader,
29}
30
31/// Represents a partition that will be paved to the disk.
32pub struct Partition {
33    pave_type: PartitionPaveType,
34    src: String,
35    size: u64,
36    block_size: u64,
37}
38
39/// This GUID is used by the installer to identify partitions that contain
40/// data that will be installed to disk. The `fx mkinstaller` tool generates
41/// images containing partitions with this GUID.
42static WORKSTATION_INSTALLER_GPT: [u8; 16] = [
43    0xce, 0x98, 0xce, 0x4d, 0x7e, 0xe7, 0xc1, 0x45, 0xa8, 0x63, 0xca, 0xf9, 0x2f, 0x13, 0x30, 0xc1,
44];
45
46/// These GUIDs are used by the installer to identify partitions that contain
47/// data that will be installed to disk from a usb disk. The `fx make-fuchsia-vol`
48/// tool generates images containing partitions with these GUIDs.
49static WORKSTATION_PARTITION_GPTS: [[u8; 16]; 5] = [
50    [
51        0xfe, 0x94, 0xce, 0x5e, 0x86, 0x4c, 0xe8, 0x11, 0xa1, 0x5b, 0x48, 0x0f, 0xcf, 0x35, 0xf8,
52        0xe6,
53    ], // bootloader
54    [
55        0x6b, 0xe1, 0x09, 0xa4, 0xaa, 0x78, 0xcc, 0x4a, 0x5c, 0x99, 0x41, 0x1a, 0x62, 0x52, 0x23,
56        0x30,
57    ], // durable_boot
58    [
59        0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
60        0xf7,
61    ], // zircon_a
62    [
63        0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
64        0xf7,
65    ], // zircon_b
66    [
67        0xf6, 0xff, 0x37, 0x9b, 0x58, 0x2e, 0x6a, 0x46, 0x3a, 0x98, 0xe0, 0x04, 0x0b, 0x6d, 0x92,
68        0xf7,
69    ], // zircon_r
70];
71
72impl Partition {
73    /// Creates a new partition. Returns `None` if the partition is not
74    /// a partition that should be paved to the disk.
75    ///
76    /// # Arguments
77    /// * `src` - path to a block device that represents this partition.
78    /// * `part` - a |BlockProxy| that is connected to this partition.
79    /// * `bootloader` - the |BootloaderType| of this device.
80    ///
81    async fn new(
82        src: String,
83        part: BlockProxy,
84        bootloader: BootloaderType,
85    ) -> Result<Option<Self>, Error> {
86        let (status, guid) = part.get_type_guid().await.context("Get type guid failed")?;
87        if let None = guid {
88            return Err(Error::new(zx::Status::from_raw(status)));
89        }
90
91        let (_status, name) = part.get_name().await.context("Get name failed")?;
92        let pave_type;
93        if let Some(string) = name {
94            let guid = guid.unwrap();
95            if guid.value != WORKSTATION_INSTALLER_GPT
96                && !(src.contains("usb-bus") && WORKSTATION_PARTITION_GPTS.contains(&guid.value))
97            {
98                return Ok(None);
99            }
100            // TODO(https://fxbug.dev/42121026) support any other partitions that might be needed
101            if string == "storage-sparse" {
102                pave_type = Some(PartitionPaveType::Volume);
103            } else if bootloader == BootloaderType::Efi {
104                pave_type = Partition::get_efi_pave_type(&string.to_lowercase());
105            } else if bootloader == BootloaderType::Coreboot {
106                pave_type = Partition::get_coreboot_pave_type(&string);
107            } else {
108                pave_type = None;
109            }
110        } else {
111            return Ok(None);
112        }
113
114        if let Some(pave_type) = pave_type {
115            let info =
116                part.get_info().await.context("Get info failed")?.map_err(zx::Status::from_raw)?;
117            let block_size = info.block_size.into();
118            let size = info.block_count * block_size;
119
120            Ok(Some(Partition { pave_type, src, size, block_size }))
121        } else {
122            Ok(None)
123        }
124    }
125
126    fn get_efi_pave_type(label: &str) -> Option<PartitionPaveType> {
127        if label.starts_with("zircon_") && label.len() == "zircon_x".len() {
128            let configuration = Partition::letter_to_configuration(label.chars().last().unwrap());
129            Some(PartitionPaveType::Asset { r#type: Asset::Kernel, config: configuration })
130        } else if label.starts_with("vbmeta_") && label.len() == "vbmeta_x".len() {
131            let configuration = Partition::letter_to_configuration(label.chars().last().unwrap());
132            Some(PartitionPaveType::Asset {
133                r#type: Asset::VerifiedBootMetadata,
134                config: configuration,
135            })
136        } else if label.starts_with("efi")
137            || label.starts_with("fuchsia.esp")
138            || label.starts_with("bootloader")
139        {
140            Some(PartitionPaveType::Bootloader)
141        } else {
142            None
143        }
144    }
145
146    fn get_coreboot_pave_type(label: &str) -> Option<PartitionPaveType> {
147        if let Ok(re) = regex::Regex::new(r"^zircon_(.)\.signed$") {
148            if let Some(captures) = re.captures(label) {
149                let config = Partition::letter_to_configuration(
150                    captures.get(1).unwrap().as_str().chars().last().unwrap(),
151                );
152                Some(PartitionPaveType::Asset { r#type: Asset::Kernel, config: config })
153            } else {
154                None
155            }
156        } else {
157            None
158        }
159    }
160
161    /// Gather all partitions that are children of the given block device,
162    /// and return them.
163    ///
164    /// # Arguments
165    /// * `block_device` - the |BlockDevice| to get partitions from.
166    /// * `all_devices` - All known block devices in the system.
167    /// * `bootloader` - the |BootloaderType| of this device.
168    pub async fn get_partitions(
169        block_device: &BlockDevice,
170        all_devices: &Vec<BlockDevice>,
171        bootloader: BootloaderType,
172    ) -> Result<Vec<Self>, Error> {
173        let mut partitions = Vec::new();
174
175        for entry in all_devices {
176            if !entry.topo_path.starts_with(&block_device.topo_path) || entry == block_device {
177                // Skip partitions that are not children of this block device, and skip the block
178                // device itself.
179                continue;
180            }
181            let (local, remote) = zx::Channel::create();
182            fdio::service_connect(&entry.class_path, remote).context("Connecting to partition")?;
183            let local = fidl::AsyncChannel::from_channel(local);
184
185            let proxy = BlockProxy::from_channel(local);
186            if let Some(partition) = Partition::new(entry.class_path.clone(), proxy, bootloader)
187                .await
188                .context(format!(
189                    "Creating partition for block device at {} ({})",
190                    entry.topo_path, entry.class_path
191                ))?
192            {
193                partitions.push(partition);
194            }
195        }
196        Ok(partitions)
197    }
198
199    /// Pave this partition to disk, using the given |DynamicDataSinkProxy|.
200    pub async fn pave<F>(
201        &self,
202        data_sink: &DynamicDataSinkProxy,
203        progress_callback: &F,
204    ) -> Result<(), Error>
205    where
206        F: Send + Sync + Fn(usize, usize) -> (),
207    {
208        match self.pave_type {
209            PartitionPaveType::Asset { r#type: asset, config } => {
210                let fidl_buf = self.read_data().await?;
211                data_sink.write_asset(config, asset, fidl_buf).await?;
212            }
213            PartitionPaveType::Bootloader => {
214                let fidl_buf = self.read_data().await?;
215                // Currently we only store the bootloader in slot A, we don't use an A/B/R scheme.
216                data_sink.write_firmware(Configuration::A, "", fidl_buf).await?;
217            }
218            PartitionPaveType::Volume => {
219                self.pave_volume(data_sink, progress_callback).await?;
220            }
221        };
222        Ok(())
223    }
224
225    async fn pave_volume<F>(
226        &self,
227        data_sink: &DynamicDataSinkProxy,
228        progress_callback: &F,
229    ) -> Result<(), Error>
230    where
231        F: Send + Sync + Fn(usize, usize) -> (),
232    {
233        // Set up a PayloadStream to serve the data sink.
234        let partition_block =
235            fuchsia_component::client::connect_to_protocol_at_path::<BlockMarker>(&self.src)?;
236        let streamer: Box<dyn PayloadStreamer> =
237            Box::new(BlockDevicePayloadStreamer::new(partition_block).await?);
238        let start_time = zx::MonotonicInstant::get();
239        let last_percent = Mutex::new(0 as i64);
240        let status_callback = move |data_read, data_total| {
241            progress_callback(data_read, data_total);
242            if data_total == 0 {
243                return;
244            }
245            let percent: i64 =
246                unsafe { (((data_read as f64) / (data_total as f64)) * 100.0).to_int_unchecked() };
247            let mut prev = last_percent.lock();
248            if percent != *prev {
249                let now = zx::MonotonicInstant::get();
250                let nanos = now.into_nanos() - start_time.into_nanos();
251                let secs = nanos / NS_PER_S;
252                let rate = ((data_read as f64) / (secs as f64)) / (1024 as f64);
253
254                log::info!("Paving FVM: {}% ({:.02} KiB/s)", percent, rate);
255                *prev = percent;
256            }
257        };
258        let (client, server) =
259            fidl::endpoints::create_request_stream::<fidl_fuchsia_paver::PayloadStreamMarker>();
260
261        // Run the server and client ends of the PayloadStream concurrently.
262        try_join(
263            streamer.service_payload_stream_requests(server, Some(&status_callback)),
264            data_sink.write_volumes(client).map_err(|e| e.into()),
265        )
266        .await?;
267
268        Ok(())
269    }
270
271    /// Pave this A/B partition to its 'B' slot.
272    /// Will return an error if the partition is not an A/B partition.
273    pub async fn pave_b(&self, data_sink: &DynamicDataSinkProxy) -> Result<(), Error> {
274        if !self.is_ab() {
275            return Err(Error::from(zx::Status::NOT_SUPPORTED));
276        }
277
278        let fidl_buf = self.read_data().await?;
279        match self.pave_type {
280            PartitionPaveType::Asset { r#type: asset, config: _ } => {
281                // pave() will always pave to A, so this always paves to B.
282                // The A/B config from the partition is not respected because on a fresh
283                // install we want A/B to be identical, so we install the same thing to both.
284                data_sink.write_asset(Configuration::B, asset, fidl_buf).await?;
285                Ok(())
286            }
287            _ => Err(Error::from(zx::Status::NOT_SUPPORTED)),
288        }
289    }
290
291    /// Returns true if this partition has A/B variants when installed.
292    pub fn is_ab(&self) -> bool {
293        if let PartitionPaveType::Asset { r#type: _, config } = self.pave_type {
294            // We only check against the A configuration because |letter_to_configuration|
295            // returns A for 'A' and 'B' configurations.
296            return config == Configuration::A;
297        }
298        return false;
299    }
300
301    /// Read this partition into a FIDL buffer.
302    async fn read_data(&self) -> Result<Buffer, Error> {
303        let mut rounded_size = self.size;
304        let page_size = u64::from(zx::system_get_page_size());
305        if rounded_size % page_size != 0 {
306            rounded_size += page_size;
307            rounded_size -= rounded_size % page_size;
308        }
309
310        let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, rounded_size)?;
311
312        let proxy =
313            fuchsia_component::client::connect_to_protocol_at_path::<BlockMarker>(&self.src)
314                .with_context(|| format!("Connecting to block device {}", &self.src))?;
315        let block_device = RemoteBlockClient::new(proxy).await?;
316        let vmo_id = block_device.attach_vmo(&vmo).await?;
317
318        // Reading too much at a time causes the UMS driver to return an error.
319        let max_read_length: u64 = self.block_size * 100;
320        let mut read: u64 = 0;
321        while read < self.size {
322            let read_size = min(self.size - read, max_read_length);
323            if let Err(e) = block_device
324                .read_at(MutableBufferSlice::new_with_vmo_id(&vmo_id, read, read_size), read)
325                .await
326                .context("Reading from partition to VMO")
327            {
328                // Need to detach before returning.
329                block_device.detach_vmo(vmo_id).await?;
330                return Err(e);
331            }
332
333            read += read_size;
334        }
335
336        block_device.detach_vmo(vmo_id).await?;
337
338        return Ok(Buffer { vmo: fidl::Vmo::from(vmo), size: self.size });
339    }
340
341    /// Return the |Configuration| that is represented by the given
342    /// character. Returns 'Recovery' for the letters 'R' and 'r', and 'A' for
343    /// anything else.
344    fn letter_to_configuration(letter: char) -> Configuration {
345        // Note that we treat 'A' and 'B' the same, as the installer will install
346        // the same image to both A and B.
347        match letter {
348            'A' | 'a' => Configuration::A,
349            'B' | 'b' => Configuration::A,
350            'R' | 'r' => Configuration::Recovery,
351            _ => Configuration::A,
352        }
353    }
354}
355
356impl fmt::Debug for Partition {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        match self.pave_type {
359            PartitionPaveType::Asset { r#type, config } => write!(
360                f,
361                "Partition[src={}, pave_type={:?}, asset={:?}, config={:?}]",
362                self.src, self.pave_type, r#type, config
363            ),
364            _ => write!(f, "Partition[src={}, pave_type={:?}]", self.src, self.pave_type),
365        }
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372    use fidl_fuchsia_storage_block::{
373        BlockInfo, BlockMarker, BlockRequest, BlockRequestStream, DeviceFlag, Guid,
374    };
375    use fuchsia_async as fasync;
376    use futures::TryStreamExt;
377
378    async fn serve_partition(
379        label: &str,
380        block_size: u32,
381        block_count: u64,
382        guid: [u8; 16],
383        mut stream: BlockRequestStream,
384    ) -> Result<(), Error> {
385        while let Some(req) = stream.try_next().await? {
386            match req {
387                BlockRequest::GetName { responder } => responder.send(0, Some(label))?,
388                BlockRequest::GetInfo { responder } => responder.send(Ok(&BlockInfo {
389                    block_count,
390                    block_size,
391                    max_transfer_size: 0,
392                    flags: DeviceFlag::empty(),
393                }))?,
394                BlockRequest::GetTypeGuid { responder } => {
395                    responder.send(0, Some(&Guid { value: guid }))?
396                }
397                _ => panic!("Expected a GetInfo/GetName request, but did not get one."),
398            }
399        }
400        Ok(())
401    }
402
403    fn mock_partition(
404        label: &'static str,
405        block_size: usize,
406        block_count: usize,
407        guid: [u8; 16],
408    ) -> Result<BlockProxy, Error> {
409        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<BlockMarker>();
410        fasync::Task::local(
411            serve_partition(
412                label,
413                block_size.try_into().unwrap(),
414                block_count.try_into().unwrap(),
415                guid,
416                stream,
417            )
418            .unwrap_or_else(|e| panic!("Error while serving fake block device: {}", e)),
419        )
420        .detach();
421        Ok(proxy)
422    }
423
424    #[fasync::run_singlethreaded(test)]
425    async fn test_new_partition_bad_guid() -> Result<(), Error> {
426        let proxy = mock_partition("zircon_a", 512, 1000, [0xaa; 16])?;
427        let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Efi).await?;
428        assert!(part.is_none());
429        Ok(())
430    }
431
432    #[fasync::run_singlethreaded(test)]
433    async fn test_new_partition_zircona() -> Result<(), Error> {
434        let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
435        let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Efi).await?;
436        assert!(part.is_some());
437        let part = part.unwrap();
438        assert_eq!(
439            part.pave_type,
440            PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
441        );
442        assert_eq!(part.size, 512 * 1000);
443        assert_eq!(part.src, "zircon_a");
444        assert!(part.is_ab());
445        Ok(())
446    }
447
448    #[fasync::run_singlethreaded(test)]
449    async fn test_new_partition_zirconb() -> Result<(), Error> {
450        let proxy = mock_partition("zircon_b", 20, 1000, WORKSTATION_INSTALLER_GPT)?;
451        let part = Partition::new("zircon_b".to_string(), proxy, BootloaderType::Efi).await?;
452        assert!(part.is_some());
453        let part = part.unwrap();
454        assert_eq!(
455            part.pave_type,
456            PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
457        );
458        assert_eq!(part.size, 20 * 1000);
459        assert_eq!(part.src, "zircon_b");
460        assert!(part.is_ab());
461        Ok(())
462    }
463
464    #[fasync::run_singlethreaded(test)]
465    async fn test_new_partition_zirconr() -> Result<(), Error> {
466        let proxy = mock_partition("zircon_r", 40, 200, WORKSTATION_INSTALLER_GPT)?;
467        let part = Partition::new("zircon_r".to_string(), proxy, BootloaderType::Efi).await?;
468        assert!(part.is_some());
469        let part = part.unwrap();
470        assert_eq!(
471            part.pave_type,
472            PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::Recovery }
473        );
474        assert_eq!(part.size, 40 * 200);
475        assert_eq!(part.src, "zircon_r");
476        assert!(!part.is_ab());
477        Ok(())
478    }
479
480    async fn new_partition_vbmetax_test_helper(
481        name: &'static str,
482        expected_config: Configuration,
483    ) -> Result<(), Error> {
484        let proxy = mock_partition(name, 40, 200, WORKSTATION_INSTALLER_GPT)?;
485        let part = Partition::new(name.to_string(), proxy, BootloaderType::Efi).await?;
486        assert!(part.is_some());
487        let part = part.unwrap();
488        assert_eq!(
489            part.pave_type,
490            PartitionPaveType::Asset {
491                r#type: Asset::VerifiedBootMetadata,
492                config: expected_config
493            }
494        );
495        assert_eq!(part.size, 40 * 200);
496        assert_eq!(part.src, name);
497        Ok(())
498    }
499
500    #[fasync::run_singlethreaded(test)]
501    async fn test_new_partition_vbmetaa() -> Result<(), Error> {
502        new_partition_vbmetax_test_helper("vbmeta_a", Configuration::A).await
503    }
504
505    #[fasync::run_singlethreaded(test)]
506    async fn test_new_partition_vbmetab() -> Result<(), Error> {
507        // 'A' and 'B' are treated the same, as the installer will install
508        // the same image to both A and B.
509        new_partition_vbmetax_test_helper("vbmeta_b", Configuration::A).await
510    }
511
512    #[fasync::run_singlethreaded(test)]
513    async fn test_new_partition_vbmetar() -> Result<(), Error> {
514        new_partition_vbmetax_test_helper("vbmeta_r", Configuration::Recovery).await
515    }
516
517    #[fasync::run_singlethreaded(test)]
518    async fn test_new_partition_efi() -> Result<(), Error> {
519        let proxy = mock_partition("efi", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
520        let part = Partition::new("efi".to_string(), proxy, BootloaderType::Efi).await?;
521        assert!(part.is_some());
522        let part = part.unwrap();
523        assert_eq!(part.pave_type, PartitionPaveType::Bootloader);
524        assert_eq!(part.size, 512 * 1000);
525        assert_eq!(part.src, "efi");
526        assert!(!part.is_ab());
527        Ok(())
528    }
529
530    #[fasync::run_singlethreaded(test)]
531    async fn test_new_partition_fvm() -> Result<(), Error> {
532        let proxy = mock_partition("storage-sparse", 2048, 4097, WORKSTATION_INSTALLER_GPT)?;
533        let part = Partition::new("storage-sparse".to_string(), proxy, BootloaderType::Efi).await?;
534        assert!(part.is_some());
535        let part = part.unwrap();
536        assert_eq!(part.pave_type, PartitionPaveType::Volume);
537        assert_eq!(part.size, 2048 * 4097);
538        assert_eq!(part.src, "storage-sparse");
539        assert!(!part.is_ab());
540        Ok(())
541    }
542
543    #[fasync::run_singlethreaded(test)]
544    async fn test_zircona_unsigned_coreboot() -> Result<(), Error> {
545        let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
546        let part = Partition::new("zircon_a".to_string(), proxy, BootloaderType::Coreboot).await?;
547        assert!(part.is_none());
548        Ok(())
549    }
550
551    #[fasync::run_singlethreaded(test)]
552    async fn test_zircona_signed_coreboot() -> Result<(), Error> {
553        let proxy = mock_partition("zircon_a.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
554        let part =
555            Partition::new("zircon_a.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
556        assert!(part.is_some());
557        let part = part.unwrap();
558        assert_eq!(
559            part.pave_type,
560            PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
561        );
562        assert_eq!(part.size, 512 * 1000);
563        assert_eq!(part.src, "zircon_a.signed");
564        assert!(part.is_ab());
565        Ok(())
566    }
567
568    #[fasync::run_singlethreaded(test)]
569    async fn test_new_partition_unknown() -> Result<(), Error> {
570        let proxy = mock_partition("unknown-label", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
571        let part = Partition::new("unknown-label".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_zedboot_efi() -> Result<(), Error> {
578        let proxy = mock_partition("zedboot-efi", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
579        let part = Partition::new("zedboot-efi".to_string(), proxy, BootloaderType::Efi).await?;
580        assert!(part.is_none());
581        Ok(())
582    }
583
584    #[fasync::run_singlethreaded(test)]
585    async fn test_invalid_partitions_coreboot() -> Result<(), Error> {
586        let proxy = mock_partition("zircon_.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
587        let part =
588            Partition::new("zircon_.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
589        assert!(part.is_none());
590
591        let proxy = mock_partition("zircon_aa.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
592        let part =
593            Partition::new("zircon_aa.signed".to_string(), proxy, BootloaderType::Coreboot).await?;
594        assert!(part.is_none());
595
596        Ok(())
597    }
598
599    #[fasync::run_singlethreaded(test)]
600    async fn test_invalid_partitions_efi() -> Result<(), Error> {
601        let proxy = mock_partition("zircon_", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
602        let part = Partition::new("zircon_".to_string(), proxy, BootloaderType::Efi).await?;
603        assert!(part.is_none());
604
605        let proxy = mock_partition("zircon_aa", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
606        let part = Partition::new("zircon_aa".to_string(), proxy, BootloaderType::Efi).await?;
607        assert!(part.is_none());
608
609        let proxy = mock_partition("zircon_a.signed", 512, 1000, WORKSTATION_INSTALLER_GPT)?;
610        let part =
611            Partition::new("zircon_a.signed".to_string(), proxy, BootloaderType::Efi).await?;
612        assert!(part.is_none());
613        Ok(())
614    }
615
616    #[fasync::run_singlethreaded(test)]
617    async fn test_new_partition_usb_bad_guid() -> Result<(), Error> {
618        let proxy = mock_partition("zircon_a", 512, 1000, [0xaa; 16])?;
619        let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
620        assert!(part.is_none());
621        Ok(())
622    }
623
624    #[fasync::run_singlethreaded(test)]
625    async fn test_new_partition_usb_zircona() -> Result<(), Error> {
626        let proxy = mock_partition("zircon_a", 512, 1000, WORKSTATION_PARTITION_GPTS[2])?;
627        let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
628        assert!(part.is_some());
629        let part = part.unwrap();
630        assert_eq!(
631            part.pave_type,
632            PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
633        );
634        assert_eq!(part.size, 512 * 1000);
635        assert_eq!(part.src, "/dev/usb-bus");
636        assert!(part.is_ab());
637        Ok(())
638    }
639
640    #[fasync::run_singlethreaded(test)]
641    async fn test_new_partition_usb_zirconb() -> Result<(), Error> {
642        let proxy = mock_partition("zircon_b", 20, 1000, WORKSTATION_PARTITION_GPTS[3])?;
643        let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
644        assert!(part.is_some());
645        let part = part.unwrap();
646        assert_eq!(
647            part.pave_type,
648            PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::A }
649        );
650        assert_eq!(part.size, 20 * 1000);
651        assert_eq!(part.src, "/dev/usb-bus");
652        assert!(part.is_ab());
653        Ok(())
654    }
655
656    #[fasync::run_singlethreaded(test)]
657    async fn test_new_partition_usb_zirconr() -> Result<(), Error> {
658        let proxy = mock_partition("zircon_r", 40, 200, WORKSTATION_PARTITION_GPTS[4])?;
659        let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
660        assert!(part.is_some());
661        let part = part.unwrap();
662        assert_eq!(
663            part.pave_type,
664            PartitionPaveType::Asset { r#type: Asset::Kernel, config: Configuration::Recovery }
665        );
666        assert_eq!(part.size, 40 * 200);
667        assert_eq!(part.src, "/dev/usb-bus");
668        assert!(!part.is_ab());
669        Ok(())
670    }
671
672    #[fasync::run_singlethreaded(test)]
673    async fn test_new_partition_usb_efi() -> Result<(), Error> {
674        let proxy = mock_partition("efi-system", 512, 1000, WORKSTATION_PARTITION_GPTS[0])?;
675        let part = Partition::new("/dev/usb-bus".to_string(), proxy, BootloaderType::Efi).await?;
676        assert!(part.is_some());
677        let part = part.unwrap();
678        assert_eq!(part.pave_type, PartitionPaveType::Bootloader);
679        assert_eq!(part.size, 512 * 1000);
680        assert_eq!(part.src, "/dev/usb-bus");
681        assert!(!part.is_ab());
682        Ok(())
683    }
684}