1mod component;
6mod component_set;
7pub mod config;
8mod crash_handler;
9pub mod crash_info;
10pub mod error;
11mod logger;
12mod memory;
13pub mod process_launcher;
14mod runtime_dir;
15pub mod 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::config::ElfProgramBadHandlesPolicy;
25use crate::crash_info::CrashRecords;
26use crate::memory::reporter::MemoryReporter;
27use crate::vdso_vmo::get_next_vdso_vmo;
28use ::routing::policy::ScopedPolicyChecker;
29use chrono::DateTime;
30use fidl::endpoints::{ClientEnd, ServerEnd};
31use fidl_fuchsia_component as fcomp;
32use fidl_fuchsia_component_runner as fcrunner;
33use fidl_fuchsia_component_runner::{
34 ComponentDiagnostics, ComponentTasks, Task as DiagnosticsTask,
35};
36use fidl_fuchsia_io as fio;
37use fidl_fuchsia_memory_attribution as fattribution;
38use fidl_fuchsia_process as fproc;
39use fidl_fuchsia_process_lifecycle::{LifecycleMarker, LifecycleProxy};
40use fuchsia_async::{self as fasync, TimeoutExt};
41use fuchsia_runtime::{HandleInfo, HandleType, UtcClock, duplicate_utc_clock_handle, job_default};
42use futures::TryStreamExt;
43use futures::channel::oneshot;
44use log::{trace, warn};
45use moniker::Moniker;
46use namespace::Namespace;
47use runner::StartInfo;
48use runner::component::StopInfo;
49use std::collections::{HashMap, HashSet};
50use std::mem;
51use std::path::Path;
52use std::sync::Arc;
53use vfs::execution_scope::ExecutionScope;
54use zx::HandleBased;
55
56const MAX_WAIT_BREAK_ON_START: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(300);
60
61const TIMER_SLACK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_micros(50);
67
68const DUPLICATE_CLOCK_RIGHTS: zx::Rights = zx::Rights::from_bits_truncate(
81 zx::Rights::READ.bits() | zx::Rights::WAIT.bits() | zx::Rights::DUPLICATE.bits() | zx::Rights::TRANSFER.bits() | zx::Rights::INSPECT.bits()
86 | zx::Rights::MAP.bits(),
91);
92
93static MONIKER_PREFIXES_TO_ACCEPTABLE_EXIT_CODES: std::sync::LazyLock<
109 HashMap<&'static str, Vec<i64>>,
110> = std::sync::LazyLock::new(|| {
111 let mut m = HashMap::new();
112 m.insert("core/sshd-host/shell:sshd-", vec![255]);
113 m
114});
115
116pub struct ElfRunner {
119 job: zx::Job,
122
123 launcher_connector: process_launcher::Connector,
124
125 utc_clock: Option<Arc<UtcClock>>,
131
132 crash_records: CrashRecords,
133
134 components: Arc<ComponentSet>,
136
137 memory_reporter: MemoryReporter,
139
140 scope: ExecutionScope,
142
143 additional_environ: Vec<String>,
146}
147
148pub enum Job {
150 Single(zx::Job),
151 Multiple { parent: zx::Job, child: zx::Job },
152}
153
154impl Job {
155 fn top(&self) -> &zx::Job {
156 match self {
157 Job::Single(job) => job,
158 Job::Multiple { parent, child: _ } => parent,
159 }
160 }
161
162 fn proc(&self) -> &zx::Job {
163 match self {
164 Job::Single(job) => job,
165 Job::Multiple { parent: _, child } => child,
166 }
167 }
168}
169
170#[derive(Debug)]
174pub struct ElfComponentLaunchInfo {
175 pub ns: Namespace,
176 pub handle_infos: Vec<fproc::HandleInfo>,
177 pub utc_clock: UtcClock,
178 pub lifecycle_client: Option<LifecycleProxy>,
179 pub outgoing_directory: Option<ClientEnd<fio::DirectoryMarker>>,
180 pub local_scope: ExecutionScope,
181}
182
183impl ElfComponentLaunchInfo {
184 pub fn new(
185 start_info: &mut StartInfo,
186 program_config: &ElfProgramConfig,
187 utc_clock: Option<&UtcClock>,
188 ) -> Result<Self, StartComponentError> {
189 let namespace = mem::replace(&mut start_info.namespace, Default::default());
191 let ns = namespace::Namespace::try_from(namespace)
192 .map_err(StartComponentError::NamespaceError)?;
193
194 let config_vmo =
195 start_info.encoded_config.take().map(runner::get_config_vmo).transpose()?;
196
197 let next_vdso = program_config.use_next_vdso.then(get_next_vdso_vmo).transpose()?;
198
199 let (lifecycle_client, lifecycle_server) = if program_config.notify_lifecycle_stop {
200 let (client, server) = fidl::endpoints::create_proxy::<LifecycleMarker>();
202 (Some(client), Some(server.into_channel()))
203 } else {
204 (None, None)
205 };
206
207 let utc_handle = start_info
209 .numbered_handles
210 .iter()
211 .position(|handles| handles.id == HandleInfo::new(HandleType::ClockUtc, 0).as_raw())
212 .map(|position| start_info.numbered_handles.swap_remove(position).handle);
213
214 let utc_clock = if let Some(handle) = utc_handle {
215 zx::Clock::from(handle)
216 } else {
217 Self::duplicate_utc_clock(utc_clock)
218 .map_err(StartComponentError::UtcClockDuplicateFailed)?
219 };
220
221 let utc_clock_dup = utc_clock
224 .duplicate_handle(zx::Rights::SAME_RIGHTS)
225 .map_err(StartComponentError::UtcClockDuplicateFailed)?;
226
227 let outgoing_directory = if program_config.memory_attribution {
230 let Some(outgoing_dir) = start_info.outgoing_dir.take() else {
231 return Err(StartComponentError::StartInfoError(
232 StartInfoError::MissingOutgoingDir,
233 ));
234 };
235 let (outgoing_dir_client, outgoing_dir_server) = fidl::endpoints::create_endpoints();
236 start_info.outgoing_dir = Some(outgoing_dir_server);
237 fdio::open_at(
238 outgoing_dir_client.channel(),
239 ".",
240 fio::Flags::PROTOCOL_DIRECTORY
241 | fio::PERM_READABLE
242 | fio::PERM_WRITABLE
243 | fio::PERM_EXECUTABLE,
244 outgoing_dir.into_channel(),
245 )
246 .unwrap();
247 Some(outgoing_dir_client)
248 } else {
249 None
250 };
251
252 let mut handle_infos = Self::create_handle_infos(
254 start_info.outgoing_dir.take().map(|dir| dir.into_channel()),
255 lifecycle_server,
256 utc_clock,
257 next_vdso,
258 config_vmo,
259 );
260
261 let (local_scope, stdout_and_stderr_handles) =
263 bind_streams_to_syslog(&ns, program_config.stdout_sink, program_config.stderr_sink);
264 handle_infos.extend(stdout_and_stderr_handles);
265
266 let numbered_handles = mem::replace(&mut start_info.numbered_handles, Default::default());
268 handle_infos.extend(numbered_handles);
269
270 if let Some(escrowed_dictionary) = start_info.escrowed_dictionary.take() {
272 handle_infos.push(fproc::HandleInfo {
273 handle: escrowed_dictionary.token.into_handle().into(),
274 id: HandleInfo::new(HandleType::EscrowedDictionary, 0).as_raw(),
275 });
276 } else if let Some(escrowed_dictionary_handle) =
277 start_info.escrowed_dictionary_handle.take()
278 {
279 handle_infos.push(fproc::HandleInfo {
280 handle: escrowed_dictionary_handle.into(),
281 id: HandleInfo::new(HandleType::EscrowedDictionary, 0).as_raw(),
282 });
283 }
284
285 Ok(Self {
286 ns,
287 handle_infos,
288 utc_clock: utc_clock_dup,
289 local_scope,
290 lifecycle_client,
291 outgoing_directory,
292 })
293 }
294
295 fn create_handle_infos(
296 outgoing_dir: Option<zx::Channel>,
297 lifecycle_server: Option<zx::Channel>,
298 utc_clock: UtcClock,
299 next_vdso: Option<zx::Vmo>,
300 config_vmo: Option<zx::Vmo>,
301 ) -> Vec<fproc::HandleInfo> {
302 let mut handle_infos = vec![];
303
304 if let Some(outgoing_dir) = outgoing_dir {
305 handle_infos.push(fproc::HandleInfo {
306 handle: outgoing_dir.into_handle(),
307 id: HandleInfo::new(HandleType::DirectoryRequest, 0).as_raw(),
308 });
309 }
310
311 if let Some(lifecycle_chan) = lifecycle_server {
312 handle_infos.push(fproc::HandleInfo {
313 handle: lifecycle_chan.into_handle(),
314 id: HandleInfo::new(HandleType::Lifecycle, 0).as_raw(),
315 })
316 };
317
318 handle_infos.push(fproc::HandleInfo {
319 handle: utc_clock.into_handle(),
320 id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
321 });
322
323 if let Some(next_vdso) = next_vdso {
324 handle_infos.push(fproc::HandleInfo {
325 handle: next_vdso.into_handle(),
326 id: HandleInfo::new(HandleType::VdsoVmo, 0).as_raw(),
327 });
328 }
329
330 if let Some(config_vmo) = config_vmo {
331 handle_infos.push(fproc::HandleInfo {
332 handle: config_vmo.into_handle(),
333 id: HandleInfo::new(HandleType::ComponentConfigVmo, 0).as_raw(),
334 });
335 }
336
337 handle_infos
338 }
339
340 fn duplicate_utc_clock(utc_clock: Option<&UtcClock>) -> Result<UtcClock, zx::Status> {
344 if let Some(utc_clock) = utc_clock {
345 utc_clock.duplicate_handle(DUPLICATE_CLOCK_RIGHTS)
346 } else {
347 duplicate_utc_clock_handle(DUPLICATE_CLOCK_RIGHTS)
348 }
349 }
350}
351
352fn merge_environ(left: &[String], right: &[String]) -> Vec<String> {
357 fn get_key(kv: &str) -> &str {
358 kv.split('=').next().unwrap_or(kv)
359 }
360 let right_keys: HashSet<&str> = right.iter().map(|kv| get_key(kv.as_str())).collect();
361 let environ: Vec<String> = left
362 .iter()
363 .filter(|&kv| !right_keys.contains(get_key(kv.as_str())))
364 .chain(right.iter())
365 .cloned()
366 .collect();
367 environ
368}
369
370impl ElfRunner {
371 pub fn new(
372 job: zx::Job,
373 launcher_connector: process_launcher::Connector,
374 utc_clock: Option<Arc<UtcClock>>,
375 crash_records: CrashRecords,
376 additional_environ: Vec<String>,
377 ) -> ElfRunner {
378 let scope = ExecutionScope::new();
379 let components = ComponentSet::new(scope.clone());
380 let memory_reporter = MemoryReporter::new(components.clone());
381 ElfRunner {
382 job,
383 launcher_connector,
384 utc_clock,
385 crash_records,
386 components,
387 memory_reporter,
388 scope,
389 additional_environ,
390 }
391 }
392
393 fn create_job(&self, program_config: &ElfProgramConfig) -> Result<Job, JobError> {
395 let job = self.job.create_child_job().map_err(JobError::CreateChild)?;
396
397 job.set_policy(zx::JobPolicy::TimerSlack(
403 TIMER_SLACK_DURATION,
404 zx::JobDefaultTimerMode::Late,
405 ))
406 .map_err(JobError::SetPolicy)?;
407
408 if !program_config.create_raw_processes {
414 job.set_policy(zx::JobPolicy::Basic(
415 zx::JobPolicyOption::Absolute,
416 vec![(zx::JobCondition::NewProcess, zx::JobAction::Deny)],
417 ))
418 .map_err(JobError::SetPolicy)?;
419 }
420
421 if !program_config.ambient_mark_vmo_exec {
424 job.set_policy(zx::JobPolicy::Basic(
425 zx::JobPolicyOption::Absolute,
426 vec![(zx::JobCondition::AmbientMarkVmoExec, zx::JobAction::Deny)],
427 ))
428 .map_err(JobError::SetPolicy)?;
429 }
430
431 if let Some(job_policy_bad_handles) = &program_config.job_policy_bad_handles {
432 let action = match job_policy_bad_handles {
433 ElfProgramBadHandlesPolicy::DenyException => zx::JobAction::DenyException,
434 ElfProgramBadHandlesPolicy::AllowException => zx::JobAction::AllowException,
435 };
436 job.set_policy(zx::JobPolicy::Basic(
437 zx::JobPolicyOption::Absolute,
438 vec![(zx::JobCondition::BadHandle, action)],
439 ))
440 .map_err(JobError::SetPolicy)?;
441 }
442
443 Ok(if program_config.job_with_available_exception_channel {
444 let child = job.create_child_job().map_err(JobError::CreateChild)?;
450 Job::Multiple { parent: job, child }
451 } else {
452 Job::Single(job)
453 })
454 }
455
456 pub async fn start_component(
457 &self,
458 start_info: fcrunner::ComponentStartInfo,
459 checker: &ScopedPolicyChecker,
460 ) -> Result<ElfComponent, StartComponentError> {
461 let start_info: StartInfo =
462 start_info.try_into().map_err(StartInfoError::StartInfoError)?;
463
464 let resolved_url = start_info.resolved_url.clone();
465
466 let program_config = ElfProgramConfig::parse_and_check(&start_info.program, Some(checker))
469 .map_err(|err| {
470 StartComponentError::StartInfoError(StartInfoError::ProgramError(err))
471 })?;
472
473 let main_process_critical = program_config.main_process_critical;
474 let res: Result<ElfComponent, StartComponentError> = self
475 .start_component_helper(start_info, Some(checker.scope.clone()), program_config)
476 .await;
477 match res {
478 Err(e) if main_process_critical => {
479 panic!(
480 "failed to launch component with a critical process ({:?}): {:?}",
481 &resolved_url, e
482 )
483 }
484 x => x,
485 }
486 }
487
488 async fn start_component_helper(
489 &self,
490 mut start_info: StartInfo,
491 moniker: Option<Moniker>,
492 program_config: ElfProgramConfig,
493 ) -> Result<ElfComponent, StartComponentError> {
494 let moniker = moniker.unwrap_or_else(|| Moniker::root());
495
496 let boot_clock = zx::Clock::<zx::MonotonicTimeline, zx::BootTimeline>::create(
498 zx::ClockOpts::CONTINUOUS,
499 None,
500 )
501 .map_err(StartComponentError::BootClockCreateFailed)?;
502
503 let ElfComponentLaunchInfo {
504 ns,
505 handle_infos,
506 utc_clock,
507 lifecycle_client,
508 outgoing_directory,
509 local_scope,
510 } = ElfComponentLaunchInfo::new(
511 &mut start_info,
512 &program_config,
513 self.utc_clock.as_ref().map(|c| &**c),
514 )?;
515
516 let resolved_url = &start_info.resolved_url;
517
518 let job = self.create_job(&program_config)?;
520
521 crash_handler::run_exceptions_server(
522 &self.scope,
523 job.top(),
524 moniker.clone(),
525 resolved_url.clone(),
526 self.crash_records.clone(),
527 )
528 .map_err(StartComponentError::ExceptionRegistrationFailed)?;
529
530 let runtime_dir_server_end = start_info
532 .runtime_dir
533 .ok_or(StartComponentError::StartInfoError(StartInfoError::MissingRuntimeDir))?;
534 let job_koid = job.proc().koid().map_err(StartComponentError::JobGetKoidFailed)?.raw_koid();
535
536 let runtime_dir = RuntimeDirBuilder::new(runtime_dir_server_end)
537 .args(program_config.args.clone())
538 .job_id(job_koid)
539 .serve();
540
541 let proc_job_dup = job
543 .proc()
544 .duplicate_handle(zx::Rights::SAME_RIGHTS)
545 .map_err(StartComponentError::JobDuplicateFailed)?;
546
547 let name = Path::new(resolved_url)
548 .file_name()
549 .and_then(|filename| filename.to_str())
550 .ok_or_else(|| {
551 StartComponentError::StartInfoError(StartInfoError::BadResolvedUrl(
552 resolved_url.clone(),
553 ))
554 })?;
555
556 if let Some(break_on_start) = start_info.break_on_start {
558 fasync::OnSignals::new(&break_on_start, zx::Signals::OBJECT_PEER_CLOSED)
559 .on_timeout(MAX_WAIT_BREAK_ON_START, || Err(zx::Status::TIMED_OUT))
560 .await
561 .err()
562 .map(|error| warn!(moniker:%, error:%; "Failed to wait break_on_start"));
563 }
564
565 let environs = merge_environ(
566 &self.additional_environ,
567 program_config.environ.as_deref().unwrap_or_default(),
568 );
569
570 let launcher = self
572 .launcher_connector
573 .connect()
574 .map_err(|err| StartComponentError::ProcessLauncherConnectError(err.into()))?;
575
576 let launch_info =
577 runner::component::configure_launcher(runner::component::LauncherConfigArgs {
578 bin_path: &program_config.binary,
579 name,
580 options: program_config.process_options(),
581 args: Some(program_config.args.clone()),
582 ns,
583 job: Some(proc_job_dup),
584 handle_infos: Some(handle_infos),
585 name_infos: None,
586 environs: (!environs.is_empty()).then_some(environs),
587 launcher: &launcher,
588 loader_proxy_chan: None,
589 executable_vmo: None,
590 })
591 .await?;
592
593 let (status, process) = launcher
595 .launch(launch_info)
596 .await
597 .map_err(StartComponentError::ProcessLauncherFidlError)?;
598 zx::Status::ok(status).map_err(StartComponentError::CreateProcessFailed)?;
599 let process = process.unwrap(); if program_config.main_process_critical {
601 job_default()
602 .set_critical(zx::JobCriticalOptions::RETCODE_NONZERO, &process)
603 .map_err(StartComponentError::ProcessMarkCriticalFailed)
604 .expect("failed to set process as critical");
605 }
606
607 let pid = process.koid().map_err(StartComponentError::ProcessGetKoidFailed)?.raw_koid();
608
609 runtime_dir.add_process_id(pid);
611
612 fuchsia_trace::instant!(
613 c"component:start",
614 c"elf",
615 fuchsia_trace::Scope::Thread,
616 "moniker" => format!("{}", moniker).as_str(),
617 "url" => resolved_url.as_str(),
618 "pid" => pid
619 );
620
621 let process_start_instant_mono =
623 process.info().map_err(StartComponentError::ProcessInfoFailed)?.start_time;
624 runtime_dir.add_process_start_time(process_start_instant_mono.into_nanos());
625
626 let utc_clock_started = fasync::OnSignals::new(&utc_clock, zx::Signals::CLOCK_STARTED)
628 .on_timeout(zx::MonotonicInstant::after(zx::MonotonicDuration::default()), || {
629 Err(zx::Status::TIMED_OUT)
630 })
631 .await
632 .is_ok();
633
634 let mono_to_clock_transformation =
637 boot_clock.get_details().map(|details| details.reference_to_synthetic).ok();
638 let boot_to_utc_transformation = utc_clock_started
639 .then(|| utc_clock.get_details().map(|details| details.reference_to_synthetic).ok())
640 .flatten();
641
642 if let Some(clock_transformation) = boot_to_utc_transformation {
643 let maybe_time_utc = mono_to_clock_transformation
655 .map(|t| t.apply(process_start_instant_mono))
656 .map(|time_boot| clock_transformation.apply(time_boot));
657
658 if let Some(utc_timestamp) = maybe_time_utc {
659 let utc_time_ns = utc_timestamp.into_nanos();
660 let seconds = (utc_time_ns / 1_000_000_000) as i64;
661 let nanos = (utc_time_ns % 1_000_000_000) as u32;
662 let dt = DateTime::from_timestamp(seconds, nanos).unwrap();
663
664 runtime_dir.add_process_start_time_utc_estimate(dt.to_string())
667 }
668 };
669
670 Ok(ElfComponent::new(
671 runtime_dir,
672 moniker,
673 job,
674 process,
675 lifecycle_client,
676 program_config.main_process_critical,
677 local_scope,
678 resolved_url.clone(),
679 outgoing_directory,
680 program_config,
681 start_info.component_instance.ok_or(StartComponentError::StartInfoError(
682 StartInfoError::MissingComponentInstanceToken,
683 ))?,
684 ))
685 }
686
687 pub fn get_scoped_runner(
688 self: Arc<Self>,
689 checker: ScopedPolicyChecker,
690 ) -> Arc<ScopedElfRunner> {
691 Arc::new(ScopedElfRunner { runner: self, checker })
692 }
693
694 pub fn serve_memory_reporter(&self, stream: fattribution::ProviderRequestStream) {
695 self.memory_reporter.serve(stream);
696 }
697}
698
699pub struct ScopedElfRunner {
700 runner: Arc<ElfRunner>,
701 checker: ScopedPolicyChecker,
702}
703
704impl ScopedElfRunner {
705 pub fn serve(&self, mut stream: fcrunner::ComponentRunnerRequestStream) {
706 let runner = self.runner.clone();
707 let checker = self.checker.clone();
708 self.scope().spawn(async move {
709 while let Ok(Some(request)) = stream.try_next().await {
710 match request {
711 fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
712 start(&runner, checker.clone(), start_info, controller).await;
713 }
714 fcrunner::ComponentRunnerRequest::_UnknownMethod { ordinal, .. } => {
715 warn!(ordinal:%; "Unknown ComponentRunner request");
716 }
717 }
718 }
719 });
720 }
721
722 pub async fn start(
723 &self,
724 start_info: fcrunner::ComponentStartInfo,
725 server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
726 ) {
727 start(&self.runner, self.checker.clone(), start_info, server_end).await
728 }
729
730 pub(crate) fn scope(&self) -> &ExecutionScope {
731 &self.runner.scope
732 }
733}
734
735fn is_acceptable_exit_code(moniker: &Moniker, code: i64) -> bool {
736 let moniker_name = moniker.to_string();
737 MONIKER_PREFIXES_TO_ACCEPTABLE_EXIT_CODES
738 .iter()
739 .any(|(prefix, codes)| moniker_name.starts_with(*prefix) && codes.contains(&code))
740}
741
742async fn start(
744 runner: &ElfRunner,
745 checker: ScopedPolicyChecker,
746 start_info: fcrunner::ComponentStartInfo,
747 server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
748) {
749 let resolved_url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
750
751 let elf_component = match runner.start_component(start_info, &checker).await {
752 Ok(elf_component) => elf_component,
753 Err(err) => {
754 runner::component::report_start_error(
755 err.as_zx_status(),
756 format!("{}", err),
757 &resolved_url,
758 server_end,
759 );
760 return;
761 }
762 };
763 let elf_component_moniker = elf_component.info().get_moniker().clone();
764
765 let (termination_tx, termination_rx) = oneshot::channel::<StopInfo>();
766 let termination_fn = Box::pin(async move {
769 termination_rx
770 .await
771 .unwrap_or_else(|_| {
772 warn!("epitaph oneshot channel closed unexpectedly");
773 StopInfo::from_error(fcomp::Error::Internal, None)
774 })
775 .into()
776 });
777
778 let Some(proc_copy) = elf_component.copy_process() else {
779 runner::component::report_start_error(
780 zx::Status::from_raw(
781 i32::try_from(fcomp::Error::InstanceCannotStart.into_primitive()).unwrap(),
782 ),
783 "Component unexpectedly had no process".to_string(),
784 &resolved_url,
785 server_end,
786 );
787 return;
788 };
789
790 let component_diagnostics = elf_component
791 .info()
792 .copy_job_for_diagnostics()
793 .map(|job| ComponentDiagnostics {
794 tasks: Some(ComponentTasks {
795 component_task: Some(DiagnosticsTask::Job(job.into())),
796 ..Default::default()
797 }),
798 ..Default::default()
799 })
800 .map_err(|error| {
801 warn!(error:%; "Failed to copy job for diagnostics");
802 ()
803 })
804 .ok();
805
806 let (server_stream, control) = server_end.into_stream_and_control_handle();
807
808 runner.scope.spawn({
810 let resolved_url = resolved_url.clone();
811 async move {
812 fasync::OnSignals::new(&proc_copy.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
813 .await
814 .map(|_: fidl::Signals| ()) .unwrap_or_else(|error| warn!(error:%; "error creating signal handler"));
816 let stop_info = match proc_copy.info() {
818 Ok(zx::ProcessInfo { return_code, .. }) => {
819 match return_code {
820 0 => StopInfo::from_ok(Some(return_code)),
821 zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL => StopInfo::from_error(
825 fcomp::Error::InstanceDied.into(),
826 Some(return_code),
827 ),
828 _ => {
829 if is_acceptable_exit_code(&elf_component_moniker, return_code) {
830 trace!(url:% = resolved_url, return_code:%; "component terminated with an acceptable non-zero exit code");
831 } else {
832 warn!(url:% = resolved_url, return_code:%;
833 "process terminated with abnormal return code");
834 }
835 StopInfo::from_error(fcomp::Error::InstanceDied, Some(return_code))
836 }
837 }
838 }
839 Err(error) => {
840 warn!(error:%; "Unable to query process info");
841 StopInfo::from_error(fcomp::Error::Internal, None)
842 }
843 };
844 termination_tx.send(stop_info).unwrap_or_else(|_| warn!("error sending done signal"));
845 }
846 });
847
848 let mut elf_component = elf_component;
849 runner.components.clone().add(&mut elf_component);
850
851 runner.scope.spawn(async move {
857 if let Some(component_diagnostics) = component_diagnostics {
858 control.send_on_publish_diagnostics(component_diagnostics).unwrap_or_else(
859 |error| warn!(url:% = resolved_url, error:%; "sending diagnostics failed"),
860 );
861 }
862 runner::component::Controller::new(elf_component, server_stream, control)
863 .serve(termination_fn)
864 .await;
865 });
866}
867
868#[cfg(test)]
869mod tests {
870 use super::runtime_dir::RuntimeDirectory;
871 use super::*;
872 use anyhow::{Context, Error};
873 use assert_matches::assert_matches;
874 use cm_config::{AllowlistEntryBuilder, JobPolicyAllowlists, SecurityPolicy};
875 use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, create_endpoints, create_proxy};
876 use fidl_connector::Connect;
877 use fidl_fuchsia_component as fcomp;
878 use fidl_fuchsia_component_runner as fcrunner;
879 use fidl_fuchsia_component_runner::Task as DiagnosticsTask;
880 use fidl_fuchsia_data as fdata;
881 use fidl_fuchsia_io as fio;
882 use fidl_fuchsia_logger::{LogSinkMarker, LogSinkRequestStream};
883 use fidl_fuchsia_process_lifecycle::LifecycleProxy;
884 use fidl_test_util::spawn_stream_handler;
885 use fuchsia_async as fasync;
886 use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
887 use futures::channel::mpsc;
888 use futures::lock::Mutex;
889 use futures::{StreamExt, join};
890 use runner::component::Controllable;
891 use std::str::FromStr;
892 use std::task::Poll;
893 use test_case::test_case;
894 use zx::{AsHandleRef, Task};
895
896 pub enum MockServiceRequest {
897 LogSink(LogSinkRequestStream),
898 }
899
900 pub type MockServiceFs<'a> = ServiceFs<ServiceObjLocal<'a, MockServiceRequest>>;
901
902 pub fn create_fs_with_mock_logsink()
905 -> Result<(MockServiceFs<'static>, Vec<fcrunner::ComponentNamespaceEntry>), Error> {
906 let (dir_client, dir_server) = create_endpoints::<fio::DirectoryMarker>();
907
908 let mut dir = ServiceFs::new_local();
909 dir.add_fidl_service_at(LogSinkMarker::PROTOCOL_NAME, MockServiceRequest::LogSink);
910 dir.serve_connection(dir_server).context("Failed to add serving channel.")?;
911
912 let namespace = vec![fcrunner::ComponentNamespaceEntry {
913 path: Some("/svc".to_string()),
914 directory: Some(dir_client),
915 ..Default::default()
916 }];
917
918 Ok((dir, namespace))
919 }
920
921 pub fn new_utc_clock_for_tests() -> Arc<UtcClock> {
925 let reference_now = zx::BootInstant::get();
926 let system_utc_clock = duplicate_utc_clock_handle(zx::Rights::SAME_RIGHTS).unwrap();
927 let utc_now = system_utc_clock.read().unwrap();
928
929 let utc_clock_for_tests =
930 Arc::new(UtcClock::create(zx::ClockOpts::MAPPABLE, None).unwrap());
931 utc_clock_for_tests
933 .update(zx::ClockUpdate::builder().absolute_value(reference_now, utc_now.into()))
934 .unwrap();
935 utc_clock_for_tests
936 }
937
938 pub fn new_elf_runner_for_test() -> Arc<ElfRunner> {
939 Arc::new(ElfRunner::new(
940 job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
941 Box::new(process_launcher::BuiltInConnector {}),
942 Some(new_utc_clock_for_tests()),
943 CrashRecords::new(),
944 vec![],
945 ))
946 }
947
948 fn namespace_entry(path: &str, flags: fio::Flags) -> fcrunner::ComponentNamespaceEntry {
949 let ns_path = path.to_string();
951 let ns_dir = fuchsia_fs::directory::open_in_namespace(path, flags).unwrap();
952 let client_end = ns_dir.into_client_end().unwrap();
953 fcrunner::ComponentNamespaceEntry {
954 path: Some(ns_path),
955 directory: Some(client_end),
956 ..Default::default()
957 }
958 }
959
960 fn pkg_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
961 namespace_entry("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
962 }
963
964 fn svc_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
965 namespace_entry("/svc", fio::PERM_READABLE)
966 }
967
968 fn hello_world_startinfo(
969 runtime_dir: ServerEnd<fio::DirectoryMarker>,
970 ) -> fcrunner::ComponentStartInfo {
971 let ns = vec![pkg_dir_namespace_entry()];
972
973 fcrunner::ComponentStartInfo {
974 resolved_url: Some(
975 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/hello-world-rust.cm".to_string(),
976 ),
977 program: Some(fdata::Dictionary {
978 entries: Some(vec![
979 fdata::DictionaryEntry {
980 key: "args".to_string(),
981 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
982 "foo".to_string(),
983 "bar".to_string(),
984 ]))),
985 },
986 fdata::DictionaryEntry {
987 key: "binary".to_string(),
988 value: Some(Box::new(fdata::DictionaryValue::Str(
989 "bin/hello_world_rust".to_string(),
990 ))),
991 },
992 ]),
993 ..Default::default()
994 }),
995 ns: Some(ns),
996 outgoing_dir: None,
997 runtime_dir: Some(runtime_dir),
998 component_instance: Some(zx::Event::create()),
999 ..Default::default()
1000 }
1001 }
1002
1003 fn invalid_binary_startinfo(
1005 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1006 ) -> fcrunner::ComponentStartInfo {
1007 let ns = vec![pkg_dir_namespace_entry()];
1008
1009 fcrunner::ComponentStartInfo {
1010 resolved_url: Some(
1011 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/does-not-exist.cm".to_string(),
1012 ),
1013 program: Some(fdata::Dictionary {
1014 entries: Some(vec![fdata::DictionaryEntry {
1015 key: "binary".to_string(),
1016 value: Some(Box::new(fdata::DictionaryValue::Str(
1017 "bin/does_not_exist".to_string(),
1018 ))),
1019 }]),
1020 ..Default::default()
1021 }),
1022 ns: Some(ns),
1023 outgoing_dir: None,
1024 runtime_dir: Some(runtime_dir),
1025 component_instance: Some(zx::Event::create()),
1026 ..Default::default()
1027 }
1028 }
1029
1030 pub fn lifecycle_startinfo(
1034 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1035 ) -> fcrunner::ComponentStartInfo {
1036 let ns = vec![pkg_dir_namespace_entry()];
1037
1038 fcrunner::ComponentStartInfo {
1039 resolved_url: Some(
1040 "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm".to_string(),
1041 ),
1042 program: Some(fdata::Dictionary {
1043 entries: Some(vec![
1044 fdata::DictionaryEntry {
1045 key: "args".to_string(),
1046 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
1047 "foo".to_string(),
1048 "bar".to_string(),
1049 ]))),
1050 },
1051 fdata::DictionaryEntry {
1052 key: "binary".to_string(),
1053 value: Some(Box::new(fdata::DictionaryValue::Str(
1054 "bin/lifecycle_placeholder".to_string(),
1055 ))),
1056 },
1057 fdata::DictionaryEntry {
1058 key: "lifecycle.stop_event".to_string(),
1059 value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1060 },
1061 ]),
1062 ..Default::default()
1063 }),
1064 ns: Some(ns),
1065 outgoing_dir: None,
1066 runtime_dir: Some(runtime_dir),
1067 component_instance: Some(zx::Event::create()),
1068 ..Default::default()
1069 }
1070 }
1071
1072 fn create_child_process(job: &zx::Job, name: &str) -> zx::Process {
1073 let (process, _vmar) = job
1074 .create_child_process(zx::ProcessOptions::empty(), name.as_bytes())
1075 .expect("could not create process");
1076 process
1077 }
1078
1079 fn make_default_elf_component(
1080 lifecycle_client: Option<LifecycleProxy>,
1081 critical: bool,
1082 ) -> (scoped_task::Scoped<zx::Job>, ElfComponent) {
1083 let job = scoped_task::create_child_job().expect("failed to make child job");
1084 let process = create_child_process(&job, "test_process");
1085 let job_copy =
1086 job.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("job handle duplication failed");
1087 let component = ElfComponent::new(
1088 RuntimeDirectory::empty(),
1089 Moniker::default(),
1090 Job::Single(job_copy),
1091 process,
1092 lifecycle_client,
1093 critical,
1094 ExecutionScope::new(),
1095 "".to_string(),
1096 None,
1097 Default::default(),
1098 zx::Event::create(),
1099 );
1100 (job, component)
1101 }
1102
1103 async fn read_file<'a>(root_proxy: &'a fio::DirectoryProxy, path: &'a str) -> String {
1106 let file_proxy =
1107 fuchsia_fs::directory::open_file_async(&root_proxy, path, fuchsia_fs::PERM_READABLE)
1108 .expect("Failed to open file.");
1109 let res = fuchsia_fs::file::read_to_string(&file_proxy).await;
1110 res.expect("Unable to read file.")
1111 }
1112
1113 #[fuchsia::test]
1114 async fn test_runtime_dir_entries() -> Result<(), Error> {
1115 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1116 let start_info = lifecycle_startinfo(runtime_dir_server);
1117
1118 let runner = new_elf_runner_for_test();
1119 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1120 Arc::new(SecurityPolicy::default()),
1121 Moniker::root(),
1122 ));
1123 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1124
1125 runner.start(start_info, server_controller).await;
1126
1127 assert_eq!("foo", read_file(&runtime_dir, "args/0").await);
1129 assert_eq!("bar", read_file(&runtime_dir, "args/1").await);
1130
1131 let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1136 let process_start_time =
1137 read_file(&runtime_dir, "elf/process_start_time").await.parse::<i64>()?;
1138 let process_start_time_utc_estimate =
1139 read_file(&runtime_dir, "elf/process_start_time_utc_estimate").await;
1140 let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>()?;
1141 assert!(process_id > 0);
1142 assert!(process_start_time > 0);
1143 assert!(process_start_time_utc_estimate.contains("UTC"));
1144 assert!(job_id > 0);
1145 assert_ne!(process_id, job_id);
1146
1147 controller.stop().expect("Stop request failed");
1148 controller.on_closed().await.expect("failed waiting for channel to close");
1151 Ok(())
1152 }
1153
1154 #[fuchsia::test]
1155 async fn test_kill_component() -> Result<(), Error> {
1156 let (job, mut component) = make_default_elf_component(None, false);
1157
1158 let job_info = job.info()?;
1159 assert!(!job_info.exited);
1160
1161 component.kill().await;
1162
1163 let h = job.as_handle_ref();
1164 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1165 .await
1166 .expect("failed waiting for termination signal");
1167
1168 let job_info = job.info()?;
1169 assert!(job_info.exited);
1170 Ok(())
1171 }
1172
1173 #[fuchsia::test]
1174 fn test_stop_critical_component() -> Result<(), Error> {
1175 let mut exec = fasync::TestExecutor::new();
1176 let (lifecycle_client, _lifecycle_server) = create_proxy::<LifecycleMarker>();
1180 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1181 let process = component.copy_process().unwrap();
1182 let job_info = job.info()?;
1183 assert!(!job_info.exited);
1184
1185 let mut completes_when_stopped = component.stop();
1189
1190 match exec.run_until_stalled(&mut completes_when_stopped) {
1193 Poll::Ready(_) => {
1194 panic!("runner should still be waiting for lifecycle channel to stop");
1195 }
1196 _ => {}
1197 }
1198 assert_eq!(process.kill(), Ok(()));
1199
1200 exec.run_singlethreaded(&mut completes_when_stopped);
1201
1202 let h = job.as_handle_ref();
1204 let termination_fut = async move {
1205 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1206 .await
1207 .expect("failed waiting for termination signal");
1208 };
1209 exec.run_singlethreaded(termination_fut);
1210
1211 let job_info = job.info()?;
1212 assert!(job_info.exited);
1213 Ok(())
1214 }
1215
1216 #[fuchsia::test]
1217 fn test_stop_noncritical_component() -> Result<(), Error> {
1218 let mut exec = fasync::TestExecutor::new();
1219 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1223 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1224
1225 let job_info = job.info()?;
1226 assert!(!job_info.exited);
1227
1228 let mut completes_when_stopped = component.stop();
1232
1233 match exec.run_until_stalled(&mut completes_when_stopped) {
1236 Poll::Ready(_) => {
1237 panic!("runner should still be waiting for lifecycle channel to stop");
1238 }
1239 _ => {}
1240 }
1241 drop(lifecycle_server);
1242
1243 match exec.run_until_stalled(&mut completes_when_stopped) {
1244 Poll::Ready(_) => {}
1245 _ => {
1246 panic!("runner future should have completed, lifecycle channel is closed.");
1247 }
1248 }
1249 let h = job.as_handle_ref();
1251 let termination_fut = async move {
1252 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1253 .await
1254 .expect("failed waiting for termination signal");
1255 };
1256 exec.run_singlethreaded(termination_fut);
1257
1258 let job_info = job.info()?;
1259 assert!(job_info.exited);
1260 Ok(())
1261 }
1262
1263 #[fuchsia::test]
1266 async fn test_stop_component_without_lifecycle() -> Result<(), Error> {
1267 let (job, mut component) = make_default_elf_component(None, false);
1268
1269 let job_info = job.info()?;
1270 assert!(!job_info.exited);
1271
1272 component.stop().await;
1273
1274 let h = job.as_handle_ref();
1275 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1276 .await
1277 .expect("failed waiting for termination signal");
1278
1279 let job_info = job.info()?;
1280 assert!(job_info.exited);
1281 Ok(())
1282 }
1283
1284 #[fuchsia::test]
1285 async fn test_stop_critical_component_with_closed_lifecycle() -> Result<(), Error> {
1286 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1287 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1288 let process = component.copy_process().unwrap();
1289 let job_info = job.info()?;
1290 assert!(!job_info.exited);
1291
1292 drop(lifecycle_server);
1294 process.kill()?;
1297 component.stop().await;
1298
1299 let h = job.as_handle_ref();
1300 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1301 .await
1302 .expect("failed waiting for termination signal");
1303
1304 let job_info = job.info()?;
1305 assert!(job_info.exited);
1306 Ok(())
1307 }
1308
1309 #[fuchsia::test]
1310 async fn test_stop_noncritical_component_with_closed_lifecycle() -> Result<(), Error> {
1311 let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1312 let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1313
1314 let job_info = job.info()?;
1315 assert!(!job_info.exited);
1316
1317 drop(lifecycle_server);
1319 component.stop().await;
1322
1323 let h = job.as_handle_ref();
1324 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1325 .await
1326 .expect("failed waiting for termination signal");
1327
1328 let job_info = job.info()?;
1329 assert!(job_info.exited);
1330 Ok(())
1331 }
1332
1333 #[fuchsia::test]
1335 async fn test_drop() -> Result<(), Error> {
1336 let (job, component) = make_default_elf_component(None, false);
1337
1338 let job_info = job.info()?;
1339 assert!(!job_info.exited);
1340
1341 drop(component);
1342
1343 let h = job.as_handle_ref();
1344 fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1345 .await
1346 .expect("failed waiting for termination signal");
1347
1348 let job_info = job.info()?;
1349 assert!(job_info.exited);
1350 Ok(())
1351 }
1352
1353 fn with_mark_vmo_exec(
1354 mut start_info: fcrunner::ComponentStartInfo,
1355 ) -> fcrunner::ComponentStartInfo {
1356 start_info.program.as_mut().map(|dict| {
1357 dict.entries.as_mut().map(|entry| {
1358 entry.push(fdata::DictionaryEntry {
1359 key: "job_policy_ambient_mark_vmo_exec".to_string(),
1360 value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1361 });
1362 entry
1363 })
1364 });
1365 start_info
1366 }
1367
1368 fn with_main_process_critical(
1369 mut start_info: fcrunner::ComponentStartInfo,
1370 ) -> fcrunner::ComponentStartInfo {
1371 start_info.program.as_mut().map(|dict| {
1372 dict.entries.as_mut().map(|entry| {
1373 entry.push(fdata::DictionaryEntry {
1374 key: "main_process_critical".to_string(),
1375 value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1376 });
1377 entry
1378 })
1379 });
1380 start_info
1381 }
1382
1383 #[fuchsia::test]
1384 async fn vmex_security_policy_denied() -> Result<(), Error> {
1385 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1386 let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1387
1388 let runner = new_elf_runner_for_test();
1390 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1391 Arc::new(SecurityPolicy::default()),
1392 Moniker::root(),
1393 ));
1394 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1395
1396 runner.start(start_info, server_controller).await;
1399 assert_matches!(
1400 controller.take_event_stream().try_next().await,
1401 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1402 );
1403
1404 Ok(())
1405 }
1406
1407 #[fuchsia::test]
1408 async fn vmex_security_policy_allowed() -> Result<(), Error> {
1409 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1410 let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1411
1412 let policy = SecurityPolicy {
1413 job_policy: JobPolicyAllowlists {
1414 ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::new().exact("foo").build()],
1415 ..Default::default()
1416 },
1417 ..Default::default()
1418 };
1419 let runner = new_elf_runner_for_test();
1420 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1421 Arc::new(policy),
1422 Moniker::try_from(["foo"]).unwrap(),
1423 ));
1424 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1425 runner.start(start_info, server_controller).await;
1426
1427 let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1429 assert!(process_id > 0);
1430 controller.kill().expect("kill failed");
1432
1433 let mut event_stream = controller.take_event_stream();
1437 expect_diagnostics_event(&mut event_stream).await;
1438
1439 let s = zx::Status::from_raw(
1440 i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1441 );
1442 expect_on_stop(&mut event_stream, s, Some(zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL)).await;
1443 expect_channel_closed(&mut event_stream).await;
1444 Ok(())
1445 }
1446
1447 #[fuchsia::test]
1448 async fn critical_security_policy_denied() -> Result<(), Error> {
1449 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1450 let start_info = with_main_process_critical(hello_world_startinfo(runtime_dir_server));
1451
1452 let runner = new_elf_runner_for_test();
1454 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1455 Arc::new(SecurityPolicy::default()),
1456 Moniker::root(),
1457 ));
1458 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1459
1460 runner.start(start_info, server_controller).await;
1463 assert_matches!(
1464 controller.take_event_stream().try_next().await,
1465 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1466 );
1467
1468 Ok(())
1469 }
1470
1471 #[fuchsia::test]
1472 #[should_panic]
1473 async fn fail_to_launch_critical_component() {
1474 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1475
1476 let start_info = with_main_process_critical(invalid_binary_startinfo(runtime_dir_server));
1479
1480 let policy = SecurityPolicy {
1483 job_policy: JobPolicyAllowlists {
1484 main_process_critical: vec![AllowlistEntryBuilder::new().build()],
1485 ..Default::default()
1486 },
1487 ..Default::default()
1488 };
1489 let runner = new_elf_runner_for_test();
1490 let runner =
1491 runner.get_scoped_runner(ScopedPolicyChecker::new(Arc::new(policy), Moniker::root()));
1492 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1493
1494 runner.start(start_info, server_controller).await;
1495
1496 controller
1497 .take_event_stream()
1498 .try_next()
1499 .await
1500 .map(|_: Option<fcrunner::ComponentControllerEvent>| ()) .unwrap_or_else(|error| warn!(error:%; "error reading from event stream"));
1502 }
1503
1504 fn hello_world_startinfo_forward_stdout_to_log(
1505 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1506 mut ns: Vec<fcrunner::ComponentNamespaceEntry>,
1507 ) -> fcrunner::ComponentStartInfo {
1508 ns.push(pkg_dir_namespace_entry());
1509
1510 fcrunner::ComponentStartInfo {
1511 resolved_url: Some(
1512 "fuchsia-pkg://fuchsia.com/hello-world-rust#meta/hello-world-rust.cm".to_string(),
1513 ),
1514 program: Some(fdata::Dictionary {
1515 entries: Some(vec![
1516 fdata::DictionaryEntry {
1517 key: "binary".to_string(),
1518 value: Some(Box::new(fdata::DictionaryValue::Str(
1519 "bin/hello_world_rust".to_string(),
1520 ))),
1521 },
1522 fdata::DictionaryEntry {
1523 key: "forward_stdout_to".to_string(),
1524 value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1525 },
1526 fdata::DictionaryEntry {
1527 key: "forward_stderr_to".to_string(),
1528 value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1529 },
1530 ]),
1531 ..Default::default()
1532 }),
1533 ns: Some(ns),
1534 outgoing_dir: None,
1535 runtime_dir: Some(runtime_dir),
1536 component_instance: Some(zx::Event::create()),
1537 ..Default::default()
1538 }
1539 }
1540
1541 #[fuchsia::test]
1542 async fn enable_stdout_and_stderr_logging() -> Result<(), Error> {
1543 let (mut dir, ns) = create_fs_with_mock_logsink()?;
1544
1545 let run_component_fut = async move {
1546 let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1547 let start_info = hello_world_startinfo_forward_stdout_to_log(runtime_dir_server, ns);
1548
1549 let runner = new_elf_runner_for_test();
1550 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1551 Arc::new(SecurityPolicy::default()),
1552 Moniker::root(),
1553 ));
1554 let (client_controller, server_controller) =
1555 create_proxy::<fcrunner::ComponentControllerMarker>();
1556
1557 runner.start(start_info, server_controller).await;
1558 let mut event_stream = client_controller.take_event_stream();
1559 expect_diagnostics_event(&mut event_stream).await;
1560 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1561 expect_channel_closed(&mut event_stream).await;
1562 };
1563
1564 let service_fs_listener_fut = async {
1566 let mut requests = Vec::new();
1567 while let Some(MockServiceRequest::LogSink(r)) = dir.next().await {
1568 requests.push(r);
1571 }
1572 requests.len()
1573 };
1574
1575 let connection_count = join!(run_component_fut, service_fs_listener_fut).1;
1576
1577 assert_eq!(connection_count, 1);
1578 Ok(())
1579 }
1580
1581 #[fuchsia::test]
1582 async fn on_publish_diagnostics_contains_job_handle() -> Result<(), Error> {
1583 let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1584 let start_info = lifecycle_startinfo(runtime_dir_server);
1585
1586 let runner = new_elf_runner_for_test();
1587 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1588 Arc::new(SecurityPolicy::default()),
1589 Moniker::root(),
1590 ));
1591 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1592
1593 runner.start(start_info, server_controller).await;
1594
1595 let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>().unwrap();
1596 let mut event_stream = controller.take_event_stream();
1597 match event_stream.try_next().await {
1598 Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1599 payload:
1600 ComponentDiagnostics {
1601 tasks:
1602 Some(ComponentTasks {
1603 component_task: Some(DiagnosticsTask::Job(job)), ..
1604 }),
1605 ..
1606 },
1607 })) => {
1608 assert_eq!(job_id, job.koid().unwrap().raw_koid());
1609 }
1610 other => panic!("unexpected event result: {:?}", other),
1611 }
1612
1613 controller.stop().expect("Stop request failed");
1614 controller.on_closed().await.expect("failed waiting for channel to close");
1617
1618 Ok(())
1619 }
1620
1621 async fn expect_diagnostics_event(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1622 let event = event_stream.try_next().await;
1623 assert_matches!(
1624 event,
1625 Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1626 payload: ComponentDiagnostics {
1627 tasks: Some(ComponentTasks {
1628 component_task: Some(DiagnosticsTask::Job(_)),
1629 ..
1630 }),
1631 ..
1632 },
1633 }))
1634 );
1635 }
1636
1637 async fn expect_on_stop(
1638 event_stream: &mut fcrunner::ComponentControllerEventStream,
1639 expected_status: zx::Status,
1640 expected_exit_code: Option<i64>,
1641 ) {
1642 let event = event_stream.try_next().await;
1643 assert_matches!(
1644 event,
1645 Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
1646 payload: fcrunner::ComponentStopInfo { termination_status: Some(s), exit_code, .. },
1647 }))
1648 if s == expected_status.into_raw() &&
1649 exit_code == expected_exit_code
1650 );
1651 }
1652
1653 async fn expect_channel_closed(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1654 let event = event_stream.try_next().await;
1655 match event {
1656 Ok(None) => {}
1657 other => panic!("Expected channel closed error, got {:?}", other),
1658 }
1659 }
1660
1661 struct LauncherConnectorForTest {
1664 sender: mpsc::UnboundedSender<LaunchPayload>,
1665 }
1666
1667 #[derive(Default)]
1670 struct LaunchPayload {
1671 launch_info: Option<fproc::LaunchInfo>,
1672 args: Vec<Vec<u8>>,
1673 environ: Vec<Vec<u8>>,
1674 name_info: Vec<fproc::NameInfo>,
1675 handles: Vec<fproc::HandleInfo>,
1676 options: u32,
1677 }
1678
1679 impl Connect for LauncherConnectorForTest {
1680 type Proxy = fproc::LauncherProxy;
1681
1682 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
1683 let sender = self.sender.clone();
1684 let payload = Arc::new(Mutex::new(LaunchPayload::default()));
1685
1686 Ok(spawn_stream_handler(move |launcher_request| {
1687 let sender = sender.clone();
1688 let payload = payload.clone();
1689 async move {
1690 let mut payload = payload.lock().await;
1691 match launcher_request {
1692 fproc::LauncherRequest::Launch { info, responder } => {
1693 let process = create_child_process(&info.job, "test_process");
1694 responder.send(zx::Status::OK.into_raw(), Some(process)).unwrap();
1695
1696 let mut payload =
1697 std::mem::replace(&mut *payload, LaunchPayload::default());
1698 payload.launch_info = Some(info);
1699 sender.unbounded_send(payload).unwrap();
1700 }
1701 fproc::LauncherRequest::CreateWithoutStarting { info: _, responder: _ } => {
1702 unimplemented!()
1703 }
1704 fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
1705 payload.args.append(&mut args);
1706 }
1707 fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
1708 payload.environ.append(&mut environ);
1709 }
1710 fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
1711 payload.name_info.append(&mut names);
1712 }
1713 fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
1714 payload.handles.append(&mut handles);
1715 }
1716 fproc::LauncherRequest::SetOptions { options, .. } => {
1717 payload.options = options;
1718 }
1719 }
1720 }
1721 }))
1722 }
1723 }
1724
1725 #[fuchsia::test]
1726 async fn process_created_with_utc_clock_from_numbered_handles() -> Result<(), Error> {
1727 let (payload_tx, mut payload_rx) = mpsc::unbounded();
1728
1729 let connector = LauncherConnectorForTest { sender: payload_tx };
1730 let runner = ElfRunner::new(
1731 job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
1732 Box::new(connector),
1733 Some(new_utc_clock_for_tests()),
1734 CrashRecords::new(),
1735 vec![],
1736 );
1737 let policy_checker = ScopedPolicyChecker::new(
1738 Arc::new(SecurityPolicy::default()),
1739 Moniker::try_from(["foo"]).unwrap(),
1740 );
1741
1742 let clock = zx::SyntheticClock::create(
1744 zx::ClockOpts::AUTO_START | zx::ClockOpts::MONOTONIC | zx::ClockOpts::MAPPABLE,
1745 None,
1746 )?;
1747 let clock_koid = clock.koid().unwrap();
1748
1749 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1750 let mut start_info = hello_world_startinfo(runtime_dir_server);
1751 start_info.numbered_handles = Some(vec![fproc::HandleInfo {
1752 handle: clock.into_handle(),
1753 id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
1754 }]);
1755
1756 let _ = runner
1758 .start_component(start_info, &policy_checker)
1759 .await
1760 .context("failed to start component")?;
1761
1762 let payload = payload_rx.next().await.unwrap();
1763 assert!(
1764 payload
1765 .handles
1766 .iter()
1767 .any(|handle_info| handle_info.handle.koid().unwrap() == clock_koid)
1768 );
1769
1770 Ok(())
1771 }
1772
1773 #[fuchsia::test]
1775 async fn test_enumerate_components() {
1776 use std::sync::atomic::{AtomicUsize, Ordering};
1777
1778 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1779 let start_info = lifecycle_startinfo(runtime_dir_server);
1780
1781 let runner = new_elf_runner_for_test();
1782 let components = runner.components.clone();
1783
1784 let count = Arc::new(AtomicUsize::new(0));
1786 components.clone().visit(|_, _| {
1787 count.fetch_add(1, Ordering::SeqCst);
1788 });
1789 assert_eq!(count.load(Ordering::SeqCst), 0);
1790
1791 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1793 Arc::new(SecurityPolicy::default()),
1794 Moniker::root(),
1795 ));
1796 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1797 runner.start(start_info, server_controller).await;
1798
1799 let count = Arc::new(AtomicUsize::new(0));
1801 components.clone().visit(|elf_component: &ElfComponentInfo, _| {
1802 assert_eq!(
1803 elf_component.get_url().as_str(),
1804 "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm"
1805 );
1806 count.fetch_add(1, Ordering::SeqCst);
1807 });
1808 assert_eq!(count.load(Ordering::SeqCst), 1);
1809
1810 controller.stop().unwrap();
1812 controller.on_closed().await.unwrap();
1813
1814 loop {
1817 let count = Arc::new(AtomicUsize::new(0));
1818 components.clone().visit(|_, _| {
1819 count.fetch_add(1, Ordering::SeqCst);
1820 });
1821 let count = count.load(Ordering::SeqCst);
1822 assert!(count == 0 || count == 1);
1823 if count == 0 {
1824 break;
1825 }
1826 yield_to_executor().await;
1829 }
1830 }
1831
1832 async fn yield_to_executor() {
1833 let mut done = false;
1834 futures::future::poll_fn(|cx| {
1835 if done {
1836 Poll::Ready(())
1837 } else {
1838 done = true;
1839 cx.waker().wake_by_ref();
1840 Poll::Pending
1841 }
1842 })
1843 .await;
1844 }
1845
1846 pub fn immediate_escrow_startinfo(
1849 outgoing_dir: ServerEnd<fio::DirectoryMarker>,
1850 runtime_dir: ServerEnd<fio::DirectoryMarker>,
1851 ) -> fcrunner::ComponentStartInfo {
1852 let ns = vec![
1853 pkg_dir_namespace_entry(),
1854 svc_dir_namespace_entry(),
1856 ];
1857
1858 fcrunner::ComponentStartInfo {
1859 resolved_url: Some("#meta/immediate_escrow_component.cm".to_string()),
1860 program: Some(fdata::Dictionary {
1861 entries: Some(vec![
1862 fdata::DictionaryEntry {
1863 key: "binary".to_string(),
1864 value: Some(Box::new(fdata::DictionaryValue::Str(
1865 "bin/immediate_escrow".to_string(),
1866 ))),
1867 },
1868 fdata::DictionaryEntry {
1869 key: "lifecycle.stop_event".to_string(),
1870 value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1871 },
1872 ]),
1873 ..Default::default()
1874 }),
1875 ns: Some(ns),
1876 outgoing_dir: Some(outgoing_dir),
1877 runtime_dir: Some(runtime_dir),
1878 component_instance: Some(zx::Event::create()),
1879 ..Default::default()
1880 }
1881 }
1882
1883 #[fuchsia::test]
1886 async fn test_lifecycle_on_escrow() {
1887 let (outgoing_dir_client, outgoing_dir_server) =
1888 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1889 let (_, runtime_dir_server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1890 let start_info = immediate_escrow_startinfo(outgoing_dir_server, runtime_dir_server);
1891
1892 let runner = new_elf_runner_for_test();
1893 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1894 Arc::new(SecurityPolicy::default()),
1895 Moniker::root(),
1896 ));
1897 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1898
1899 runner.start(start_info, server_controller).await;
1900
1901 let mut event_stream = controller.take_event_stream();
1902
1903 expect_diagnostics_event(&mut event_stream).await;
1904
1905 match event_stream.try_next().await {
1906 Ok(Some(fcrunner::ComponentControllerEvent::OnEscrow {
1907 payload: fcrunner::ComponentControllerOnEscrowRequest { outgoing_dir, .. },
1908 })) => {
1909 let outgoing_dir_server = outgoing_dir.unwrap();
1910
1911 assert_eq!(
1912 outgoing_dir_client.as_handle_ref().basic_info().unwrap().koid,
1913 outgoing_dir_server.as_handle_ref().basic_info().unwrap().related_koid
1914 );
1915 }
1916 other => panic!("unexpected event result: {:?}", other),
1917 }
1918
1919 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1920 expect_channel_closed(&mut event_stream).await;
1921 }
1922
1923 fn exit_with_code_startinfo(exit_code: i64) -> fcrunner::ComponentStartInfo {
1924 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1925 let ns = vec![pkg_dir_namespace_entry()];
1926
1927 fcrunner::ComponentStartInfo {
1928 resolved_url: Some(
1929 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code.cm".to_string(),
1930 ),
1931 program: Some(fdata::Dictionary {
1932 entries: Some(vec![
1933 fdata::DictionaryEntry {
1934 key: "args".to_string(),
1935 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![format!(
1936 "{}",
1937 exit_code
1938 )]))),
1939 },
1940 fdata::DictionaryEntry {
1941 key: "binary".to_string(),
1942 value: Some(Box::new(fdata::DictionaryValue::Str(
1943 "bin/exit_with_code".to_string(),
1944 ))),
1945 },
1946 ]),
1947 ..Default::default()
1948 }),
1949 ns: Some(ns),
1950 outgoing_dir: None,
1951 runtime_dir: Some(runtime_dir_server),
1952 component_instance: Some(zx::Event::create()),
1953 ..Default::default()
1954 }
1955 }
1956
1957 fn exit_with_code_startinfo_from_env(exit_code: Option<i64>) -> fcrunner::ComponentStartInfo {
1958 let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1959 let ns = vec![pkg_dir_namespace_entry()];
1960 let environ = match exit_code {
1961 Some(code) => vec![format!("EXIT_CODE={}", code)],
1962 None => vec![],
1963 };
1964 fcrunner::ComponentStartInfo {
1965 resolved_url: Some(
1966 "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code-from_env.cm"
1967 .to_string(),
1968 ),
1969 program: Some(fdata::Dictionary {
1970 entries: Some(vec![
1971 fdata::DictionaryEntry {
1972 key: "environ".to_string(),
1973 value: Some(Box::new(fdata::DictionaryValue::StrVec(environ))),
1974 },
1975 fdata::DictionaryEntry {
1976 key: "binary".to_string(),
1977 value: Some(Box::new(fdata::DictionaryValue::Str(
1978 "bin/exit_with_code_from_env".to_string(),
1979 ))),
1980 },
1981 ]),
1982 ..Default::default()
1983 }),
1984 ns: Some(ns),
1985 outgoing_dir: None,
1986 runtime_dir: Some(runtime_dir_server),
1987 component_instance: Some(zx::Event::create()),
1988 ..Default::default()
1989 }
1990 }
1991
1992 #[test_case(exit_with_code_startinfo(0) ; "args")]
1993 #[test_case(exit_with_code_startinfo_from_env(Some(0)) ; "env")]
1994 #[fuchsia::test]
1995 async fn test_return_code_success(start_info: fcrunner::ComponentStartInfo) {
1996 let runner = new_elf_runner_for_test();
1997 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1998 Arc::new(SecurityPolicy::default()),
1999 Moniker::root(),
2000 ));
2001 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
2002 runner.start(start_info, server_controller).await;
2003
2004 let mut event_stream = controller.take_event_stream();
2005 expect_diagnostics_event(&mut event_stream).await;
2006 expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
2007 expect_channel_closed(&mut event_stream).await;
2008 }
2009
2010 #[test_case(exit_with_code_startinfo(123), vec![] ; "args")]
2011 #[test_case(exit_with_code_startinfo_from_env(Some(123)), vec![] ; "component_env")]
2012 #[test_case(exit_with_code_startinfo_from_env(Some(123)), vec!["EXIT_CODE=2"] ; "additional_env_shadowed")]
2013 #[test_case(exit_with_code_startinfo_from_env(None), vec!["EXIT_CODE=123"] ; "additional_env")]
2014 #[fuchsia::test]
2015 async fn test_return_code_failure(
2016 start_info: fcrunner::ComponentStartInfo,
2017 additional_environ: Vec<&str>,
2018 ) {
2019 let runner = Arc::new(ElfRunner::new(
2020 job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
2021 Box::new(process_launcher::BuiltInConnector {}),
2022 Some(new_utc_clock_for_tests()),
2023 CrashRecords::new(),
2024 additional_environ.iter().map(|s| s.to_string()).collect(),
2025 ));
2026 let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
2027 Arc::new(SecurityPolicy::default()),
2028 Moniker::root(),
2029 ));
2030 let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
2031 runner.start(start_info, server_controller).await;
2032
2033 let mut event_stream = controller.take_event_stream();
2034 expect_diagnostics_event(&mut event_stream).await;
2035 let s = zx::Status::from_raw(
2036 i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
2037 );
2038 expect_on_stop(&mut event_stream, s, Some(123)).await;
2039 expect_channel_closed(&mut event_stream).await;
2040 }
2041
2042 #[fuchsia::test]
2043 fn test_is_acceptable_exit_code() {
2044 assert!(is_acceptable_exit_code(
2046 &Moniker::from_str("core/sshd-host/shell:sshd-1").expect("valid moniker"),
2047 255
2048 ));
2049
2050 assert!(!is_acceptable_exit_code(
2052 &Moniker::from_str("core/sshd-host/shell:sshd-1").expect("valid moniker"),
2053 1
2054 ));
2055
2056 assert!(!is_acceptable_exit_code(
2058 &Moniker::from_str("not_core/ssh-host/shell:sshd-1").expect("valid moniker"),
2059 255
2060 ));
2061
2062 assert!(!is_acceptable_exit_code(
2064 &Moniker::from_str("foo/debug").expect("valid moniker"),
2065 255
2066 ));
2067 }
2068
2069 #[fuchsia::test]
2070 fn test_merge_environ() {
2071 assert_eq!(merge_environ(&vec![], &vec![]), Vec::<String>::new());
2072
2073 assert_eq!(
2074 merge_environ(&vec!["A".to_string(), "B=".to_string(), "C=c".to_string()], &vec![]),
2075 vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]
2076 );
2077 assert_eq!(
2078 merge_environ(
2079 &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2080 &vec!["A".to_string(), "B".to_string(), "C".to_string()]
2081 ),
2082 vec!["A".to_string(), "B".to_string(), "C".to_string()]
2083 );
2084 assert_eq!(
2085 merge_environ(
2086 &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2087 &vec!["A=".to_string(), "B=".to_string(), "C=".to_string()]
2088 ),
2089 vec!["A=".to_string(), "B=".to_string(), "C=".to_string()]
2090 );
2091 assert_eq!(
2092 merge_environ(
2093 &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()],
2094 &vec!["A=aa".to_string(), "B=bb".to_string(), "C=cc".to_string()]
2095 ),
2096 vec!["A=aa".to_string(), "B=bb".to_string(), "C=cc".to_string()]
2097 );
2098 assert_eq!(
2099 merge_environ(&vec![], &vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]),
2100 vec!["A".to_string(), "B=".to_string(), "C=c".to_string()]
2101 );
2102 }
2103}