1mod component;
6mod component_set;
7mod config;
8mod crash_handler;
9pub mod crash_info;
10mod error;
11mod memory;
12pub mod process_launcher;
13mod runtime_dir;
14mod stdout;
15pub mod vdso_vmo;
16
17use self::component::{ElfComponent, ElfComponentInfo};
18use self::config::ElfProgramConfig;
19use self::error::{JobError, StartComponentError, StartInfoError};
20use self::runtime_dir::RuntimeDirBuilder;
21use self::stdout::bind_streams_to_syslog;
22use crate::component_set::ComponentSet;
23use crate::crash_info::CrashRecords;
24use crate::memory::reporter::MemoryReporter;
25use crate::vdso_vmo::get_next_vdso_vmo;
26use ::routing::policy::ScopedPolicyChecker;
27use chrono::{NaiveDateTime, TimeZone as _, Utc};
28use fidl::endpoints::ServerEnd;
29use fidl_fuchsia_component_runner::{
30 ComponentDiagnostics, ComponentTasks, Task as DiagnosticsTask,
31};
32use fidl_fuchsia_process_lifecycle::LifecycleMarker;
33use fuchsia_async::{self as fasync, TimeoutExt};
34use fuchsia_runtime::{duplicate_utc_clock_handle, job_default, HandleInfo, HandleType, UtcClock};
35use futures::channel::oneshot;
36use futures::TryStreamExt;
37use log::warn;
38use moniker::Moniker;
39use runner::component::StopInfo;
40use runner::StartInfo;
41use std::path::Path;
42use std::sync::Arc;
43use zx::{self as zx, AsHandleRef, HandleBased};
44use {
45 fidl_fuchsia_component as fcomp, fidl_fuchsia_component_runner as fcrunner,
46 fidl_fuchsia_io as fio, fidl_fuchsia_memory_attribution as fattribution,
47 fidl_fuchsia_process as fproc,
48};
49
50const MAX_WAIT_BREAK_ON_START: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(300);
54
55const TIMER_SLACK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_micros(50);
61
62const DUPLICATE_CLOCK_RIGHTS: zx::Rights = zx::Rights::from_bits_truncate(
67 zx::Rights::READ.bits()
68 | zx::Rights::WAIT.bits()
69 | zx::Rights::DUPLICATE.bits()
70 | zx::Rights::TRANSFER.bits(),
71);
72
73pub struct ElfRunner {
76 job: zx::Job,
79
80 launcher_connector: process_launcher::Connector,
81
82 utc_clock: Option<Arc<UtcClock>>,
88
89 crash_records: CrashRecords,
90
91 components: Arc<ComponentSet>,
93
94 memory_reporter: MemoryReporter,
96}
97
98pub enum Job {
100 Single(zx::Job),
101 Multiple { parent: zx::Job, child: zx::Job },
102}
103
104impl Job {
105 fn top(&self) -> &zx::Job {
106 match self {
107 Job::Single(job) => job,
108 Job::Multiple { parent, child: _ } => parent,
109 }
110 }
111
112 fn proc(&self) -> &zx::Job {
113 match self {
114 Job::Single(job) => job,
115 Job::Multiple { parent: _, child } => child,
116 }
117 }
118}
119
120impl ElfRunner {
121 pub fn new(
122 job: zx::Job,
123 launcher_connector: process_launcher::Connector,
124 utc_clock: Option<Arc<UtcClock>>,
125 crash_records: CrashRecords,
126 ) -> ElfRunner {
127 let components = ComponentSet::new();
128 let memory_reporter = MemoryReporter::new(components.clone());
129 ElfRunner { job, launcher_connector, utc_clock, crash_records, components, memory_reporter }
130 }
131
132 async fn duplicate_utc_clock(&self) -> Result<UtcClock, zx::Status> {
136 if let Some(utc_clock) = &self.utc_clock {
137 utc_clock.duplicate_handle(DUPLICATE_CLOCK_RIGHTS)
138 } else {
139 duplicate_utc_clock_handle(DUPLICATE_CLOCK_RIGHTS)
140 }
141 }
142
143 fn create_job(&self, program_config: &ElfProgramConfig) -> Result<Job, JobError> {
145 let job = self.job.create_child_job().map_err(JobError::CreateChild)?;
146
147 job.set_policy(zx::JobPolicy::TimerSlack(
153 TIMER_SLACK_DURATION,
154 zx::JobDefaultTimerMode::Late,
155 ))
156 .map_err(JobError::SetPolicy)?;
157
158 if !program_config.create_raw_processes {
164 job.set_policy(zx::JobPolicy::Basic(
165 zx::JobPolicyOption::Absolute,
166 vec![(zx::JobCondition::NewProcess, zx::JobAction::Deny)],
167 ))
168 .map_err(JobError::SetPolicy)?;
169 }
170
171 if !program_config.ambient_mark_vmo_exec {
174 job.set_policy(zx::JobPolicy::Basic(
175 zx::JobPolicyOption::Absolute,
176 vec![(zx::JobCondition::AmbientMarkVmoExec, zx::JobAction::Deny)],
177 ))
178 .map_err(JobError::SetPolicy)?;
179 }
180
181 Ok(if program_config.job_with_available_exception_channel {
182 let child = job.create_child_job().map_err(JobError::CreateChild)?;
188 Job::Multiple { parent: job, child }
189 } else {
190 Job::Single(job)
191 })
192 }
193
194 fn create_handle_infos(
195 outgoing_dir: Option<zx::Channel>,
196 lifecycle_server: Option<zx::Channel>,
197 utc_clock: UtcClock,
198 next_vdso: Option<zx::Vmo>,
199 config_vmo: Option<zx::Vmo>,
200 ) -> Vec<fproc::HandleInfo> {
201 let mut handle_infos = vec![];
202
203 if let Some(outgoing_dir) = outgoing_dir {
204 handle_infos.push(fproc::HandleInfo {
205 handle: outgoing_dir.into_handle(),
206 id: HandleInfo::new(HandleType::DirectoryRequest, 0).as_raw(),
207 });
208 }
209
210 if let Some(lifecycle_chan) = lifecycle_server {
211 handle_infos.push(fproc::HandleInfo {
212 handle: lifecycle_chan.into_handle(),
213 id: HandleInfo::new(HandleType::Lifecycle, 0).as_raw(),
214 })
215 };
216
217 handle_infos.push(fproc::HandleInfo {
218 handle: utc_clock.into_handle(),
219 id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
220 });
221
222 if let Some(next_vdso) = next_vdso {
223 handle_infos.push(fproc::HandleInfo {
224 handle: next_vdso.into_handle(),
225 id: HandleInfo::new(HandleType::VdsoVmo, 0).as_raw(),
226 });
227 }
228
229 if let Some(config_vmo) = config_vmo {
230 handle_infos.push(fproc::HandleInfo {
231 handle: config_vmo.into_handle(),
232 id: HandleInfo::new(HandleType::ComponentConfigVmo, 0).as_raw(),
233 });
234 }
235
236 handle_infos
237 }
238
239 pub async fn start_component(
240 &self,
241 start_info: fcrunner::ComponentStartInfo,
242 checker: &ScopedPolicyChecker,
243 ) -> Result<ElfComponent, StartComponentError> {
244 let start_info: StartInfo =
245 start_info.try_into().map_err(StartInfoError::StartInfoError)?;
246
247 let resolved_url = start_info.resolved_url.clone();
248
249 let program_config = ElfProgramConfig::parse_and_check(&start_info.program, &checker)
252 .map_err(|err| {
253 StartComponentError::StartInfoError(StartInfoError::ProgramError(err))
254 })?;
255
256 let main_process_critical = program_config.main_process_critical;
257 let res: Result<ElfComponent, StartComponentError> =
258 self.start_component_helper(start_info, checker.scope.clone(), program_config).await;
259 match res {
260 Err(e) if main_process_critical => {
261 panic!(
262 "failed to launch component with a critical process ({:?}): {:?}",
263 &resolved_url, e
264 )
265 }
266 x => x,
267 }
268 }
269
270 async fn start_component_helper(
271 &self,
272 mut start_info: StartInfo,
273 moniker: Moniker,
274 program_config: ElfProgramConfig,
275 ) -> Result<ElfComponent, StartComponentError> {
276 let resolved_url = &start_info.resolved_url;
277
278 let boot_clock = zx::Clock::<zx::MonotonicTimeline, zx::BootTimeline>::create(
280 zx::ClockOpts::CONTINUOUS,
281 None,
282 )
283 .map_err(StartComponentError::BootClockCreateFailed)?;
284
285 let launcher = self
287 .launcher_connector
288 .connect()
289 .map_err(|err| StartComponentError::ProcessLauncherConnectError(err.into()))?;
290
291 let job = self.create_job(&program_config)?;
293
294 crash_handler::run_exceptions_server(
295 job.top(),
296 moniker.clone(),
297 resolved_url.clone(),
298 self.crash_records.clone(),
299 )
300 .map_err(StartComponentError::ExceptionRegistrationFailed)?;
301
302 let ns = namespace::Namespace::try_from(start_info.namespace)
304 .map_err(StartComponentError::NamespaceError)?;
305
306 let config_vmo =
307 start_info.encoded_config.take().map(runner::get_config_vmo).transpose()?;
308
309 let next_vdso = program_config.use_next_vdso.then(get_next_vdso_vmo).transpose()?;
310
311 let (lifecycle_client, lifecycle_server) = if program_config.notify_lifecycle_stop {
312 let (client, server) = fidl::endpoints::create_proxy::<LifecycleMarker>();
314 (Some(client), Some(server.into_channel()))
315 } else {
316 (None, None)
317 };
318
319 let utc_handle = start_info
321 .numbered_handles
322 .iter()
323 .position(|handles| handles.id == HandleInfo::new(HandleType::ClockUtc, 0).as_raw())
324 .map(|position| start_info.numbered_handles.swap_remove(position).handle);
325
326 let utc_clock = if let Some(handle) = utc_handle {
327 zx::Clock::from(handle)
328 } else {
329 self.duplicate_utc_clock()
330 .await
331 .map_err(StartComponentError::UtcClockDuplicateFailed)?
332 };
333
334 let utc_clock_dup = utc_clock
337 .duplicate_handle(zx::Rights::SAME_RIGHTS)
338 .map_err(StartComponentError::UtcClockDuplicateFailed)?;
339
340 let runtime_dir_server_end = start_info
342 .runtime_dir
343 .ok_or(StartComponentError::StartInfoError(StartInfoError::MissingRuntimeDir))?;
344 let job_koid =
345 job.proc().get_koid().map_err(StartComponentError::JobGetKoidFailed)?.raw_koid();
346
347 let runtime_dir = RuntimeDirBuilder::new(runtime_dir_server_end)
348 .args(program_config.args.clone())
349 .job_id(job_koid)
350 .serve();
351
352 let outgoing_directory = if program_config.memory_attribution {
355 let Some(outgoing_dir) = start_info.outgoing_dir else {
356 return Err(StartComponentError::StartInfoError(
357 StartInfoError::MissingOutgoingDir,
358 ));
359 };
360 let (outgoing_dir_client, outgoing_dir_server) = fidl::endpoints::create_endpoints();
361 start_info.outgoing_dir = Some(outgoing_dir_server);
362 fdio::open_at(
363 outgoing_dir_client.channel(),
364 ".",
365 fio::Flags::PROTOCOL_DIRECTORY,
366 outgoing_dir.into_channel(),
367 )
368 .unwrap();
369 Some(outgoing_dir_client)
370 } else {
371 None
372 };
373
374 let mut handle_infos = ElfRunner::create_handle_infos(
376 start_info.outgoing_dir.map(|dir| dir.into_channel()),
377 lifecycle_server,
378 utc_clock,
379 next_vdso,
380 config_vmo,
381 );
382
383 let (stdout_and_stderr_tasks, stdout_and_stderr_handles) =
385 bind_streams_to_syslog(&ns, program_config.stdout_sink, program_config.stderr_sink);
386 handle_infos.extend(stdout_and_stderr_handles);
387
388 handle_infos.extend(start_info.numbered_handles);
390
391 if let Some(escrowed_dictionary) = start_info.escrowed_dictionary {
393 handle_infos.push(fproc::HandleInfo {
394 handle: escrowed_dictionary.token.into_handle().into(),
395 id: HandleInfo::new(HandleType::EscrowedDictionary, 0).as_raw(),
396 });
397 }
398
399 let proc_job_dup = job
401 .proc()
402 .duplicate_handle(zx::Rights::SAME_RIGHTS)
403 .map_err(StartComponentError::JobDuplicateFailed)?;
404
405 let name = Path::new(resolved_url)
406 .file_name()
407 .and_then(|filename| filename.to_str())
408 .ok_or_else(|| {
409 StartComponentError::StartInfoError(StartInfoError::BadResolvedUrl(
410 resolved_url.clone(),
411 ))
412 })?;
413
414 let launch_info =
415 runner::component::configure_launcher(runner::component::LauncherConfigArgs {
416 bin_path: &program_config.binary,
417 name,
418 options: program_config.process_options(),
419 args: Some(program_config.args.clone()),
420 ns,
421 job: Some(proc_job_dup),
422 handle_infos: Some(handle_infos),
423 name_infos: None,
424 environs: program_config.environ.clone(),
425 launcher: &launcher,
426 loader_proxy_chan: None,
427 executable_vmo: None,
428 })
429 .await?;
430
431 if let Some(break_on_start) = start_info.break_on_start {
433 fasync::OnSignals::new(&break_on_start, zx::Signals::OBJECT_PEER_CLOSED)
434 .on_timeout(MAX_WAIT_BREAK_ON_START, || Err(zx::Status::TIMED_OUT))
435 .await
436 .err()
437 .map(|error| warn!(moniker:%, error:%; "Failed to wait break_on_start"));
438 }
439
440 let (status, process) = launcher
442 .launch(launch_info)
443 .await
444 .map_err(StartComponentError::ProcessLauncherFidlError)?;
445 zx::Status::ok(status).map_err(StartComponentError::CreateProcessFailed)?;
446 let process = process.unwrap(); if program_config.main_process_critical {
448 job_default()
449 .set_critical(zx::JobCriticalOptions::RETCODE_NONZERO, &process)
450 .map_err(StartComponentError::ProcessMarkCriticalFailed)
451 .expect("failed to set process as critical");
452 }
453
454 let pid = process.get_koid().map_err(StartComponentError::ProcessGetKoidFailed)?.raw_koid();
455
456 runtime_dir.add_process_id(pid);
458
459 fuchsia_trace::instant!(
460 c"component:start",
461 c"elf",
462 fuchsia_trace::Scope::Thread,
463 "moniker" => format!("{}", moniker).as_str(),
464 "url" => resolved_url.as_str(),
465 "pid" => pid
466 );
467
468 let process_start_mono_ns =
470 process.info().map_err(StartComponentError::ProcessInfoFailed)?.start_time;
471 runtime_dir.add_process_start_time(process_start_mono_ns);
472
473 let utc_clock_started = fasync::OnSignals::new(&utc_clock_dup, zx::Signals::CLOCK_STARTED)
475 .on_timeout(zx::MonotonicInstant::after(zx::MonotonicDuration::default()), || {
476 Err(zx::Status::TIMED_OUT)
477 })
478 .await
479 .is_ok();
480
481 let mono_to_clock_transformation =
484 boot_clock.get_details().map(|details| details.reference_to_synthetic).ok();
485 let boot_to_utc_transformation = utc_clock_started
486 .then(|| utc_clock_dup.get_details().map(|details| details.reference_to_synthetic).ok())
487 .flatten();
488
489 if let Some(clock_transformation) = boot_to_utc_transformation {
490 let process_start_instant_mono =
502 zx::MonotonicInstant::from_nanos(process_start_mono_ns);
503 let maybe_time_utc = mono_to_clock_transformation
504 .map(|t| t.apply(process_start_instant_mono))
505 .map(|time_boot| clock_transformation.apply(time_boot));
506
507 if let Some(utc_timestamp) = maybe_time_utc {
508 let utc_time_ns = utc_timestamp.into_nanos();
509 let seconds = (utc_time_ns / 1_000_000_000) as i64;
510 let nanos = (utc_time_ns % 1_000_000_000) as u32;
511 let dt = Utc
512 .from_utc_datetime(&NaiveDateTime::from_timestamp_opt(seconds, nanos).unwrap());
513
514 runtime_dir.add_process_start_time_utc_estimate(dt.to_string())
517 }
518 };
519
520 Ok(ElfComponent::new(
521 runtime_dir,
522 moniker,
523 job,
524 process,
525 lifecycle_client,
526 program_config.main_process_critical,
527 stdout_and_stderr_tasks,
528 resolved_url.clone(),
529 outgoing_directory,
530 program_config,
531 start_info.component_instance.ok_or(StartComponentError::StartInfoError(
532 StartInfoError::MissingComponentInstanceToken,
533 ))?,
534 ))
535 }
536
537 pub fn get_scoped_runner(
538 self: Arc<Self>,
539 checker: ScopedPolicyChecker,
540 ) -> Arc<ScopedElfRunner> {
541 Arc::new(ScopedElfRunner { runner: self, checker })
542 }
543
544 pub fn serve_memory_reporter(&self, stream: fattribution::ProviderRequestStream) {
545 self.memory_reporter.serve(stream);
546 }
547}
548
549pub struct ScopedElfRunner {
550 runner: Arc<ElfRunner>,
551 checker: ScopedPolicyChecker,
552}
553
554impl ScopedElfRunner {
555 pub fn serve(&self, mut stream: fcrunner::ComponentRunnerRequestStream) {
556 let runner = self.runner.clone();
557 let checker = self.checker.clone();
558 fasync::Task::spawn(async move {
559 while let Ok(Some(request)) = stream.try_next().await {
560 match request {
561 fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
562 start(&runner, checker.clone(), start_info, controller).await;
563 }
564 fcrunner::ComponentRunnerRequest::_UnknownMethod { ordinal, .. } => {
565 warn!(ordinal:%; "Unknown ComponentRunner request");
566 }
567 }
568 }
569 })
570 .detach();
571 }
572
573 pub async fn start(
574 &self,
575 start_info: fcrunner::ComponentStartInfo,
576 server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
577 ) {
578 start(&self.runner, self.checker.clone(), start_info, server_end).await
579 }
580}
581
582async fn start(
584 runner: &ElfRunner,
585 checker: ScopedPolicyChecker,
586 start_info: fcrunner::ComponentStartInfo,
587 server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
588) {
589 let resolved_url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
590
591 let elf_component = match runner.start_component(start_info, &checker).await {
592 Ok(elf_component) => elf_component,
593 Err(err) => {
594 runner::component::report_start_error(
595 err.as_zx_status(),
596 format!("{}", err),
597 &resolved_url,
598 server_end,
599 );
600 return;
601 }
602 };
603
604 let (termination_tx, termination_rx) = oneshot::channel::<StopInfo>();
605 let termination_fn = Box::pin(async move {
608 termination_rx
609 .await
610 .unwrap_or_else(|_| {
611 warn!("epitaph oneshot channel closed unexpectedly");
612 StopInfo::from_error(fcomp::Error::Internal, None)
613 })
614 .into()
615 });
616
617 let Some(proc_copy) = elf_component.copy_process() else {
618 runner::component::report_start_error(
619 zx::Status::from_raw(
620 i32::try_from(fcomp::Error::InstanceCannotStart.into_primitive()).unwrap(),
621 ),
622 "Component unexpectedly had no process".to_string(),
623 &resolved_url,
624 server_end,
625 );
626 return;
627 };
628
629 let component_diagnostics = elf_component
630 .info()
631 .copy_job_for_diagnostics()
632 .map(|job| ComponentDiagnostics {
633 tasks: Some(ComponentTasks {
634 component_task: Some(DiagnosticsTask::Job(job.into())),
635 ..Default::default()
636 }),
637 ..Default::default()
638 })
639 .map_err(|error| {
640 warn!(error:%; "Failed to copy job for diagnostics");
641 ()
642 })
643 .ok();
644
645 let (server_stream, control) = server_end.into_stream_and_control_handle();
646
647 fasync::Task::spawn({
649 let resolved_url = resolved_url.clone();
650 async move {
651 fasync::OnSignals::new(&proc_copy.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
652 .await
653 .map(|_: fidl::Signals| ()) .unwrap_or_else(|error| warn!(error:%; "error creating signal handler"));
655 let stop_info = match proc_copy.info() {
660 Ok(zx::ProcessInfo { return_code, .. }) => {
661 match return_code {
662 0 => StopInfo::from_ok(Some(return_code)),
663 zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL => StopInfo::from_error(
667 fcomp::Error::InstanceDied.into(),
668 Some(return_code),
669 ),
670 _ => {
671 warn!(url:% = resolved_url, return_code:%;
672 "process terminated with abnormal return code");
673 StopInfo::from_error(fcomp::Error::InstanceDied, Some(return_code))
674 }
675 }
676 }
677 Err(error) => {
678 warn!(error:%; "Unable to query process info");
679 StopInfo::from_error(fcomp::Error::Internal, None)
680 }
681 };
682 termination_tx.send(stop_info).unwrap_or_else(|_| warn!("error sending done signal"));
683 }
684 })
685 .detach();
686
687 let mut elf_component = elf_component;
688 runner.components.clone().add(&mut elf_component);
689
690 fasync::Task::spawn(async move {
696 if let Some(component_diagnostics) = component_diagnostics {
697 control.send_on_publish_diagnostics(component_diagnostics).unwrap_or_else(
698 |error| warn!(url:% = resolved_url, error:%; "sending diagnostics failed"),
699 );
700 }
701 runner::component::Controller::new(elf_component, server_stream, control)
702 .serve(termination_fn)
703 .await;
704 })
705 .detach();
706}
707
708#[cfg(test)]
709mod tests {
710 use super::runtime_dir::RuntimeDirectory;
711 use super::*;
712 use anyhow::{Context, Error};
713 use assert_matches::assert_matches;
714 use cm_config::{AllowlistEntryBuilder, JobPolicyAllowlists, SecurityPolicy};
715 use fidl::endpoints::{
716 create_endpoints, create_proxy, ClientEnd, DiscoverableProtocolMarker, Proxy,
717 };
718 use fidl_connector::Connect;
719 use fidl_fuchsia_component_runner::Task as DiagnosticsTask;
720 use fidl_fuchsia_logger::{LogSinkMarker, LogSinkRequest, LogSinkRequestStream};
721 use fidl_fuchsia_process_lifecycle::LifecycleProxy;
722 use fidl_test_util::spawn_stream_handler;
723 use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
724 use futures::channel::mpsc;
725 use futures::lock::Mutex;
726 use futures::{join, StreamExt};
727 use runner::component::Controllable;
728 use std::task::Poll;
729 use zx::{self as zx, Task};
730 use {
731 fidl_fuchsia_component as fcomp, fidl_fuchsia_component_runner as fcrunner,
732 fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, fuchsia_async as fasync,
733 };
734
735 pub enum MockServiceRequest {
736 LogSink(LogSinkRequestStream),
737 }
738
739 pub type MockServiceFs<'a> = ServiceFs<ServiceObjLocal<'a, MockServiceRequest>>;
740
741 pub fn create_fs_with_mock_logsink(
744 ) -> Result<(MockServiceFs<'static>, Vec<fcrunner::ComponentNamespaceEntry>), Error> {
745 let (dir_client, dir_server) = create_endpoints::<fio::DirectoryMarker>();
746
747 let mut dir = ServiceFs::new_local();
748 dir.add_fidl_service_at(LogSinkMarker::PROTOCOL_NAME, MockServiceRequest::LogSink);
749 dir.serve_connection(dir_server).context("Failed to add serving channel.")?;
750
751 let namespace = vec![fcrunner::ComponentNamespaceEntry {
752 path: Some("/svc".to_string()),
753 directory: Some(dir_client),
754 ..Default::default()
755 }];
756
757 Ok((dir, namespace))
758 }
759
760 pub fn new_elf_runner_for_test() -> Arc<ElfRunner> {
761 Arc::new(ElfRunner::new(
762 job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
763 Box::new(process_launcher::BuiltInConnector {}),
764 None,
765 CrashRecords::new(),
766 ))
767 }
768
769 fn namespace_entry(path: &str, flags: fio::Flags) -> fcrunner::ComponentNamespaceEntry {
770 let ns_path = path.to_string();
772 let ns_dir = fuchsia_fs::directory::open_in_namespace(path, flags).unwrap();
773 let client_end = ClientEnd::new(
775 ns_dir.into_channel().expect("could not convert proxy to channel").into_zx_channel(),
776 );
777 fcrunner::ComponentNamespaceEntry {
778 path: Some(ns_path),
779 directory: Some(client_end),
780 ..Default::default()
781 }
782 }
783
784 fn pkg_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
785 namespace_entry("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
786 }
787
788 fn svc_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
789 namespace_entry("/svc", fio::PERM_READABLE)
790 }
791
792 fn hello_world_startinfo(
793 runtime_dir: ServerEnd<fio::DirectoryMarker>,
794 ) -> fcrunner::ComponentStartInfo {
795 let ns = vec![pkg_dir_namespace_entry()];
796
797 fcrunner::ComponentStartInfo {
798 resolved_url: Some(
799 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/hello-world-rust.cm".to_string(),
800 ),
801 program: Some(fdata::Dictionary {
802 entries: Some(vec![
803 fdata::DictionaryEntry {
804 key: "args".to_string(),
805 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
806 "foo".to_string(),
807 "bar".to_string(),
808 ]))),
809 },
810 fdata::DictionaryEntry {
811 key: "binary".to_string(),
812 value: Some(Box::new(fdata::DictionaryValue::Str(
813 "bin/hello_world_rust".to_string(),
814 ))),
815 },
816 ]),
817 ..Default::default()
818 }),
819 ns: Some(ns),
820 outgoing_dir: None,
821 runtime_dir: Some(runtime_dir),
822 component_instance: Some(zx::Event::create()),
823 ..Default::default()
824 }
825 }
826
827 fn invalid_binary_startinfo(
829 runtime_dir: ServerEnd<fio::DirectoryMarker>,
830 ) -> fcrunner::ComponentStartInfo {
831 let ns = vec![pkg_dir_namespace_entry()];
832
833 fcrunner::ComponentStartInfo {
834 resolved_url: Some(
835 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/does-not-exist.cm".to_string(),
836 ),
837 program: Some(fdata::Dictionary {
838 entries: Some(vec![fdata::DictionaryEntry {
839 key: "binary".to_string(),
840 value: Some(Box::new(fdata::DictionaryValue::Str(
841 "bin/does_not_exist".to_string(),
842 ))),
843 }]),
844 ..Default::default()
845 }),
846 ns: Some(ns),
847 outgoing_dir: None,
848 runtime_dir: Some(runtime_dir),
849 component_instance: Some(zx::Event::create()),
850 ..Default::default()
851 }
852 }
853
854 pub fn lifecycle_startinfo(
858 runtime_dir: ServerEnd<fio::DirectoryMarker>,
859 ) -> fcrunner::ComponentStartInfo {
860 let ns = vec![pkg_dir_namespace_entry()];
861
862 fcrunner::ComponentStartInfo {
863 resolved_url: Some(
864 "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm".to_string(),
865 ),
866 program: Some(fdata::Dictionary {
867 entries: Some(vec![
868 fdata::DictionaryEntry {
869 key: "args".to_string(),
870 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
871 "foo".to_string(),
872 "bar".to_string(),
873 ]))),
874 },
875 fdata::DictionaryEntry {
876 key: "binary".to_string(),
877 value: Some(Box::new(fdata::DictionaryValue::Str(
878 "bin/lifecycle_placeholder".to_string(),
879 ))),
880 },
881 fdata::DictionaryEntry {
882 key: "lifecycle.stop_event".to_string(),
883 value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
884 },
885 ]),
886 ..Default::default()
887 }),
888 ns: Some(ns),
889 outgoing_dir: None,
890 runtime_dir: Some(runtime_dir),
891 component_instance: Some(zx::Event::create()),
892 ..Default::default()
893 }
894 }
895
896 fn create_child_process(job: &zx::Job, name: &str) -> zx::Process {
897 let (process, _vmar) = job
898 .create_child_process(zx::ProcessOptions::empty(), name.as_bytes())
899 .expect("could not create process");
900 process
901 }
902
903 fn make_default_elf_component(
904 lifecycle_client: Option<LifecycleProxy>,
905 critical: bool,
906 ) -> (scoped_task::Scoped<zx::Job>, ElfComponent) {
907 let job = scoped_task::create_child_job().expect("failed to make child job");
908 let process = create_child_process(&job, "test_process");
909 let job_copy =
910 job.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("job handle duplication failed");
911 let component = ElfComponent::new(
912 RuntimeDirectory::empty(),
913 Moniker::default(),
914 Job::Single(job_copy),
915 process,
916 lifecycle_client,
917 critical,
918 Vec::new(),
919 "".to_string(),
920 None,
921 Default::default(),
922 zx::Event::create(),
923 );
924 (job, component)
925 }
926
927 async fn read_file<'a>(root_proxy: &'a fio::DirectoryProxy, path: &'a str) -> String {
930 let file_proxy =
931 fuchsia_fs::directory::open_file_async(&root_proxy, path, fuchsia_fs::PERM_READABLE)
932 .expect("Failed to open file.");
933 let res = fuchsia_fs::file::read_to_string(&file_proxy).await;
934 res.expect("Unable to read file.")
935 }
936
937 #[fuchsia::test]
938 async fn test_runtime_dir_entries() -> Result<(), Error> {
939 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
940 let start_info = lifecycle_startinfo(runtime_dir_server);
941
942 let runner = new_elf_runner_for_test();
943 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
944 Arc::new(SecurityPolicy::default()),
945 Moniker::root(),
946 ));
947 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
948
949 runner.start(start_info, server_controller).await;
950
951 assert_eq!("foo", read_file(&runtime_dir, "args/0").await);
953 assert_eq!("bar", read_file(&runtime_dir, "args/1").await);
954
955 let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
960 let process_start_time =
961 read_file(&runtime_dir, "elf/process_start_time").await.parse::<i64>()?;
962 let process_start_time_utc_estimate =
963 read_file(&runtime_dir, "elf/process_start_time_utc_estimate").await;
964 let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>()?;
965 assert!(process_id > 0);
966 assert!(process_start_time > 0);
967 assert!(process_start_time_utc_estimate.contains("UTC"));
968 assert!(job_id > 0);
969 assert_ne!(process_id, job_id);
970
971 controller.stop().expect("Stop request failed");
972 controller.on_closed().await.expect("failed waiting for channel to close");
975 Ok(())
976 }
977
978 #[fuchsia::test]
979 async fn test_kill_component() -> Result<(), Error> {
980 let (job, mut component) = make_default_elf_component(None, false);
981
982 let job_info = job.info()?;
983 assert!(!job_info.exited);
984
985 component.kill().await;
986
987 let h = job.as_handle_ref();
988 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
989 .await
990 .expect("failed waiting for termination signal");
991
992 let job_info = job.info()?;
993 assert!(job_info.exited);
994 Ok(())
995 }
996
997 #[fuchsia::test]
998 fn test_stop_critical_component() -> Result<(), Error> {
999 let mut exec = fasync::TestExecutor::new();
1000 let (lifecycle_client, _lifecycle_server) = create_proxy::<LifecycleMarker>();
1004 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1005 let process = component.copy_process().unwrap();
1006 let job_info = job.info()?;
1007 assert!(!job_info.exited);
1008
1009 let mut completes_when_stopped = component.stop();
1013
1014 match exec.run_until_stalled(&mut completes_when_stopped) {
1017 Poll::Ready(_) => {
1018 panic!("runner should still be waiting for lifecycle channel to stop");
1019 }
1020 _ => {}
1021 }
1022 assert_eq!(process.kill(), Ok(()));
1023
1024 exec.run_singlethreaded(&mut completes_when_stopped);
1025
1026 let h = job.as_handle_ref();
1028 let termination_fut = async move {
1029 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1030 .await
1031 .expect("failed waiting for termination signal");
1032 };
1033 exec.run_singlethreaded(termination_fut);
1034
1035 let job_info = job.info()?;
1036 assert!(job_info.exited);
1037 Ok(())
1038 }
1039
1040 #[fuchsia::test]
1041 fn test_stop_noncritical_component() -> Result<(), Error> {
1042 let mut exec = fasync::TestExecutor::new();
1043 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1047 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1048
1049 let job_info = job.info()?;
1050 assert!(!job_info.exited);
1051
1052 let mut completes_when_stopped = component.stop();
1056
1057 match exec.run_until_stalled(&mut completes_when_stopped) {
1060 Poll::Ready(_) => {
1061 panic!("runner should still be waiting for lifecycle channel to stop");
1062 }
1063 _ => {}
1064 }
1065 drop(lifecycle_server);
1066
1067 match exec.run_until_stalled(&mut completes_when_stopped) {
1068 Poll::Ready(_) => {}
1069 _ => {
1070 panic!("runner future should have completed, lifecycle channel is closed.");
1071 }
1072 }
1073 let h = job.as_handle_ref();
1075 let termination_fut = async move {
1076 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1077 .await
1078 .expect("failed waiting for termination signal");
1079 };
1080 exec.run_singlethreaded(termination_fut);
1081
1082 let job_info = job.info()?;
1083 assert!(job_info.exited);
1084 Ok(())
1085 }
1086
1087 #[fuchsia::test]
1090 async fn test_stop_component_without_lifecycle() -> Result<(), Error> {
1091 let (job, mut component) = make_default_elf_component(None, false);
1092
1093 let job_info = job.info()?;
1094 assert!(!job_info.exited);
1095
1096 component.stop().await;
1097
1098 let h = job.as_handle_ref();
1099 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1100 .await
1101 .expect("failed waiting for termination signal");
1102
1103 let job_info = job.info()?;
1104 assert!(job_info.exited);
1105 Ok(())
1106 }
1107
1108 #[fuchsia::test]
1109 async fn test_stop_critical_component_with_closed_lifecycle() -> Result<(), Error> {
1110 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1111 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1112 let process = component.copy_process().unwrap();
1113 let job_info = job.info()?;
1114 assert!(!job_info.exited);
1115
1116 drop(lifecycle_server);
1118 process.kill()?;
1121 component.stop().await;
1122
1123 let h = job.as_handle_ref();
1124 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1125 .await
1126 .expect("failed waiting for termination signal");
1127
1128 let job_info = job.info()?;
1129 assert!(job_info.exited);
1130 Ok(())
1131 }
1132
1133 #[fuchsia::test]
1134 async fn test_stop_noncritical_component_with_closed_lifecycle() -> Result<(), Error> {
1135 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1136 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1137
1138 let job_info = job.info()?;
1139 assert!(!job_info.exited);
1140
1141 drop(lifecycle_server);
1143 component.stop().await;
1146
1147 let h = job.as_handle_ref();
1148 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1149 .await
1150 .expect("failed waiting for termination signal");
1151
1152 let job_info = job.info()?;
1153 assert!(job_info.exited);
1154 Ok(())
1155 }
1156
1157 #[fuchsia::test]
1159 async fn test_drop() -> Result<(), Error> {
1160 let (job, component) = make_default_elf_component(None, false);
1161
1162 let job_info = job.info()?;
1163 assert!(!job_info.exited);
1164
1165 drop(component);
1166
1167 let h = job.as_handle_ref();
1168 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1169 .await
1170 .expect("failed waiting for termination signal");
1171
1172 let job_info = job.info()?;
1173 assert!(job_info.exited);
1174 Ok(())
1175 }
1176
1177 fn with_mark_vmo_exec(
1178 mut start_info: fcrunner::ComponentStartInfo,
1179 ) -> fcrunner::ComponentStartInfo {
1180 start_info.program.as_mut().map(|dict| {
1181 dict.entries.as_mut().map(|entry| {
1182 entry.push(fdata::DictionaryEntry {
1183 key: "job_policy_ambient_mark_vmo_exec".to_string(),
1184 value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1185 });
1186 entry
1187 })
1188 });
1189 start_info
1190 }
1191
1192 fn with_main_process_critical(
1193 mut start_info: fcrunner::ComponentStartInfo,
1194 ) -> fcrunner::ComponentStartInfo {
1195 start_info.program.as_mut().map(|dict| {
1196 dict.entries.as_mut().map(|entry| {
1197 entry.push(fdata::DictionaryEntry {
1198 key: "main_process_critical".to_string(),
1199 value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1200 });
1201 entry
1202 })
1203 });
1204 start_info
1205 }
1206
1207 #[fuchsia::test]
1208 async fn vmex_security_policy_denied() -> Result<(), Error> {
1209 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1210 let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1211
1212 let runner = new_elf_runner_for_test();
1214 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1215 Arc::new(SecurityPolicy::default()),
1216 Moniker::root(),
1217 ));
1218 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1219
1220 runner.start(start_info, server_controller).await;
1223 assert_matches!(
1224 controller.take_event_stream().try_next().await,
1225 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1226 );
1227
1228 Ok(())
1229 }
1230
1231 #[fuchsia::test]
1232 async fn vmex_security_policy_allowed() -> Result<(), Error> {
1233 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1234 let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1235
1236 let policy = SecurityPolicy {
1237 job_policy: JobPolicyAllowlists {
1238 ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::new().exact("foo").build()],
1239 ..Default::default()
1240 },
1241 ..Default::default()
1242 };
1243 let runner = new_elf_runner_for_test();
1244 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1245 Arc::new(policy),
1246 Moniker::try_from(vec!["foo"]).unwrap(),
1247 ));
1248 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1249 runner.start(start_info, server_controller).await;
1250
1251 let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1253 assert!(process_id > 0);
1254 controller.kill().expect("kill failed");
1256
1257 let mut event_stream = controller.take_event_stream();
1261 expect_diagnostics_event(&mut event_stream).await;
1262
1263 let s = zx::Status::from_raw(
1264 i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1265 );
1266 expect_on_stop(&mut event_stream, s, Some(zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL)).await;
1267 expect_channel_closed(&mut event_stream).await;
1268 Ok(())
1269 }
1270
1271 #[fuchsia::test]
1272 async fn critical_security_policy_denied() -> Result<(), Error> {
1273 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1274 let start_info = with_main_process_critical(hello_world_startinfo(runtime_dir_server));
1275
1276 let runner = new_elf_runner_for_test();
1278 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1279 Arc::new(SecurityPolicy::default()),
1280 Moniker::root(),
1281 ));
1282 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1283
1284 runner.start(start_info, server_controller).await;
1287 assert_matches!(
1288 controller.take_event_stream().try_next().await,
1289 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1290 );
1291
1292 Ok(())
1293 }
1294
1295 #[fuchsia::test]
1296 #[should_panic]
1297 async fn fail_to_launch_critical_component() {
1298 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1299
1300 let start_info = with_main_process_critical(invalid_binary_startinfo(runtime_dir_server));
1303
1304 let policy = SecurityPolicy {
1307 job_policy: JobPolicyAllowlists {
1308 main_process_critical: vec![AllowlistEntryBuilder::new().build()],
1309 ..Default::default()
1310 },
1311 ..Default::default()
1312 };
1313 let runner = new_elf_runner_for_test();
1314 let runner =
1315 runner.get_scoped_runner(ScopedPolicyChecker::new(Arc::new(policy), Moniker::root()));
1316 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1317
1318 runner.start(start_info, server_controller).await;
1319
1320 controller
1321 .take_event_stream()
1322 .try_next()
1323 .await
1324 .map(|_: Option<fcrunner::ComponentControllerEvent>| ()) .unwrap_or_else(|error| warn!(error:%; "error reading from event stream"));
1326 }
1327
1328 fn hello_world_startinfo_forward_stdout_to_log(
1329 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1330 mut ns: Vec<fcrunner::ComponentNamespaceEntry>,
1331 ) -> fcrunner::ComponentStartInfo {
1332 ns.push(pkg_dir_namespace_entry());
1333
1334 fcrunner::ComponentStartInfo {
1335 resolved_url: Some(
1336 "fuchsia-pkg://fuchsia.com/hello-world-rust#meta/hello-world-rust.cm".to_string(),
1337 ),
1338 program: Some(fdata::Dictionary {
1339 entries: Some(vec![
1340 fdata::DictionaryEntry {
1341 key: "binary".to_string(),
1342 value: Some(Box::new(fdata::DictionaryValue::Str(
1343 "bin/hello_world_rust".to_string(),
1344 ))),
1345 },
1346 fdata::DictionaryEntry {
1347 key: "forward_stdout_to".to_string(),
1348 value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1349 },
1350 fdata::DictionaryEntry {
1351 key: "forward_stderr_to".to_string(),
1352 value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1353 },
1354 ]),
1355 ..Default::default()
1356 }),
1357 ns: Some(ns),
1358 outgoing_dir: None,
1359 runtime_dir: Some(runtime_dir),
1360 component_instance: Some(zx::Event::create()),
1361 ..Default::default()
1362 }
1363 }
1364
1365 #[fuchsia::test]
1369 async fn enable_stdout_and_stderr_logging() -> Result<(), Error> {
1370 let (dir, ns) = create_fs_with_mock_logsink()?;
1371
1372 let run_component_fut = async move {
1373 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1374 let start_info = hello_world_startinfo_forward_stdout_to_log(runtime_dir_server, ns);
1375
1376 let runner = new_elf_runner_for_test();
1377 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1378 Arc::new(SecurityPolicy::default()),
1379 Moniker::root(),
1380 ));
1381 let (client_controller, server_controller) =
1382 create_proxy::<fcrunner::ComponentControllerMarker>();
1383
1384 runner.start(start_info, server_controller).await;
1385 let mut event_stream = client_controller.take_event_stream();
1386 expect_diagnostics_event(&mut event_stream).await;
1387 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1388 expect_channel_closed(&mut event_stream).await;
1389 };
1390
1391 let connection_count = 1u8;
1393 let request_count = Arc::new(Mutex::new(0u8));
1394 let request_count_copy = request_count.clone();
1395
1396 let service_fs_listener_fut = async move {
1397 dir.for_each_concurrent(None, move |request: MockServiceRequest| match request {
1398 MockServiceRequest::LogSink(mut r) => {
1399 let req_count = request_count_copy.clone();
1400 async move {
1401 while let Some(Ok(req)) = r.next().await {
1402 match req {
1403 LogSinkRequest::Connect { .. } => {
1404 panic!("Unexpected call to `Connect`");
1405 }
1406 LogSinkRequest::ConnectStructured { .. } => {
1407 let mut count = req_count.lock().await;
1408 *count += 1;
1409 }
1410 LogSinkRequest::WaitForInterestChange { .. } => {
1411 }
1414 LogSinkRequest::_UnknownMethod { .. } => {
1415 panic!("Unexpected unknown method")
1416 }
1417 }
1418 }
1419 }
1420 }
1421 })
1422 .await;
1423 };
1424
1425 join!(run_component_fut, service_fs_listener_fut);
1426
1427 assert_eq!(*request_count.lock().await, connection_count);
1428 Ok(())
1429 }
1430
1431 #[fuchsia::test]
1432 async fn on_publish_diagnostics_contains_job_handle() -> Result<(), Error> {
1433 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1434 let start_info = lifecycle_startinfo(runtime_dir_server);
1435
1436 let runner = new_elf_runner_for_test();
1437 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1438 Arc::new(SecurityPolicy::default()),
1439 Moniker::root(),
1440 ));
1441 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1442
1443 runner.start(start_info, server_controller).await;
1444
1445 let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>().unwrap();
1446 let mut event_stream = controller.take_event_stream();
1447 match event_stream.try_next().await {
1448 Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1449 payload:
1450 ComponentDiagnostics {
1451 tasks:
1452 Some(ComponentTasks {
1453 component_task: Some(DiagnosticsTask::Job(job)), ..
1454 }),
1455 ..
1456 },
1457 })) => {
1458 assert_eq!(job_id, job.get_koid().unwrap().raw_koid());
1459 }
1460 other => panic!("unexpected event result: {:?}", other),
1461 }
1462
1463 controller.stop().expect("Stop request failed");
1464 controller.on_closed().await.expect("failed waiting for channel to close");
1467
1468 Ok(())
1469 }
1470
1471 async fn expect_diagnostics_event(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1472 let event = event_stream.try_next().await;
1473 assert_matches!(
1474 event,
1475 Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1476 payload: ComponentDiagnostics {
1477 tasks: Some(ComponentTasks {
1478 component_task: Some(DiagnosticsTask::Job(_)),
1479 ..
1480 }),
1481 ..
1482 },
1483 }))
1484 );
1485 }
1486
1487 async fn expect_on_stop(
1488 event_stream: &mut fcrunner::ComponentControllerEventStream,
1489 expected_status: zx::Status,
1490 expected_exit_code: Option<i64>,
1491 ) {
1492 let event = event_stream.try_next().await;
1493 assert_matches!(
1494 event,
1495 Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
1496 payload: fcrunner::ComponentStopInfo { termination_status: Some(s), exit_code, .. },
1497 }))
1498 if s == expected_status.into_raw() &&
1499 exit_code == expected_exit_code
1500 );
1501 }
1502
1503 async fn expect_channel_closed(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1504 let event = event_stream.try_next().await;
1505 match event {
1506 Ok(None) => {}
1507 other => panic!("Expected channel closed error, got {:?}", other),
1508 }
1509 }
1510
1511 struct LauncherConnectorForTest {
1514 sender: mpsc::UnboundedSender<LaunchPayload>,
1515 }
1516
1517 #[derive(Default)]
1520 struct LaunchPayload {
1521 launch_info: Option<fproc::LaunchInfo>,
1522 args: Vec<Vec<u8>>,
1523 environ: Vec<Vec<u8>>,
1524 name_info: Vec<fproc::NameInfo>,
1525 handles: Vec<fproc::HandleInfo>,
1526 options: u32,
1527 }
1528
1529 impl Connect for LauncherConnectorForTest {
1530 type Proxy = fproc::LauncherProxy;
1531
1532 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
1533 let sender = self.sender.clone();
1534 let payload = Arc::new(Mutex::new(LaunchPayload::default()));
1535
1536 Ok(spawn_stream_handler(move |launcher_request| {
1537 let sender = sender.clone();
1538 let payload = payload.clone();
1539 async move {
1540 let mut payload = payload.lock().await;
1541 match launcher_request {
1542 fproc::LauncherRequest::Launch { info, responder } => {
1543 let process = create_child_process(&info.job, "test_process");
1544 responder.send(zx::Status::OK.into_raw(), Some(process)).unwrap();
1545
1546 let mut payload =
1547 std::mem::replace(&mut *payload, LaunchPayload::default());
1548 payload.launch_info = Some(info);
1549 sender.unbounded_send(payload).unwrap();
1550 }
1551 fproc::LauncherRequest::CreateWithoutStarting { info: _, responder: _ } => {
1552 unimplemented!()
1553 }
1554 fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
1555 payload.args.append(&mut args);
1556 }
1557 fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
1558 payload.environ.append(&mut environ);
1559 }
1560 fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
1561 payload.name_info.append(&mut names);
1562 }
1563 fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
1564 payload.handles.append(&mut handles);
1565 }
1566 fproc::LauncherRequest::SetOptions { options, .. } => {
1567 payload.options = options;
1568 }
1569 }
1570 }
1571 }))
1572 }
1573 }
1574
1575 #[fuchsia::test]
1576 async fn process_created_with_utc_clock_from_numbered_handles() -> Result<(), Error> {
1577 let (payload_tx, mut payload_rx) = mpsc::unbounded();
1578
1579 let connector = LauncherConnectorForTest { sender: payload_tx };
1580 let runner = ElfRunner::new(
1581 job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
1582 Box::new(connector),
1583 None,
1584 CrashRecords::new(),
1585 );
1586 let policy_checker = ScopedPolicyChecker::new(
1587 Arc::new(SecurityPolicy::default()),
1588 Moniker::try_from(vec!["foo"]).unwrap(),
1589 );
1590
1591 let clock =
1593 zx::SyntheticClock::create(zx::ClockOpts::AUTO_START | zx::ClockOpts::MONOTONIC, None)?;
1594 let clock_koid = clock.get_koid().unwrap();
1595
1596 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1597 let mut start_info = hello_world_startinfo(runtime_dir_server);
1598 start_info.numbered_handles = Some(vec![fproc::HandleInfo {
1599 handle: clock.into_handle(),
1600 id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
1601 }]);
1602
1603 let _ = runner
1605 .start_component(start_info, &policy_checker)
1606 .await
1607 .context("failed to start component")?;
1608
1609 let payload = payload_rx.next().await.unwrap();
1610 assert!(payload
1611 .handles
1612 .iter()
1613 .any(|handle_info| handle_info.handle.get_koid().unwrap() == clock_koid));
1614
1615 Ok(())
1616 }
1617
1618 #[fuchsia::test]
1620 async fn test_enumerate_components() {
1621 use std::sync::atomic::{AtomicUsize, Ordering};
1622
1623 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1624 let start_info = lifecycle_startinfo(runtime_dir_server);
1625
1626 let runner = new_elf_runner_for_test();
1627 let components = runner.components.clone();
1628
1629 let count = Arc::new(AtomicUsize::new(0));
1631 components.clone().visit(|_, _| {
1632 count.fetch_add(1, Ordering::SeqCst);
1633 });
1634 assert_eq!(count.load(Ordering::SeqCst), 0);
1635
1636 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1638 Arc::new(SecurityPolicy::default()),
1639 Moniker::root(),
1640 ));
1641 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1642 runner.start(start_info, server_controller).await;
1643
1644 let count = Arc::new(AtomicUsize::new(0));
1646 components.clone().visit(|elf_component: &ElfComponentInfo, _| {
1647 assert_eq!(
1648 elf_component.get_url().as_str(),
1649 "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm"
1650 );
1651 count.fetch_add(1, Ordering::SeqCst);
1652 });
1653 assert_eq!(count.load(Ordering::SeqCst), 1);
1654
1655 controller.stop().unwrap();
1657 controller.on_closed().await.unwrap();
1658
1659 loop {
1662 let count = Arc::new(AtomicUsize::new(0));
1663 components.clone().visit(|_, _| {
1664 count.fetch_add(1, Ordering::SeqCst);
1665 });
1666 let count = count.load(Ordering::SeqCst);
1667 assert!(count == 0 || count == 1);
1668 if count == 0 {
1669 break;
1670 }
1671 yield_to_executor().await;
1674 }
1675 }
1676
1677 async fn yield_to_executor() {
1678 let mut done = false;
1679 futures::future::poll_fn(|cx| {
1680 if done {
1681 Poll::Ready(())
1682 } else {
1683 done = true;
1684 cx.waker().wake_by_ref();
1685 Poll::Pending
1686 }
1687 })
1688 .await;
1689 }
1690
1691 pub fn immediate_escrow_startinfo(
1694 outgoing_dir: ServerEnd<fio::DirectoryMarker>,
1695 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1696 ) -> fcrunner::ComponentStartInfo {
1697 let ns = vec![
1698 pkg_dir_namespace_entry(),
1699 svc_dir_namespace_entry(),
1701 ];
1702
1703 fcrunner::ComponentStartInfo {
1704 resolved_url: Some("#meta/immediate_escrow_component.cm".to_string()),
1705 program: Some(fdata::Dictionary {
1706 entries: Some(vec![
1707 fdata::DictionaryEntry {
1708 key: "binary".to_string(),
1709 value: Some(Box::new(fdata::DictionaryValue::Str(
1710 "bin/immediate_escrow".to_string(),
1711 ))),
1712 },
1713 fdata::DictionaryEntry {
1714 key: "lifecycle.stop_event".to_string(),
1715 value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1716 },
1717 ]),
1718 ..Default::default()
1719 }),
1720 ns: Some(ns),
1721 outgoing_dir: Some(outgoing_dir),
1722 runtime_dir: Some(runtime_dir),
1723 component_instance: Some(zx::Event::create()),
1724 ..Default::default()
1725 }
1726 }
1727
1728 #[fuchsia::test]
1731 async fn test_lifecycle_on_escrow() {
1732 let (outgoing_dir_client, outgoing_dir_server) =
1733 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1734 let (_, runtime_dir_server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1735 let start_info = immediate_escrow_startinfo(outgoing_dir_server, runtime_dir_server);
1736
1737 let runner = new_elf_runner_for_test();
1738 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1739 Arc::new(SecurityPolicy::default()),
1740 Moniker::root(),
1741 ));
1742 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1743
1744 runner.start(start_info, server_controller).await;
1745
1746 let mut event_stream = controller.take_event_stream();
1747
1748 expect_diagnostics_event(&mut event_stream).await;
1749
1750 match event_stream.try_next().await {
1751 Ok(Some(fcrunner::ComponentControllerEvent::OnEscrow {
1752 payload: fcrunner::ComponentControllerOnEscrowRequest { outgoing_dir, .. },
1753 })) => {
1754 let outgoing_dir_server = outgoing_dir.unwrap();
1755
1756 assert_eq!(
1757 outgoing_dir_client.basic_info().unwrap().koid,
1758 outgoing_dir_server.basic_info().unwrap().related_koid
1759 );
1760 }
1761 other => panic!("unexpected event result: {:?}", other),
1762 }
1763
1764 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1765 expect_channel_closed(&mut event_stream).await;
1766 }
1767
1768 fn exit_with_code_startinfo(exit_code: i64) -> fcrunner::ComponentStartInfo {
1769 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1770 let ns = vec![pkg_dir_namespace_entry()];
1771
1772 fcrunner::ComponentStartInfo {
1773 resolved_url: Some(
1774 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code.cm".to_string(),
1775 ),
1776 program: Some(fdata::Dictionary {
1777 entries: Some(vec![
1778 fdata::DictionaryEntry {
1779 key: "args".to_string(),
1780 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![format!(
1781 "{}",
1782 exit_code
1783 )]))),
1784 },
1785 fdata::DictionaryEntry {
1786 key: "binary".to_string(),
1787 value: Some(Box::new(fdata::DictionaryValue::Str(
1788 "bin/exit_with_code".to_string(),
1789 ))),
1790 },
1791 ]),
1792 ..Default::default()
1793 }),
1794 ns: Some(ns),
1795 outgoing_dir: None,
1796 runtime_dir: Some(runtime_dir_server),
1797 component_instance: Some(zx::Event::create()),
1798 ..Default::default()
1799 }
1800 }
1801
1802 #[fuchsia::test]
1803 async fn test_return_code_success() {
1804 let start_info = exit_with_code_startinfo(0);
1805
1806 let runner = new_elf_runner_for_test();
1807 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1808 Arc::new(SecurityPolicy::default()),
1809 Moniker::root(),
1810 ));
1811 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1812 runner.start(start_info, server_controller).await;
1813
1814 let mut event_stream = controller.take_event_stream();
1815 expect_diagnostics_event(&mut event_stream).await;
1816 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1817 expect_channel_closed(&mut event_stream).await;
1818 }
1819
1820 #[fuchsia::test]
1821 async fn test_return_code_failure() {
1822 let start_info = exit_with_code_startinfo(123);
1823
1824 let runner = new_elf_runner_for_test();
1825 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1826 Arc::new(SecurityPolicy::default()),
1827 Moniker::root(),
1828 ));
1829 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1830 runner.start(start_info, server_controller).await;
1831
1832 let mut event_stream = controller.take_event_stream();
1833 expect_diagnostics_event(&mut event_stream).await;
1834 let s = zx::Status::from_raw(
1835 i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1836 );
1837 expect_on_stop(&mut event_stream, s, Some(123)).await;
1838 expect_channel_closed(&mut event_stream).await;
1839 }
1840}