1pub mod component;
6pub mod serde;
7
8use fidl::endpoints::ServerEnd;
9#[cfg(fuchsia_api_level_at_least = "HEAD")]
10use fidl_fuchsia_component_sandbox as fsandbox;
11use std::path::Path;
12use thiserror::Error;
13use {
14 fidl_fuchsia_component_runner as fcrunner, fidl_fuchsia_component_sandbox as _,
15 fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, fidl_fuchsia_mem as fmem,
16 fidl_fuchsia_process as fprocess,
17};
18
19const ARGS_KEY: &str = "args";
20const BINARY_KEY: &str = "binary";
21const ENVIRON_KEY: &str = "environ";
22
23#[derive(Clone, Debug, PartialEq, Eq, Error)]
25pub enum StartInfoProgramError {
26 #[error("\"program.binary\" must be specified")]
27 MissingBinary,
28
29 #[error("the value of \"program.binary\" must be a string")]
30 InValidBinaryType,
31
32 #[error("the value of \"program.binary\" must be a relative path")]
33 BinaryPathNotRelative,
34
35 #[error("the value of \"program.{0}\" must be an array of strings")]
36 InvalidStrVec(String),
37
38 #[error("\"program\" must be specified")]
39 NotFound,
40
41 #[error("invalid type for key \"{0}\", expected string")]
42 InvalidType(String),
43
44 #[error("invalid value for key \"{0}\", expected one of \"{1}\", found \"{2}\"")]
45 InvalidValue(String, String, String),
46
47 #[error("environ value at index \"{0}\" is invalid. Value must be format of 'VARIABLE=VALUE'")]
48 InvalidEnvironValue(usize),
49}
50
51pub fn get_resolved_url(start_info: &fcrunner::ComponentStartInfo) -> Option<String> {
53 start_info.resolved_url.clone()
54}
55
56pub fn get_value<'a>(dict: &'a fdata::Dictionary, key: &str) -> Option<&'a fdata::DictionaryValue> {
58 match &dict.entries {
59 Some(entries) => {
60 for entry in entries {
61 if entry.key == key {
62 return entry.value.as_ref().map(|val| &**val);
63 }
64 }
65 None
66 }
67 _ => None,
68 }
69}
70
71pub fn get_enum<'a>(
73 dict: &'a fdata::Dictionary,
74 key: &str,
75 variants: &[&str],
76) -> Result<Option<&'a str>, StartInfoProgramError> {
77 match get_value(dict, key) {
78 Some(fdata::DictionaryValue::Str(value)) => {
79 if variants.contains(&value.as_str()) {
80 Ok(Some(value.as_ref()))
81 } else {
82 Err(StartInfoProgramError::InvalidValue(
83 key.to_owned(),
84 format!("{:?}", variants),
85 value.to_owned(),
86 ))
87 }
88 }
89 Some(_) => Err(StartInfoProgramError::InvalidType(key.to_owned())),
90 None => Ok(None),
91 }
92}
93
94pub fn get_bool<'a>(dict: &'a fdata::Dictionary, key: &str) -> Result<bool, StartInfoProgramError> {
96 match get_enum(dict, key, &["true", "false"])? {
97 Some("true") => Ok(true),
98 _ => Ok(false),
99 }
100}
101
102pub fn get_string<'a>(dict: &'a fdata::Dictionary, key: &str) -> Option<&'a str> {
104 if let fdata::DictionaryValue::Str(value) = get_value(dict, key)? { Some(value) } else { None }
105}
106
107fn get_program_value<'a>(
108 start_info: &'a fcrunner::ComponentStartInfo,
109 key: &str,
110) -> Option<&'a fdata::DictionaryValue> {
111 get_value(start_info.program.as_ref()?, key)
112}
113
114pub fn get_program_string<'a>(
116 start_info: &'a fcrunner::ComponentStartInfo,
117 key: &str,
118) -> Option<&'a str> {
119 if let fdata::DictionaryValue::Str(value) = get_program_value(start_info, key)? {
120 Some(value)
121 } else {
122 None
123 }
124}
125
126pub fn get_program_strvec<'a>(
129 start_info: &'a fcrunner::ComponentStartInfo,
130 key: &str,
131) -> Result<Option<&'a Vec<String>>, StartInfoProgramError> {
132 match get_program_value(start_info, key) {
133 Some(args_value) => match args_value {
134 fdata::DictionaryValue::StrVec(vec) => Ok(Some(vec)),
135 _ => Err(StartInfoProgramError::InvalidStrVec(key.to_string())),
136 },
137 None => Ok(None),
138 }
139}
140
141pub fn get_program_binary(
144 start_info: &fcrunner::ComponentStartInfo,
145) -> Result<String, StartInfoProgramError> {
146 if let Some(program) = &start_info.program {
147 get_program_binary_from_dict(&program)
148 } else {
149 Err(StartInfoProgramError::NotFound)
150 }
151}
152
153pub fn get_program_binary_from_dict(
155 dict: &fdata::Dictionary,
156) -> Result<String, StartInfoProgramError> {
157 if let Some(val) = get_value(&dict, BINARY_KEY) {
158 if let fdata::DictionaryValue::Str(bin) = val {
159 if !Path::new(bin).is_absolute() {
160 Ok(bin.to_string())
161 } else {
162 Err(StartInfoProgramError::BinaryPathNotRelative)
163 }
164 } else {
165 Err(StartInfoProgramError::InValidBinaryType)
166 }
167 } else {
168 Err(StartInfoProgramError::MissingBinary)
169 }
170}
171
172pub fn get_program_args(
175 start_info: &fcrunner::ComponentStartInfo,
176) -> Result<Vec<String>, StartInfoProgramError> {
177 match get_program_strvec(start_info, ARGS_KEY)? {
178 Some(vec) => Ok(vec.iter().map(|v| v.clone()).collect()),
179 None => Ok(vec![]),
180 }
181}
182
183pub fn get_program_args_from_dict(
185 dict: &fdata::Dictionary,
186) -> Result<Vec<String>, StartInfoProgramError> {
187 match get_value(&dict, ARGS_KEY) {
188 Some(args_value) => match args_value {
189 fdata::DictionaryValue::StrVec(vec) => Ok(vec.iter().map(|v| v.clone()).collect()),
190 _ => Err(StartInfoProgramError::InvalidStrVec(ARGS_KEY.to_string())),
191 },
192 None => Ok(vec![]),
193 }
194}
195
196pub fn get_environ(dict: &fdata::Dictionary) -> Result<Option<Vec<String>>, StartInfoProgramError> {
197 match get_value(dict, ENVIRON_KEY) {
198 Some(fdata::DictionaryValue::StrVec(values)) => {
199 if values.is_empty() {
200 return Ok(None);
201 }
202 for (i, value) in values.iter().enumerate() {
203 let parts = value.split_once("=");
204 if parts.is_none() {
205 return Err(StartInfoProgramError::InvalidEnvironValue(i));
206 }
207 let parts = parts.unwrap();
208 if parts.0.is_empty() {
210 return Err(StartInfoProgramError::InvalidEnvironValue(i));
211 }
212 }
213 Ok(Some(values.clone()))
214 }
215 Some(fdata::DictionaryValue::Str(_)) => Err(StartInfoProgramError::InvalidValue(
216 ENVIRON_KEY.to_owned(),
217 "vector of string".to_owned(),
218 "string".to_owned(),
219 )),
220 Some(other) => Err(StartInfoProgramError::InvalidValue(
221 ENVIRON_KEY.to_owned(),
222 "vector of string".to_owned(),
223 format!("{:?}", other),
224 )),
225 None => Ok(None),
226 }
227}
228
229#[derive(Debug, Clone, Error)]
231pub enum ConfigDataError {
232 #[error("failed to create a vmo: {_0}")]
233 VmoCreate(#[source] zx::Status),
234 #[error("failed to write to vmo: {_0}")]
235 VmoWrite(#[source] zx::Status),
236 #[error("encountered an unrecognized variant of fuchsia.mem.Data")]
237 UnrecognizedDataVariant,
238}
239
240pub fn get_config_vmo(encoded_config: fmem::Data) -> Result<zx::Vmo, ConfigDataError> {
241 match encoded_config {
242 fmem::Data::Buffer(fmem::Buffer {
243 vmo,
244 size: _, }) => Ok(vmo),
246 fmem::Data::Bytes(bytes) => {
247 let size = bytes.len() as u64;
248 let vmo = zx::Vmo::create(size).map_err(ConfigDataError::VmoCreate)?;
249 vmo.write(&bytes, 0).map_err(ConfigDataError::VmoWrite)?;
250 Ok(vmo)
251 }
252 _ => Err(ConfigDataError::UnrecognizedDataVariant.into()),
253 }
254}
255
256#[derive(Debug, Clone, Error)]
258pub enum StartInfoError {
259 #[error("missing program")]
260 MissingProgram,
261 #[error("missing resolved URL")]
262 MissingResolvedUrl,
263}
264
265impl StartInfoError {
266 pub fn as_zx_status(&self) -> zx::Status {
268 match self {
269 StartInfoError::MissingProgram => zx::Status::INVALID_ARGS,
270 StartInfoError::MissingResolvedUrl => zx::Status::INVALID_ARGS,
271 }
272 }
273}
274
275pub struct StartInfo {
278 pub resolved_url: String,
283
284 pub program: fdata::Dictionary,
287
288 pub namespace: Vec<fcrunner::ComponentNamespaceEntry>,
309
310 pub outgoing_dir: Option<ServerEnd<fio::DirectoryMarker>>,
312
313 pub runtime_dir: Option<ServerEnd<fio::DirectoryMarker>>,
317
318 pub numbered_handles: Vec<fprocess::HandleInfo>,
323
324 pub encoded_config: Option<fmem::Data>,
335
336 pub break_on_start: Option<zx::EventPair>,
345
346 #[cfg(fuchsia_api_level_at_least = "HEAD")]
356 pub component_instance: Option<zx::Event>,
357
358 #[cfg(fuchsia_api_level_at_least = "HEAD")]
362 pub escrowed_dictionary: Option<fsandbox::DictionaryRef>,
363
364 #[cfg(fuchsia_api_level_at_least = "HEAD")]
368 pub escrowed_dictionary_handle: Option<zx::EventPair>,
369}
370
371impl TryFrom<fcrunner::ComponentStartInfo> for StartInfo {
372 type Error = StartInfoError;
373 fn try_from(start_info: fcrunner::ComponentStartInfo) -> Result<Self, Self::Error> {
374 let resolved_url = start_info.resolved_url.ok_or(StartInfoError::MissingResolvedUrl)?;
375 let program = start_info.program.ok_or(StartInfoError::MissingProgram)?;
376 Ok(Self {
377 resolved_url,
378 program,
379 namespace: start_info.ns.unwrap_or_else(|| Vec::new()),
380 outgoing_dir: start_info.outgoing_dir,
381 runtime_dir: start_info.runtime_dir,
382 numbered_handles: start_info.numbered_handles.unwrap_or_else(|| Vec::new()),
383 encoded_config: start_info.encoded_config,
384 break_on_start: start_info.break_on_start,
385 #[cfg(fuchsia_api_level_at_least = "HEAD")]
386 component_instance: start_info.component_instance,
387 #[cfg(fuchsia_api_level_at_least = "HEAD")]
388 escrowed_dictionary: start_info.escrowed_dictionary,
389 #[cfg(fuchsia_api_level_at_least = "HEAD")]
390 escrowed_dictionary_handle: start_info.escrowed_dictionary_handle,
391 })
392 }
393}
394
395impl From<StartInfo> for fcrunner::ComponentStartInfo {
396 fn from(start_info: StartInfo) -> Self {
397 Self {
398 resolved_url: Some(start_info.resolved_url),
399 program: Some(start_info.program),
400 ns: Some(start_info.namespace),
401 outgoing_dir: start_info.outgoing_dir,
402 runtime_dir: start_info.runtime_dir,
403 numbered_handles: Some(start_info.numbered_handles),
404 encoded_config: start_info.encoded_config,
405 break_on_start: start_info.break_on_start,
406 ..Default::default()
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use test_case::test_case;
415
416 #[test_case(Some("some_url"), Some("some_url".to_owned()) ; "when url is valid")]
417 #[test_case(None, None ; "when url is missing")]
418 fn get_resolved_url_test(maybe_url: Option<&str>, expected: Option<String>) {
419 let start_info = fcrunner::ComponentStartInfo {
420 resolved_url: maybe_url.map(str::to_owned),
421 program: None,
422 ns: None,
423 outgoing_dir: None,
424 runtime_dir: None,
425 ..Default::default()
426 };
427 assert_eq!(get_resolved_url(&start_info), expected,);
428 }
429
430 #[test_case(Some("bin/myexecutable"), Ok("bin/myexecutable".to_owned()) ; "when binary value is valid")]
431 #[test_case(Some("/bin/myexecutable"), Err(StartInfoProgramError::BinaryPathNotRelative) ; "when binary path is not relative")]
432 #[test_case(None, Err(StartInfoProgramError::NotFound) ; "when program stanza is not set")]
433 fn get_program_binary_test(
434 maybe_value: Option<&str>,
435 expected: Result<String, StartInfoProgramError>,
436 ) {
437 let start_info = match maybe_value {
438 Some(value) => new_start_info(Some(new_program_stanza("binary", value))),
439 None => new_start_info(None),
440 };
441 assert_eq!(get_program_binary(&start_info), expected);
442 }
443
444 #[test]
445 fn get_program_binary_test_when_binary_key_is_missing() {
446 let start_info = new_start_info(Some(new_program_stanza("some_other_key", "bin/foo")));
447 assert_eq!(get_program_binary(&start_info), Err(StartInfoProgramError::MissingBinary));
448 }
449
450 #[test_case("bin/myexecutable", Ok("bin/myexecutable".to_owned()) ; "when binary value is valid")]
451 #[test_case("/bin/myexecutable", Err(StartInfoProgramError::BinaryPathNotRelative) ; "when binary path is not relative")]
452 fn get_program_binary_from_dict_test(
453 value: &str,
454 expected: Result<String, StartInfoProgramError>,
455 ) {
456 let program = new_program_stanza("binary", value);
457 assert_eq!(get_program_binary_from_dict(&program), expected);
458 }
459
460 #[test]
461 fn get_program_binary_from_dict_test_when_binary_key_is_missing() {
462 let program = new_program_stanza("some_other_key", "bin/foo");
463 assert_eq!(
464 get_program_binary_from_dict(&program),
465 Err(StartInfoProgramError::MissingBinary)
466 );
467 }
468
469 #[test_case(&[], vec![] ; "when args is empty")]
470 #[test_case(&["a".to_owned()], vec!["a".to_owned()] ; "when args is a")]
471 #[test_case(&["a".to_owned(), "b".to_owned()], vec!["a".to_owned(), "b".to_owned()] ; "when args a and b")]
472 fn get_program_args_test(args: &[String], expected: Vec<String>) {
473 let start_info =
474 new_start_info(Some(new_program_stanza_with_vec(ARGS_KEY, Vec::from(args))));
475 assert_eq!(get_program_args(&start_info).unwrap(), expected);
476 }
477
478 #[test_case(&[], vec![] ; "when args is empty")]
479 #[test_case(&["a".to_owned()], vec!["a".to_owned()] ; "when args is a")]
480 #[test_case(&["a".to_owned(), "b".to_owned()], vec!["a".to_owned(), "b".to_owned()] ; "when args a and b")]
481 fn get_program_args_from_dict_test(args: &[String], expected: Vec<String>) {
482 let program = new_program_stanza_with_vec(ARGS_KEY, Vec::from(args));
483 assert_eq!(get_program_args_from_dict(&program).unwrap(), expected);
484 }
485
486 #[test]
487 fn get_program_args_invalid() {
488 let program = fdata::Dictionary {
489 entries: Some(vec![fdata::DictionaryEntry {
490 key: ARGS_KEY.to_string(),
491 value: Some(Box::new(fdata::DictionaryValue::Str("hello".to_string()))),
492 }]),
493 ..Default::default()
494 };
495 assert_eq!(
496 get_program_args_from_dict(&program),
497 Err(StartInfoProgramError::InvalidStrVec(ARGS_KEY.to_string()))
498 );
499 }
500
501 #[test_case(fdata::DictionaryValue::StrVec(vec!["foo=bar".to_owned(), "bar=baz".to_owned()]), Ok(Some(vec!["foo=bar".to_owned(), "bar=baz".to_owned()])); "when_values_are_valid")]
502 #[test_case(fdata::DictionaryValue::StrVec(vec![]), Ok(None); "when_value_is_empty")]
503 #[test_case(fdata::DictionaryValue::StrVec(vec!["=bad".to_owned()]), Err(StartInfoProgramError::InvalidEnvironValue(0)); "for_environ_with_empty_left_hand_side")]
504 #[test_case(fdata::DictionaryValue::StrVec(vec!["good=".to_owned()]), Ok(Some(vec!["good=".to_owned()])); "for_environ_with_empty_right_hand_side")]
505 #[test_case(fdata::DictionaryValue::StrVec(vec!["no_equal_sign".to_owned()]), Err(StartInfoProgramError::InvalidEnvironValue(0)); "for_environ_with_no_delimiter")]
506 #[test_case(fdata::DictionaryValue::StrVec(vec!["foo=bar=baz".to_owned()]), Ok(Some(vec!["foo=bar=baz".to_owned()])); "for_environ_with_multiple_delimiters")]
507 #[test_case(fdata::DictionaryValue::Str("foo=bar".to_owned()), Err(StartInfoProgramError::InvalidValue(ENVIRON_KEY.to_owned(), "vector of string".to_owned(), "string".to_owned())); "for_environ_as_invalid_type")]
508 fn get_environ_test(
509 value: fdata::DictionaryValue,
510 expected: Result<Option<Vec<String>>, StartInfoProgramError>,
511 ) {
512 let program = fdata::Dictionary {
513 entries: Some(vec![fdata::DictionaryEntry {
514 key: ENVIRON_KEY.to_owned(),
515 value: Some(Box::new(value)),
516 }]),
517 ..Default::default()
518 };
519
520 assert_eq!(get_environ(&program), expected);
521 }
522
523 fn new_start_info(program: Option<fdata::Dictionary>) -> fcrunner::ComponentStartInfo {
524 fcrunner::ComponentStartInfo {
525 program: program,
526 ns: None,
527 outgoing_dir: None,
528 runtime_dir: None,
529 resolved_url: None,
530 ..Default::default()
531 }
532 }
533
534 fn new_program_stanza(key: &str, value: &str) -> fdata::Dictionary {
535 fdata::Dictionary {
536 entries: Some(vec![fdata::DictionaryEntry {
537 key: key.to_owned(),
538 value: Some(Box::new(fdata::DictionaryValue::Str(value.to_owned()))),
539 }]),
540 ..Default::default()
541 }
542 }
543
544 fn new_program_stanza_with_vec(key: &str, values: Vec<String>) -> fdata::Dictionary {
545 fdata::Dictionary {
546 entries: Some(vec![fdata::DictionaryEntry {
547 key: key.to_owned(),
548 value: Some(Box::new(fdata::DictionaryValue::StrVec(values))),
549 }]),
550 ..Default::default()
551 }
552 }
553}