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 { status: zx::Status::ACCESS_DENIED, .. })
1453        );
1454
1455        Ok(())
1456    }
1457
1458    #[fuchsia::test]
1459    async fn vmex_security_policy_allowed() -> Result<(), Error> {
1460        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1461        let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1462
1463        let policy = SecurityPolicy {
1464            job_policy: JobPolicyAllowlists {
1465                ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::new().exact("foo").build()],
1466                ..Default::default()
1467            },
1468            ..Default::default()
1469        };
1470        let runner = new_elf_runner_for_test();
1471        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1472            Arc::new(policy),
1473            Moniker::try_from(["foo"]).unwrap(),
1474        ));
1475        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1476        runner.start(start_info, server_controller).await;
1477
1478        // Runtime dir won't exist if the component failed to start.
1479        let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1480        assert!(process_id > 0);
1481        // Component controller should get shutdown normally; no ACCESS_DENIED epitaph.
1482        controller.kill().expect("kill failed");
1483
1484        // We expect the event stream to have closed, which is reported as an
1485        // error and the value of the error should match the epitaph for a
1486        // process that was killed.
1487        let mut event_stream = controller.take_event_stream();
1488        expect_diagnostics_event(&mut event_stream).await;
1489
1490        let s = zx::Status::from_raw(
1491            i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1492        );
1493        expect_on_stop(&mut event_stream, s, Some(zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL)).await;
1494        expect_channel_closed(&mut event_stream).await;
1495        Ok(())
1496    }
1497
1498    #[fuchsia::test]
1499    async fn critical_security_policy_denied() -> Result<(), Error> {
1500        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1501        let start_info = with_main_process_critical(hello_world_startinfo(runtime_dir_server));
1502
1503        // Default policy does not allowlist any monikers to be marked as critical
1504        let runner = new_elf_runner_for_test();
1505        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1506            Arc::new(SecurityPolicy::default()),
1507            Moniker::root(),
1508        ));
1509        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1510
1511        // Attempting to start the component should fail, which we detect by looking for an
1512        // ACCESS_DENIED epitaph on the ComponentController's event stream.
1513        runner.start(start_info, server_controller).await;
1514        assert_matches!(
1515            controller.take_event_stream().try_next().await,
1516            Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1517        );
1518
1519        Ok(())
1520    }
1521
1522    #[fuchsia::test]
1523    #[should_panic]
1524    async fn fail_to_launch_critical_component() {
1525        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1526
1527        // ElfRunner should fail to start the component because this start_info points
1528        // to a binary that does not exist in the test package.
1529        let start_info = with_main_process_critical(invalid_binary_startinfo(runtime_dir_server));
1530
1531        // Policy does not allowlist any monikers to be marked as critical without being
1532        // allowlisted, so make sure we permit this one.
1533        let policy = SecurityPolicy {
1534            job_policy: JobPolicyAllowlists {
1535                main_process_critical: vec![AllowlistEntryBuilder::new().build()],
1536                ..Default::default()
1537            },
1538            ..Default::default()
1539        };
1540        let runner = new_elf_runner_for_test();
1541        let runner =
1542            runner.get_scoped_runner(ScopedPolicyChecker::new(Arc::new(policy), Moniker::root()));
1543        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1544
1545        runner.start(start_info, server_controller).await;
1546
1547        controller
1548            .take_event_stream()
1549            .try_next()
1550            .await
1551            .map(|_: Option<fcrunner::ComponentControllerEvent>| ()) // Discard.
1552            .unwrap_or_else(|error| warn!(error:%; "error reading from event stream"));
1553    }
1554
1555    fn hello_world_startinfo_forward_stdout_to_log(
1556        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1557        mut ns: Vec<fcrunner::ComponentNamespaceEntry>,
1558    ) -> fcrunner::ComponentStartInfo {
1559        ns.push(pkg_dir_namespace_entry());
1560
1561        fcrunner::ComponentStartInfo {
1562            resolved_url: Some(
1563                "fuchsia-pkg://fuchsia.com/hello-world-rust#meta/hello-world-rust.cm".to_string(),
1564            ),
1565            program: Some(fdata::Dictionary {
1566                entries: Some(vec![
1567                    fdata::DictionaryEntry {
1568                        key: "binary".to_string(),
1569                        value: Some(Box::new(fdata::DictionaryValue::Str(
1570                            "bin/hello_world_rust".to_string(),
1571                        ))),
1572                    },
1573                    fdata::DictionaryEntry {
1574                        key: "forward_stdout_to".to_string(),
1575                        value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1576                    },
1577                    fdata::DictionaryEntry {
1578                        key: "forward_stderr_to".to_string(),
1579                        value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1580                    },
1581                ]),
1582                ..Default::default()
1583            }),
1584            ns: Some(ns),
1585            outgoing_dir: None,
1586            runtime_dir: Some(runtime_dir),
1587            component_instance: Some(zx::Event::create()),
1588            ..Default::default()
1589        }
1590    }
1591
1592    #[fuchsia::test]
1593    async fn enable_stdout_and_stderr_logging() -> Result<(), Error> {
1594        let (mut dir, ns) = create_fs_with_mock_logsink()?;
1595
1596        let run_component_fut = async move {
1597            let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1598            let start_info = hello_world_startinfo_forward_stdout_to_log(runtime_dir_server, ns);
1599
1600            let runner = new_elf_runner_for_test();
1601            let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1602                Arc::new(SecurityPolicy::default()),
1603                Moniker::root(),
1604            ));
1605            let (client_controller, server_controller) =
1606                create_proxy::<fcrunner::ComponentControllerMarker>();
1607
1608            runner.start(start_info, server_controller).await;
1609            let mut event_stream = client_controller.take_event_stream();
1610            expect_diagnostics_event(&mut event_stream).await;
1611            expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1612            expect_channel_closed(&mut event_stream).await;
1613        };
1614
1615        // Just check for connection count, other integration tests cover decoding the actual logs.
1616        let service_fs_listener_fut = async {
1617            let mut requests = Vec::new();
1618            while let Some(MockServiceRequest::LogSink(r)) = dir.next().await {
1619                // The client is expecting us to send OnInit, but we're not testing that, so just
1620                // park the requests.
1621                requests.push(r);
1622            }
1623            requests.len()
1624        };
1625
1626        let connection_count = join!(run_component_fut, service_fs_listener_fut).1;
1627
1628        assert_eq!(connection_count, 1);
1629        Ok(())
1630    }
1631
1632    #[fuchsia::test]
1633    async fn on_publish_diagnostics_contains_job_handle() -> Result<(), Error> {
1634        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1635        let start_info = lifecycle_startinfo(runtime_dir_server);
1636
1637        let runner = new_elf_runner_for_test();
1638        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1639            Arc::new(SecurityPolicy::default()),
1640            Moniker::root(),
1641        ));
1642        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1643
1644        runner.start(start_info, server_controller).await;
1645
1646        let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>().unwrap();
1647        let mut event_stream = controller.take_event_stream();
1648        match event_stream.try_next().await {
1649            Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1650                payload:
1651                    ComponentDiagnostics {
1652                        tasks:
1653                            Some(ComponentTasks {
1654                                component_task: Some(DiagnosticsTask::Job(job)), ..
1655                            }),
1656                        ..
1657                    },
1658            })) => {
1659                assert_eq!(job_id, job.koid().unwrap().raw_koid());
1660            }
1661            other => panic!("unexpected event result: {:?}", other),
1662        }
1663
1664        controller.stop().expect("Stop request failed");
1665        // Wait for the process to exit so the test doesn't pagefault due to an invalid stdout
1666        // handle.
1667        controller.on_closed().await.expect("failed waiting for channel to close");
1668
1669        Ok(())
1670    }
1671
1672    async fn expect_diagnostics_event(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1673        let event = event_stream.try_next().await;
1674        assert_matches!(
1675            event,
1676            Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1677                payload: ComponentDiagnostics {
1678                    tasks: Some(ComponentTasks {
1679                        component_task: Some(DiagnosticsTask::Job(_)),
1680                        ..
1681                    }),
1682                    ..
1683                },
1684            }))
1685        );
1686    }
1687
1688    async fn expect_on_stop(
1689        event_stream: &mut fcrunner::ComponentControllerEventStream,
1690        expected_status: zx::Status,
1691        expected_exit_code: Option<i64>,
1692    ) {
1693        let event = event_stream.try_next().await;
1694        assert_matches!(
1695            event,
1696            Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
1697                payload: fcrunner::ComponentStopInfo { termination_status: Some(s), exit_code, .. },
1698            }))
1699            if s == expected_status.into_raw() &&
1700                exit_code == expected_exit_code
1701        );
1702    }
1703
1704    async fn expect_channel_closed(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1705        let event = event_stream.try_next().await;
1706        match event {
1707            Ok(None) => {}
1708            other => panic!("Expected channel closed error, got {:?}", other),
1709        }
1710    }
1711
1712    /// An implementation of launcher that sends a complete launch request payload back to
1713    /// a test through an mpsc channel.
1714    struct LauncherConnectorForTest {
1715        sender: mpsc::UnboundedSender<LaunchPayload>,
1716    }
1717
1718    /// Contains all the information passed to fuchsia.process.Launcher before and up to calling
1719    /// Launch/CreateWithoutStarting.
1720    #[derive(Default)]
1721    struct LaunchPayload {
1722        launch_info: Option<fproc::LaunchInfo>,
1723        args: Vec<Vec<u8>>,
1724        environ: Vec<Vec<u8>>,
1725        name_info: Vec<fproc::NameInfo>,
1726        handles: Vec<fproc::HandleInfo>,
1727        options: u32,
1728    }
1729
1730    impl Connect for LauncherConnectorForTest {
1731        type Proxy = fproc::LauncherProxy;
1732
1733        fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
1734            let sender = self.sender.clone();
1735            let payload = Arc::new(Mutex::new(LaunchPayload::default()));
1736
1737            Ok(spawn_stream_handler(move |launcher_request| {
1738                let sender = sender.clone();
1739                let payload = payload.clone();
1740                async move {
1741                    let mut payload = payload.lock().await;
1742                    match launcher_request {
1743                        fproc::LauncherRequest::Launch { info, responder } => {
1744                            let process = create_child_process(&info.job, "test_process");
1745                            responder.send(zx::Status::OK.into_raw(), Some(process)).unwrap();
1746
1747                            let mut payload =
1748                                std::mem::replace(&mut *payload, LaunchPayload::default());
1749                            payload.launch_info = Some(info);
1750                            sender.unbounded_send(payload).unwrap();
1751                        }
1752                        fproc::LauncherRequest::CreateWithoutStarting { info: _, responder: _ } => {
1753                            unimplemented!()
1754                        }
1755                        fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
1756                            payload.args.append(&mut args);
1757                        }
1758                        fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
1759                            payload.environ.append(&mut environ);
1760                        }
1761                        fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
1762                            payload.name_info.append(&mut names);
1763                        }
1764                        fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
1765                            payload.handles.append(&mut handles);
1766                        }
1767                        fproc::LauncherRequest::SetOptions { options, .. } => {
1768                            payload.options = options;
1769                        }
1770                    }
1771                }
1772            }))
1773        }
1774    }
1775
1776    #[fuchsia::test]
1777    async fn process_created_with_utc_clock_from_numbered_handles() -> Result<(), Error> {
1778        let (payload_tx, mut payload_rx) = mpsc::unbounded();
1779
1780        let connector = LauncherConnectorForTest { sender: payload_tx };
1781        let runner = ElfRunner::new(
1782            job_default().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
1783            Box::new(connector),
1784            Some(new_utc_clock_for_tests()),
1785            CrashRecords::new(),
1786            vec![],
1787        );
1788        let policy_checker = ScopedPolicyChecker::new(
1789            Arc::new(SecurityPolicy::default()),
1790            Moniker::try_from(["foo"]).unwrap(),
1791        );
1792
1793        // Create a clock and pass it to the component as the UTC clock through numbered_handles.
1794        let clock = zx::SyntheticClock::create(
1795            zx::ClockOpts::AUTO_START | zx::ClockOpts::MONOTONIC | zx::ClockOpts::MAPPABLE,
1796            None,
1797        )?;
1798        let clock_koid = clock.koid().unwrap();
1799
1800        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1801        let mut start_info = hello_world_startinfo(runtime_dir_server);
1802        start_info.numbered_handles = Some(vec![fproc::HandleInfo {
1803            handle: clock.into_handle(),
1804            id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
1805        }]);
1806
1807        // Start the component.
1808        let _ = runner
1809            .start_component(start_info, &policy_checker)
1810            .await
1811            .context("failed to start component")?;
1812
1813        let payload = payload_rx.next().await.unwrap();
1814        assert!(
1815            payload
1816                .handles
1817                .iter()
1818                .any(|handle_info| handle_info.handle.koid().unwrap() == clock_koid)
1819        );
1820
1821        Ok(())
1822    }
1823
1824    /// Test visiting running components using [`ComponentSet`].
1825    #[fuchsia::test]
1826    async fn test_enumerate_components() {
1827        use std::sync::atomic::{AtomicUsize, Ordering};
1828
1829        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1830        let start_info = lifecycle_startinfo(runtime_dir_server);
1831
1832        let runner = new_elf_runner_for_test();
1833        let components = runner.components.clone();
1834
1835        // Initially there are zero components.
1836        let count = Arc::new(AtomicUsize::new(0));
1837        components.clone().visit(|_, _| {
1838            count.fetch_add(1, Ordering::SeqCst);
1839        });
1840        assert_eq!(count.load(Ordering::SeqCst), 0);
1841
1842        // Run a component.
1843        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1844            Arc::new(SecurityPolicy::default()),
1845            Moniker::root(),
1846        ));
1847        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1848        runner.start(start_info, server_controller).await;
1849
1850        // There should now be one component in the set.
1851        let count = Arc::new(AtomicUsize::new(0));
1852        components.clone().visit(|elf_component: &ElfComponentInfo, _| {
1853            assert_eq!(
1854                elf_component.get_url().as_str(),
1855                "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm"
1856            );
1857            count.fetch_add(1, Ordering::SeqCst);
1858        });
1859        assert_eq!(count.load(Ordering::SeqCst), 1);
1860
1861        // Stop the component.
1862        controller.stop().unwrap();
1863        controller.on_closed().await.unwrap();
1864
1865        // There should now be zero components in the set.
1866        // Keep retrying until the component is asynchronously deregistered.
1867        loop {
1868            let count = Arc::new(AtomicUsize::new(0));
1869            components.clone().visit(|_, _| {
1870                count.fetch_add(1, Ordering::SeqCst);
1871            });
1872            let count = count.load(Ordering::SeqCst);
1873            assert!(count == 0 || count == 1);
1874            if count == 0 {
1875                break;
1876            }
1877            // Yield to the executor once so that we are not starving the
1878            // asynchronous deregistration task from running.
1879            yield_to_executor().await;
1880        }
1881    }
1882
1883    async fn yield_to_executor() {
1884        let mut done = false;
1885        futures::future::poll_fn(|cx| {
1886            if done {
1887                Poll::Ready(())
1888            } else {
1889                done = true;
1890                cx.waker().wake_by_ref();
1891                Poll::Pending
1892            }
1893        })
1894        .await;
1895    }
1896
1897    /// Creates start info for a component which runs immediately escrows its
1898    /// outgoing directory and then exits.
1899    pub fn immediate_escrow_startinfo(
1900        outgoing_dir: ServerEnd<fio::DirectoryMarker>,
1901        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1902    ) -> fcrunner::ComponentStartInfo {
1903        let ns = vec![
1904            pkg_dir_namespace_entry(),
1905            // Give the test component LogSink.
1906            svc_dir_namespace_entry(),
1907        ];
1908
1909        fcrunner::ComponentStartInfo {
1910            resolved_url: Some("#meta/immediate_escrow_component.cm".to_string()),
1911            program: Some(fdata::Dictionary {
1912                entries: Some(vec![
1913                    fdata::DictionaryEntry {
1914                        key: "binary".to_string(),
1915                        value: Some(Box::new(fdata::DictionaryValue::Str(
1916                            "bin/immediate_escrow".to_string(),
1917                        ))),
1918                    },
1919                    fdata::DictionaryEntry {
1920                        key: "lifecycle.stop_event".to_string(),
1921                        value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1922                    },
1923                ]),
1924                ..Default::default()
1925            }),
1926            ns: Some(ns),
1927            outgoing_dir: Some(outgoing_dir),
1928            runtime_dir: Some(runtime_dir),
1929            component_instance: Some(zx::Event::create()),
1930            ..Default::default()
1931        }
1932    }
1933
1934    /// Test that an ELF component can send an `OnEscrow` event on its lifecycle
1935    /// channel and this event is forwarded to the `ComponentController`.
1936    #[fuchsia::test]
1937    async fn test_lifecycle_on_escrow() {
1938        let (outgoing_dir_client, outgoing_dir_server) =
1939            fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1940        let (_, runtime_dir_server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1941        let start_info = immediate_escrow_startinfo(outgoing_dir_server, runtime_dir_server);
1942
1943        let runner = new_elf_runner_for_test();
1944        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1945            Arc::new(SecurityPolicy::default()),
1946            Moniker::root(),
1947        ));
1948        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1949
1950        runner.start(start_info, server_controller).await;
1951
1952        let mut event_stream = controller.take_event_stream();
1953
1954        expect_diagnostics_event(&mut event_stream).await;
1955
1956        match event_stream.try_next().await {
1957            Ok(Some(fcrunner::ComponentControllerEvent::OnEscrow {
1958                payload: fcrunner::ComponentControllerOnEscrowRequest { outgoing_dir, .. },
1959            })) => {
1960                let outgoing_dir_server = outgoing_dir.unwrap();
1961
1962                assert_eq!(
1963                    outgoing_dir_client.as_handle_ref().basic_info().unwrap().koid,
1964                    outgoing_dir_server.as_handle_ref().basic_info().unwrap().related_koid
1965                );
1966            }
1967            other => panic!("unexpected event result: {:?}", other),
1968        }
1969
1970        expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1971        expect_channel_closed(&mut event_stream).await;
1972    }
1973
1974    fn exit_with_code_startinfo(exit_code: i64) -> fcrunner::ComponentStartInfo {
1975        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1976        let ns = vec![pkg_dir_namespace_entry()];
1977
1978        fcrunner::ComponentStartInfo {
1979            resolved_url: Some(
1980                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code.cm".to_string(),
1981            ),
1982            program: Some(fdata::Dictionary {
1983                entries: Some(vec![
1984                    fdata::DictionaryEntry {
1985                        key: "args".to_string(),
1986                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![format!(
1987                            "{}",
1988                            exit_code
1989                        )]))),
1990                    },
1991                    fdata::DictionaryEntry {
1992                        key: "binary".to_string(),
1993                        value: Some(Box::new(fdata::DictionaryValue::Str(
1994                            "bin/exit_with_code".to_string(),
1995                        ))),
1996                    },
1997                ]),
1998                ..Default::default()
1999            }),
2000            ns: Some(ns),
2001            outgoing_dir: None,
2002            runtime_dir: Some(runtime_dir_server),
2003            component_instance: Some(zx::Event::create()),
2004            ..Default::default()
2005        }
2006    }
2007
2008    fn exit_with_code_startinfo_from_env(exit_code: Option<i64>) -> fcrunner::ComponentStartInfo {
2009        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
2010        let ns = vec![pkg_dir_namespace_entry()];
2011        let environ = match exit_code {
2012            Some(code) => vec![format!("EXIT_CODE={}", code)],
2013            None => vec![],
2014        };
2015        fcrunner::ComponentStartInfo {
2016            resolved_url: Some(
2017                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code-from_env.cm"
2018                    .to_string(),
2019            ),
2020            program: Some(fdata::Dictionary {
2021                entries: Some(vec![
2022                    fdata::DictionaryEntry {
2023                        key: "environ".to_string(),
2024                        value: Some(Box::new(fdata::DictionaryValue::StrVec(environ))),
2025                    },
2026                    fdata::DictionaryEntry {
2027                        key: "binary".to_string(),
2028                        value: Some(Box::new(fdata::DictionaryValue::Str(
2029                            "bin/exit_with_code_from_env".to_string(),
2030                        ))),
2031                    },
2032                ]),
2033                ..Default::default()
2034            }),
2035            ns: Some(ns),
2036            outgoing_dir: None,
2037            runtime_dir: Some(runtime_dir_server),
2038            component_instance: Some(zx::Event::create()),
2039            ..Default::default()
2040        }
2041    }
2042
2043    #[test_case(exit_with_code_startinfo(0) ; "args")]
2044    #[test_case(exit_with_code_startinfo_from_env(Some(0)) ; "env")]
2045    #[fuchsia::test]
2046    async fn test_return_code_success(start_info: fcrunner::ComponentStartInfo) {
2047        let runner = new_elf_runner_for_test();
2048        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
2049            Arc::new(SecurityPolicy::default()),
2050            Moniker::root(),
2051        ));
2052        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
2053        runner.start(start_info, server_controller).await;
2054
2055        let mut event_stream = controller.take_event_stream();
2056        expect_diagnostics_event(&mut event_stream).await;
2057        expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
2058        expect_channel_closed(&mut event_stream).await;
2059    }
2060
2061    #[test_case(exit_with_code_startinfo(123), vec![] ; "args")]
2062    #[test_case(exit_with_code_startinfo_from_env(Some(123)), vec![] ; "component_env")]
2063    #[test_case(exit_with_code_startinfo_from_env(Some(123)), vec!["EXIT_CODE=2"] ; "additional_env_shadowed")]
2064    #[test_case(exit_with_code_startinfo_from_env(None), vec!["EXIT_CODE=123"] ; "additional_env")]
2065    #[fuchsia::test]
2066    async fn test_return_code_failure(
2067        start_info: fcrunner::ComponentStartInfo,
2068        additional_environ: Vec<&str>,
2069    ) {
2070        let runner = Arc::new(ElfRunner::new(
2071            job_default().duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2072            Box::new(process_launcher::BuiltInConnector {}),
2073            Some(new_utc_clock_for_tests()),
2074            CrashRecords::new(),
2075            additional_environ.iter().map(|s| s.to_string()).collect(),
2076        ));
2077        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
2078            Arc::new(SecurityPolicy::default()),
2079            Moniker::root(),
2080        ));
2081        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
2082        runner.start(start_info, server_controller).await;
2083
2084        let mut event_stream = controller.take_event_stream();
2085        expect_diagnostics_event(&mut event_stream).await;
2086        let s = zx::Status::from_raw(
2087            i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
2088        );
2089        expect_on_stop(&mut event_stream, s, Some(123)).await;
2090        expect_channel_closed(&mut event_stream).await;
2091    }
2092
2093    #[fuchsia::test]
2094    fn test_is_acceptable_exit_code() {
2095        // Test sshd with its acceptable code
2096        assert!(is_acceptable_exit_code(
2097            &Moniker::from_str("core/sshd-host/shell:sshd-1").expect("valid moniker"),
2098            255
2099        ));
2100
2101        // Test sshd with a non-acceptable code
2102        assert!(!is_acceptable_exit_code(
2103            &Moniker::from_str("core/sshd-host/shell:sshd-1").expect("valid moniker"),
2104            1
2105        ));
2106
2107        // Test a URL that doesn't match
2108        assert!(!is_acceptable_exit_code(
2109            &Moniker::from_str("not_core/ssh-host/shell:sshd-1").expect("valid moniker"),
2110            255
2111        ));
2112
2113        // Test an unknown component with a code that happens to be acceptable for another
2114        assert!(!is_acceptable_exit_code(
2115            &Moniker::from_str("foo/debug").expect("valid moniker"),
2116            255
2117        ));
2118    }
2119
2120    #[fuchsia::test]
2121    fn test_merge_environ() {
2122        assert_eq!(merge_environ(&vec![], &vec![]), Vec::<String>::new());
2123
2124        assert_eq!(
2125            merge_environ(&vec!["A".to_string(), "B=".to_string(), "C=c".to_string()], &vec![]),
2126            vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]
2127        );
2128        assert_eq!(
2129            merge_environ(
2130                &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2131                &vec!["A".to_string(), "B".to_string(), "C".to_string()]
2132            ),
2133            vec!["A".to_string(), "B".to_string(), "C".to_string()]
2134        );
2135        assert_eq!(
2136            merge_environ(
2137                &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2138                &vec!["A=".to_string(), "B=".to_string(), "C=".to_string()]
2139            ),
2140            vec!["A=".to_string(), "B=".to_string(), "C=".to_string()]
2141        );
2142        assert_eq!(
2143            merge_environ(
2144                &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2145                &vec!["A=aa".to_string(), "B=bb".to_string(), "C=cc".to_string()]
2146            ),
2147            vec!["A=aa".to_string(), "B=bb".to_string(), "C=cc".to_string()]
2148        );
2149        assert_eq!(
2150            merge_environ(&vec![], &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]),
2151            vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]
2152        );
2153    }
2154}