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