blob: 044748cd94d757b4f7d9e07e4a7f094a0946504d [file] [log] [blame]
// 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)
}
}