| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::{ |
| features::CmxFeature, warnings::Warning, PROTOCOLS_FOR_HERMETIC_TESTS, |
| PROTOCOLS_FOR_SYSTEM_TESTS, SYSTEM_TEST_SHARD, |
| }; |
| use anyhow::{bail, Context, Error}; |
| use serde::Deserialize; |
| use std::collections::BTreeSet; |
| |
| #[derive(Debug, Deserialize)] |
| #[serde(deny_unknown_fields)] |
| pub struct CmxSandbox { |
| services: Option<Vec<String>>, |
| dev: Option<Vec<String>>, |
| features: Option<Vec<CmxFeature>>, |
| |
| // unsupported |
| boot: Option<Vec<String>>, |
| pkgfs: Option<Vec<String>>, |
| system: Option<Vec<String>>, |
| } |
| |
| impl CmxSandbox { |
| pub fn uses( |
| &self, |
| warnings: &mut BTreeSet<Warning>, |
| include: &mut BTreeSet<String>, |
| injected_protocols: &BTreeSet<String>, |
| is_test: bool, |
| ) -> Result<Vec<cml::Use>, Error> { |
| if self.boot.is_some() { |
| bail!("`sandbox.boot` key is not supported for automatic conversion"); |
| } |
| if self.pkgfs.is_some() { |
| bail!("`sandbox.pkgfs` key is not supported for automatic conversion"); |
| } |
| if self.system.is_some() { |
| bail!("`sandbox.system` key is not supported for automatic conversion"); |
| } |
| |
| // accumulate protocols-from-parent separately from general uses so we can merge protocols |
| let mut uses = vec![]; |
| let mut protocols_from_parent = vec![]; |
| |
| // add everything from sandbox.services to use-from-parent except for protocols that came |
| // from injected services, since in v1 all protocols come from the component's parent |
| // (often the sys realm) |
| if let Some(services) = &self.services { |
| for protocol in services { |
| if injected_protocols.contains(&*protocol) { |
| continue; |
| } |
| |
| let available_for_hermetic = PROTOCOLS_FOR_HERMETIC_TESTS.contains(&&**protocol); |
| let available_for_system = PROTOCOLS_FOR_SYSTEM_TESTS.contains(&&**protocol); |
| |
| // if the protocol isn't available to hermetic tests, run as system test |
| if is_test && !available_for_hermetic { |
| include.insert(SYSTEM_TEST_SHARD.to_string()); |
| } |
| |
| // if the protocol isn't available to system tests, warn the user |
| if is_test && !available_for_hermetic && !available_for_system { |
| warnings.insert(Warning::TestWithUnavailableProtocol(protocol.clone())); |
| } |
| |
| protocols_from_parent.push( |
| cml::Name::new(protocol) |
| .with_context(|| format!("parsing {protocol} into a cml name"))?, |
| ); |
| } |
| } |
| |
| if let Some(devices) = &self.dev { |
| warnings.insert(Warning::DeviceDirectoryBestEffort); |
| if is_test { |
| // system components will get device directories from their parent but we need to |
| // ask to be run in the system test realm to get them in tests |
| include.insert(SYSTEM_TEST_SHARD.to_string()); |
| } |
| |
| for device in devices { |
| // these strings are in the form `class/CLASS`, we want a directory named |
| // `dev-CLASS` mounted at a component's `/dev/class/CLASS` path |
| let (_class_literal, device_class) = device.split_once("/").with_context(|| { |
| format!("supported devices are under /dev/class/*, got `/dev/{}`", device) |
| })?; |
| uses.push(cml::Use { |
| directory: Some( |
| cml::Name::new(format!("dev-{}", device_class)) |
| .with_context(|| format!("creating a `use` name for {device_class}"))?, |
| ), |
| rights: Some(cml::Rights(vec![cml::Right::ReadAlias])), |
| path: Some( |
| cml::Path::new(format!("/dev/{}", device)) |
| .with_context(|| format!("creating a `use` path for {device_class}"))?, |
| ), |
| ..Default::default() |
| }); |
| } |
| } |
| |
| if let Some(features) = &self.features { |
| for feature in features { |
| let (use_decl, warning, shard) = feature.uses(is_test)?; |
| if let Some(u) = use_decl { |
| if u.protocol.is_some() && u.from.is_none() { |
| match u.protocol.expect("protocol is_some was just true") { |
| cml::OneOrMany::One(p) => protocols_from_parent.push(p), |
| cml::OneOrMany::Many(_) => { |
| panic!("features should only produce a single protocol to use") |
| } |
| } |
| } else { |
| uses.push(u); |
| } |
| } |
| if let Some(w) = warning { |
| warnings.insert(w); |
| } |
| if let Some(s) = shard { |
| include.insert(s); |
| } |
| } |
| } |
| |
| // merge all protocol uses from parent into a single use with multiple protocols, put it 1st |
| if !protocols_from_parent.is_empty() { |
| let other_uses = uses; |
| uses = vec![cml::Use { |
| protocol: Some(cml::OneOrMany::Many(protocols_from_parent)), |
| ..Default::default() |
| }]; |
| uses.extend(other_uses.into_iter()); |
| } |
| |
| Ok(uses) |
| } |
| } |