Skip to main content

elf_runner/
lib.rs

1// Copyright 2019 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
5mod 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::vdso_vmo::get_next_vdso_vmo;
28use ::routing::policy::ScopedPolicyChecker;
29use chrono::DateTime;
30use fidl::endpoints::{ClientEnd, ServerEnd};
31use fidl_fuchsia_component as fcomp;
32use fidl_fuchsia_component_runner as fcrunner;
33use fidl_fuchsia_component_runner::{
34    ComponentDiagnostics, ComponentTasks, Task as DiagnosticsTask,
35};
36use fidl_fuchsia_io as fio;
37use fidl_fuchsia_memory_attribution as fattribution;
38use fidl_fuchsia_process as fproc;
39use fidl_fuchsia_process_lifecycle::{LifecycleMarker, LifecycleProxy};
40use fuchsia_async::{self as fasync, TimeoutExt};
41use fuchsia_runtime::{HandleInfo, HandleType, UtcClock, duplicate_utc_clock_handle, job_default};
42use futures::TryStreamExt;
43use futures::channel::oneshot;
44use log::{trace, warn};
45use moniker::Moniker;
46use namespace::Namespace;
47use runner::StartInfo;
48use runner::component::StopInfo;
49use std::collections::{HashMap, HashSet};
50use std::mem;
51use std::path::Path;
52use std::sync::Arc;
53use vfs::execution_scope::ExecutionScope;
54use zx::HandleBased;
55
56// Maximum time that the runner will wait for break_on_start eventpair to signal.
57// This is set to prevent debuggers from blocking us for too long, either intentionally
58// or unintentionally.
59const MAX_WAIT_BREAK_ON_START: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(300);
60
61// Minimum timer slack amount and default mode. The amount should be large enough to allow for some
62// coalescing of timers, but small enough to ensure applications don't miss deadlines.
63//
64// TODO(https://fxbug.dev/42120293): For now, set the value to 50us to avoid delaying performance-critical
65// timers in Scenic and other system services.
66const TIMER_SLACK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_micros(50);
67
68// Rights used when duplicating the UTC clock handle.
69//
70// Formed out of:
71// * `zx::Rights::BASIC`, but
72// * with `zx::Rights::WRITE` stripped (UTC clock is normally read-only), and
73// * with `zx::Rights::INSPECT` added (so that `ZX_INFO_CLOCK_MAPPED_SIZE` can be queried).
74//
75// Rather than subtracting `WRITE` from `BASIC`, we build the rights explicitly to avoid
76// including unintended rights by accident.
77//
78// The bitwise `|` operator for `bitflags` is implemented through the `std::ops::BitOr` trait,
79// which cannot be used in a const context. The workaround is to bitwise OR the raw bits.
80const DUPLICATE_CLOCK_RIGHTS: zx::Rights = zx::Rights::from_bits_truncate(
81    zx::Rights::READ.bits() // BASIC
82        | zx::Rights::WAIT.bits() // BASIC
83        | zx::Rights::DUPLICATE.bits() // BASIC
84        | zx::Rights::TRANSFER.bits() // BASIC
85        | zx::Rights::INSPECT.bits()
86        // Allows calls to zx_clock_read_mappable and zx_clock_get_details_mappable.
87        // Since "regular" read and details only require READ, and mappable read
88        // and details read the clock the same way, it seems safe to include MAP
89        // in this set of rights.
90        | zx::Rights::MAP.bits(),
91);
92
93// Mapping of component monikers to "acceptable" exit codes
94//
95// There are some ELF programs that upon exit, produce certain exit codes that
96// are "normal" part of operation. The most interesting of these from Fuchsia's
97// perspective is the sshd binary, which returns a 255 exit code when the client
98// hangs up unexpectedly (e.g. sending SIGINT to a running ssh process).
99//
100// Due to how `ffx` interacts with the Target over ssh in a user-interactive mode,
101// it is commonplace for the user to SIGINT their locally running ffx processes
102// which SIGINT's the SSH process running on the Host, which causes misleading
103// logs on the Target, implying that sshd has had an error (when in-fact there is none).
104//
105// If this list grows significantly (not expected). We may consider adding
106// this as a formal configuration option somewhere. That said this is (currently)
107// only for suppressing diagnostic logs, so this is unlikely.
108static MONIKER_PREFIXES_TO_ACCEPTABLE_EXIT_CODES: std::sync::LazyLock<
109    HashMap<&'static str, Vec<i64>>,
110> = std::sync::LazyLock::new(|| {
111    let mut m = HashMap::new();
112    m.insert("core/sshd-host/shell:sshd-", vec![255]);
113    m
114});
115
116// Builds and serves the runtime directory
117/// Runs components with ELF binaries.
118pub struct ElfRunner {
119    /// Each ELF component run by this runner will live inside a job that is a
120    /// child of this job.
121    job: zx::Job,
122
123    launcher_connector: process_launcher::Connector,
124
125    /// If `utc_clock` is populated then that Clock's handle will
126    /// be passed into the newly created process. Otherwise, the UTC
127    /// clock will be duplicated from current process' process table.
128    /// The latter is typically the case in unit tests and nested
129    /// component managers.
130    utc_clock: Option<Arc<UtcClock>>,
131
132    crash_records: CrashRecords,
133
134    /// Tracks the ELF components that are currently running under this runner.
135    components: Arc<ComponentSet>,
136
137    /// Tracks reporting memory changes to an observer.
138    memory_reporter: MemoryReporter,
139
140    /// Tasks that support the runner are launched in this scope
141    scope: ExecutionScope,
142
143    /// Environment variables to be injected in the form KEY=VALUE.
144    /// Values are shadowed by identical keys found in the component manifest.
145    additional_environ: Vec<String>,
146}
147
148/// The job for a component.
149pub enum Job {
150    Single(zx::Job),
151    Multiple { parent: zx::Job, child: zx::Job },
152}
153
154impl Job {
155    fn top(&self) -> &zx::Job {
156        match self {
157            Job::Single(job) => job,
158            Job::Multiple { parent, child: _ } => parent,
159        }
160    }
161
162    fn proc(&self) -> &zx::Job {
163        match self {
164            Job::Single(job) => job,
165            Job::Multiple { parent: _, child } => child,
166        }
167    }
168}
169
170/// Resources and data to launch a component, generated by [`ElfComponentLaunchInfo::new`]. This is
171/// a public type so other runners may use it to assembly the launch info independently of
172/// [`ElfRunner`].
173#[derive(Debug)]
174pub struct ElfComponentLaunchInfo {
175    pub ns: Namespace,
176    pub handle_infos: Vec<fproc::HandleInfo>,
177    pub utc_clock: UtcClock,
178    pub lifecycle_client: Option<LifecycleProxy>,
179    pub outgoing_directory: Option<ClientEnd<fio::DirectoryMarker>>,
180    pub local_scope: ExecutionScope,
181}
182
183impl ElfComponentLaunchInfo {
184    pub fn new(
185        start_info: &mut StartInfo,
186        program_config: &ElfProgramConfig,
187        utc_clock: Option<&UtcClock>,
188    ) -> Result<Self, StartComponentError> {
189        // Convert the directories into proxies, so we can find "/pkg" and open "lib" and bin_path
190        let namespace = mem::replace(&mut start_info.namespace, Default::default());
191        let ns = namespace::Namespace::try_from(namespace)
192            .map_err(StartComponentError::NamespaceError)?;
193
194        let config_vmo =
195            start_info.encoded_config.take().map(runner::get_config_vmo).transpose()?;
196
197        let next_vdso = program_config.use_next_vdso.then(get_next_vdso_vmo).transpose()?;
198
199        let (lifecycle_client, lifecycle_server) = if program_config.notify_lifecycle_stop {
200            // Creating a channel is not expected to fail.
201            let (client, server) = fidl::endpoints::create_proxy::<LifecycleMarker>();
202            (Some(client), Some(server.into_channel()))
203        } else {
204            (None, None)
205        };
206
207        // Take the UTC clock handle out of `start_info.numbered_handles`, if available.
208        let utc_handle = start_info
209            .numbered_handles
210            .iter()
211            .position(|handles| handles.id == HandleInfo::new(HandleType::ClockUtc, 0).as_raw())
212            .map(|position| start_info.numbered_handles.swap_remove(position).handle);
213
214        let utc_clock = if let Some(handle) = utc_handle {
215            zx::Clock::from(handle)
216        } else {
217            Self::duplicate_utc_clock(utc_clock)
218                .map_err(StartComponentError::UtcClockDuplicateFailed)?
219        };
220
221        // Duplicate the clock handle again, used later to wait for the clock to start, while
222        // the original handle is passed to the process.
223        let utc_clock_dup = utc_clock
224            .duplicate_handle(zx::Rights::SAME_RIGHTS)
225            .map_err(StartComponentError::UtcClockDuplicateFailed)?;
226
227        // If the component supports memory attribution, clone its outgoing directory connection
228        // so that we may later connect to it.
229        let outgoing_directory = if program_config.memory_attribution {
230            let Some(outgoing_dir) = start_info.outgoing_dir.take() else {
231                return Err(StartComponentError::StartInfoError(
232                    StartInfoError::MissingOutgoingDir,
233                ));
234            };
235            let (outgoing_dir_client, outgoing_dir_server) = fidl::endpoints::create_endpoints();
236            start_info.outgoing_dir = Some(outgoing_dir_server);
237            fdio::open_at(
238                outgoing_dir_client.channel(),
239                ".",
240                fio::Flags::PROTOCOL_DIRECTORY
241                    | fio::PERM_READABLE
242                    | fio::PERM_WRITABLE
243                    | fio::PERM_EXECUTABLE,
244                outgoing_dir.into_channel(),
245            )
246            .unwrap();
247            Some(outgoing_dir_client)
248        } else {
249            None
250        };
251
252        // Create procarg handles.
253        let mut handle_infos = Self::create_handle_infos(
254            start_info.outgoing_dir.take().map(|dir| dir.into_channel()),
255            lifecycle_server,
256            utc_clock,
257            next_vdso,
258            config_vmo,
259        );
260
261        // Add stdout and stderr handles that forward to syslog.
262        let (local_scope, stdout_and_stderr_handles) =
263            bind_streams_to_syslog(&ns, program_config.stdout_sink, program_config.stderr_sink);
264        handle_infos.extend(stdout_and_stderr_handles);
265
266        // Add any external numbered handles.
267        let numbered_handles = mem::replace(&mut start_info.numbered_handles, Default::default());
268        handle_infos.extend(numbered_handles);
269
270        // If the program escrowed a dictionary, give it back via `numbered_handles`.
271        if let Some(escrowed_dictionary) = start_info.escrowed_dictionary.take() {
272            handle_infos.push(fproc::HandleInfo {
273                handle: escrowed_dictionary.token.into_handle().into(),
274                id: HandleInfo::new(HandleType::EscrowedDictionary, 0).as_raw(),
275            });
276        } else if let Some(escrowed_dictionary_handle) =
277            start_info.escrowed_dictionary_handle.take()
278        {
279            handle_infos.push(fproc::HandleInfo {
280                handle: escrowed_dictionary_handle.into(),
281                id: HandleInfo::new(HandleType::EscrowedDictionary, 0).as_raw(),
282            });
283        }
284
285        Ok(Self {
286            ns,
287            handle_infos,
288            utc_clock: utc_clock_dup,
289            local_scope,
290            lifecycle_client,
291            outgoing_directory,
292        })
293    }
294
295    fn create_handle_infos(
296        outgoing_dir: Option<zx::Channel>,
297        lifecycle_server: Option<zx::Channel>,
298        utc_clock: UtcClock,
299        next_vdso: Option<zx::Vmo>,
300        config_vmo: Option<zx::Vmo>,
301    ) -> Vec<fproc::HandleInfo> {
302        let mut handle_infos = vec![];
303
304        if let Some(outgoing_dir) = outgoing_dir {
305            handle_infos.push(fproc::HandleInfo {
306                handle: outgoing_dir.into_handle(),
307                id: HandleInfo::new(HandleType::DirectoryRequest, 0).as_raw(),
308            });
309        }
310
311        if let Some(lifecycle_chan) = lifecycle_server {
312            handle_infos.push(fproc::HandleInfo {
313                handle: lifecycle_chan.into_handle(),
314                id: HandleInfo::new(HandleType::Lifecycle, 0).as_raw(),
315            })
316        };
317
318        handle_infos.push(fproc::HandleInfo {
319            handle: utc_clock.into_handle(),
320            id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
321        });
322
323        if let Some(next_vdso) = next_vdso {
324            handle_infos.push(fproc::HandleInfo {
325                handle: next_vdso.into_handle(),
326                id: HandleInfo::new(HandleType::VdsoVmo, 0).as_raw(),
327            });
328        }
329
330        if let Some(config_vmo) = config_vmo {
331            handle_infos.push(fproc::HandleInfo {
332                handle: config_vmo.into_handle(),
333                id: HandleInfo::new(HandleType::ComponentConfigVmo, 0).as_raw(),
334            });
335        }
336
337        handle_infos
338    }
339
340    /// Returns a UTC clock handle.
341    ///
342    /// Duplicates `self.utc_clock` if populated, or the UTC clock assigned to the current process.
343    fn duplicate_utc_clock(utc_clock: Option<&UtcClock>) -> Result<UtcClock, zx::Status> {
344        if let Some(utc_clock) = utc_clock {
345            utc_clock.duplicate_handle(DUPLICATE_CLOCK_RIGHTS)
346        } else {
347            duplicate_utc_clock_handle(DUPLICATE_CLOCK_RIGHTS)
348        }
349    }
350}
351
352/// Merges environment slices, prioritizing `right` over `left`.
353///
354/// Keys are determined by the first `=` delimiter, falling back to the
355/// full string if missing. Non-shadowed `left` entries are returned first.
356fn merge_environ(left: &[String], right: &[String]) -> Vec<String> {
357    fn get_key(kv: &str) -> &str {
358        kv.split('=').next().unwrap_or(kv)
359    }
360    let right_keys: HashSet<&str> = right.iter().map(|kv| get_key(kv.as_str())).collect();
361    let environ: Vec<String> = left
362        .iter()
363        .filter(|&kv| !right_keys.contains(get_key(kv.as_str())))
364        .chain(right.iter())
365        .cloned()
366        .collect();
367    environ
368}
369
370impl ElfRunner {
371    pub fn new(
372        job: zx::Job,
373        launcher_connector: process_launcher::Connector,
374        utc_clock: Option<Arc<UtcClock>>,
375        crash_records: CrashRecords,
376        additional_environ: Vec<String>,
377    ) -> ElfRunner {
378        let scope = ExecutionScope::new();
379        let components = ComponentSet::new(scope.clone());
380        let memory_reporter = MemoryReporter::new(components.clone());
381        ElfRunner {
382            job,
383            launcher_connector,
384            utc_clock,
385            crash_records,
386            components,
387            memory_reporter,
388            scope,
389            additional_environ,
390        }
391    }
392
393    /// Creates a job for a component.
394    fn create_job(&self, program_config: &ElfProgramConfig) -> Result<Job, JobError> {
395        let job = self.job.create_child_job().map_err(JobError::CreateChild)?;
396
397        // Set timer slack.
398        //
399        // Why Late and not Center or Early? Timers firing a little later than requested is not
400        // uncommon in non-realtime systems. Programs are generally tolerant of some
401        // delays. However, timers firing before their deadline can be unexpected and lead to bugs.
402        job.set_policy(zx::JobPolicy::TimerSlack(
403            TIMER_SLACK_DURATION,
404            zx::JobDefaultTimerMode::Late,
405        ))
406        .map_err(JobError::SetPolicy)?;
407
408        // Prevent direct creation of processes.
409        //
410        // The kernel-level mechanisms for creating processes are very low-level. We require that
411        // all processes be created via fuchsia.process.Launcher in order for the platform to
412        // maintain change-control over how processes are created.
413        if !program_config.create_raw_processes {
414            job.set_policy(zx::JobPolicy::Basic(
415                zx::JobPolicyOption::Absolute,
416                vec![(zx::JobCondition::NewProcess, zx::JobAction::Deny)],
417            ))
418            .map_err(JobError::SetPolicy)?;
419        }
420
421        // Default deny the job policy which allows ambiently marking VMOs executable, i.e. calling
422        // vmo_replace_as_executable without an appropriate resource handle.
423        if !program_config.ambient_mark_vmo_exec {
424            job.set_policy(zx::JobPolicy::Basic(
425                zx::JobPolicyOption::Absolute,
426                vec![(zx::JobCondition::AmbientMarkVmoExec, zx::JobAction::Deny)],
427            ))
428            .map_err(JobError::SetPolicy)?;
429        }
430
431        if let Some(job_policy_bad_handles) = &program_config.job_policy_bad_handles {
432            let action = match job_policy_bad_handles {
433                ElfProgramBadHandlesPolicy::DenyException => zx::JobAction::DenyException,
434                ElfProgramBadHandlesPolicy::AllowException => zx::JobAction::AllowException,
435            };
436            job.set_policy(zx::JobPolicy::Basic(
437                zx::JobPolicyOption::Absolute,
438                vec![(zx::JobCondition::BadHandle, action)],
439            ))
440            .map_err(JobError::SetPolicy)?;
441        }
442
443        Ok(if program_config.job_with_available_exception_channel {
444            // Create a new job to hold the process because the component wants
445            // the process to be a direct child of a job that has its exception
446            // channel available for taking. Note that we (the ELF runner) uses
447            // a job's exception channel for crash recording so we create a new
448            // job underneath the original job to hold the process.
449            let child = job.create_child_job().map_err(JobError::CreateChild)?;
450            Job::Multiple { parent: job, child }
451        } else {
452            Job::Single(job)
453        })
454    }
455
456    pub async fn start_component(
457        &self,
458        start_info: fcrunner::ComponentStartInfo,
459        checker: &ScopedPolicyChecker,
460    ) -> Result<ElfComponent, StartComponentError> {
461        let start_info: StartInfo =
462            start_info.try_into().map_err(StartInfoError::StartInfoError)?;
463
464        let resolved_url = start_info.resolved_url.clone();
465
466        // This also checks relevant security policy for config that it wraps using the provided
467        // PolicyChecker.
468        let program_config = ElfProgramConfig::parse_and_check(&start_info.program, Some(checker))
469            .map_err(|err| {
470                StartComponentError::StartInfoError(StartInfoError::ProgramError(err))
471            })?;
472
473        let main_process_critical = program_config.main_process_critical;
474        let res: Result<ElfComponent, StartComponentError> = self
475            .start_component_helper(start_info, Some(checker.scope.clone()), program_config)
476            .await;
477        match res {
478            Err(e) if main_process_critical => {
479                panic!(
480                    "failed to launch component with a critical process ({:?}): {:?}",
481                    &resolved_url, e
482                )
483            }
484            x => x,
485        }
486    }
487
488    async fn start_component_helper(
489        &self,
490        mut start_info: StartInfo,
491        moniker: Option<Moniker>,
492        program_config: ElfProgramConfig,
493    ) -> Result<ElfComponent, StartComponentError> {
494        let moniker = moniker.unwrap_or_else(|| Moniker::root());
495
496        // Fail early if there are clock issues.
497        let boot_clock = zx::Clock::<zx::MonotonicTimeline, zx::BootTimeline>::create(
498            zx::ClockOpts::CONTINUOUS,
499            /*backstop=*/ None,
500        )
501        .map_err(StartComponentError::BootClockCreateFailed)?;
502
503        let ElfComponentLaunchInfo {
504            ns,
505            handle_infos,
506            utc_clock,
507            lifecycle_client,
508            outgoing_directory,
509            local_scope,
510        } = ElfComponentLaunchInfo::new(
511            &mut start_info,
512            &program_config,
513            self.utc_clock.as_ref().map(|c| &**c),
514        )?;
515
516        let resolved_url = &start_info.resolved_url;
517
518        // Create a job for this component that will contain its process.
519        let job = self.create_job(&program_config)?;
520
521        crash_handler::run_exceptions_server(
522            &self.scope,
523            job.top(),
524            moniker.clone(),
525            resolved_url.clone(),
526            self.crash_records.clone(),
527        )
528        .map_err(StartComponentError::ExceptionRegistrationFailed)?;
529
530        // Create and serve the runtime dir.
531        let runtime_dir_server_end = start_info
532            .runtime_dir
533            .ok_or(StartComponentError::StartInfoError(StartInfoError::MissingRuntimeDir))?;
534        let job_koid = job.proc().koid().map_err(StartComponentError::JobGetKoidFailed)?.raw_koid();
535
536        let runtime_dir = RuntimeDirBuilder::new(runtime_dir_server_end)
537            .args(program_config.args.clone())
538            .job_id(job_koid)
539            .serve();
540
541        // Configure the process launcher.
542        let proc_job_dup = job
543            .proc()
544            .duplicate_handle(zx::Rights::SAME_RIGHTS)
545            .map_err(StartComponentError::JobDuplicateFailed)?;
546
547        let name = Path::new(resolved_url)
548            .file_name()
549            .and_then(|filename| filename.to_str())
550            .ok_or_else(|| {
551                StartComponentError::StartInfoError(StartInfoError::BadResolvedUrl(
552                    resolved_url.clone(),
553                ))
554            })?;
555
556        // Wait on break_on_start with a timeout and don't fail.
557        if let Some(break_on_start) = start_info.break_on_start {
558            fasync::OnSignals::new(&break_on_start, zx::Signals::OBJECT_PEER_CLOSED)
559                .on_timeout(MAX_WAIT_BREAK_ON_START, || Err(zx::Status::TIMED_OUT))
560                .await
561                .err()
562                .map(|error| warn!(moniker:%, error:%; "Failed to wait break_on_start"));
563        }
564
565        let environs = merge_environ(
566            &self.additional_environ,
567            program_config.environ.as_deref().unwrap_or_default(),
568        );
569
570        // Connect to `fuchsia.process.Launcher`.
571        let launcher = self
572            .launcher_connector
573            .connect()
574            .map_err(|err| StartComponentError::ProcessLauncherConnectError(err.into()))?;
575
576        let launch_info =
577            runner::component::configure_launcher(runner::component::LauncherConfigArgs {
578                bin_path: &program_config.binary,
579                name,
580                options: program_config.process_options(),
581                args: Some(program_config.args.clone()),
582                ns,
583                job: Some(proc_job_dup),
584                handle_infos: Some(handle_infos),
585                name_infos: None,
586                environs: (!environs.is_empty()).then_some(environs),
587                launcher: &launcher,
588                loader_proxy_chan: None,
589                executable_vmo: None,
590            })
591            .await?;
592
593        // Launch the process.
594        let (status, process) = launcher
595            .launch(launch_info)
596            .await
597            .map_err(StartComponentError::ProcessLauncherFidlError)?;
598        zx::Status::ok(status).map_err(StartComponentError::CreateProcessFailed)?;
599        let process = process.unwrap(); // Process is present iff status is OK.
600        if program_config.main_process_critical {
601            job_default()
602                .set_critical(zx::JobCriticalOptions::RETCODE_NONZERO, &process)
603                .map_err(StartComponentError::ProcessMarkCriticalFailed)
604                .expect("failed to set process as critical");
605        }
606
607        let pid = process.koid().map_err(StartComponentError::ProcessGetKoidFailed)?.raw_koid();
608
609        // Add process ID to the runtime dir.
610        runtime_dir.add_process_id(pid);
611
612        fuchsia_trace::instant!(
613            c"component:start",
614            c"elf",
615            fuchsia_trace::Scope::Thread,
616            "moniker" => format!("{}", moniker).as_str(),
617            "url" => resolved_url.as_str(),
618            "pid" => pid
619        );
620
621        // Add process start time to the runtime dir.
622        let process_start_instant_mono =
623            process.info().map_err(StartComponentError::ProcessInfoFailed)?.start_time;
624        runtime_dir.add_process_start_time(process_start_instant_mono.into_nanos());
625
626        // Add UTC estimate of the process start time to the runtime dir.
627        let utc_clock_started = fasync::OnSignals::new(&utc_clock, zx::Signals::CLOCK_STARTED)
628            .on_timeout(zx::MonotonicInstant::after(zx::MonotonicDuration::default()), || {
629                Err(zx::Status::TIMED_OUT)
630            })
631            .await
632            .is_ok();
633
634        // The clock transformations needed to map a timestamp on a monotonic timeline
635        // to a timestamp on the UTC timeline.
636        let mono_to_clock_transformation =
637            boot_clock.get_details().map(|details| details.reference_to_synthetic).ok();
638        let boot_to_utc_transformation = utc_clock_started
639            .then(|| utc_clock.get_details().map(|details| details.reference_to_synthetic).ok())
640            .flatten();
641
642        if let Some(clock_transformation) = boot_to_utc_transformation {
643            // This composes two transformations, to get from a timestamp expressed in
644            // nanoseconds on the monotonic timeline, to our best estimate of the
645            // corresponding UTC date-time.
646            //
647            // The clock transformations are computed before they are applied. If
648            // a suspend intervenes exactly between the computation and application,
649            // the timelines will drift away during sleep, causing a wrong date-time
650            // to be exposed in `runtime_dir`.
651            //
652            // This should not be a huge issue in practice, as the chances of that
653            // happening are vanishingly small.
654            let maybe_time_utc = mono_to_clock_transformation
655                .map(|t| t.apply(process_start_instant_mono))
656                .map(|time_boot| clock_transformation.apply(time_boot));
657
658            if let Some(utc_timestamp) = maybe_time_utc {
659                let utc_time_ns = utc_timestamp.into_nanos();
660                let seconds = (utc_time_ns / 1_000_000_000) as i64;
661                let nanos = (utc_time_ns % 1_000_000_000) as u32;
662                let dt = DateTime::from_timestamp(seconds, nanos).unwrap();
663
664                // If any of the above values are unavailable (unlikely), then this
665                // does not happen.
666                runtime_dir.add_process_start_time_utc_estimate(dt.to_string())
667            }
668        };
669
670        Ok(ElfComponent::new(
671            runtime_dir,
672            moniker,
673            job,
674            process,
675            lifecycle_client,
676            program_config.main_process_critical,
677            local_scope,
678            resolved_url.clone(),
679            outgoing_directory,
680            program_config,
681            start_info.component_instance.ok_or(StartComponentError::StartInfoError(
682                StartInfoError::MissingComponentInstanceToken,
683            ))?,
684        ))
685    }
686
687    pub fn get_scoped_runner(
688        self: Arc<Self>,
689        checker: ScopedPolicyChecker,
690    ) -> Arc<ScopedElfRunner> {
691        Arc::new(ScopedElfRunner { runner: self, checker })
692    }
693
694    pub fn serve_memory_reporter(&self, stream: fattribution::ProviderRequestStream) {
695        self.memory_reporter.serve(stream);
696    }
697}
698
699pub struct ScopedElfRunner {
700    runner: Arc<ElfRunner>,
701    checker: ScopedPolicyChecker,
702}
703
704impl ScopedElfRunner {
705    pub fn serve(&self, mut stream: fcrunner::ComponentRunnerRequestStream) {
706        let runner = self.runner.clone();
707        let checker = self.checker.clone();
708        self.scope().spawn(async move {
709            while let Ok(Some(request)) = stream.try_next().await {
710                match request {
711                    fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
712                        start(&runner, checker.clone(), start_info, controller).await;
713                    }
714                    fcrunner::ComponentRunnerRequest::_UnknownMethod { ordinal, .. } => {
715                        warn!(ordinal:%; "Unknown ComponentRunner request");
716                    }
717                }
718            }
719        });
720    }
721
722    pub async fn start(
723        &self,
724        start_info: fcrunner::ComponentStartInfo,
725        server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
726    ) {
727        start(&self.runner, self.checker.clone(), start_info, server_end).await
728    }
729
730    pub(crate) fn scope(&self) -> &ExecutionScope {
731        &self.runner.scope
732    }
733}
734
735fn is_acceptable_exit_code(moniker: &Moniker, code: i64) -> bool {
736    let moniker_name = moniker.to_string();
737    MONIKER_PREFIXES_TO_ACCEPTABLE_EXIT_CODES
738        .iter()
739        .any(|(prefix, codes)| moniker_name.starts_with(*prefix) && codes.contains(&code))
740}
741
742/// Starts a component by creating a new Job and Process for the component.
743async fn start(
744    runner: &ElfRunner,
745    checker: ScopedPolicyChecker,
746    start_info: fcrunner::ComponentStartInfo,
747    server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
748) {
749    let resolved_url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
750
751    let elf_component = match runner.start_component(start_info, &checker).await {
752        Ok(elf_component) => elf_component,
753        Err(err) => {
754            runner::component::report_start_error(
755                err.as_zx_status(),
756                format!("{}", err),
757                &resolved_url,
758                server_end,
759            );
760            return;
761        }
762    };
763    let elf_component_moniker = elf_component.info().get_moniker().clone();
764
765    let (termination_tx, termination_rx) = oneshot::channel::<StopInfo>();
766    // This function waits for something from the channel and
767    // returns it or Error::Internal if the channel is closed
768    let termination_fn = Box::pin(async move {
769        termination_rx
770            .await
771            .unwrap_or_else(|_| {
772                warn!("epitaph oneshot channel closed unexpectedly");
773                StopInfo::from_error(fcomp::Error::Internal, None)
774            })
775            .into()
776    });
777
778    let Some(proc_copy) = elf_component.copy_process() else {
779        runner::component::report_start_error(
780            zx::Status::from_raw(
781                i32::try_from(fcomp::Error::InstanceCannotStart.into_primitive()).unwrap(),
782            ),
783            "Component unexpectedly had no process".to_string(),
784            &resolved_url,
785            server_end,
786        );
787        return;
788    };
789
790    let component_diagnostics = elf_component
791        .info()
792        .copy_job_for_diagnostics()
793        .map(|job| ComponentDiagnostics {
794            tasks: Some(ComponentTasks {
795                component_task: Some(DiagnosticsTask::Job(job.into())),
796                ..Default::default()
797            }),
798            ..Default::default()
799        })
800        .map_err(|error| {
801            warn!(error:%; "Failed to copy job for diagnostics");
802            ()
803        })
804        .ok();
805
806    let (server_stream, control) = server_end.into_stream_and_control_handle();
807
808    // Spawn a future that watches for the process to exit
809    runner.scope.spawn({
810        let resolved_url = resolved_url.clone();
811        async move {
812            fasync::OnSignals::new(&proc_copy.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
813                .await
814                .map(|_: fidl::Signals| ()) // Discard.
815                .unwrap_or_else(|error| warn!(error:%; "error creating signal handler"));
816            // Process exit code '0' is considered a clean return.
817            let stop_info = match proc_copy.info() {
818                Ok(zx::ProcessInfo { return_code, .. }) => {
819                    match return_code {
820                        0 => StopInfo::from_ok(Some(return_code)),
821                        // Don't log SYSCALL_KILL codes because they are expected in the course
822                        // of normal operation. When elf_runner process a `Kill` method call for
823                        // a component it makes a zx_task_kill syscall which sets this return code.
824                        zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL => StopInfo::from_error(
825                            fcomp::Error::InstanceDied.into(),
826                            Some(return_code),
827                        ),
828                        _ => {
829                            if is_acceptable_exit_code(&elf_component_moniker, return_code) {
830                                trace!(url:% = resolved_url, return_code:%; "component terminated with an acceptable non-zero exit code");
831                            } else {
832                                warn!(url:% = resolved_url, return_code:%;
833                                    "process terminated with abnormal return code");
834                            }
835                            StopInfo::from_error(fcomp::Error::InstanceDied, Some(return_code))
836                        }
837                    }
838                }
839                Err(error) => {
840                    warn!(error:%; "Unable to query process info");
841                    StopInfo::from_error(fcomp::Error::Internal, None)
842                }
843            };
844            termination_tx.send(stop_info).unwrap_or_else(|_| warn!("error sending done signal"));
845        }
846    });
847
848    let mut elf_component = elf_component;
849    runner.components.clone().add(&mut elf_component);
850
851    // Create a future which owns and serves the controller
852    // channel. The `epitaph_fn` future completes when the
853    // component's main process exits. The controller then sets the
854    // epitaph on the controller channel, closes it, and stops
855    // serving the protocol.
856    runner.scope.spawn(async move {
857        if let Some(component_diagnostics) = component_diagnostics {
858            control.send_on_publish_diagnostics(component_diagnostics).unwrap_or_else(
859                |error| warn!(url:% = resolved_url, error:%; "sending diagnostics failed"),
860            );
861        }
862        runner::component::Controller::new(elf_component, server_stream, control)
863            .serve(termination_fn)
864            .await;
865    });
866}
867
868#[cfg(test)]
869mod tests {
870    use super::runtime_dir::RuntimeDirectory;
871    use super::*;
872    use anyhow::{Context, Error};
873    use assert_matches::assert_matches;
874    use cm_config::{AllowlistEntryBuilder, JobPolicyAllowlists, SecurityPolicy};
875    use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, create_endpoints, create_proxy};
876    use fidl_connector::Connect;
877    use fidl_fuchsia_component as fcomp;
878    use fidl_fuchsia_component_runner as fcrunner;
879    use fidl_fuchsia_component_runner::Task as DiagnosticsTask;
880    use fidl_fuchsia_data as fdata;
881    use fidl_fuchsia_io as fio;
882    use fidl_fuchsia_logger::{LogSinkMarker, LogSinkRequestStream};
883    use fidl_fuchsia_process_lifecycle::LifecycleProxy;
884    use fidl_test_util::spawn_stream_handler;
885    use fuchsia_async as fasync;
886    use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
887    use futures::channel::mpsc;
888    use futures::lock::Mutex;
889    use futures::{StreamExt, join};
890    use runner::component::Controllable;
891    use std::str::FromStr;
892    use std::task::Poll;
893    use test_case::test_case;
894    use zx::{AsHandleRef, Task};
895
896    pub enum MockServiceRequest {
897        LogSink(LogSinkRequestStream),
898    }
899
900    pub type MockServiceFs<'a> = ServiceFs<ServiceObjLocal<'a, MockServiceRequest>>;
901
902    /// Create a new local fs and install a mock LogSink service into.
903    /// Returns the created directory and corresponding namespace entries.
904    pub fn create_fs_with_mock_logsink()
905    -> Result<(MockServiceFs<'static>, Vec<fcrunner::ComponentNamespaceEntry>), Error> {
906        let (dir_client, dir_server) = create_endpoints::<fio::DirectoryMarker>();
907
908        let mut dir = ServiceFs::new_local();
909        dir.add_fidl_service_at(LogSinkMarker::PROTOCOL_NAME, MockServiceRequest::LogSink);
910        dir.serve_connection(dir_server).context("Failed to add serving channel.")?;
911
912        let namespace = vec![fcrunner::ComponentNamespaceEntry {
913            path: Some("/svc".to_string()),
914            directory: Some(dir_client),
915            ..Default::default()
916        }];
917
918        Ok((dir, namespace))
919    }
920
921    // Provide a UTC clock to avoid reusing the system UTC clock in tests, which may
922    // limit the changes that are allowed to be made to this code. We create this clock
923    // here, and start it from current time.
924    pub fn new_utc_clock_for_tests() -> Arc<UtcClock> {
925        let reference_now = zx::BootInstant::get();
926        let system_utc_clock = duplicate_utc_clock_handle(zx::Rights::SAME_RIGHTS).unwrap();
927        let utc_now = system_utc_clock.read().unwrap();
928
929        let utc_clock_for_tests =
930            Arc::new(UtcClock::create(zx::ClockOpts::MAPPABLE, /*backstop=*/ None).unwrap());
931        // This will start the test-only UTC clock.
932        utc_clock_for_tests
933            .update(zx::ClockUpdate::builder().absolute_value(reference_now, utc_now.into()))
934            .unwrap();
935        utc_clock_for_tests
936    }
937
938    pub fn new_elf_runner_for_test() -> Arc<ElfRunner> {
939        Arc::new(ElfRunner::new(
940            job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
941            Box::new(process_launcher::BuiltInConnector {}),
942            Some(new_utc_clock_for_tests()),
943            CrashRecords::new(),
944            vec![],
945        ))
946    }
947
948    fn namespace_entry(path: &str, flags: fio::Flags) -> fcrunner::ComponentNamespaceEntry {
949        // Get a handle to /pkg
950        let ns_path = path.to_string();
951        let ns_dir = fuchsia_fs::directory::open_in_namespace(path, flags).unwrap();
952        let client_end = ns_dir.into_client_end().unwrap();
953        fcrunner::ComponentNamespaceEntry {
954            path: Some(ns_path),
955            directory: Some(client_end),
956            ..Default::default()
957        }
958    }
959
960    fn pkg_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
961        namespace_entry("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
962    }
963
964    fn svc_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
965        namespace_entry("/svc", fio::PERM_READABLE)
966    }
967
968    fn hello_world_startinfo(
969        runtime_dir: ServerEnd<fio::DirectoryMarker>,
970    ) -> fcrunner::ComponentStartInfo {
971        let ns = vec![pkg_dir_namespace_entry()];
972
973        fcrunner::ComponentStartInfo {
974            resolved_url: Some(
975                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/hello-world-rust.cm".to_string(),
976            ),
977            program: Some(fdata::Dictionary {
978                entries: Some(vec![
979                    fdata::DictionaryEntry {
980                        key: "args".to_string(),
981                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
982                            "foo".to_string(),
983                            "bar".to_string(),
984                        ]))),
985                    },
986                    fdata::DictionaryEntry {
987                        key: "binary".to_string(),
988                        value: Some(Box::new(fdata::DictionaryValue::Str(
989                            "bin/hello_world_rust".to_string(),
990                        ))),
991                    },
992                ]),
993                ..Default::default()
994            }),
995            ns: Some(ns),
996            outgoing_dir: None,
997            runtime_dir: Some(runtime_dir),
998            component_instance: Some(zx::Event::create()),
999            ..Default::default()
1000        }
1001    }
1002
1003    /// ComponentStartInfo that points to a non-existent binary.
1004    fn invalid_binary_startinfo(
1005        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1006    ) -> fcrunner::ComponentStartInfo {
1007        let ns = vec![pkg_dir_namespace_entry()];
1008
1009        fcrunner::ComponentStartInfo {
1010            resolved_url: Some(
1011                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/does-not-exist.cm".to_string(),
1012            ),
1013            program: Some(fdata::Dictionary {
1014                entries: Some(vec![fdata::DictionaryEntry {
1015                    key: "binary".to_string(),
1016                    value: Some(Box::new(fdata::DictionaryValue::Str(
1017                        "bin/does_not_exist".to_string(),
1018                    ))),
1019                }]),
1020                ..Default::default()
1021            }),
1022            ns: Some(ns),
1023            outgoing_dir: None,
1024            runtime_dir: Some(runtime_dir),
1025            component_instance: Some(zx::Event::create()),
1026            ..Default::default()
1027        }
1028    }
1029
1030    /// Creates start info for a component which runs until told to exit. The
1031    /// ComponentController protocol can be used to stop the component when the
1032    /// test is done inspecting the launched component.
1033    pub fn lifecycle_startinfo(
1034        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1035    ) -> fcrunner::ComponentStartInfo {
1036        let ns = vec![pkg_dir_namespace_entry()];
1037
1038        fcrunner::ComponentStartInfo {
1039            resolved_url: Some(
1040                "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm".to_string(),
1041            ),
1042            program: Some(fdata::Dictionary {
1043                entries: Some(vec![
1044                    fdata::DictionaryEntry {
1045                        key: "args".to_string(),
1046                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
1047                            "foo".to_string(),
1048                            "bar".to_string(),
1049                        ]))),
1050                    },
1051                    fdata::DictionaryEntry {
1052                        key: "binary".to_string(),
1053                        value: Some(Box::new(fdata::DictionaryValue::Str(
1054                            "bin/lifecycle_placeholder".to_string(),
1055                        ))),
1056                    },
1057                    fdata::DictionaryEntry {
1058                        key: "lifecycle.stop_event".to_string(),
1059                        value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1060                    },
1061                ]),
1062                ..Default::default()
1063            }),
1064            ns: Some(ns),
1065            outgoing_dir: None,
1066            runtime_dir: Some(runtime_dir),
1067            component_instance: Some(zx::Event::create()),
1068            ..Default::default()
1069        }
1070    }
1071
1072    fn create_child_process(job: &zx::Job, name: &str) -> zx::Process {
1073        let (process, _vmar) = job
1074            .create_child_process(zx::ProcessOptions::empty(), name.as_bytes())
1075            .expect("could not create process");
1076        process
1077    }
1078
1079    fn make_default_elf_component(
1080        lifecycle_client: Option<LifecycleProxy>,
1081        critical: bool,
1082    ) -> (scoped_task::Scoped<zx::Job>, ElfComponent) {
1083        let job = scoped_task::create_child_job().expect("failed to make child job");
1084        let process = create_child_process(&job, "test_process");
1085        let job_copy =
1086            job.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("job handle duplication failed");
1087        let component = ElfComponent::new(
1088            RuntimeDirectory::empty(),
1089            Moniker::default(),
1090            Job::Single(job_copy),
1091            process,
1092            lifecycle_client,
1093            critical,
1094            ExecutionScope::new(),
1095            "".to_string(),
1096            None,
1097            Default::default(),
1098            zx::Event::create(),
1099        );
1100        (job, component)
1101    }
1102
1103    // TODO(https://fxbug.dev/42073224): A variation of this is used in a couple of places. We should consider
1104    // refactoring this into a test util file.
1105    async fn read_file<'a>(root_proxy: &'a fio::DirectoryProxy, path: &'a str) -> String {
1106        let file_proxy =
1107            fuchsia_fs::directory::open_file_async(&root_proxy, path, fuchsia_fs::PERM_READABLE)
1108                .expect("Failed to open file.");
1109        let res = fuchsia_fs::file::read_to_string(&file_proxy).await;
1110        res.expect("Unable to read file.")
1111    }
1112
1113    #[fuchsia::test]
1114    async fn test_runtime_dir_entries() -> Result<(), Error> {
1115        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1116        let start_info = lifecycle_startinfo(runtime_dir_server);
1117
1118        let runner = new_elf_runner_for_test();
1119        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1120            Arc::new(SecurityPolicy::default()),
1121            Moniker::root(),
1122        ));
1123        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1124
1125        runner.start(start_info, server_controller).await;
1126
1127        // Verify that args are added to the runtime directory.
1128        assert_eq!("foo", read_file(&runtime_dir, "args/0").await);
1129        assert_eq!("bar", read_file(&runtime_dir, "args/1").await);
1130
1131        // Process Id, Process Start Time, Job Id will vary with every run of this test. Here we
1132        // verify that they exist in the runtime directory, they can be parsed as integers,
1133        // they're greater than zero and they are not the same value. Those are about the only
1134        // invariants we can verify across test runs.
1135        let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1136        let process_start_time =
1137            read_file(&runtime_dir, "elf/process_start_time").await.parse::<i64>()?;
1138        let process_start_time_utc_estimate =
1139            read_file(&runtime_dir, "elf/process_start_time_utc_estimate").await;
1140        let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>()?;
1141        assert!(process_id > 0);
1142        assert!(process_start_time > 0);
1143        assert!(process_start_time_utc_estimate.contains("UTC"));
1144        assert!(job_id > 0);
1145        assert_ne!(process_id, job_id);
1146
1147        controller.stop().expect("Stop request failed");
1148        // Wait for the process to exit so the test doesn't pagefault due to an invalid stdout
1149        // handle.
1150        controller.on_closed().await.expect("failed waiting for channel to close");
1151        Ok(())
1152    }
1153
1154    #[fuchsia::test]
1155    async fn test_kill_component() -> Result<(), Error> {
1156        let (job, mut component) = make_default_elf_component(None, false);
1157
1158        let job_info = job.info()?;
1159        assert!(!job_info.exited);
1160
1161        component.kill().await;
1162
1163        let h = job.as_handle_ref();
1164        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1165            .await
1166            .expect("failed waiting for termination signal");
1167
1168        let job_info = job.info()?;
1169        assert!(job_info.exited);
1170        Ok(())
1171    }
1172
1173    #[fuchsia::test]
1174    fn test_stop_critical_component() -> Result<(), Error> {
1175        let mut exec = fasync::TestExecutor::new();
1176        // Presence of the Lifecycle channel isn't used by ElfComponent to sense
1177        // component exit, but it does modify the stop behavior and this is
1178        // what we want to test.
1179        let (lifecycle_client, _lifecycle_server) = create_proxy::<LifecycleMarker>();
1180        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1181        let process = component.copy_process().unwrap();
1182        let job_info = job.info()?;
1183        assert!(!job_info.exited);
1184
1185        // Ask the runner to stop the component, it returns a future which
1186        // completes when the component closes its side of the lifecycle
1187        // channel
1188        let mut completes_when_stopped = component.stop();
1189
1190        // The returned future shouldn't complete because we're holding the
1191        // lifecycle channel open.
1192        match exec.run_until_stalled(&mut completes_when_stopped) {
1193            Poll::Ready(_) => {
1194                panic!("runner should still be waiting for lifecycle channel to stop");
1195            }
1196            _ => {}
1197        }
1198        assert_eq!(process.kill(), Ok(()));
1199
1200        exec.run_singlethreaded(&mut completes_when_stopped);
1201
1202        // Check that the runner killed the job hosting the exited component.
1203        let h = job.as_handle_ref();
1204        let termination_fut = async move {
1205            fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1206                .await
1207                .expect("failed waiting for termination signal");
1208        };
1209        exec.run_singlethreaded(termination_fut);
1210
1211        let job_info = job.info()?;
1212        assert!(job_info.exited);
1213        Ok(())
1214    }
1215
1216    #[fuchsia::test]
1217    fn test_stop_noncritical_component() -> Result<(), Error> {
1218        let mut exec = fasync::TestExecutor::new();
1219        // Presence of the Lifecycle channel isn't used by ElfComponent to sense
1220        // component exit, but it does modify the stop behavior and this is
1221        // what we want to test.
1222        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1223        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1224
1225        let job_info = job.info()?;
1226        assert!(!job_info.exited);
1227
1228        // Ask the runner to stop the component, it returns a future which
1229        // completes when the component closes its side of the lifecycle
1230        // channel
1231        let mut completes_when_stopped = component.stop();
1232
1233        // The returned future shouldn't complete because we're holding the
1234        // lifecycle channel open.
1235        match exec.run_until_stalled(&mut completes_when_stopped) {
1236            Poll::Ready(_) => {
1237                panic!("runner should still be waiting for lifecycle channel to stop");
1238            }
1239            _ => {}
1240        }
1241        drop(lifecycle_server);
1242
1243        match exec.run_until_stalled(&mut completes_when_stopped) {
1244            Poll::Ready(_) => {}
1245            _ => {
1246                panic!("runner future should have completed, lifecycle channel is closed.");
1247            }
1248        }
1249        // Check that the runner killed the job hosting the exited component.
1250        let h = job.as_handle_ref();
1251        let termination_fut = async move {
1252            fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1253                .await
1254                .expect("failed waiting for termination signal");
1255        };
1256        exec.run_singlethreaded(termination_fut);
1257
1258        let job_info = job.info()?;
1259        assert!(job_info.exited);
1260        Ok(())
1261    }
1262
1263    /// Stopping a component which doesn't have a lifecycle channel should be
1264    /// equivalent to killing a component directly.
1265    #[fuchsia::test]
1266    async fn test_stop_component_without_lifecycle() -> Result<(), Error> {
1267        let (job, mut component) = make_default_elf_component(None, false);
1268
1269        let job_info = job.info()?;
1270        assert!(!job_info.exited);
1271
1272        component.stop().await;
1273
1274        let h = job.as_handle_ref();
1275        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1276            .await
1277            .expect("failed waiting for termination signal");
1278
1279        let job_info = job.info()?;
1280        assert!(job_info.exited);
1281        Ok(())
1282    }
1283
1284    #[fuchsia::test]
1285    async fn test_stop_critical_component_with_closed_lifecycle() -> Result<(), Error> {
1286        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1287        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1288        let process = component.copy_process().unwrap();
1289        let job_info = job.info()?;
1290        assert!(!job_info.exited);
1291
1292        // Close the lifecycle channel
1293        drop(lifecycle_server);
1294        // Kill the process because this is what ElfComponent monitors to
1295        // determine if the component exited.
1296        process.kill()?;
1297        component.stop().await;
1298
1299        let h = job.as_handle_ref();
1300        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1301            .await
1302            .expect("failed waiting for termination signal");
1303
1304        let job_info = job.info()?;
1305        assert!(job_info.exited);
1306        Ok(())
1307    }
1308
1309    #[fuchsia::test]
1310    async fn test_stop_noncritical_component_with_closed_lifecycle() -> Result<(), Error> {
1311        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1312        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1313
1314        let job_info = job.info()?;
1315        assert!(!job_info.exited);
1316
1317        // Close the lifecycle channel
1318        drop(lifecycle_server);
1319        // Kill the process because this is what ElfComponent monitors to
1320        // determine if the component exited.
1321        component.stop().await;
1322
1323        let h = job.as_handle_ref();
1324        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1325            .await
1326            .expect("failed waiting for termination signal");
1327
1328        let job_info = job.info()?;
1329        assert!(job_info.exited);
1330        Ok(())
1331    }
1332
1333    /// Dropping the component should kill the job hosting it.
1334    #[fuchsia::test]
1335    async fn test_drop() -> Result<(), Error> {
1336        let (job, component) = make_default_elf_component(None, false);
1337
1338        let job_info = job.info()?;
1339        assert!(!job_info.exited);
1340
1341        drop(component);
1342
1343        let h = job.as_handle_ref();
1344        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1345            .await
1346            .expect("failed waiting for termination signal");
1347
1348        let job_info = job.info()?;
1349        assert!(job_info.exited);
1350        Ok(())
1351    }
1352
1353    fn with_mark_vmo_exec(
1354        mut start_info: fcrunner::ComponentStartInfo,
1355    ) -> fcrunner::ComponentStartInfo {
1356        start_info.program.as_mut().map(|dict| {
1357            dict.entries.as_mut().map(|entry| {
1358                entry.push(fdata::DictionaryEntry {
1359                    key: "job_policy_ambient_mark_vmo_exec".to_string(),
1360                    value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1361                });
1362                entry
1363            })
1364        });
1365        start_info
1366    }
1367
1368    fn with_main_process_critical(
1369        mut start_info: fcrunner::ComponentStartInfo,
1370    ) -> fcrunner::ComponentStartInfo {
1371        start_info.program.as_mut().map(|dict| {
1372            dict.entries.as_mut().map(|entry| {
1373                entry.push(fdata::DictionaryEntry {
1374                    key: "main_process_critical".to_string(),
1375                    value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1376                });
1377                entry
1378            })
1379        });
1380        start_info
1381    }
1382
1383    #[fuchsia::test]
1384    async fn vmex_security_policy_denied() -> Result<(), Error> {
1385        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1386        let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1387
1388        // Config does not allowlist any monikers to have access to the job policy.
1389        let runner = new_elf_runner_for_test();
1390        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1391            Arc::new(SecurityPolicy::default()),
1392            Moniker::root(),
1393        ));
1394        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1395
1396        // Attempting to start the component should fail, which we detect by looking for an
1397        // ACCESS_DENIED epitaph on the ComponentController's event stream.
1398        runner.start(start_info, server_controller).await;
1399        assert_matches!(
1400            controller.take_event_stream().try_next().await,
1401            Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1402        );
1403
1404        Ok(())
1405    }
1406
1407    #[fuchsia::test]
1408    async fn vmex_security_policy_allowed() -> Result<(), Error> {
1409        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1410        let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1411
1412        let policy = SecurityPolicy {
1413            job_policy: JobPolicyAllowlists {
1414                ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::new().exact("foo").build()],
1415                ..Default::default()
1416            },
1417            ..Default::default()
1418        };
1419        let runner = new_elf_runner_for_test();
1420        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1421            Arc::new(policy),
1422            Moniker::try_from(["foo"]).unwrap(),
1423        ));
1424        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1425        runner.start(start_info, server_controller).await;
1426
1427        // Runtime dir won't exist if the component failed to start.
1428        let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1429        assert!(process_id > 0);
1430        // Component controller should get shutdown normally; no ACCESS_DENIED epitaph.
1431        controller.kill().expect("kill failed");
1432
1433        // We expect the event stream to have closed, which is reported as an
1434        // error and the value of the error should match the epitaph for a
1435        // process that was killed.
1436        let mut event_stream = controller.take_event_stream();
1437        expect_diagnostics_event(&mut event_stream).await;
1438
1439        let s = zx::Status::from_raw(
1440            i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1441        );
1442        expect_on_stop(&mut event_stream, s, Some(zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL)).await;
1443        expect_channel_closed(&mut event_stream).await;
1444        Ok(())
1445    }
1446
1447    #[fuchsia::test]
1448    async fn critical_security_policy_denied() -> Result<(), Error> {
1449        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1450        let start_info = with_main_process_critical(hello_world_startinfo(runtime_dir_server));
1451
1452        // Default policy does not allowlist any monikers to be marked as critical
1453        let runner = new_elf_runner_for_test();
1454        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1455            Arc::new(SecurityPolicy::default()),
1456            Moniker::root(),
1457        ));
1458        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1459
1460        // Attempting to start the component should fail, which we detect by looking for an
1461        // ACCESS_DENIED epitaph on the ComponentController's event stream.
1462        runner.start(start_info, server_controller).await;
1463        assert_matches!(
1464            controller.take_event_stream().try_next().await,
1465            Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1466        );
1467
1468        Ok(())
1469    }
1470
1471    #[fuchsia::test]
1472    #[should_panic]
1473    async fn fail_to_launch_critical_component() {
1474        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1475
1476        // ElfRunner should fail to start the component because this start_info points
1477        // to a binary that does not exist in the test package.
1478        let start_info = with_main_process_critical(invalid_binary_startinfo(runtime_dir_server));
1479
1480        // Policy does not allowlist any monikers to be marked as critical without being
1481        // allowlisted, so make sure we permit this one.
1482        let policy = SecurityPolicy {
1483            job_policy: JobPolicyAllowlists {
1484                main_process_critical: vec![AllowlistEntryBuilder::new().build()],
1485                ..Default::default()
1486            },
1487            ..Default::default()
1488        };
1489        let runner = new_elf_runner_for_test();
1490        let runner =
1491            runner.get_scoped_runner(ScopedPolicyChecker::new(Arc::new(policy), Moniker::root()));
1492        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1493
1494        runner.start(start_info, server_controller).await;
1495
1496        controller
1497            .take_event_stream()
1498            .try_next()
1499            .await
1500            .map(|_: Option<fcrunner::ComponentControllerEvent>| ()) // Discard.
1501            .unwrap_or_else(|error| warn!(error:%; "error reading from event stream"));
1502    }
1503
1504    fn hello_world_startinfo_forward_stdout_to_log(
1505        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1506        mut ns: Vec<fcrunner::ComponentNamespaceEntry>,
1507    ) -> fcrunner::ComponentStartInfo {
1508        ns.push(pkg_dir_namespace_entry());
1509
1510        fcrunner::ComponentStartInfo {
1511            resolved_url: Some(
1512                "fuchsia-pkg://fuchsia.com/hello-world-rust#meta/hello-world-rust.cm".to_string(),
1513            ),
1514            program: Some(fdata::Dictionary {
1515                entries: Some(vec![
1516                    fdata::DictionaryEntry {
1517                        key: "binary".to_string(),
1518                        value: Some(Box::new(fdata::DictionaryValue::Str(
1519                            "bin/hello_world_rust".to_string(),
1520                        ))),
1521                    },
1522                    fdata::DictionaryEntry {
1523                        key: "forward_stdout_to".to_string(),
1524                        value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1525                    },
1526                    fdata::DictionaryEntry {
1527                        key: "forward_stderr_to".to_string(),
1528                        value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1529                    },
1530                ]),
1531                ..Default::default()
1532            }),
1533            ns: Some(ns),
1534            outgoing_dir: None,
1535            runtime_dir: Some(runtime_dir),
1536            component_instance: Some(zx::Event::create()),
1537            ..Default::default()
1538        }
1539    }
1540
1541    #[fuchsia::test]
1542    async fn enable_stdout_and_stderr_logging() -> Result<(), Error> {
1543        let (mut dir, ns) = create_fs_with_mock_logsink()?;
1544
1545        let run_component_fut = async move {
1546            let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1547            let start_info = hello_world_startinfo_forward_stdout_to_log(runtime_dir_server, ns);
1548
1549            let runner = new_elf_runner_for_test();
1550            let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1551                Arc::new(SecurityPolicy::default()),
1552                Moniker::root(),
1553            ));
1554            let (client_controller, server_controller) =
1555                create_proxy::<fcrunner::ComponentControllerMarker>();
1556
1557            runner.start(start_info, server_controller).await;
1558            let mut event_stream = client_controller.take_event_stream();
1559            expect_diagnostics_event(&mut event_stream).await;
1560            expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1561            expect_channel_closed(&mut event_stream).await;
1562        };
1563
1564        // Just check for connection count, other integration tests cover decoding the actual logs.
1565        let service_fs_listener_fut = async {
1566            let mut requests = Vec::new();
1567            while let Some(MockServiceRequest::LogSink(r)) = dir.next().await {
1568                // The client is expecting us to send OnInit, but we're not testing that, so just
1569                // park the requests.
1570                requests.push(r);
1571            }
1572            requests.len()
1573        };
1574
1575        let connection_count = join!(run_component_fut, service_fs_listener_fut).1;
1576
1577        assert_eq!(connection_count, 1);
1578        Ok(())
1579    }
1580
1581    #[fuchsia::test]
1582    async fn on_publish_diagnostics_contains_job_handle() -> Result<(), Error> {
1583        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1584        let start_info = lifecycle_startinfo(runtime_dir_server);
1585
1586        let runner = new_elf_runner_for_test();
1587        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1588            Arc::new(SecurityPolicy::default()),
1589            Moniker::root(),
1590        ));
1591        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1592
1593        runner.start(start_info, server_controller).await;
1594
1595        let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>().unwrap();
1596        let mut event_stream = controller.take_event_stream();
1597        match event_stream.try_next().await {
1598            Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1599                payload:
1600                    ComponentDiagnostics {
1601                        tasks:
1602                            Some(ComponentTasks {
1603                                component_task: Some(DiagnosticsTask::Job(job)), ..
1604                            }),
1605                        ..
1606                    },
1607            })) => {
1608                assert_eq!(job_id, job.koid().unwrap().raw_koid());
1609            }
1610            other => panic!("unexpected event result: {:?}", other),
1611        }
1612
1613        controller.stop().expect("Stop request failed");
1614        // Wait for the process to exit so the test doesn't pagefault due to an invalid stdout
1615        // handle.
1616        controller.on_closed().await.expect("failed waiting for channel to close");
1617
1618        Ok(())
1619    }
1620
1621    async fn expect_diagnostics_event(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1622        let event = event_stream.try_next().await;
1623        assert_matches!(
1624            event,
1625            Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1626                payload: ComponentDiagnostics {
1627                    tasks: Some(ComponentTasks {
1628                        component_task: Some(DiagnosticsTask::Job(_)),
1629                        ..
1630                    }),
1631                    ..
1632                },
1633            }))
1634        );
1635    }
1636
1637    async fn expect_on_stop(
1638        event_stream: &mut fcrunner::ComponentControllerEventStream,
1639        expected_status: zx::Status,
1640        expected_exit_code: Option<i64>,
1641    ) {
1642        let event = event_stream.try_next().await;
1643        assert_matches!(
1644            event,
1645            Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
1646                payload: fcrunner::ComponentStopInfo { termination_status: Some(s), exit_code, .. },
1647            }))
1648            if s == expected_status.into_raw() &&
1649                exit_code == expected_exit_code
1650        );
1651    }
1652
1653    async fn expect_channel_closed(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1654        let event = event_stream.try_next().await;
1655        match event {
1656            Ok(None) => {}
1657            other => panic!("Expected channel closed error, got {:?}", other),
1658        }
1659    }
1660
1661    /// An implementation of launcher that sends a complete launch request payload back to
1662    /// a test through an mpsc channel.
1663    struct LauncherConnectorForTest {
1664        sender: mpsc::UnboundedSender<LaunchPayload>,
1665    }
1666
1667    /// Contains all the information passed to fuchsia.process.Launcher before and up to calling
1668    /// Launch/CreateWithoutStarting.
1669    #[derive(Default)]
1670    struct LaunchPayload {
1671        launch_info: Option<fproc::LaunchInfo>,
1672        args: Vec<Vec<u8>>,
1673        environ: Vec<Vec<u8>>,
1674        name_info: Vec<fproc::NameInfo>,
1675        handles: Vec<fproc::HandleInfo>,
1676        options: u32,
1677    }
1678
1679    impl Connect for LauncherConnectorForTest {
1680        type Proxy = fproc::LauncherProxy;
1681
1682        fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
1683            let sender = self.sender.clone();
1684            let payload = Arc::new(Mutex::new(LaunchPayload::default()));
1685
1686            Ok(spawn_stream_handler(move |launcher_request| {
1687                let sender = sender.clone();
1688                let payload = payload.clone();
1689                async move {
1690                    let mut payload = payload.lock().await;
1691                    match launcher_request {
1692                        fproc::LauncherRequest::Launch { info, responder } => {
1693                            let process = create_child_process(&info.job, "test_process");
1694                            responder.send(zx::Status::OK.into_raw(), Some(process)).unwrap();
1695
1696                            let mut payload =
1697                                std::mem::replace(&mut *payload, LaunchPayload::default());
1698                            payload.launch_info = Some(info);
1699                            sender.unbounded_send(payload).unwrap();
1700                        }
1701                        fproc::LauncherRequest::CreateWithoutStarting { info: _, responder: _ } => {
1702                            unimplemented!()
1703                        }
1704                        fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
1705                            payload.args.append(&mut args);
1706                        }
1707                        fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
1708                            payload.environ.append(&mut environ);
1709                        }
1710                        fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
1711                            payload.name_info.append(&mut names);
1712                        }
1713                        fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
1714                            payload.handles.append(&mut handles);
1715                        }
1716                        fproc::LauncherRequest::SetOptions { options, .. } => {
1717                            payload.options = options;
1718                        }
1719                    }
1720                }
1721            }))
1722        }
1723    }
1724
1725    #[fuchsia::test]
1726    async fn process_created_with_utc_clock_from_numbered_handles() -> Result<(), Error> {
1727        let (payload_tx, mut payload_rx) = mpsc::unbounded();
1728
1729        let connector = LauncherConnectorForTest { sender: payload_tx };
1730        let runner = ElfRunner::new(
1731            job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
1732            Box::new(connector),
1733            Some(new_utc_clock_for_tests()),
1734            CrashRecords::new(),
1735            vec![],
1736        );
1737        let policy_checker = ScopedPolicyChecker::new(
1738            Arc::new(SecurityPolicy::default()),
1739            Moniker::try_from(["foo"]).unwrap(),
1740        );
1741
1742        // Create a clock and pass it to the component as the UTC clock through numbered_handles.
1743        let clock = zx::SyntheticClock::create(
1744            zx::ClockOpts::AUTO_START | zx::ClockOpts::MONOTONIC | zx::ClockOpts::MAPPABLE,
1745            None,
1746        )?;
1747        let clock_koid = clock.koid().unwrap();
1748
1749        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1750        let mut start_info = hello_world_startinfo(runtime_dir_server);
1751        start_info.numbered_handles = Some(vec![fproc::HandleInfo {
1752            handle: clock.into_handle(),
1753            id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
1754        }]);
1755
1756        // Start the component.
1757        let _ = runner
1758            .start_component(start_info, &policy_checker)
1759            .await
1760            .context("failed to start component")?;
1761
1762        let payload = payload_rx.next().await.unwrap();
1763        assert!(
1764            payload
1765                .handles
1766                .iter()
1767                .any(|handle_info| handle_info.handle.koid().unwrap() == clock_koid)
1768        );
1769
1770        Ok(())
1771    }
1772
1773    /// Test visiting running components using [`ComponentSet`].
1774    #[fuchsia::test]
1775    async fn test_enumerate_components() {
1776        use std::sync::atomic::{AtomicUsize, Ordering};
1777
1778        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1779        let start_info = lifecycle_startinfo(runtime_dir_server);
1780
1781        let runner = new_elf_runner_for_test();
1782        let components = runner.components.clone();
1783
1784        // Initially there are zero components.
1785        let count = Arc::new(AtomicUsize::new(0));
1786        components.clone().visit(|_, _| {
1787            count.fetch_add(1, Ordering::SeqCst);
1788        });
1789        assert_eq!(count.load(Ordering::SeqCst), 0);
1790
1791        // Run a component.
1792        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1793            Arc::new(SecurityPolicy::default()),
1794            Moniker::root(),
1795        ));
1796        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1797        runner.start(start_info, server_controller).await;
1798
1799        // There should now be one component in the set.
1800        let count = Arc::new(AtomicUsize::new(0));
1801        components.clone().visit(|elf_component: &ElfComponentInfo, _| {
1802            assert_eq!(
1803                elf_component.get_url().as_str(),
1804                "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm"
1805            );
1806            count.fetch_add(1, Ordering::SeqCst);
1807        });
1808        assert_eq!(count.load(Ordering::SeqCst), 1);
1809
1810        // Stop the component.
1811        controller.stop().unwrap();
1812        controller.on_closed().await.unwrap();
1813
1814        // There should now be zero components in the set.
1815        // Keep retrying until the component is asynchronously deregistered.
1816        loop {
1817            let count = Arc::new(AtomicUsize::new(0));
1818            components.clone().visit(|_, _| {
1819                count.fetch_add(1, Ordering::SeqCst);
1820            });
1821            let count = count.load(Ordering::SeqCst);
1822            assert!(count == 0 || count == 1);
1823            if count == 0 {
1824                break;
1825            }
1826            // Yield to the executor once so that we are not starving the
1827            // asynchronous deregistration task from running.
1828            yield_to_executor().await;
1829        }
1830    }
1831
1832    async fn yield_to_executor() {
1833        let mut done = false;
1834        futures::future::poll_fn(|cx| {
1835            if done {
1836                Poll::Ready(())
1837            } else {
1838                done = true;
1839                cx.waker().wake_by_ref();
1840                Poll::Pending
1841            }
1842        })
1843        .await;
1844    }
1845
1846    /// Creates start info for a component which runs immediately escrows its
1847    /// outgoing directory and then exits.
1848    pub fn immediate_escrow_startinfo(
1849        outgoing_dir: ServerEnd<fio::DirectoryMarker>,
1850        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1851    ) -> fcrunner::ComponentStartInfo {
1852        let ns = vec![
1853            pkg_dir_namespace_entry(),
1854            // Give the test component LogSink.
1855            svc_dir_namespace_entry(),
1856        ];
1857
1858        fcrunner::ComponentStartInfo {
1859            resolved_url: Some("#meta/immediate_escrow_component.cm".to_string()),
1860            program: Some(fdata::Dictionary {
1861                entries: Some(vec![
1862                    fdata::DictionaryEntry {
1863                        key: "binary".to_string(),
1864                        value: Some(Box::new(fdata::DictionaryValue::Str(
1865                            "bin/immediate_escrow".to_string(),
1866                        ))),
1867                    },
1868                    fdata::DictionaryEntry {
1869                        key: "lifecycle.stop_event".to_string(),
1870                        value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1871                    },
1872                ]),
1873                ..Default::default()
1874            }),
1875            ns: Some(ns),
1876            outgoing_dir: Some(outgoing_dir),
1877            runtime_dir: Some(runtime_dir),
1878            component_instance: Some(zx::Event::create()),
1879            ..Default::default()
1880        }
1881    }
1882
1883    /// Test that an ELF component can send an `OnEscrow` event on its lifecycle
1884    /// channel and this event is forwarded to the `ComponentController`.
1885    #[fuchsia::test]
1886    async fn test_lifecycle_on_escrow() {
1887        let (outgoing_dir_client, outgoing_dir_server) =
1888            fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1889        let (_, runtime_dir_server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1890        let start_info = immediate_escrow_startinfo(outgoing_dir_server, runtime_dir_server);
1891
1892        let runner = new_elf_runner_for_test();
1893        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1894            Arc::new(SecurityPolicy::default()),
1895            Moniker::root(),
1896        ));
1897        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1898
1899        runner.start(start_info, server_controller).await;
1900
1901        let mut event_stream = controller.take_event_stream();
1902
1903        expect_diagnostics_event(&mut event_stream).await;
1904
1905        match event_stream.try_next().await {
1906            Ok(Some(fcrunner::ComponentControllerEvent::OnEscrow {
1907                payload: fcrunner::ComponentControllerOnEscrowRequest { outgoing_dir, .. },
1908            })) => {
1909                let outgoing_dir_server = outgoing_dir.unwrap();
1910
1911                assert_eq!(
1912                    outgoing_dir_client.as_handle_ref().basic_info().unwrap().koid,
1913                    outgoing_dir_server.as_handle_ref().basic_info().unwrap().related_koid
1914                );
1915            }
1916            other => panic!("unexpected event result: {:?}", other),
1917        }
1918
1919        expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1920        expect_channel_closed(&mut event_stream).await;
1921    }
1922
1923    fn exit_with_code_startinfo(exit_code: i64) -> fcrunner::ComponentStartInfo {
1924        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1925        let ns = vec![pkg_dir_namespace_entry()];
1926
1927        fcrunner::ComponentStartInfo {
1928            resolved_url: Some(
1929                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code.cm".to_string(),
1930            ),
1931            program: Some(fdata::Dictionary {
1932                entries: Some(vec![
1933                    fdata::DictionaryEntry {
1934                        key: "args".to_string(),
1935                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![format!(
1936                            "{}",
1937                            exit_code
1938                        )]))),
1939                    },
1940                    fdata::DictionaryEntry {
1941                        key: "binary".to_string(),
1942                        value: Some(Box::new(fdata::DictionaryValue::Str(
1943                            "bin/exit_with_code".to_string(),
1944                        ))),
1945                    },
1946                ]),
1947                ..Default::default()
1948            }),
1949            ns: Some(ns),
1950            outgoing_dir: None,
1951            runtime_dir: Some(runtime_dir_server),
1952            component_instance: Some(zx::Event::create()),
1953            ..Default::default()
1954        }
1955    }
1956
1957    fn exit_with_code_startinfo_from_env(exit_code: Option<i64>) -> fcrunner::ComponentStartInfo {
1958        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1959        let ns = vec![pkg_dir_namespace_entry()];
1960        let environ = match exit_code {
1961            Some(code) => vec![format!("EXIT_CODE={}", code)],
1962            None => vec![],
1963        };
1964        fcrunner::ComponentStartInfo {
1965            resolved_url: Some(
1966                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code-from_env.cm"
1967                    .to_string(),
1968            ),
1969            program: Some(fdata::Dictionary {
1970                entries: Some(vec![
1971                    fdata::DictionaryEntry {
1972                        key: "environ".to_string(),
1973                        value: Some(Box::new(fdata::DictionaryValue::StrVec(environ))),
1974                    },
1975                    fdata::DictionaryEntry {
1976                        key: "binary".to_string(),
1977                        value: Some(Box::new(fdata::DictionaryValue::Str(
1978                            "bin/exit_with_code_from_env".to_string(),
1979                        ))),
1980                    },
1981                ]),
1982                ..Default::default()
1983            }),
1984            ns: Some(ns),
1985            outgoing_dir: None,
1986            runtime_dir: Some(runtime_dir_server),
1987            component_instance: Some(zx::Event::create()),
1988            ..Default::default()
1989        }
1990    }
1991
1992    #[test_case(exit_with_code_startinfo(0) ; "args")]
1993    #[test_case(exit_with_code_startinfo_from_env(Some(0)) ; "env")]
1994    #[fuchsia::test]
1995    async fn test_return_code_success(start_info: fcrunner::ComponentStartInfo) {
1996        let runner = new_elf_runner_for_test();
1997        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1998            Arc::new(SecurityPolicy::default()),
1999            Moniker::root(),
2000        ));
2001        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
2002        runner.start(start_info, server_controller).await;
2003
2004        let mut event_stream = controller.take_event_stream();
2005        expect_diagnostics_event(&mut event_stream).await;
2006        expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
2007        expect_channel_closed(&mut event_stream).await;
2008    }
2009
2010    #[test_case(exit_with_code_startinfo(123), vec![] ; "args")]
2011    #[test_case(exit_with_code_startinfo_from_env(Some(123)), vec![] ; "component_env")]
2012    #[test_case(exit_with_code_startinfo_from_env(Some(123)), vec!["EXIT_CODE=2"] ; "additional_env_shadowed")]
2013    #[test_case(exit_with_code_startinfo_from_env(None), vec!["EXIT_CODE=123"] ; "additional_env")]
2014    #[fuchsia::test]
2015    async fn test_return_code_failure(
2016        start_info: fcrunner::ComponentStartInfo,
2017        additional_environ: Vec<&str>,
2018    ) {
2019        let runner = Arc::new(ElfRunner::new(
2020            job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
2021            Box::new(process_launcher::BuiltInConnector {}),
2022            Some(new_utc_clock_for_tests()),
2023            CrashRecords::new(),
2024            additional_environ.iter().map(|s| s.to_string()).collect(),
2025        ));
2026        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
2027            Arc::new(SecurityPolicy::default()),
2028            Moniker::root(),
2029        ));
2030        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
2031        runner.start(start_info, server_controller).await;
2032
2033        let mut event_stream = controller.take_event_stream();
2034        expect_diagnostics_event(&mut event_stream).await;
2035        let s = zx::Status::from_raw(
2036            i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
2037        );
2038        expect_on_stop(&mut event_stream, s, Some(123)).await;
2039        expect_channel_closed(&mut event_stream).await;
2040    }
2041
2042    #[fuchsia::test]
2043    fn test_is_acceptable_exit_code() {
2044        // Test sshd with its acceptable code
2045        assert!(is_acceptable_exit_code(
2046            &Moniker::from_str("core/sshd-host/shell:sshd-1").expect("valid moniker"),
2047            255
2048        ));
2049
2050        // Test sshd with a non-acceptable code
2051        assert!(!is_acceptable_exit_code(
2052            &Moniker::from_str("core/sshd-host/shell:sshd-1").expect("valid moniker"),
2053            1
2054        ));
2055
2056        // Test a URL that doesn't match
2057        assert!(!is_acceptable_exit_code(
2058            &Moniker::from_str("not_core/ssh-host/shell:sshd-1").expect("valid moniker"),
2059            255
2060        ));
2061
2062        // Test an unknown component with a code that happens to be acceptable for another
2063        assert!(!is_acceptable_exit_code(
2064            &Moniker::from_str("foo/debug").expect("valid moniker"),
2065            255
2066        ));
2067    }
2068
2069    #[fuchsia::test]
2070    fn test_merge_environ() {
2071        assert_eq!(merge_environ(&vec![], &vec![]), Vec::<String>::new());
2072
2073        assert_eq!(
2074            merge_environ(&vec!["A".to_string(), "B=".to_string(), "C=c".to_string()], &vec![]),
2075            vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]
2076        );
2077        assert_eq!(
2078            merge_environ(
2079                &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2080                &vec!["A".to_string(), "B".to_string(), "C".to_string()]
2081            ),
2082            vec!["A".to_string(), "B".to_string(), "C".to_string()]
2083        );
2084        assert_eq!(
2085            merge_environ(
2086                &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2087                &vec!["A=".to_string(), "B=".to_string(), "C=".to_string()]
2088            ),
2089            vec!["A=".to_string(), "B=".to_string(), "C=".to_string()]
2090        );
2091        assert_eq!(
2092            merge_environ(
2093                &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2094                &vec!["A=aa".to_string(), "B=bb".to_string(), "C=cc".to_string()]
2095            ),
2096            vec!["A=aa".to_string(), "B=bb".to_string(), "C=cc".to_string()]
2097        );
2098        assert_eq!(
2099            merge_environ(&vec![], &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]),
2100            vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]
2101        );
2102    }
2103}