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_mono_ns =
507 process.info().map_err(StartComponentError::ProcessInfoFailed)?.start_time;
508 runtime_dir.add_process_start_time(process_start_mono_ns);
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 process_start_instant_mono =
539 zx::MonotonicInstant::from_nanos(process_start_mono_ns);
540 let maybe_time_utc = mono_to_clock_transformation
541 .map(|t| t.apply(process_start_instant_mono))
542 .map(|time_boot| clock_transformation.apply(time_boot));
543
544 if let Some(utc_timestamp) = maybe_time_utc {
545 let utc_time_ns = utc_timestamp.into_nanos();
546 let seconds = (utc_time_ns / 1_000_000_000) as i64;
547 let nanos = (utc_time_ns % 1_000_000_000) as u32;
548 let dt = DateTime::from_timestamp(seconds, nanos).unwrap();
549
550 runtime_dir.add_process_start_time_utc_estimate(dt.to_string())
553 }
554 };
555
556 Ok(ElfComponent::new(
557 runtime_dir,
558 moniker,
559 job,
560 process,
561 lifecycle_client,
562 program_config.main_process_critical,
563 local_scope,
564 resolved_url.clone(),
565 outgoing_directory,
566 program_config,
567 start_info.component_instance.ok_or(StartComponentError::StartInfoError(
568 StartInfoError::MissingComponentInstanceToken,
569 ))?,
570 ))
571 }
572
573 pub fn get_scoped_runner(
574 self: Arc<Self>,
575 checker: ScopedPolicyChecker,
576 ) -> Arc<ScopedElfRunner> {
577 Arc::new(ScopedElfRunner { runner: self, checker })
578 }
579
580 pub fn serve_memory_reporter(&self, stream: fattribution::ProviderRequestStream) {
581 self.memory_reporter.serve(stream);
582 }
583}
584
585pub struct ScopedElfRunner {
586 runner: Arc<ElfRunner>,
587 checker: ScopedPolicyChecker,
588}
589
590impl ScopedElfRunner {
591 pub fn serve(&self, mut stream: fcrunner::ComponentRunnerRequestStream) {
592 let runner = self.runner.clone();
593 let checker = self.checker.clone();
594 self.scope().spawn(async move {
595 while let Ok(Some(request)) = stream.try_next().await {
596 match request {
597 fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
598 start(&runner, checker.clone(), start_info, controller).await;
599 }
600 fcrunner::ComponentRunnerRequest::_UnknownMethod { ordinal, .. } => {
601 warn!(ordinal:%; "Unknown ComponentRunner request");
602 }
603 }
604 }
605 });
606 }
607
608 pub async fn start(
609 &self,
610 start_info: fcrunner::ComponentStartInfo,
611 server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
612 ) {
613 start(&self.runner, self.checker.clone(), start_info, server_end).await
614 }
615
616 pub(crate) fn scope(&self) -> &ExecutionScope {
617 &self.runner.scope
618 }
619}
620
621async fn start(
623 runner: &ElfRunner,
624 checker: ScopedPolicyChecker,
625 start_info: fcrunner::ComponentStartInfo,
626 server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
627) {
628 let resolved_url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
629
630 let elf_component = match runner.start_component(start_info, &checker).await {
631 Ok(elf_component) => elf_component,
632 Err(err) => {
633 runner::component::report_start_error(
634 err.as_zx_status(),
635 format!("{}", err),
636 &resolved_url,
637 server_end,
638 );
639 return;
640 }
641 };
642
643 let (termination_tx, termination_rx) = oneshot::channel::<StopInfo>();
644 let termination_fn = Box::pin(async move {
647 termination_rx
648 .await
649 .unwrap_or_else(|_| {
650 warn!("epitaph oneshot channel closed unexpectedly");
651 StopInfo::from_error(fcomp::Error::Internal, None)
652 })
653 .into()
654 });
655
656 let Some(proc_copy) = elf_component.copy_process() else {
657 runner::component::report_start_error(
658 zx::Status::from_raw(
659 i32::try_from(fcomp::Error::InstanceCannotStart.into_primitive()).unwrap(),
660 ),
661 "Component unexpectedly had no process".to_string(),
662 &resolved_url,
663 server_end,
664 );
665 return;
666 };
667
668 let component_diagnostics = elf_component
669 .info()
670 .copy_job_for_diagnostics()
671 .map(|job| ComponentDiagnostics {
672 tasks: Some(ComponentTasks {
673 component_task: Some(DiagnosticsTask::Job(job.into())),
674 ..Default::default()
675 }),
676 ..Default::default()
677 })
678 .map_err(|error| {
679 warn!(error:%; "Failed to copy job for diagnostics");
680 ()
681 })
682 .ok();
683
684 let (server_stream, control) = server_end.into_stream_and_control_handle();
685
686 runner.scope.spawn({
688 let resolved_url = resolved_url.clone();
689 async move {
690 fasync::OnSignals::new(&proc_copy.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
691 .await
692 .map(|_: fidl::Signals| ()) .unwrap_or_else(|error| warn!(error:%; "error creating signal handler"));
694 let stop_info = match proc_copy.info() {
699 Ok(zx::ProcessInfo { return_code, .. }) => {
700 match return_code {
701 0 => StopInfo::from_ok(Some(return_code)),
702 zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL => StopInfo::from_error(
706 fcomp::Error::InstanceDied.into(),
707 Some(return_code),
708 ),
709 _ => {
710 warn!(url:% = resolved_url, return_code:%;
711 "process terminated with abnormal return code");
712 StopInfo::from_error(fcomp::Error::InstanceDied, Some(return_code))
713 }
714 }
715 }
716 Err(error) => {
717 warn!(error:%; "Unable to query process info");
718 StopInfo::from_error(fcomp::Error::Internal, None)
719 }
720 };
721 termination_tx.send(stop_info).unwrap_or_else(|_| warn!("error sending done signal"));
722 }
723 });
724
725 let mut elf_component = elf_component;
726 runner.components.clone().add(&mut elf_component);
727
728 runner.scope.spawn(async move {
734 if let Some(component_diagnostics) = component_diagnostics {
735 control.send_on_publish_diagnostics(component_diagnostics).unwrap_or_else(
736 |error| warn!(url:% = resolved_url, error:%; "sending diagnostics failed"),
737 );
738 }
739 runner::component::Controller::new(elf_component, server_stream, control)
740 .serve(termination_fn)
741 .await;
742 });
743}
744
745#[cfg(test)]
746mod tests {
747 use super::runtime_dir::RuntimeDirectory;
748 use super::*;
749 use anyhow::{Context, Error};
750 use assert_matches::assert_matches;
751 use cm_config::{AllowlistEntryBuilder, JobPolicyAllowlists, SecurityPolicy};
752 use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, create_endpoints, create_proxy};
753 use fidl_connector::Connect;
754 use fidl_fuchsia_component_runner::Task as DiagnosticsTask;
755 use fidl_fuchsia_logger::{LogSinkMarker, LogSinkRequestStream};
756 use fidl_fuchsia_process_lifecycle::LifecycleProxy;
757 use fidl_test_util::spawn_stream_handler;
758 use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
759 use futures::channel::mpsc;
760 use futures::lock::Mutex;
761 use futures::{StreamExt, join};
762 use runner::component::Controllable;
763 use std::task::Poll;
764 use zx::{self as zx, Task};
765 use {
766 fidl_fuchsia_component as fcomp, fidl_fuchsia_component_runner as fcrunner,
767 fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, fuchsia_async as fasync,
768 };
769
770 pub enum MockServiceRequest {
771 LogSink(LogSinkRequestStream),
772 }
773
774 pub type MockServiceFs<'a> = ServiceFs<ServiceObjLocal<'a, MockServiceRequest>>;
775
776 pub fn create_fs_with_mock_logsink()
779 -> Result<(MockServiceFs<'static>, Vec<fcrunner::ComponentNamespaceEntry>), Error> {
780 let (dir_client, dir_server) = create_endpoints::<fio::DirectoryMarker>();
781
782 let mut dir = ServiceFs::new_local();
783 dir.add_fidl_service_at(LogSinkMarker::PROTOCOL_NAME, MockServiceRequest::LogSink);
784 dir.serve_connection(dir_server).context("Failed to add serving channel.")?;
785
786 let namespace = vec![fcrunner::ComponentNamespaceEntry {
787 path: Some("/svc".to_string()),
788 directory: Some(dir_client),
789 ..Default::default()
790 }];
791
792 Ok((dir, namespace))
793 }
794
795 pub fn new_utc_clock_for_tests() -> Arc<UtcClock> {
799 let reference_now = zx::BootInstant::get();
800 let system_utc_clock = duplicate_utc_clock_handle(zx::Rights::SAME_RIGHTS).unwrap();
801 let utc_now = system_utc_clock.read().unwrap();
802
803 let utc_clock_for_tests =
804 Arc::new(UtcClock::create(zx::ClockOpts::MAPPABLE, None).unwrap());
805 utc_clock_for_tests
807 .update(zx::ClockUpdate::builder().absolute_value(reference_now, utc_now.into()))
808 .unwrap();
809 utc_clock_for_tests
810 }
811
812 pub fn new_elf_runner_for_test() -> Arc<ElfRunner> {
813 Arc::new(ElfRunner::new(
814 job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
815 Box::new(process_launcher::BuiltInConnector {}),
816 Some(new_utc_clock_for_tests()),
817 CrashRecords::new(),
818 ))
819 }
820
821 fn namespace_entry(path: &str, flags: fio::Flags) -> fcrunner::ComponentNamespaceEntry {
822 let ns_path = path.to_string();
824 let ns_dir = fuchsia_fs::directory::open_in_namespace(path, flags).unwrap();
825 let client_end = ns_dir.into_client_end().unwrap();
826 fcrunner::ComponentNamespaceEntry {
827 path: Some(ns_path),
828 directory: Some(client_end),
829 ..Default::default()
830 }
831 }
832
833 fn pkg_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
834 namespace_entry("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
835 }
836
837 fn svc_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
838 namespace_entry("/svc", fio::PERM_READABLE)
839 }
840
841 fn hello_world_startinfo(
842 runtime_dir: ServerEnd<fio::DirectoryMarker>,
843 ) -> fcrunner::ComponentStartInfo {
844 let ns = vec![pkg_dir_namespace_entry()];
845
846 fcrunner::ComponentStartInfo {
847 resolved_url: Some(
848 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/hello-world-rust.cm".to_string(),
849 ),
850 program: Some(fdata::Dictionary {
851 entries: Some(vec![
852 fdata::DictionaryEntry {
853 key: "args".to_string(),
854 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
855 "foo".to_string(),
856 "bar".to_string(),
857 ]))),
858 },
859 fdata::DictionaryEntry {
860 key: "binary".to_string(),
861 value: Some(Box::new(fdata::DictionaryValue::Str(
862 "bin/hello_world_rust".to_string(),
863 ))),
864 },
865 ]),
866 ..Default::default()
867 }),
868 ns: Some(ns),
869 outgoing_dir: None,
870 runtime_dir: Some(runtime_dir),
871 component_instance: Some(zx::Event::create()),
872 ..Default::default()
873 }
874 }
875
876 fn invalid_binary_startinfo(
878 runtime_dir: ServerEnd<fio::DirectoryMarker>,
879 ) -> fcrunner::ComponentStartInfo {
880 let ns = vec![pkg_dir_namespace_entry()];
881
882 fcrunner::ComponentStartInfo {
883 resolved_url: Some(
884 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/does-not-exist.cm".to_string(),
885 ),
886 program: Some(fdata::Dictionary {
887 entries: Some(vec![fdata::DictionaryEntry {
888 key: "binary".to_string(),
889 value: Some(Box::new(fdata::DictionaryValue::Str(
890 "bin/does_not_exist".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 pub fn lifecycle_startinfo(
907 runtime_dir: ServerEnd<fio::DirectoryMarker>,
908 ) -> fcrunner::ComponentStartInfo {
909 let ns = vec![pkg_dir_namespace_entry()];
910
911 fcrunner::ComponentStartInfo {
912 resolved_url: Some(
913 "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm".to_string(),
914 ),
915 program: Some(fdata::Dictionary {
916 entries: Some(vec![
917 fdata::DictionaryEntry {
918 key: "args".to_string(),
919 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
920 "foo".to_string(),
921 "bar".to_string(),
922 ]))),
923 },
924 fdata::DictionaryEntry {
925 key: "binary".to_string(),
926 value: Some(Box::new(fdata::DictionaryValue::Str(
927 "bin/lifecycle_placeholder".to_string(),
928 ))),
929 },
930 fdata::DictionaryEntry {
931 key: "lifecycle.stop_event".to_string(),
932 value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
933 },
934 ]),
935 ..Default::default()
936 }),
937 ns: Some(ns),
938 outgoing_dir: None,
939 runtime_dir: Some(runtime_dir),
940 component_instance: Some(zx::Event::create()),
941 ..Default::default()
942 }
943 }
944
945 fn create_child_process(job: &zx::Job, name: &str) -> zx::Process {
946 let (process, _vmar) = job
947 .create_child_process(zx::ProcessOptions::empty(), name.as_bytes())
948 .expect("could not create process");
949 process
950 }
951
952 fn make_default_elf_component(
953 lifecycle_client: Option<LifecycleProxy>,
954 critical: bool,
955 ) -> (scoped_task::Scoped<zx::Job>, ElfComponent) {
956 let job = scoped_task::create_child_job().expect("failed to make child job");
957 let process = create_child_process(&job, "test_process");
958 let job_copy =
959 job.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("job handle duplication failed");
960 let component = ElfComponent::new(
961 RuntimeDirectory::empty(),
962 Moniker::default(),
963 Job::Single(job_copy),
964 process,
965 lifecycle_client,
966 critical,
967 ExecutionScope::new(),
968 "".to_string(),
969 None,
970 Default::default(),
971 zx::Event::create(),
972 );
973 (job, component)
974 }
975
976 async fn read_file<'a>(root_proxy: &'a fio::DirectoryProxy, path: &'a str) -> String {
979 let file_proxy =
980 fuchsia_fs::directory::open_file_async(&root_proxy, path, fuchsia_fs::PERM_READABLE)
981 .expect("Failed to open file.");
982 let res = fuchsia_fs::file::read_to_string(&file_proxy).await;
983 res.expect("Unable to read file.")
984 }
985
986 #[fuchsia::test]
987 async fn test_runtime_dir_entries() -> Result<(), Error> {
988 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
989 let start_info = lifecycle_startinfo(runtime_dir_server);
990
991 let runner = new_elf_runner_for_test();
992 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
993 Arc::new(SecurityPolicy::default()),
994 Moniker::root(),
995 ));
996 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
997
998 runner.start(start_info, server_controller).await;
999
1000 assert_eq!("foo", read_file(&runtime_dir, "args/0").await);
1002 assert_eq!("bar", read_file(&runtime_dir, "args/1").await);
1003
1004 let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1009 let process_start_time =
1010 read_file(&runtime_dir, "elf/process_start_time").await.parse::<i64>()?;
1011 let process_start_time_utc_estimate =
1012 read_file(&runtime_dir, "elf/process_start_time_utc_estimate").await;
1013 let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>()?;
1014 assert!(process_id > 0);
1015 assert!(process_start_time > 0);
1016 assert!(process_start_time_utc_estimate.contains("UTC"));
1017 assert!(job_id > 0);
1018 assert_ne!(process_id, job_id);
1019
1020 controller.stop().expect("Stop request failed");
1021 controller.on_closed().await.expect("failed waiting for channel to close");
1024 Ok(())
1025 }
1026
1027 #[fuchsia::test]
1028 async fn test_kill_component() -> Result<(), Error> {
1029 let (job, mut component) = make_default_elf_component(None, false);
1030
1031 let job_info = job.info()?;
1032 assert!(!job_info.exited);
1033
1034 component.kill().await;
1035
1036 let h = job.as_handle_ref();
1037 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1038 .await
1039 .expect("failed waiting for termination signal");
1040
1041 let job_info = job.info()?;
1042 assert!(job_info.exited);
1043 Ok(())
1044 }
1045
1046 #[fuchsia::test]
1047 fn test_stop_critical_component() -> Result<(), Error> {
1048 let mut exec = fasync::TestExecutor::new();
1049 let (lifecycle_client, _lifecycle_server) = create_proxy::<LifecycleMarker>();
1053 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1054 let process = component.copy_process().unwrap();
1055 let job_info = job.info()?;
1056 assert!(!job_info.exited);
1057
1058 let mut completes_when_stopped = component.stop();
1062
1063 match exec.run_until_stalled(&mut completes_when_stopped) {
1066 Poll::Ready(_) => {
1067 panic!("runner should still be waiting for lifecycle channel to stop");
1068 }
1069 _ => {}
1070 }
1071 assert_eq!(process.kill(), Ok(()));
1072
1073 exec.run_singlethreaded(&mut completes_when_stopped);
1074
1075 let h = job.as_handle_ref();
1077 let termination_fut = async move {
1078 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1079 .await
1080 .expect("failed waiting for termination signal");
1081 };
1082 exec.run_singlethreaded(termination_fut);
1083
1084 let job_info = job.info()?;
1085 assert!(job_info.exited);
1086 Ok(())
1087 }
1088
1089 #[fuchsia::test]
1090 fn test_stop_noncritical_component() -> Result<(), Error> {
1091 let mut exec = fasync::TestExecutor::new();
1092 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1096 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1097
1098 let job_info = job.info()?;
1099 assert!(!job_info.exited);
1100
1101 let mut completes_when_stopped = component.stop();
1105
1106 match exec.run_until_stalled(&mut completes_when_stopped) {
1109 Poll::Ready(_) => {
1110 panic!("runner should still be waiting for lifecycle channel to stop");
1111 }
1112 _ => {}
1113 }
1114 drop(lifecycle_server);
1115
1116 match exec.run_until_stalled(&mut completes_when_stopped) {
1117 Poll::Ready(_) => {}
1118 _ => {
1119 panic!("runner future should have completed, lifecycle channel is closed.");
1120 }
1121 }
1122 let h = job.as_handle_ref();
1124 let termination_fut = async move {
1125 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1126 .await
1127 .expect("failed waiting for termination signal");
1128 };
1129 exec.run_singlethreaded(termination_fut);
1130
1131 let job_info = job.info()?;
1132 assert!(job_info.exited);
1133 Ok(())
1134 }
1135
1136 #[fuchsia::test]
1139 async fn test_stop_component_without_lifecycle() -> Result<(), Error> {
1140 let (job, mut component) = make_default_elf_component(None, false);
1141
1142 let job_info = job.info()?;
1143 assert!(!job_info.exited);
1144
1145 component.stop().await;
1146
1147 let h = job.as_handle_ref();
1148 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1149 .await
1150 .expect("failed waiting for termination signal");
1151
1152 let job_info = job.info()?;
1153 assert!(job_info.exited);
1154 Ok(())
1155 }
1156
1157 #[fuchsia::test]
1158 async fn test_stop_critical_component_with_closed_lifecycle() -> Result<(), Error> {
1159 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1160 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1161 let process = component.copy_process().unwrap();
1162 let job_info = job.info()?;
1163 assert!(!job_info.exited);
1164
1165 drop(lifecycle_server);
1167 process.kill()?;
1170 component.stop().await;
1171
1172 let h = job.as_handle_ref();
1173 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1174 .await
1175 .expect("failed waiting for termination signal");
1176
1177 let job_info = job.info()?;
1178 assert!(job_info.exited);
1179 Ok(())
1180 }
1181
1182 #[fuchsia::test]
1183 async fn test_stop_noncritical_component_with_closed_lifecycle() -> Result<(), Error> {
1184 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1185 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1186
1187 let job_info = job.info()?;
1188 assert!(!job_info.exited);
1189
1190 drop(lifecycle_server);
1192 component.stop().await;
1195
1196 let h = job.as_handle_ref();
1197 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1198 .await
1199 .expect("failed waiting for termination signal");
1200
1201 let job_info = job.info()?;
1202 assert!(job_info.exited);
1203 Ok(())
1204 }
1205
1206 #[fuchsia::test]
1208 async fn test_drop() -> Result<(), Error> {
1209 let (job, component) = make_default_elf_component(None, false);
1210
1211 let job_info = job.info()?;
1212 assert!(!job_info.exited);
1213
1214 drop(component);
1215
1216 let h = job.as_handle_ref();
1217 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1218 .await
1219 .expect("failed waiting for termination signal");
1220
1221 let job_info = job.info()?;
1222 assert!(job_info.exited);
1223 Ok(())
1224 }
1225
1226 fn with_mark_vmo_exec(
1227 mut start_info: fcrunner::ComponentStartInfo,
1228 ) -> fcrunner::ComponentStartInfo {
1229 start_info.program.as_mut().map(|dict| {
1230 dict.entries.as_mut().map(|entry| {
1231 entry.push(fdata::DictionaryEntry {
1232 key: "job_policy_ambient_mark_vmo_exec".to_string(),
1233 value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1234 });
1235 entry
1236 })
1237 });
1238 start_info
1239 }
1240
1241 fn with_main_process_critical(
1242 mut start_info: fcrunner::ComponentStartInfo,
1243 ) -> fcrunner::ComponentStartInfo {
1244 start_info.program.as_mut().map(|dict| {
1245 dict.entries.as_mut().map(|entry| {
1246 entry.push(fdata::DictionaryEntry {
1247 key: "main_process_critical".to_string(),
1248 value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1249 });
1250 entry
1251 })
1252 });
1253 start_info
1254 }
1255
1256 #[fuchsia::test]
1257 async fn vmex_security_policy_denied() -> Result<(), Error> {
1258 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1259 let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1260
1261 let runner = new_elf_runner_for_test();
1263 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1264 Arc::new(SecurityPolicy::default()),
1265 Moniker::root(),
1266 ));
1267 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1268
1269 runner.start(start_info, server_controller).await;
1272 assert_matches!(
1273 controller.take_event_stream().try_next().await,
1274 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1275 );
1276
1277 Ok(())
1278 }
1279
1280 #[fuchsia::test]
1281 async fn vmex_security_policy_allowed() -> Result<(), Error> {
1282 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1283 let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1284
1285 let policy = SecurityPolicy {
1286 job_policy: JobPolicyAllowlists {
1287 ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::new().exact("foo").build()],
1288 ..Default::default()
1289 },
1290 ..Default::default()
1291 };
1292 let runner = new_elf_runner_for_test();
1293 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1294 Arc::new(policy),
1295 Moniker::try_from(["foo"]).unwrap(),
1296 ));
1297 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1298 runner.start(start_info, server_controller).await;
1299
1300 let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1302 assert!(process_id > 0);
1303 controller.kill().expect("kill failed");
1305
1306 let mut event_stream = controller.take_event_stream();
1310 expect_diagnostics_event(&mut event_stream).await;
1311
1312 let s = zx::Status::from_raw(
1313 i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1314 );
1315 expect_on_stop(&mut event_stream, s, Some(zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL)).await;
1316 expect_channel_closed(&mut event_stream).await;
1317 Ok(())
1318 }
1319
1320 #[fuchsia::test]
1321 async fn critical_security_policy_denied() -> Result<(), Error> {
1322 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1323 let start_info = with_main_process_critical(hello_world_startinfo(runtime_dir_server));
1324
1325 let runner = new_elf_runner_for_test();
1327 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1328 Arc::new(SecurityPolicy::default()),
1329 Moniker::root(),
1330 ));
1331 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1332
1333 runner.start(start_info, server_controller).await;
1336 assert_matches!(
1337 controller.take_event_stream().try_next().await,
1338 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1339 );
1340
1341 Ok(())
1342 }
1343
1344 #[fuchsia::test]
1345 #[should_panic]
1346 async fn fail_to_launch_critical_component() {
1347 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1348
1349 let start_info = with_main_process_critical(invalid_binary_startinfo(runtime_dir_server));
1352
1353 let policy = SecurityPolicy {
1356 job_policy: JobPolicyAllowlists {
1357 main_process_critical: vec![AllowlistEntryBuilder::new().build()],
1358 ..Default::default()
1359 },
1360 ..Default::default()
1361 };
1362 let runner = new_elf_runner_for_test();
1363 let runner =
1364 runner.get_scoped_runner(ScopedPolicyChecker::new(Arc::new(policy), Moniker::root()));
1365 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1366
1367 runner.start(start_info, server_controller).await;
1368
1369 controller
1370 .take_event_stream()
1371 .try_next()
1372 .await
1373 .map(|_: Option<fcrunner::ComponentControllerEvent>| ()) .unwrap_or_else(|error| warn!(error:%; "error reading from event stream"));
1375 }
1376
1377 fn hello_world_startinfo_forward_stdout_to_log(
1378 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1379 mut ns: Vec<fcrunner::ComponentNamespaceEntry>,
1380 ) -> fcrunner::ComponentStartInfo {
1381 ns.push(pkg_dir_namespace_entry());
1382
1383 fcrunner::ComponentStartInfo {
1384 resolved_url: Some(
1385 "fuchsia-pkg://fuchsia.com/hello-world-rust#meta/hello-world-rust.cm".to_string(),
1386 ),
1387 program: Some(fdata::Dictionary {
1388 entries: Some(vec![
1389 fdata::DictionaryEntry {
1390 key: "binary".to_string(),
1391 value: Some(Box::new(fdata::DictionaryValue::Str(
1392 "bin/hello_world_rust".to_string(),
1393 ))),
1394 },
1395 fdata::DictionaryEntry {
1396 key: "forward_stdout_to".to_string(),
1397 value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1398 },
1399 fdata::DictionaryEntry {
1400 key: "forward_stderr_to".to_string(),
1401 value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1402 },
1403 ]),
1404 ..Default::default()
1405 }),
1406 ns: Some(ns),
1407 outgoing_dir: None,
1408 runtime_dir: Some(runtime_dir),
1409 component_instance: Some(zx::Event::create()),
1410 ..Default::default()
1411 }
1412 }
1413
1414 #[fuchsia::test]
1418 async fn enable_stdout_and_stderr_logging() -> Result<(), Error> {
1419 let (mut dir, ns) = create_fs_with_mock_logsink()?;
1420
1421 let run_component_fut = async move {
1422 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1423 let start_info = hello_world_startinfo_forward_stdout_to_log(runtime_dir_server, ns);
1424
1425 let runner = new_elf_runner_for_test();
1426 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1427 Arc::new(SecurityPolicy::default()),
1428 Moniker::root(),
1429 ));
1430 let (client_controller, server_controller) =
1431 create_proxy::<fcrunner::ComponentControllerMarker>();
1432
1433 runner.start(start_info, server_controller).await;
1434 let mut event_stream = client_controller.take_event_stream();
1435 expect_diagnostics_event(&mut event_stream).await;
1436 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1437 expect_channel_closed(&mut event_stream).await;
1438 };
1439
1440 let service_fs_listener_fut = async {
1442 let mut requests = Vec::new();
1443 while let Some(MockServiceRequest::LogSink(r)) = dir.next().await {
1444 requests.push(r);
1447 }
1448 requests.len()
1449 };
1450
1451 let connection_count = join!(run_component_fut, service_fs_listener_fut).1;
1452
1453 assert_eq!(connection_count, 1);
1454 Ok(())
1455 }
1456
1457 #[fuchsia::test]
1458 async fn on_publish_diagnostics_contains_job_handle() -> Result<(), Error> {
1459 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1460 let start_info = lifecycle_startinfo(runtime_dir_server);
1461
1462 let runner = new_elf_runner_for_test();
1463 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1464 Arc::new(SecurityPolicy::default()),
1465 Moniker::root(),
1466 ));
1467 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1468
1469 runner.start(start_info, server_controller).await;
1470
1471 let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>().unwrap();
1472 let mut event_stream = controller.take_event_stream();
1473 match event_stream.try_next().await {
1474 Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1475 payload:
1476 ComponentDiagnostics {
1477 tasks:
1478 Some(ComponentTasks {
1479 component_task: Some(DiagnosticsTask::Job(job)), ..
1480 }),
1481 ..
1482 },
1483 })) => {
1484 assert_eq!(job_id, job.get_koid().unwrap().raw_koid());
1485 }
1486 other => panic!("unexpected event result: {:?}", other),
1487 }
1488
1489 controller.stop().expect("Stop request failed");
1490 controller.on_closed().await.expect("failed waiting for channel to close");
1493
1494 Ok(())
1495 }
1496
1497 async fn expect_diagnostics_event(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1498 let event = event_stream.try_next().await;
1499 assert_matches!(
1500 event,
1501 Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1502 payload: ComponentDiagnostics {
1503 tasks: Some(ComponentTasks {
1504 component_task: Some(DiagnosticsTask::Job(_)),
1505 ..
1506 }),
1507 ..
1508 },
1509 }))
1510 );
1511 }
1512
1513 async fn expect_on_stop(
1514 event_stream: &mut fcrunner::ComponentControllerEventStream,
1515 expected_status: zx::Status,
1516 expected_exit_code: Option<i64>,
1517 ) {
1518 let event = event_stream.try_next().await;
1519 assert_matches!(
1520 event,
1521 Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
1522 payload: fcrunner::ComponentStopInfo { termination_status: Some(s), exit_code, .. },
1523 }))
1524 if s == expected_status.into_raw() &&
1525 exit_code == expected_exit_code
1526 );
1527 }
1528
1529 async fn expect_channel_closed(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1530 let event = event_stream.try_next().await;
1531 match event {
1532 Ok(None) => {}
1533 other => panic!("Expected channel closed error, got {:?}", other),
1534 }
1535 }
1536
1537 struct LauncherConnectorForTest {
1540 sender: mpsc::UnboundedSender<LaunchPayload>,
1541 }
1542
1543 #[derive(Default)]
1546 struct LaunchPayload {
1547 launch_info: Option<fproc::LaunchInfo>,
1548 args: Vec<Vec<u8>>,
1549 environ: Vec<Vec<u8>>,
1550 name_info: Vec<fproc::NameInfo>,
1551 handles: Vec<fproc::HandleInfo>,
1552 options: u32,
1553 }
1554
1555 impl Connect for LauncherConnectorForTest {
1556 type Proxy = fproc::LauncherProxy;
1557
1558 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
1559 let sender = self.sender.clone();
1560 let payload = Arc::new(Mutex::new(LaunchPayload::default()));
1561
1562 Ok(spawn_stream_handler(move |launcher_request| {
1563 let sender = sender.clone();
1564 let payload = payload.clone();
1565 async move {
1566 let mut payload = payload.lock().await;
1567 match launcher_request {
1568 fproc::LauncherRequest::Launch { info, responder } => {
1569 let process = create_child_process(&info.job, "test_process");
1570 responder.send(zx::Status::OK.into_raw(), Some(process)).unwrap();
1571
1572 let mut payload =
1573 std::mem::replace(&mut *payload, LaunchPayload::default());
1574 payload.launch_info = Some(info);
1575 sender.unbounded_send(payload).unwrap();
1576 }
1577 fproc::LauncherRequest::CreateWithoutStarting { info: _, responder: _ } => {
1578 unimplemented!()
1579 }
1580 fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
1581 payload.args.append(&mut args);
1582 }
1583 fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
1584 payload.environ.append(&mut environ);
1585 }
1586 fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
1587 payload.name_info.append(&mut names);
1588 }
1589 fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
1590 payload.handles.append(&mut handles);
1591 }
1592 fproc::LauncherRequest::SetOptions { options, .. } => {
1593 payload.options = options;
1594 }
1595 }
1596 }
1597 }))
1598 }
1599 }
1600
1601 #[fuchsia::test]
1602 async fn process_created_with_utc_clock_from_numbered_handles() -> Result<(), Error> {
1603 let (payload_tx, mut payload_rx) = mpsc::unbounded();
1604
1605 let connector = LauncherConnectorForTest { sender: payload_tx };
1606 let runner = ElfRunner::new(
1607 job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
1608 Box::new(connector),
1609 Some(new_utc_clock_for_tests()),
1610 CrashRecords::new(),
1611 );
1612 let policy_checker = ScopedPolicyChecker::new(
1613 Arc::new(SecurityPolicy::default()),
1614 Moniker::try_from(["foo"]).unwrap(),
1615 );
1616
1617 let clock = zx::SyntheticClock::create(
1619 zx::ClockOpts::AUTO_START | zx::ClockOpts::MONOTONIC | zx::ClockOpts::MAPPABLE,
1620 None,
1621 )?;
1622 let clock_koid = clock.get_koid().unwrap();
1623
1624 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1625 let mut start_info = hello_world_startinfo(runtime_dir_server);
1626 start_info.numbered_handles = Some(vec![fproc::HandleInfo {
1627 handle: clock.into_handle(),
1628 id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
1629 }]);
1630
1631 let _ = runner
1633 .start_component(start_info, &policy_checker)
1634 .await
1635 .context("failed to start component")?;
1636
1637 let payload = payload_rx.next().await.unwrap();
1638 assert!(
1639 payload
1640 .handles
1641 .iter()
1642 .any(|handle_info| handle_info.handle.get_koid().unwrap() == clock_koid)
1643 );
1644
1645 Ok(())
1646 }
1647
1648 #[fuchsia::test]
1650 async fn test_enumerate_components() {
1651 use std::sync::atomic::{AtomicUsize, Ordering};
1652
1653 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1654 let start_info = lifecycle_startinfo(runtime_dir_server);
1655
1656 let runner = new_elf_runner_for_test();
1657 let components = runner.components.clone();
1658
1659 let count = Arc::new(AtomicUsize::new(0));
1661 components.clone().visit(|_, _| {
1662 count.fetch_add(1, Ordering::SeqCst);
1663 });
1664 assert_eq!(count.load(Ordering::SeqCst), 0);
1665
1666 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1668 Arc::new(SecurityPolicy::default()),
1669 Moniker::root(),
1670 ));
1671 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1672 runner.start(start_info, server_controller).await;
1673
1674 let count = Arc::new(AtomicUsize::new(0));
1676 components.clone().visit(|elf_component: &ElfComponentInfo, _| {
1677 assert_eq!(
1678 elf_component.get_url().as_str(),
1679 "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm"
1680 );
1681 count.fetch_add(1, Ordering::SeqCst);
1682 });
1683 assert_eq!(count.load(Ordering::SeqCst), 1);
1684
1685 controller.stop().unwrap();
1687 controller.on_closed().await.unwrap();
1688
1689 loop {
1692 let count = Arc::new(AtomicUsize::new(0));
1693 components.clone().visit(|_, _| {
1694 count.fetch_add(1, Ordering::SeqCst);
1695 });
1696 let count = count.load(Ordering::SeqCst);
1697 assert!(count == 0 || count == 1);
1698 if count == 0 {
1699 break;
1700 }
1701 yield_to_executor().await;
1704 }
1705 }
1706
1707 async fn yield_to_executor() {
1708 let mut done = false;
1709 futures::future::poll_fn(|cx| {
1710 if done {
1711 Poll::Ready(())
1712 } else {
1713 done = true;
1714 cx.waker().wake_by_ref();
1715 Poll::Pending
1716 }
1717 })
1718 .await;
1719 }
1720
1721 pub fn immediate_escrow_startinfo(
1724 outgoing_dir: ServerEnd<fio::DirectoryMarker>,
1725 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1726 ) -> fcrunner::ComponentStartInfo {
1727 let ns = vec![
1728 pkg_dir_namespace_entry(),
1729 svc_dir_namespace_entry(),
1731 ];
1732
1733 fcrunner::ComponentStartInfo {
1734 resolved_url: Some("#meta/immediate_escrow_component.cm".to_string()),
1735 program: Some(fdata::Dictionary {
1736 entries: Some(vec![
1737 fdata::DictionaryEntry {
1738 key: "binary".to_string(),
1739 value: Some(Box::new(fdata::DictionaryValue::Str(
1740 "bin/immediate_escrow".to_string(),
1741 ))),
1742 },
1743 fdata::DictionaryEntry {
1744 key: "lifecycle.stop_event".to_string(),
1745 value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1746 },
1747 ]),
1748 ..Default::default()
1749 }),
1750 ns: Some(ns),
1751 outgoing_dir: Some(outgoing_dir),
1752 runtime_dir: Some(runtime_dir),
1753 component_instance: Some(zx::Event::create()),
1754 ..Default::default()
1755 }
1756 }
1757
1758 #[fuchsia::test]
1761 async fn test_lifecycle_on_escrow() {
1762 let (outgoing_dir_client, outgoing_dir_server) =
1763 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1764 let (_, runtime_dir_server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1765 let start_info = immediate_escrow_startinfo(outgoing_dir_server, runtime_dir_server);
1766
1767 let runner = new_elf_runner_for_test();
1768 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1769 Arc::new(SecurityPolicy::default()),
1770 Moniker::root(),
1771 ));
1772 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1773
1774 runner.start(start_info, server_controller).await;
1775
1776 let mut event_stream = controller.take_event_stream();
1777
1778 expect_diagnostics_event(&mut event_stream).await;
1779
1780 match event_stream.try_next().await {
1781 Ok(Some(fcrunner::ComponentControllerEvent::OnEscrow {
1782 payload: fcrunner::ComponentControllerOnEscrowRequest { outgoing_dir, .. },
1783 })) => {
1784 let outgoing_dir_server = outgoing_dir.unwrap();
1785
1786 assert_eq!(
1787 outgoing_dir_client.basic_info().unwrap().koid,
1788 outgoing_dir_server.basic_info().unwrap().related_koid
1789 );
1790 }
1791 other => panic!("unexpected event result: {:?}", other),
1792 }
1793
1794 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1795 expect_channel_closed(&mut event_stream).await;
1796 }
1797
1798 fn exit_with_code_startinfo(exit_code: i64) -> fcrunner::ComponentStartInfo {
1799 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1800 let ns = vec![pkg_dir_namespace_entry()];
1801
1802 fcrunner::ComponentStartInfo {
1803 resolved_url: Some(
1804 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code.cm".to_string(),
1805 ),
1806 program: Some(fdata::Dictionary {
1807 entries: Some(vec![
1808 fdata::DictionaryEntry {
1809 key: "args".to_string(),
1810 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![format!(
1811 "{}",
1812 exit_code
1813 )]))),
1814 },
1815 fdata::DictionaryEntry {
1816 key: "binary".to_string(),
1817 value: Some(Box::new(fdata::DictionaryValue::Str(
1818 "bin/exit_with_code".to_string(),
1819 ))),
1820 },
1821 ]),
1822 ..Default::default()
1823 }),
1824 ns: Some(ns),
1825 outgoing_dir: None,
1826 runtime_dir: Some(runtime_dir_server),
1827 component_instance: Some(zx::Event::create()),
1828 ..Default::default()
1829 }
1830 }
1831
1832 #[fuchsia::test]
1833 async fn test_return_code_success() {
1834 let start_info = exit_with_code_startinfo(0);
1835
1836 let runner = new_elf_runner_for_test();
1837 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1838 Arc::new(SecurityPolicy::default()),
1839 Moniker::root(),
1840 ));
1841 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1842 runner.start(start_info, server_controller).await;
1843
1844 let mut event_stream = controller.take_event_stream();
1845 expect_diagnostics_event(&mut event_stream).await;
1846 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1847 expect_channel_closed(&mut event_stream).await;
1848 }
1849
1850 #[fuchsia::test]
1851 async fn test_return_code_failure() {
1852 let start_info = exit_with_code_startinfo(123);
1853
1854 let runner = new_elf_runner_for_test();
1855 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1856 Arc::new(SecurityPolicy::default()),
1857 Moniker::root(),
1858 ));
1859 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1860 runner.start(start_info, server_controller).await;
1861
1862 let mut event_stream = controller.take_event_stream();
1863 expect_diagnostics_event(&mut event_stream).await;
1864 let s = zx::Status::from_raw(
1865 i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1866 );
1867 expect_on_stop(&mut event_stream, s, Some(123)).await;
1868 expect_channel_closed(&mut event_stream).await;
1869 }
1870}