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