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