1mod component;
6mod component_set;
7pub mod config;
8mod crash_handler;
9pub mod crash_info;
10pub mod error;
11mod logger;
12mod memory;
13pub mod process_launcher;
14mod runtime_dir;
15pub mod stdout;
16pub mod vdso_vmo;
17
18use self::component::{ElfComponent, ElfComponentInfo};
19use self::config::ElfProgramConfig;
20use self::error::{JobError, StartComponentError, StartInfoError};
21use self::runtime_dir::RuntimeDirBuilder;
22use self::stdout::bind_streams_to_syslog;
23use crate::component_set::ComponentSet;
24use crate::config::ElfProgramBadHandlesPolicy;
25use crate::crash_info::CrashRecords;
26use crate::memory::reporter::MemoryReporter;
27use crate::runtime_dir::RuntimeDirectory;
28use crate::vdso_vmo::get_next_vdso_vmo;
29use ::routing::policy::ScopedPolicyChecker;
30use chrono::DateTime;
31use fidl::endpoints::{ClientEnd, ServerEnd};
32use fidl_fuchsia_component as fcomp;
33use fidl_fuchsia_component_runner as fcrunner;
34use fidl_fuchsia_component_runner::{
35 ComponentDiagnostics, ComponentTasks, Task as DiagnosticsTask,
36};
37use fidl_fuchsia_io as fio;
38use fidl_fuchsia_memory_attribution as fattribution;
39use fidl_fuchsia_process as fproc;
40use fidl_fuchsia_process_lifecycle::{LifecycleMarker, LifecycleProxy};
41use fuchsia_async::{self as fasync, TimeoutExt};
42use fuchsia_runtime::{
43 HandleInfo, HandleType, UtcClock, UtcTimeline, duplicate_utc_clock_handle, job_default,
44};
45use futures::channel::oneshot;
46use futures::{FutureExt, TryStreamExt};
47use log::{trace, warn};
48use moniker::Moniker;
49use namespace::Namespace;
50use runner::StartInfo;
51use runner::component::StopInfo;
52use std::collections::{HashMap, HashSet};
53use std::mem;
54use std::path::Path;
55use std::sync::Arc;
56use vfs::execution_scope::ExecutionScope;
57
58const MAX_WAIT_BREAK_ON_START: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(300);
62
63const TIMER_SLACK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_micros(50);
69
70const DUPLICATE_CLOCK_RIGHTS: zx::Rights = zx::Rights::from_bits_truncate(
83 zx::Rights::READ.bits() | zx::Rights::WAIT.bits() | zx::Rights::DUPLICATE.bits() | zx::Rights::TRANSFER.bits() | zx::Rights::INSPECT.bits()
88 | zx::Rights::MAP.bits(),
93);
94
95static MONIKER_PREFIXES_TO_ACCEPTABLE_EXIT_CODES: std::sync::LazyLock<
111 HashMap<&'static str, Vec<i64>>,
112> = std::sync::LazyLock::new(|| {
113 let mut m = HashMap::new();
114 m.insert("core/sshd-host/shell:sshd-", vec![255]);
115 m
116});
117
118pub struct ElfRunner {
121 job: zx::Job,
124
125 launcher_connector: process_launcher::Connector,
126
127 utc_clock: Option<Arc<UtcClock>>,
133
134 crash_records: CrashRecords,
135
136 components: Arc<ComponentSet>,
138
139 memory_reporter: MemoryReporter,
141
142 scope: ExecutionScope,
144
145 additional_environ: Vec<String>,
148}
149
150pub enum Job {
152 Single(zx::Job),
153 Multiple { parent: zx::Job, child: zx::Job },
154}
155
156impl Job {
157 fn top(&self) -> &zx::Job {
158 match self {
159 Job::Single(job) => job,
160 Job::Multiple { parent, child: _ } => parent,
161 }
162 }
163
164 fn proc(&self) -> &zx::Job {
165 match self {
166 Job::Single(job) => job,
167 Job::Multiple { parent: _, child } => child,
168 }
169 }
170}
171
172#[derive(Debug)]
176pub struct ElfComponentLaunchInfo {
177 pub ns: Namespace,
178 pub handle_infos: Vec<fproc::HandleInfo>,
179 pub utc_clock: UtcClock,
180 pub lifecycle_client: Option<LifecycleProxy>,
181 pub outgoing_directory: Option<ClientEnd<fio::DirectoryMarker>>,
182 pub local_scope: ExecutionScope,
183}
184
185impl ElfComponentLaunchInfo {
186 pub fn new(
187 start_info: &mut StartInfo,
188 program_config: &ElfProgramConfig,
189 utc_clock: Option<&UtcClock>,
190 ) -> Result<Self, StartComponentError> {
191 let namespace = mem::replace(&mut start_info.namespace, Default::default());
193 let ns = namespace::Namespace::try_from(namespace)
194 .map_err(StartComponentError::NamespaceError)?;
195
196 let config_vmo =
197 start_info.encoded_config.take().map(runner::get_config_vmo).transpose()?;
198
199 let next_vdso = program_config.use_next_vdso.then(get_next_vdso_vmo).transpose()?;
200
201 let (lifecycle_client, lifecycle_server) = if program_config.notify_lifecycle_stop {
202 let (client, server) = fidl::endpoints::create_proxy::<LifecycleMarker>();
204 (Some(client), Some(server.into_channel()))
205 } else {
206 (None, None)
207 };
208
209 let utc_handle = start_info
211 .numbered_handles
212 .iter()
213 .position(|handles| handles.id == HandleInfo::new(HandleType::ClockUtc, 0).as_raw())
214 .map(|position| start_info.numbered_handles.swap_remove(position).handle);
215
216 let utc_clock = if let Some(handle) = utc_handle {
217 zx::Clock::from(handle)
218 } else {
219 Self::duplicate_utc_clock(utc_clock)
220 .map_err(StartComponentError::UtcClockDuplicateFailed)?
221 };
222
223 let utc_clock_dup = utc_clock
226 .duplicate_handle(zx::Rights::SAME_RIGHTS)
227 .map_err(StartComponentError::UtcClockDuplicateFailed)?;
228
229 let outgoing_directory = if program_config.memory_attribution {
232 let Some(outgoing_dir) = start_info.outgoing_dir.take() else {
233 return Err(StartComponentError::StartInfoError(
234 StartInfoError::MissingOutgoingDir,
235 ));
236 };
237 let (outgoing_dir_client, outgoing_dir_server) = fidl::endpoints::create_endpoints();
238 start_info.outgoing_dir = Some(outgoing_dir_server);
239 fdio::open_at(
240 outgoing_dir_client.channel(),
241 ".",
242 fio::Flags::PROTOCOL_DIRECTORY
243 | fio::PERM_READABLE
244 | fio::PERM_WRITABLE
245 | fio::PERM_EXECUTABLE,
246 outgoing_dir.into_channel(),
247 )
248 .unwrap();
249 Some(outgoing_dir_client)
250 } else {
251 None
252 };
253
254 let mut handle_infos = Self::create_handle_infos(
256 start_info.outgoing_dir.take().map(|dir| dir.into_channel()),
257 lifecycle_server,
258 utc_clock,
259 next_vdso,
260 config_vmo,
261 );
262
263 let (local_scope, stdout_and_stderr_handles) =
265 bind_streams_to_syslog(&ns, program_config.stdout_sink, program_config.stderr_sink);
266 handle_infos.extend(stdout_and_stderr_handles);
267
268 let numbered_handles = mem::replace(&mut start_info.numbered_handles, Default::default());
270 handle_infos.extend(numbered_handles);
271
272 if let Some(escrowed_dictionary) = start_info.escrowed_dictionary.take() {
274 handle_infos.push(fproc::HandleInfo {
275 handle: escrowed_dictionary.token.into_handle().into(),
276 id: HandleInfo::new(HandleType::EscrowedDictionary, 0).as_raw(),
277 });
278 } else if let Some(escrowed_dictionary_handle) =
279 start_info.escrowed_dictionary_handle.take()
280 {
281 handle_infos.push(fproc::HandleInfo {
282 handle: escrowed_dictionary_handle.into(),
283 id: HandleInfo::new(HandleType::EscrowedDictionary, 0).as_raw(),
284 });
285 }
286
287 Ok(Self {
288 ns,
289 handle_infos,
290 utc_clock: utc_clock_dup,
291 local_scope,
292 lifecycle_client,
293 outgoing_directory,
294 })
295 }
296
297 fn create_handle_infos(
298 outgoing_dir: Option<zx::Channel>,
299 lifecycle_server: Option<zx::Channel>,
300 utc_clock: UtcClock,
301 next_vdso: Option<zx::Vmo>,
302 config_vmo: Option<zx::Vmo>,
303 ) -> Vec<fproc::HandleInfo> {
304 let mut handle_infos = vec![];
305
306 if let Some(outgoing_dir) = outgoing_dir {
307 handle_infos.push(fproc::HandleInfo {
308 handle: outgoing_dir.into_handle(),
309 id: HandleInfo::new(HandleType::DirectoryRequest, 0).as_raw(),
310 });
311 }
312
313 if let Some(lifecycle_chan) = lifecycle_server {
314 handle_infos.push(fproc::HandleInfo {
315 handle: lifecycle_chan.into_handle(),
316 id: HandleInfo::new(HandleType::Lifecycle, 0).as_raw(),
317 })
318 };
319
320 handle_infos.push(fproc::HandleInfo {
321 handle: utc_clock.into_handle(),
322 id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
323 });
324
325 if let Some(next_vdso) = next_vdso {
326 handle_infos.push(fproc::HandleInfo {
327 handle: next_vdso.into_handle(),
328 id: HandleInfo::new(HandleType::VdsoVmo, 0).as_raw(),
329 });
330 }
331
332 if let Some(config_vmo) = config_vmo {
333 handle_infos.push(fproc::HandleInfo {
334 handle: config_vmo.into_handle(),
335 id: HandleInfo::new(HandleType::ComponentConfigVmo, 0).as_raw(),
336 });
337 }
338
339 handle_infos
340 }
341
342 fn duplicate_utc_clock(utc_clock: Option<&UtcClock>) -> Result<UtcClock, zx::Status> {
346 if let Some(utc_clock) = utc_clock {
347 utc_clock.duplicate_handle(DUPLICATE_CLOCK_RIGHTS)
348 } else {
349 duplicate_utc_clock_handle(DUPLICATE_CLOCK_RIGHTS)
350 }
351 }
352}
353
354fn merge_environ(left: &[String], right: &[String]) -> Vec<String> {
359 fn get_key(kv: &str) -> &str {
360 kv.split('=').next().unwrap_or(kv)
361 }
362 let right_keys: HashSet<&str> = right.iter().map(|kv| get_key(kv.as_str())).collect();
363 let environ: Vec<String> = left
364 .iter()
365 .filter(|&kv| !right_keys.contains(get_key(kv.as_str())))
366 .chain(right.iter())
367 .cloned()
368 .collect();
369 environ
370}
371
372impl ElfRunner {
373 pub fn new(
374 job: zx::Job,
375 launcher_connector: process_launcher::Connector,
376 utc_clock: Option<Arc<UtcClock>>,
377 crash_records: CrashRecords,
378 additional_environ: Vec<String>,
379 ) -> ElfRunner {
380 let scope = ExecutionScope::new();
381 let components = ComponentSet::new(scope.clone());
382 let memory_reporter = MemoryReporter::new(components.clone());
383 ElfRunner {
384 job,
385 launcher_connector,
386 utc_clock,
387 crash_records,
388 components,
389 memory_reporter,
390 scope,
391 additional_environ,
392 }
393 }
394
395 fn create_job(&self, program_config: &ElfProgramConfig) -> Result<Job, JobError> {
397 let job = self.job.create_child_job().map_err(JobError::CreateChild)?;
398
399 job.set_policy(zx::JobPolicy::TimerSlack(
405 TIMER_SLACK_DURATION,
406 zx::JobDefaultTimerMode::Late,
407 ))
408 .map_err(JobError::SetPolicy)?;
409
410 if !program_config.create_raw_processes {
416 job.set_policy(zx::JobPolicy::Basic(
417 zx::JobPolicyOption::Absolute,
418 vec![(zx::JobCondition::NewProcess, zx::JobAction::Deny)],
419 ))
420 .map_err(JobError::SetPolicy)?;
421 }
422
423 if !program_config.ambient_mark_vmo_exec {
426 job.set_policy(zx::JobPolicy::Basic(
427 zx::JobPolicyOption::Absolute,
428 vec![(zx::JobCondition::AmbientMarkVmoExec, zx::JobAction::Deny)],
429 ))
430 .map_err(JobError::SetPolicy)?;
431 }
432
433 if let Some(job_policy_bad_handles) = &program_config.job_policy_bad_handles {
434 let action = match job_policy_bad_handles {
435 ElfProgramBadHandlesPolicy::DenyException => zx::JobAction::DenyException,
436 ElfProgramBadHandlesPolicy::AllowException => zx::JobAction::AllowException,
437 };
438 job.set_policy(zx::JobPolicy::Basic(
439 zx::JobPolicyOption::Absolute,
440 vec![(zx::JobCondition::BadHandle, action)],
441 ))
442 .map_err(JobError::SetPolicy)?;
443 }
444
445 Ok(if program_config.job_with_available_exception_channel {
446 let child = job.create_child_job().map_err(JobError::CreateChild)?;
452 Job::Multiple { parent: job, child }
453 } else {
454 Job::Single(job)
455 })
456 }
457
458 pub async fn start_component(
459 &self,
460 start_info: fcrunner::ComponentStartInfo,
461 checker: &ScopedPolicyChecker,
462 ) -> Result<ElfComponent, StartComponentError> {
463 let start_info: StartInfo =
464 start_info.try_into().map_err(StartInfoError::StartInfoError)?;
465
466 let resolved_url = start_info.resolved_url.clone();
467
468 let program_config = ElfProgramConfig::parse_and_check(&start_info.program, Some(checker))
471 .map_err(|err| {
472 StartComponentError::StartInfoError(StartInfoError::ProgramError(err))
473 })?;
474
475 let main_process_critical = program_config.main_process_critical;
476 let res: Result<ElfComponent, StartComponentError> = self
477 .start_component_helper(start_info, Some(checker.scope.clone()), program_config)
478 .boxed()
479 .await;
480 match res {
481 Err(e) if main_process_critical => {
482 panic!(
483 "failed to launch component with a critical process ({:?}): {:?}",
484 resolved_url, e
485 )
486 }
487 x => x,
488 }
489 }
490
491 async fn start_component_helper(
492 &self,
493 mut start_info: StartInfo,
494 moniker: Option<Moniker>,
495 program_config: ElfProgramConfig,
496 ) -> Result<ElfComponent, StartComponentError> {
497 let moniker = moniker.unwrap_or_else(|| Moniker::root());
498 let resolved_url = &start_info.resolved_url.clone();
499
500 let prep = self.prepare_launch(&moniker, &program_config, &mut start_info)?;
501
502 let launcher = self
504 .launcher_connector
505 .connect()
506 .map_err(|err| StartComponentError::ProcessLauncherConnectError(err.into()))?;
507
508 if let Some(break_on_start) = start_info.break_on_start {
510 fasync::OnSignals::new(&break_on_start, zx::Signals::OBJECT_PEER_CLOSED)
511 .on_timeout(MAX_WAIT_BREAK_ON_START, || Err(zx::Status::TIMED_OUT))
512 .await
513 .err()
514 .map(|error| warn!(moniker:%, error:%; "Failed to wait break_on_start"));
515 }
516
517 let launch_info =
518 runner::component::configure_launcher(runner::component::LauncherConfigArgs {
519 bin_path: &program_config.binary,
520 name: &prep.name,
521 options: program_config.process_options(),
522 args: Some(program_config.args.clone()),
523 ns: prep.ns,
524 job: Some(prep.proc_job_dup),
525 handle_infos: Some(prep.handle_infos),
526 name_infos: None,
527 environs: (!prep.environs.is_empty()).then_some(prep.environs),
528 launcher: &launcher,
529 loader_proxy_chan: None,
530 executable_vmo: None,
531 })
532 .await?;
533
534 let (status, process) = launcher
536 .launch(launch_info)
537 .await
538 .map_err(StartComponentError::ProcessLauncherFidlError)?;
539
540 zx::Status::ok(status).map_err(StartComponentError::CreateProcessFailed)?;
541 let process = process.unwrap(); if program_config.main_process_critical {
544 job_default()
545 .set_critical(zx::JobCriticalOptions::RETCODE_NONZERO, &process)
546 .map_err(StartComponentError::ProcessMarkCriticalFailed)
547 .expect("failed to set process as critical");
548 }
549
550 let pid = process.koid().map_err(StartComponentError::ProcessGetKoidFailed)?.raw_koid();
551
552 prep.runtime_dir.add_process_id(pid);
554
555 fuchsia_trace::instant!(
556 c"component:start",
557 c"elf",
558 fuchsia_trace::Scope::Thread,
559 "moniker" => format!("{}", moniker).as_str(),
560 "url" => resolved_url.as_str(),
561 "pid" => pid
562 );
563
564 let process_start_instant_mono =
566 process.info().map_err(StartComponentError::ProcessInfoFailed)?.start_time;
567 prep.runtime_dir.add_process_start_time(process_start_instant_mono.into_nanos());
568
569 let utc_clock_started = fasync::OnSignals::new(&prep.utc_clock, zx::Signals::CLOCK_STARTED)
571 .on_timeout(zx::MonotonicInstant::after(zx::MonotonicDuration::default()), || {
572 Err(zx::Status::TIMED_OUT)
573 })
574 .await
575 .is_ok();
576
577 let mono_to_clock_transformation =
580 prep.boot_clock.get_details().map(|details| details.reference_to_synthetic).ok();
581 let boot_to_utc_transformation = utc_clock_started
582 .then(|| {
583 prep.utc_clock.get_details().map(|details| details.reference_to_synthetic).ok()
584 })
585 .flatten();
586
587 if let Some(clock_transformation) = boot_to_utc_transformation {
588 let maybe_time_utc = mono_to_clock_transformation
600 .map(|t| t.apply(process_start_instant_mono))
601 .map(|time_boot| clock_transformation.apply(time_boot));
602
603 if let Some(utc_timestamp) = maybe_time_utc {
604 let utc_time_ns = utc_timestamp.into_nanos();
605 let seconds = (utc_time_ns / 1_000_000_000) as i64;
606 let nanos = (utc_time_ns % 1_000_000_000) as u32;
607 let dt = DateTime::from_timestamp(seconds, nanos).unwrap();
608
609 prep.runtime_dir.add_process_start_time_utc_estimate(dt.to_string())
612 }
613 };
614
615 Ok(ElfComponent::new(
616 prep.runtime_dir,
617 moniker,
618 prep.job,
619 process,
620 prep.lifecycle_client,
621 program_config.main_process_critical,
622 prep.local_scope,
623 resolved_url.clone(),
624 prep.outgoing_directory,
625 program_config,
626 start_info.component_instance.ok_or(StartComponentError::StartInfoError(
627 StartInfoError::MissingComponentInstanceToken,
628 ))?,
629 ))
630 }
631
632 pub fn get_scoped_runner(
633 self: Arc<Self>,
634 checker: ScopedPolicyChecker,
635 ) -> Arc<ScopedElfRunner> {
636 Arc::new(ScopedElfRunner { runner: self, checker })
637 }
638
639 pub fn serve_memory_reporter(&self, stream: fattribution::ProviderRequestStream) {
640 self.memory_reporter.serve(stream);
641 }
642
643 fn prepare_launch(
644 &self,
645 moniker: &Moniker,
646 program_config: &ElfProgramConfig,
647 start_info: &mut StartInfo,
648 ) -> Result<PreparedLaunch, StartComponentError> {
649 let boot_clock = zx::Clock::<zx::MonotonicTimeline, zx::BootTimeline>::create(
651 zx::ClockOpts::CONTINUOUS,
652 None,
653 )
654 .map_err(StartComponentError::BootClockCreateFailed)?;
655
656 let ElfComponentLaunchInfo {
657 ns,
658 handle_infos,
659 utc_clock,
660 lifecycle_client,
661 outgoing_directory,
662 local_scope,
663 } = ElfComponentLaunchInfo::new(
664 start_info,
665 &program_config,
666 self.utc_clock.as_ref().map(|c| &**c),
667 )?;
668
669 let resolved_url = &start_info.resolved_url;
670
671 let job = self.create_job(&program_config)?;
673
674 crash_handler::run_exceptions_server(
675 &self.scope,
676 job.top(),
677 moniker.clone(),
678 resolved_url.clone(),
679 self.crash_records.clone(),
680 )
681 .map_err(StartComponentError::ExceptionRegistrationFailed)?;
682
683 let runtime_dir_server_end = start_info
685 .runtime_dir
686 .take()
687 .ok_or(StartComponentError::StartInfoError(StartInfoError::MissingRuntimeDir))?;
688
689 let job_koid = job.proc().koid().map_err(StartComponentError::JobGetKoidFailed)?.raw_koid();
690
691 let runtime_dir = RuntimeDirBuilder::new(runtime_dir_server_end)
692 .args(program_config.args.clone())
693 .job_id(job_koid)
694 .serve();
695
696 let proc_job_dup = job
698 .proc()
699 .duplicate_handle(zx::Rights::SAME_RIGHTS)
700 .map_err(StartComponentError::JobDuplicateFailed)?;
701
702 let name = Path::new(resolved_url)
703 .file_name()
704 .and_then(|filename| filename.to_str())
705 .ok_or_else(|| {
706 StartComponentError::StartInfoError(StartInfoError::BadResolvedUrl(
707 resolved_url.clone(),
708 ))
709 })?
710 .to_owned();
711
712 let environs = merge_environ(
713 &self.additional_environ,
714 program_config.environ.as_deref().unwrap_or_default(),
715 );
716
717 Ok(PreparedLaunch {
718 ns,
719 handle_infos,
720 name,
721 environs,
722 proc_job_dup,
723 job,
724 runtime_dir,
725 boot_clock,
726 utc_clock,
727 lifecycle_client,
728 outgoing_directory,
729 local_scope,
730 })
731 }
732}
733
734struct PreparedLaunch {
735 ns: namespace::Namespace,
736 handle_infos: Vec<fidl_fuchsia_process::HandleInfo>,
737 name: String,
738 environs: Vec<String>,
739 proc_job_dup: zx::Job,
740
741 job: Job,
742 runtime_dir: RuntimeDirectory,
743 boot_clock: zx::Clock<zx::MonotonicTimeline, zx::BootTimeline>,
744 utc_clock: zx::Clock<zx::BootTimeline, UtcTimeline>,
745 lifecycle_client: Option<LifecycleProxy>,
746 outgoing_directory: Option<ClientEnd<fio::DirectoryMarker>>,
747 local_scope: ExecutionScope,
748}
749
750pub struct ScopedElfRunner {
751 runner: Arc<ElfRunner>,
752 checker: ScopedPolicyChecker,
753}
754
755impl ScopedElfRunner {
756 pub fn serve(&self, mut stream: fcrunner::ComponentRunnerRequestStream) {
757 let runner = self.runner.clone();
758 let checker = self.checker.clone();
759 self.scope().spawn(async move {
760 while let Ok(Some(request)) = stream.try_next().await {
761 match request {
762 fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
763 start(&runner, checker.clone(), start_info, controller).await;
764 }
765 fcrunner::ComponentRunnerRequest::_UnknownMethod { ordinal, .. } => {
766 warn!(ordinal:%; "Unknown ComponentRunner request");
767 }
768 }
769 }
770 });
771 }
772
773 pub async fn start(
774 &self,
775 start_info: fcrunner::ComponentStartInfo,
776 server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
777 ) {
778 start(&self.runner, self.checker.clone(), start_info, server_end).boxed().await
779 }
780
781 pub(crate) fn scope(&self) -> &ExecutionScope {
782 &self.runner.scope
783 }
784}
785
786fn is_acceptable_exit_code(moniker: &Moniker, code: i64) -> bool {
787 let moniker_name = moniker.to_string();
788 MONIKER_PREFIXES_TO_ACCEPTABLE_EXIT_CODES
789 .iter()
790 .any(|(prefix, codes)| moniker_name.starts_with(*prefix) && codes.contains(&code))
791}
792
793async fn start(
795 runner: &ElfRunner,
796 checker: ScopedPolicyChecker,
797 start_info: fcrunner::ComponentStartInfo,
798 server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
799) {
800 let resolved_url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
801
802 let elf_component = match runner.start_component(start_info, &checker).boxed().await {
803 Ok(elf_component) => elf_component,
804 Err(err) => {
805 runner::component::report_start_error(
806 err.as_zx_status(),
807 format!("{}", err),
808 &resolved_url,
809 server_end,
810 );
811 return;
812 }
813 };
814 let elf_component_moniker = elf_component.info().get_moniker().clone();
815
816 let (termination_tx, termination_rx) = oneshot::channel::<StopInfo>();
817 let termination_fn = Box::pin(async move {
820 termination_rx
821 .await
822 .unwrap_or_else(|_| {
823 warn!("epitaph oneshot channel closed unexpectedly");
824 StopInfo::from_error(fcomp::Error::Internal, None)
825 })
826 .into()
827 });
828
829 let Some(proc_copy) = elf_component.copy_process() else {
830 runner::component::report_start_error(
831 zx::Status::from_raw(
832 i32::try_from(fcomp::Error::InstanceCannotStart.into_primitive()).unwrap(),
833 ),
834 "Component unexpectedly had no process".to_string(),
835 &resolved_url,
836 server_end,
837 );
838 return;
839 };
840
841 let component_diagnostics = elf_component
842 .info()
843 .copy_job_for_diagnostics()
844 .map(|job| ComponentDiagnostics {
845 tasks: Some(ComponentTasks {
846 component_task: Some(DiagnosticsTask::Job(job.into())),
847 ..Default::default()
848 }),
849 ..Default::default()
850 })
851 .map_err(|error| {
852 warn!(error:%; "Failed to copy job for diagnostics");
853 ()
854 })
855 .ok();
856
857 let (server_stream, control) = server_end.into_stream_and_control_handle();
858
859 runner.scope.spawn({
861 let resolved_url = resolved_url.clone();
862 async move {
863 fasync::OnSignals::new(&proc_copy.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
864 .await
865 .map(|_: fidl::Signals| ()) .unwrap_or_else(|error| warn!(error:%; "error creating signal handler"));
867 let stop_info = match proc_copy.info() {
869 Ok(zx::ProcessInfo { return_code, .. }) => {
870 match return_code {
871 0 => StopInfo::from_ok(Some(return_code)),
872 zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL => StopInfo::from_error(
876 fcomp::Error::InstanceDied.into(),
877 Some(return_code),
878 ),
879 _ => {
880 if is_acceptable_exit_code(&elf_component_moniker, return_code) {
881 trace!(url:% = resolved_url, return_code:%; "component terminated with an acceptable non-zero exit code");
882 } else {
883 warn!(url:% = resolved_url, return_code:%;
884 "process terminated with abnormal return code");
885 }
886 StopInfo::from_error(fcomp::Error::InstanceDied, Some(return_code))
887 }
888 }
889 }
890 Err(error) => {
891 warn!(error:%; "Unable to query process info");
892 StopInfo::from_error(fcomp::Error::Internal, None)
893 }
894 };
895 termination_tx.send(stop_info).unwrap_or_else(|_| warn!("error sending done signal"));
896 }
897 });
898
899 let mut elf_component = elf_component;
900 runner.components.clone().add(&mut elf_component);
901
902 runner.scope.spawn(async move {
908 if let Some(component_diagnostics) = component_diagnostics {
909 control.send_on_publish_diagnostics(component_diagnostics).unwrap_or_else(
910 |error| warn!(url:% = resolved_url, error:%; "sending diagnostics failed"),
911 );
912 }
913 runner::component::Controller::new(elf_component, server_stream, control)
914 .serve(termination_fn)
915 .await;
916 });
917}
918
919#[cfg(test)]
920mod tests {
921 use super::runtime_dir::RuntimeDirectory;
922 use super::*;
923 use anyhow::{Context, Error};
924 use assert_matches::assert_matches;
925 use cm_config::{AllowlistEntryBuilder, JobPolicyAllowlists, SecurityPolicy};
926 use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, create_endpoints, create_proxy};
927 use fidl_connector::Connect;
928 use fidl_fuchsia_component as fcomp;
929 use fidl_fuchsia_component_runner as fcrunner;
930 use fidl_fuchsia_component_runner::Task as DiagnosticsTask;
931 use fidl_fuchsia_data as fdata;
932 use fidl_fuchsia_io as fio;
933 use fidl_fuchsia_logger::{LogSinkMarker, LogSinkRequestStream};
934 use fidl_fuchsia_process_lifecycle::LifecycleProxy;
935 use fidl_test_util::spawn_stream_handler;
936 use fuchsia_async as fasync;
937 use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
938 use futures::channel::mpsc;
939 use futures::lock::Mutex;
940 use futures::{StreamExt, join};
941 use runner::component::Controllable;
942 use std::str::FromStr;
943 use std::task::Poll;
944 use test_case::test_case;
945 use zx::{AsHandleRef, Task};
946
947 pub enum MockServiceRequest {
948 LogSink(LogSinkRequestStream),
949 }
950
951 pub type MockServiceFs<'a> = ServiceFs<ServiceObjLocal<'a, MockServiceRequest>>;
952
953 pub fn create_fs_with_mock_logsink()
956 -> Result<(MockServiceFs<'static>, Vec<fcrunner::ComponentNamespaceEntry>), Error> {
957 let (dir_client, dir_server) = create_endpoints::<fio::DirectoryMarker>();
958
959 let mut dir = ServiceFs::new_local();
960 dir.add_fidl_service_at(LogSinkMarker::PROTOCOL_NAME, MockServiceRequest::LogSink);
961 dir.serve_connection(dir_server).context("Failed to add serving channel.")?;
962
963 let namespace = vec![fcrunner::ComponentNamespaceEntry {
964 path: Some("/svc".to_string()),
965 directory: Some(dir_client),
966 ..Default::default()
967 }];
968
969 Ok((dir, namespace))
970 }
971
972 pub fn new_utc_clock_for_tests() -> Arc<UtcClock> {
976 let reference_now = zx::BootInstant::get();
977 let system_utc_clock = duplicate_utc_clock_handle(zx::Rights::SAME_RIGHTS).unwrap();
978 let utc_now = system_utc_clock.read().unwrap();
979
980 let utc_clock_for_tests =
981 Arc::new(UtcClock::create(zx::ClockOpts::MAPPABLE, None).unwrap());
982 utc_clock_for_tests
984 .update(zx::ClockUpdate::builder().absolute_value(reference_now, utc_now.into()))
985 .unwrap();
986 utc_clock_for_tests
987 }
988
989 pub fn new_elf_runner_for_test() -> Arc<ElfRunner> {
990 Arc::new(ElfRunner::new(
991 job_default().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
992 Box::new(process_launcher::BuiltInConnector {}),
993 Some(new_utc_clock_for_tests()),
994 CrashRecords::new(),
995 vec![],
996 ))
997 }
998
999 fn namespace_entry(path: &str, flags: fio::Flags) -> fcrunner::ComponentNamespaceEntry {
1000 let ns_path = path.to_string();
1002 let ns_dir = fuchsia_fs::directory::open_in_namespace(path, flags).unwrap();
1003 let client_end = ns_dir.into_client_end().unwrap();
1004 fcrunner::ComponentNamespaceEntry {
1005 path: Some(ns_path),
1006 directory: Some(client_end),
1007 ..Default::default()
1008 }
1009 }
1010
1011 fn pkg_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
1012 namespace_entry("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
1013 }
1014
1015 fn svc_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
1016 namespace_entry("/svc", fio::PERM_READABLE)
1017 }
1018
1019 fn hello_world_startinfo(
1020 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1021 ) -> fcrunner::ComponentStartInfo {
1022 let ns = vec![pkg_dir_namespace_entry()];
1023
1024 fcrunner::ComponentStartInfo {
1025 resolved_url: Some(
1026 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/hello-world-rust.cm".to_string(),
1027 ),
1028 program: Some(fdata::Dictionary {
1029 entries: Some(vec![
1030 fdata::DictionaryEntry {
1031 key: "args".to_string(),
1032 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
1033 "foo".to_string(),
1034 "bar".to_string(),
1035 ]))),
1036 },
1037 fdata::DictionaryEntry {
1038 key: "binary".to_string(),
1039 value: Some(Box::new(fdata::DictionaryValue::Str(
1040 "bin/hello_world_rust".to_string(),
1041 ))),
1042 },
1043 ]),
1044 ..Default::default()
1045 }),
1046 ns: Some(ns),
1047 outgoing_dir: None,
1048 runtime_dir: Some(runtime_dir),
1049 component_instance: Some(zx::Event::create()),
1050 ..Default::default()
1051 }
1052 }
1053
1054 fn invalid_binary_startinfo(
1056 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1057 ) -> fcrunner::ComponentStartInfo {
1058 let ns = vec![pkg_dir_namespace_entry()];
1059
1060 fcrunner::ComponentStartInfo {
1061 resolved_url: Some(
1062 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/does-not-exist.cm".to_string(),
1063 ),
1064 program: Some(fdata::Dictionary {
1065 entries: Some(vec![fdata::DictionaryEntry {
1066 key: "binary".to_string(),
1067 value: Some(Box::new(fdata::DictionaryValue::Str(
1068 "bin/does_not_exist".to_string(),
1069 ))),
1070 }]),
1071 ..Default::default()
1072 }),
1073 ns: Some(ns),
1074 outgoing_dir: None,
1075 runtime_dir: Some(runtime_dir),
1076 component_instance: Some(zx::Event::create()),
1077 ..Default::default()
1078 }
1079 }
1080
1081 pub fn lifecycle_startinfo(
1085 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1086 ) -> fcrunner::ComponentStartInfo {
1087 let ns = vec![pkg_dir_namespace_entry()];
1088
1089 fcrunner::ComponentStartInfo {
1090 resolved_url: Some(
1091 "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm".to_string(),
1092 ),
1093 program: Some(fdata::Dictionary {
1094 entries: Some(vec![
1095 fdata::DictionaryEntry {
1096 key: "args".to_string(),
1097 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
1098 "foo".to_string(),
1099 "bar".to_string(),
1100 ]))),
1101 },
1102 fdata::DictionaryEntry {
1103 key: "binary".to_string(),
1104 value: Some(Box::new(fdata::DictionaryValue::Str(
1105 "bin/lifecycle_placeholder".to_string(),
1106 ))),
1107 },
1108 fdata::DictionaryEntry {
1109 key: "lifecycle.stop_event".to_string(),
1110 value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1111 },
1112 ]),
1113 ..Default::default()
1114 }),
1115 ns: Some(ns),
1116 outgoing_dir: None,
1117 runtime_dir: Some(runtime_dir),
1118 component_instance: Some(zx::Event::create()),
1119 ..Default::default()
1120 }
1121 }
1122
1123 fn create_child_process(job: &zx::Job, name: &str) -> zx::Process {
1124 let (process, _vmar) = job
1125 .create_child_process(zx::ProcessOptions::empty(), name.as_bytes())
1126 .expect("could not create process");
1127 process
1128 }
1129
1130 fn make_default_elf_component(
1131 lifecycle_client: Option<LifecycleProxy>,
1132 critical: bool,
1133 ) -> (scoped_task::Scoped<zx::Job>, ElfComponent) {
1134 let job = scoped_task::create_child_job().expect("failed to make child job");
1135 let process = create_child_process(&job, "test_process");
1136 let job_copy =
1137 job.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("job handle duplication failed");
1138 let component = ElfComponent::new(
1139 RuntimeDirectory::empty(),
1140 Moniker::default(),
1141 Job::Single(job_copy),
1142 process,
1143 lifecycle_client,
1144 critical,
1145 ExecutionScope::new(),
1146 "".to_string(),
1147 None,
1148 Default::default(),
1149 zx::Event::create(),
1150 );
1151 (job, component)
1152 }
1153
1154 async fn read_file<'a>(root_proxy: &'a fio::DirectoryProxy, path: &'a str) -> String {
1157 let file_proxy =
1158 fuchsia_fs::directory::open_file_async(&root_proxy, path, fuchsia_fs::PERM_READABLE)
1159 .expect("Failed to open file.");
1160 let res = fuchsia_fs::file::read_to_string(&file_proxy).await;
1161 res.expect("Unable to read file.")
1162 }
1163
1164 #[fuchsia::test]
1165 async fn test_runtime_dir_entries() -> Result<(), Error> {
1166 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1167 let start_info = lifecycle_startinfo(runtime_dir_server);
1168
1169 let runner = new_elf_runner_for_test();
1170 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1171 Arc::new(SecurityPolicy::default()),
1172 Moniker::root(),
1173 ));
1174 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1175
1176 runner.start(start_info, server_controller).await;
1177
1178 assert_eq!("foo", read_file(&runtime_dir, "args/0").await);
1180 assert_eq!("bar", read_file(&runtime_dir, "args/1").await);
1181
1182 let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1187 let process_start_time =
1188 read_file(&runtime_dir, "elf/process_start_time").await.parse::<i64>()?;
1189 let process_start_time_utc_estimate =
1190 read_file(&runtime_dir, "elf/process_start_time_utc_estimate").await;
1191 let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>()?;
1192 assert!(process_id > 0);
1193 assert!(process_start_time > 0);
1194 assert!(process_start_time_utc_estimate.contains("UTC"));
1195 assert!(job_id > 0);
1196 assert_ne!(process_id, job_id);
1197
1198 controller.stop().expect("Stop request failed");
1199 controller.on_closed().await.expect("failed waiting for channel to close");
1202 Ok(())
1203 }
1204
1205 #[fuchsia::test]
1206 async fn test_kill_component() -> Result<(), Error> {
1207 let (job, mut component) = make_default_elf_component(None, false);
1208
1209 let job_info = job.info()?;
1210 assert!(!job_info.exited);
1211
1212 component.kill().await;
1213
1214 let h = job.as_handle_ref();
1215 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1216 .await
1217 .expect("failed waiting for termination signal");
1218
1219 let job_info = job.info()?;
1220 assert!(job_info.exited);
1221 Ok(())
1222 }
1223
1224 #[fuchsia::test]
1225 fn test_stop_critical_component() -> Result<(), Error> {
1226 let mut exec = fasync::TestExecutor::new();
1227 let (lifecycle_client, _lifecycle_server) = create_proxy::<LifecycleMarker>();
1231 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1232 let process = component.copy_process().unwrap();
1233 let job_info = job.info()?;
1234 assert!(!job_info.exited);
1235
1236 let mut completes_when_stopped = component.stop();
1240
1241 match exec.run_until_stalled(&mut completes_when_stopped) {
1244 Poll::Ready(_) => {
1245 panic!("runner should still be waiting for lifecycle channel to stop");
1246 }
1247 _ => {}
1248 }
1249 assert_eq!(process.kill(), Ok(()));
1250
1251 exec.run_singlethreaded(&mut completes_when_stopped);
1252
1253 let h = job.as_handle_ref();
1255 let termination_fut = async move {
1256 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1257 .await
1258 .expect("failed waiting for termination signal");
1259 };
1260 exec.run_singlethreaded(termination_fut);
1261
1262 let job_info = job.info()?;
1263 assert!(job_info.exited);
1264 Ok(())
1265 }
1266
1267 #[fuchsia::test]
1268 fn test_stop_noncritical_component() -> Result<(), Error> {
1269 let mut exec = fasync::TestExecutor::new();
1270 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1274 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1275
1276 let job_info = job.info()?;
1277 assert!(!job_info.exited);
1278
1279 let mut completes_when_stopped = component.stop();
1283
1284 match exec.run_until_stalled(&mut completes_when_stopped) {
1287 Poll::Ready(_) => {
1288 panic!("runner should still be waiting for lifecycle channel to stop");
1289 }
1290 _ => {}
1291 }
1292 drop(lifecycle_server);
1293
1294 match exec.run_until_stalled(&mut completes_when_stopped) {
1295 Poll::Ready(_) => {}
1296 _ => {
1297 panic!("runner future should have completed, lifecycle channel is closed.");
1298 }
1299 }
1300 let h = job.as_handle_ref();
1302 let termination_fut = async move {
1303 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1304 .await
1305 .expect("failed waiting for termination signal");
1306 };
1307 exec.run_singlethreaded(termination_fut);
1308
1309 let job_info = job.info()?;
1310 assert!(job_info.exited);
1311 Ok(())
1312 }
1313
1314 #[fuchsia::test]
1317 async fn test_stop_component_without_lifecycle() -> Result<(), Error> {
1318 let (job, mut component) = make_default_elf_component(None, false);
1319
1320 let job_info = job.info()?;
1321 assert!(!job_info.exited);
1322
1323 component.stop().await;
1324
1325 let h = job.as_handle_ref();
1326 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1327 .await
1328 .expect("failed waiting for termination signal");
1329
1330 let job_info = job.info()?;
1331 assert!(job_info.exited);
1332 Ok(())
1333 }
1334
1335 #[fuchsia::test]
1336 async fn test_stop_critical_component_with_closed_lifecycle() -> Result<(), Error> {
1337 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1338 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1339 let process = component.copy_process().unwrap();
1340 let job_info = job.info()?;
1341 assert!(!job_info.exited);
1342
1343 drop(lifecycle_server);
1345 process.kill()?;
1348 component.stop().await;
1349
1350 let h = job.as_handle_ref();
1351 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1352 .await
1353 .expect("failed waiting for termination signal");
1354
1355 let job_info = job.info()?;
1356 assert!(job_info.exited);
1357 Ok(())
1358 }
1359
1360 #[fuchsia::test]
1361 async fn test_stop_noncritical_component_with_closed_lifecycle() -> Result<(), Error> {
1362 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1363 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1364
1365 let job_info = job.info()?;
1366 assert!(!job_info.exited);
1367
1368 drop(lifecycle_server);
1370 component.stop().await;
1373
1374 let h = job.as_handle_ref();
1375 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1376 .await
1377 .expect("failed waiting for termination signal");
1378
1379 let job_info = job.info()?;
1380 assert!(job_info.exited);
1381 Ok(())
1382 }
1383
1384 #[fuchsia::test]
1386 async fn test_drop() -> Result<(), Error> {
1387 let (job, component) = make_default_elf_component(None, false);
1388
1389 let job_info = job.info()?;
1390 assert!(!job_info.exited);
1391
1392 drop(component);
1393
1394 let h = job.as_handle_ref();
1395 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1396 .await
1397 .expect("failed waiting for termination signal");
1398
1399 let job_info = job.info()?;
1400 assert!(job_info.exited);
1401 Ok(())
1402 }
1403
1404 fn with_mark_vmo_exec(
1405 mut start_info: fcrunner::ComponentStartInfo,
1406 ) -> fcrunner::ComponentStartInfo {
1407 start_info.program.as_mut().map(|dict| {
1408 dict.entries.as_mut().map(|entry| {
1409 entry.push(fdata::DictionaryEntry {
1410 key: "job_policy_ambient_mark_vmo_exec".to_string(),
1411 value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1412 });
1413 entry
1414 })
1415 });
1416 start_info
1417 }
1418
1419 fn with_main_process_critical(
1420 mut start_info: fcrunner::ComponentStartInfo,
1421 ) -> fcrunner::ComponentStartInfo {
1422 start_info.program.as_mut().map(|dict| {
1423 dict.entries.as_mut().map(|entry| {
1424 entry.push(fdata::DictionaryEntry {
1425 key: "main_process_critical".to_string(),
1426 value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1427 });
1428 entry
1429 })
1430 });
1431 start_info
1432 }
1433
1434 #[fuchsia::test]
1435 async fn vmex_security_policy_denied() -> Result<(), Error> {
1436 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1437 let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1438
1439 let runner = new_elf_runner_for_test();
1441 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1442 Arc::new(SecurityPolicy::default()),
1443 Moniker::root(),
1444 ));
1445 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1446
1447 runner.start(start_info, server_controller).await;
1450 assert_matches!(
1451 controller.take_event_stream().try_next().await,
1452 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1453 );
1454
1455 Ok(())
1456 }
1457
1458 #[fuchsia::test]
1459 async fn vmex_security_policy_allowed() -> Result<(), Error> {
1460 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1461 let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1462
1463 let policy = SecurityPolicy {
1464 job_policy: JobPolicyAllowlists {
1465 ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::new().exact("foo").build()],
1466 ..Default::default()
1467 },
1468 ..Default::default()
1469 };
1470 let runner = new_elf_runner_for_test();
1471 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1472 Arc::new(policy),
1473 Moniker::try_from(["foo"]).unwrap(),
1474 ));
1475 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1476 runner.start(start_info, server_controller).await;
1477
1478 let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1480 assert!(process_id > 0);
1481 controller.kill().expect("kill failed");
1483
1484 let mut event_stream = controller.take_event_stream();
1488 expect_diagnostics_event(&mut event_stream).await;
1489
1490 let s = zx::Status::from_raw(
1491 i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1492 );
1493 expect_on_stop(&mut event_stream, s, Some(zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL)).await;
1494 expect_channel_closed(&mut event_stream).await;
1495 Ok(())
1496 }
1497
1498 #[fuchsia::test]
1499 async fn critical_security_policy_denied() -> Result<(), Error> {
1500 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1501 let start_info = with_main_process_critical(hello_world_startinfo(runtime_dir_server));
1502
1503 let runner = new_elf_runner_for_test();
1505 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1506 Arc::new(SecurityPolicy::default()),
1507 Moniker::root(),
1508 ));
1509 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1510
1511 runner.start(start_info, server_controller).await;
1514 assert_matches!(
1515 controller.take_event_stream().try_next().await,
1516 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1517 );
1518
1519 Ok(())
1520 }
1521
1522 #[fuchsia::test]
1523 #[should_panic]
1524 async fn fail_to_launch_critical_component() {
1525 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1526
1527 let start_info = with_main_process_critical(invalid_binary_startinfo(runtime_dir_server));
1530
1531 let policy = SecurityPolicy {
1534 job_policy: JobPolicyAllowlists {
1535 main_process_critical: vec![AllowlistEntryBuilder::new().build()],
1536 ..Default::default()
1537 },
1538 ..Default::default()
1539 };
1540 let runner = new_elf_runner_for_test();
1541 let runner =
1542 runner.get_scoped_runner(ScopedPolicyChecker::new(Arc::new(policy), Moniker::root()));
1543 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1544
1545 runner.start(start_info, server_controller).await;
1546
1547 controller
1548 .take_event_stream()
1549 .try_next()
1550 .await
1551 .map(|_: Option<fcrunner::ComponentControllerEvent>| ()) .unwrap_or_else(|error| warn!(error:%; "error reading from event stream"));
1553 }
1554
1555 fn hello_world_startinfo_forward_stdout_to_log(
1556 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1557 mut ns: Vec<fcrunner::ComponentNamespaceEntry>,
1558 ) -> fcrunner::ComponentStartInfo {
1559 ns.push(pkg_dir_namespace_entry());
1560
1561 fcrunner::ComponentStartInfo {
1562 resolved_url: Some(
1563 "fuchsia-pkg://fuchsia.com/hello-world-rust#meta/hello-world-rust.cm".to_string(),
1564 ),
1565 program: Some(fdata::Dictionary {
1566 entries: Some(vec![
1567 fdata::DictionaryEntry {
1568 key: "binary".to_string(),
1569 value: Some(Box::new(fdata::DictionaryValue::Str(
1570 "bin/hello_world_rust".to_string(),
1571 ))),
1572 },
1573 fdata::DictionaryEntry {
1574 key: "forward_stdout_to".to_string(),
1575 value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1576 },
1577 fdata::DictionaryEntry {
1578 key: "forward_stderr_to".to_string(),
1579 value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1580 },
1581 ]),
1582 ..Default::default()
1583 }),
1584 ns: Some(ns),
1585 outgoing_dir: None,
1586 runtime_dir: Some(runtime_dir),
1587 component_instance: Some(zx::Event::create()),
1588 ..Default::default()
1589 }
1590 }
1591
1592 #[fuchsia::test]
1593 async fn enable_stdout_and_stderr_logging() -> Result<(), Error> {
1594 let (mut dir, ns) = create_fs_with_mock_logsink()?;
1595
1596 let run_component_fut = async move {
1597 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1598 let start_info = hello_world_startinfo_forward_stdout_to_log(runtime_dir_server, ns);
1599
1600 let runner = new_elf_runner_for_test();
1601 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1602 Arc::new(SecurityPolicy::default()),
1603 Moniker::root(),
1604 ));
1605 let (client_controller, server_controller) =
1606 create_proxy::<fcrunner::ComponentControllerMarker>();
1607
1608 runner.start(start_info, server_controller).await;
1609 let mut event_stream = client_controller.take_event_stream();
1610 expect_diagnostics_event(&mut event_stream).await;
1611 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1612 expect_channel_closed(&mut event_stream).await;
1613 };
1614
1615 let service_fs_listener_fut = async {
1617 let mut requests = Vec::new();
1618 while let Some(MockServiceRequest::LogSink(r)) = dir.next().await {
1619 requests.push(r);
1622 }
1623 requests.len()
1624 };
1625
1626 let connection_count = join!(run_component_fut, service_fs_listener_fut).1;
1627
1628 assert_eq!(connection_count, 1);
1629 Ok(())
1630 }
1631
1632 #[fuchsia::test]
1633 async fn on_publish_diagnostics_contains_job_handle() -> Result<(), Error> {
1634 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1635 let start_info = lifecycle_startinfo(runtime_dir_server);
1636
1637 let runner = new_elf_runner_for_test();
1638 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1639 Arc::new(SecurityPolicy::default()),
1640 Moniker::root(),
1641 ));
1642 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1643
1644 runner.start(start_info, server_controller).await;
1645
1646 let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>().unwrap();
1647 let mut event_stream = controller.take_event_stream();
1648 match event_stream.try_next().await {
1649 Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1650 payload:
1651 ComponentDiagnostics {
1652 tasks:
1653 Some(ComponentTasks {
1654 component_task: Some(DiagnosticsTask::Job(job)), ..
1655 }),
1656 ..
1657 },
1658 })) => {
1659 assert_eq!(job_id, job.koid().unwrap().raw_koid());
1660 }
1661 other => panic!("unexpected event result: {:?}", other),
1662 }
1663
1664 controller.stop().expect("Stop request failed");
1665 controller.on_closed().await.expect("failed waiting for channel to close");
1668
1669 Ok(())
1670 }
1671
1672 async fn expect_diagnostics_event(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1673 let event = event_stream.try_next().await;
1674 assert_matches!(
1675 event,
1676 Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1677 payload: ComponentDiagnostics {
1678 tasks: Some(ComponentTasks {
1679 component_task: Some(DiagnosticsTask::Job(_)),
1680 ..
1681 }),
1682 ..
1683 },
1684 }))
1685 );
1686 }
1687
1688 async fn expect_on_stop(
1689 event_stream: &mut fcrunner::ComponentControllerEventStream,
1690 expected_status: zx::Status,
1691 expected_exit_code: Option<i64>,
1692 ) {
1693 let event = event_stream.try_next().await;
1694 assert_matches!(
1695 event,
1696 Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
1697 payload: fcrunner::ComponentStopInfo { termination_status: Some(s), exit_code, .. },
1698 }))
1699 if s == expected_status.into_raw() &&
1700 exit_code == expected_exit_code
1701 );
1702 }
1703
1704 async fn expect_channel_closed(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1705 let event = event_stream.try_next().await;
1706 match event {
1707 Ok(None) => {}
1708 other => panic!("Expected channel closed error, got {:?}", other),
1709 }
1710 }
1711
1712 struct LauncherConnectorForTest {
1715 sender: mpsc::UnboundedSender<LaunchPayload>,
1716 }
1717
1718 #[derive(Default)]
1721 struct LaunchPayload {
1722 launch_info: Option<fproc::LaunchInfo>,
1723 args: Vec<Vec<u8>>,
1724 environ: Vec<Vec<u8>>,
1725 name_info: Vec<fproc::NameInfo>,
1726 handles: Vec<fproc::HandleInfo>,
1727 options: u32,
1728 }
1729
1730 impl Connect for LauncherConnectorForTest {
1731 type Proxy = fproc::LauncherProxy;
1732
1733 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
1734 let sender = self.sender.clone();
1735 let payload = Arc::new(Mutex::new(LaunchPayload::default()));
1736
1737 Ok(spawn_stream_handler(move |launcher_request| {
1738 let sender = sender.clone();
1739 let payload = payload.clone();
1740 async move {
1741 let mut payload = payload.lock().await;
1742 match launcher_request {
1743 fproc::LauncherRequest::Launch { info, responder } => {
1744 let process = create_child_process(&info.job, "test_process");
1745 responder.send(zx::Status::OK.into_raw(), Some(process)).unwrap();
1746
1747 let mut payload =
1748 std::mem::replace(&mut *payload, LaunchPayload::default());
1749 payload.launch_info = Some(info);
1750 sender.unbounded_send(payload).unwrap();
1751 }
1752 fproc::LauncherRequest::CreateWithoutStarting { info: _, responder: _ } => {
1753 unimplemented!()
1754 }
1755 fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
1756 payload.args.append(&mut args);
1757 }
1758 fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
1759 payload.environ.append(&mut environ);
1760 }
1761 fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
1762 payload.name_info.append(&mut names);
1763 }
1764 fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
1765 payload.handles.append(&mut handles);
1766 }
1767 fproc::LauncherRequest::SetOptions { options, .. } => {
1768 payload.options = options;
1769 }
1770 }
1771 }
1772 }))
1773 }
1774 }
1775
1776 #[fuchsia::test]
1777 async fn process_created_with_utc_clock_from_numbered_handles() -> Result<(), Error> {
1778 let (payload_tx, mut payload_rx) = mpsc::unbounded();
1779
1780 let connector = LauncherConnectorForTest { sender: payload_tx };
1781 let runner = ElfRunner::new(
1782 job_default().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
1783 Box::new(connector),
1784 Some(new_utc_clock_for_tests()),
1785 CrashRecords::new(),
1786 vec![],
1787 );
1788 let policy_checker = ScopedPolicyChecker::new(
1789 Arc::new(SecurityPolicy::default()),
1790 Moniker::try_from(["foo"]).unwrap(),
1791 );
1792
1793 let clock = zx::SyntheticClock::create(
1795 zx::ClockOpts::AUTO_START | zx::ClockOpts::MONOTONIC | zx::ClockOpts::MAPPABLE,
1796 None,
1797 )?;
1798 let clock_koid = clock.koid().unwrap();
1799
1800 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1801 let mut start_info = hello_world_startinfo(runtime_dir_server);
1802 start_info.numbered_handles = Some(vec![fproc::HandleInfo {
1803 handle: clock.into_handle(),
1804 id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
1805 }]);
1806
1807 let _ = runner
1809 .start_component(start_info, &policy_checker)
1810 .await
1811 .context("failed to start component")?;
1812
1813 let payload = payload_rx.next().await.unwrap();
1814 assert!(
1815 payload
1816 .handles
1817 .iter()
1818 .any(|handle_info| handle_info.handle.koid().unwrap() == clock_koid)
1819 );
1820
1821 Ok(())
1822 }
1823
1824 #[fuchsia::test]
1826 async fn test_enumerate_components() {
1827 use std::sync::atomic::{AtomicUsize, Ordering};
1828
1829 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1830 let start_info = lifecycle_startinfo(runtime_dir_server);
1831
1832 let runner = new_elf_runner_for_test();
1833 let components = runner.components.clone();
1834
1835 let count = Arc::new(AtomicUsize::new(0));
1837 components.clone().visit(|_, _| {
1838 count.fetch_add(1, Ordering::SeqCst);
1839 });
1840 assert_eq!(count.load(Ordering::SeqCst), 0);
1841
1842 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1844 Arc::new(SecurityPolicy::default()),
1845 Moniker::root(),
1846 ));
1847 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1848 runner.start(start_info, server_controller).await;
1849
1850 let count = Arc::new(AtomicUsize::new(0));
1852 components.clone().visit(|elf_component: &ElfComponentInfo, _| {
1853 assert_eq!(
1854 elf_component.get_url().as_str(),
1855 "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm"
1856 );
1857 count.fetch_add(1, Ordering::SeqCst);
1858 });
1859 assert_eq!(count.load(Ordering::SeqCst), 1);
1860
1861 controller.stop().unwrap();
1863 controller.on_closed().await.unwrap();
1864
1865 loop {
1868 let count = Arc::new(AtomicUsize::new(0));
1869 components.clone().visit(|_, _| {
1870 count.fetch_add(1, Ordering::SeqCst);
1871 });
1872 let count = count.load(Ordering::SeqCst);
1873 assert!(count == 0 || count == 1);
1874 if count == 0 {
1875 break;
1876 }
1877 yield_to_executor().await;
1880 }
1881 }
1882
1883 async fn yield_to_executor() {
1884 let mut done = false;
1885 futures::future::poll_fn(|cx| {
1886 if done {
1887 Poll::Ready(())
1888 } else {
1889 done = true;
1890 cx.waker().wake_by_ref();
1891 Poll::Pending
1892 }
1893 })
1894 .await;
1895 }
1896
1897 pub fn immediate_escrow_startinfo(
1900 outgoing_dir: ServerEnd<fio::DirectoryMarker>,
1901 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1902 ) -> fcrunner::ComponentStartInfo {
1903 let ns = vec![
1904 pkg_dir_namespace_entry(),
1905 svc_dir_namespace_entry(),
1907 ];
1908
1909 fcrunner::ComponentStartInfo {
1910 resolved_url: Some("#meta/immediate_escrow_component.cm".to_string()),
1911 program: Some(fdata::Dictionary {
1912 entries: Some(vec![
1913 fdata::DictionaryEntry {
1914 key: "binary".to_string(),
1915 value: Some(Box::new(fdata::DictionaryValue::Str(
1916 "bin/immediate_escrow".to_string(),
1917 ))),
1918 },
1919 fdata::DictionaryEntry {
1920 key: "lifecycle.stop_event".to_string(),
1921 value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1922 },
1923 ]),
1924 ..Default::default()
1925 }),
1926 ns: Some(ns),
1927 outgoing_dir: Some(outgoing_dir),
1928 runtime_dir: Some(runtime_dir),
1929 component_instance: Some(zx::Event::create()),
1930 ..Default::default()
1931 }
1932 }
1933
1934 #[fuchsia::test]
1937 async fn test_lifecycle_on_escrow() {
1938 let (outgoing_dir_client, outgoing_dir_server) =
1939 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1940 let (_, runtime_dir_server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1941 let start_info = immediate_escrow_startinfo(outgoing_dir_server, runtime_dir_server);
1942
1943 let runner = new_elf_runner_for_test();
1944 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1945 Arc::new(SecurityPolicy::default()),
1946 Moniker::root(),
1947 ));
1948 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1949
1950 runner.start(start_info, server_controller).await;
1951
1952 let mut event_stream = controller.take_event_stream();
1953
1954 expect_diagnostics_event(&mut event_stream).await;
1955
1956 match event_stream.try_next().await {
1957 Ok(Some(fcrunner::ComponentControllerEvent::OnEscrow {
1958 payload: fcrunner::ComponentControllerOnEscrowRequest { outgoing_dir, .. },
1959 })) => {
1960 let outgoing_dir_server = outgoing_dir.unwrap();
1961
1962 assert_eq!(
1963 outgoing_dir_client.as_handle_ref().basic_info().unwrap().koid,
1964 outgoing_dir_server.as_handle_ref().basic_info().unwrap().related_koid
1965 );
1966 }
1967 other => panic!("unexpected event result: {:?}", other),
1968 }
1969
1970 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1971 expect_channel_closed(&mut event_stream).await;
1972 }
1973
1974 fn exit_with_code_startinfo(exit_code: i64) -> fcrunner::ComponentStartInfo {
1975 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1976 let ns = vec![pkg_dir_namespace_entry()];
1977
1978 fcrunner::ComponentStartInfo {
1979 resolved_url: Some(
1980 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code.cm".to_string(),
1981 ),
1982 program: Some(fdata::Dictionary {
1983 entries: Some(vec![
1984 fdata::DictionaryEntry {
1985 key: "args".to_string(),
1986 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![format!(
1987 "{}",
1988 exit_code
1989 )]))),
1990 },
1991 fdata::DictionaryEntry {
1992 key: "binary".to_string(),
1993 value: Some(Box::new(fdata::DictionaryValue::Str(
1994 "bin/exit_with_code".to_string(),
1995 ))),
1996 },
1997 ]),
1998 ..Default::default()
1999 }),
2000 ns: Some(ns),
2001 outgoing_dir: None,
2002 runtime_dir: Some(runtime_dir_server),
2003 component_instance: Some(zx::Event::create()),
2004 ..Default::default()
2005 }
2006 }
2007
2008 fn exit_with_code_startinfo_from_env(exit_code: Option<i64>) -> fcrunner::ComponentStartInfo {
2009 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
2010 let ns = vec![pkg_dir_namespace_entry()];
2011 let environ = match exit_code {
2012 Some(code) => vec![format!("EXIT_CODE={}", code)],
2013 None => vec![],
2014 };
2015 fcrunner::ComponentStartInfo {
2016 resolved_url: Some(
2017 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code-from_env.cm"
2018 .to_string(),
2019 ),
2020 program: Some(fdata::Dictionary {
2021 entries: Some(vec![
2022 fdata::DictionaryEntry {
2023 key: "environ".to_string(),
2024 value: Some(Box::new(fdata::DictionaryValue::StrVec(environ))),
2025 },
2026 fdata::DictionaryEntry {
2027 key: "binary".to_string(),
2028 value: Some(Box::new(fdata::DictionaryValue::Str(
2029 "bin/exit_with_code_from_env".to_string(),
2030 ))),
2031 },
2032 ]),
2033 ..Default::default()
2034 }),
2035 ns: Some(ns),
2036 outgoing_dir: None,
2037 runtime_dir: Some(runtime_dir_server),
2038 component_instance: Some(zx::Event::create()),
2039 ..Default::default()
2040 }
2041 }
2042
2043 #[test_case(exit_with_code_startinfo(0) ; "args")]
2044 #[test_case(exit_with_code_startinfo_from_env(Some(0)) ; "env")]
2045 #[fuchsia::test]
2046 async fn test_return_code_success(start_info: fcrunner::ComponentStartInfo) {
2047 let runner = new_elf_runner_for_test();
2048 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
2049 Arc::new(SecurityPolicy::default()),
2050 Moniker::root(),
2051 ));
2052 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
2053 runner.start(start_info, server_controller).await;
2054
2055 let mut event_stream = controller.take_event_stream();
2056 expect_diagnostics_event(&mut event_stream).await;
2057 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
2058 expect_channel_closed(&mut event_stream).await;
2059 }
2060
2061 #[test_case(exit_with_code_startinfo(123), vec![] ; "args")]
2062 #[test_case(exit_with_code_startinfo_from_env(Some(123)), vec![] ; "component_env")]
2063 #[test_case(exit_with_code_startinfo_from_env(Some(123)), vec!["EXIT_CODE=2"] ; "additional_env_shadowed")]
2064 #[test_case(exit_with_code_startinfo_from_env(None), vec!["EXIT_CODE=123"] ; "additional_env")]
2065 #[fuchsia::test]
2066 async fn test_return_code_failure(
2067 start_info: fcrunner::ComponentStartInfo,
2068 additional_environ: Vec<&str>,
2069 ) {
2070 let runner = Arc::new(ElfRunner::new(
2071 job_default().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2072 Box::new(process_launcher::BuiltInConnector {}),
2073 Some(new_utc_clock_for_tests()),
2074 CrashRecords::new(),
2075 additional_environ.iter().map(|s| s.to_string()).collect(),
2076 ));
2077 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
2078 Arc::new(SecurityPolicy::default()),
2079 Moniker::root(),
2080 ));
2081 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
2082 runner.start(start_info, server_controller).await;
2083
2084 let mut event_stream = controller.take_event_stream();
2085 expect_diagnostics_event(&mut event_stream).await;
2086 let s = zx::Status::from_raw(
2087 i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
2088 );
2089 expect_on_stop(&mut event_stream, s, Some(123)).await;
2090 expect_channel_closed(&mut event_stream).await;
2091 }
2092
2093 #[fuchsia::test]
2094 fn test_is_acceptable_exit_code() {
2095 assert!(is_acceptable_exit_code(
2097 &Moniker::from_str("core/sshd-host/shell:sshd-1").expect("valid moniker"),
2098 255
2099 ));
2100
2101 assert!(!is_acceptable_exit_code(
2103 &Moniker::from_str("core/sshd-host/shell:sshd-1").expect("valid moniker"),
2104 1
2105 ));
2106
2107 assert!(!is_acceptable_exit_code(
2109 &Moniker::from_str("not_core/ssh-host/shell:sshd-1").expect("valid moniker"),
2110 255
2111 ));
2112
2113 assert!(!is_acceptable_exit_code(
2115 &Moniker::from_str("foo/debug").expect("valid moniker"),
2116 255
2117 ));
2118 }
2119
2120 #[fuchsia::test]
2121 fn test_merge_environ() {
2122 assert_eq!(merge_environ(&vec![], &vec![]), Vec::<String>::new());
2123
2124 assert_eq!(
2125 merge_environ(&vec!["A".to_string(), "B=".to_string(), "C=c".to_string()], &vec![]),
2126 vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]
2127 );
2128 assert_eq!(
2129 merge_environ(
2130 &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2131 &vec!["A".to_string(), "B".to_string(), "C".to_string()]
2132 ),
2133 vec!["A".to_string(), "B".to_string(), "C".to_string()]
2134 );
2135 assert_eq!(
2136 merge_environ(
2137 &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2138 &vec!["A=".to_string(), "B=".to_string(), "C=".to_string()]
2139 ),
2140 vec!["A=".to_string(), "B=".to_string(), "C=".to_string()]
2141 );
2142 assert_eq!(
2143 merge_environ(
2144 &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2145 &vec!["A=aa".to_string(), "B=bb".to_string(), "C=cc".to_string()]
2146 ),
2147 vec!["A=aa".to_string(), "B=bb".to_string(), "C=cc".to_string()]
2148 );
2149 assert_eq!(
2150 merge_environ(&vec![], &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]),
2151 vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]
2152 );
2153 }
2154}