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