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