1use crate::logs::{LoggerError, LoggerStream, create_log_stream, create_std_combined_log_stream};
8use anyhow::Error;
9use cm_types::NamespacePath;
10use fidl_fuchsia_component::IntrospectorMarker;
11use fuchsia_component::client::connect_to_protocol;
12use fuchsia_component::directory::AsRefDirectory;
13use namespace::Namespace;
14use runtime::{HandleInfo, HandleType};
15use thiserror::Error;
16use zx::{HandleBased, Process, Rights, Task};
17use {fidl_fuchsia_io as fio, fidl_fuchsia_process as fproc, fuchsia_runtime as runtime};
18
19pub static UTC_CLOCK_BASIC_RIGHTS: std::sync::LazyLock<zx::Rights> =
32 std::sync::LazyLock::new(|| {
33 Rights::DUPLICATE
34 | Rights::READ
35 | Rights::WAIT
36 | Rights::TRANSFER
37 | Rights::MAP
38 | Rights::INSPECT
39 });
40
41#[derive(Debug, Error)]
43pub enum LaunchError {
44 #[error("{:?}", _0)]
45 Logger(#[from] LoggerError),
46
47 #[error("Error connecting to launcher: {:?}", _0)]
48 Launcher(Error),
49
50 #[error("{:?}", _0)]
51 LoadInfo(runner::component::LaunchError),
52
53 #[error("Error launching process: {:?}", _0)]
54 LaunchCall(fidl::Error),
55
56 #[error("Error launching process: {:?}", _0)]
57 ProcessLaunch(zx::Status),
58
59 #[error("Error duplicating vDSO: {:?}", _0)]
60 DuplicateVdso(zx::Status),
61
62 #[error("Error launching process: {:?}", _0)]
63 Fidl(#[from] fidl::Error),
64
65 #[error("Error launching process, cannot create socket {:?}", _0)]
66 CreateSocket(zx::Status),
67
68 #[error("Error cloning UTC clock: {:?}", _0)]
69 UtcClock(zx::Status),
70
71 #[error("unexpected error")]
72 UnExpectedError,
73}
74
75pub struct LaunchProcessArgs<'a> {
77 pub bin_path: &'a str,
79 pub process_name: &'a str,
82 pub job: Option<zx::Job>,
84 pub ns: Namespace,
86 pub args: Option<Vec<String>>,
88 pub name_infos: Option<Vec<fproc::NameInfo>>,
90 pub environs: Option<Vec<String>>,
92 pub handle_infos: Option<Vec<fproc::HandleInfo>>,
95 pub loader_proxy_chan: Option<zx::Channel>,
97 pub executable_vmo: Option<zx::Vmo>,
99 pub options: zx::ProcessOptions,
101 pub config_vmo: Option<zx::Vmo>,
103 pub component_instance: Option<fidl::Event>,
105 pub url: Option<String>,
107}
108
109pub async fn launch_process(
111 args: LaunchProcessArgs<'_>,
112) -> Result<(Process, ScopedJob, LoggerStream), LaunchError> {
113 let launcher = connect_to_protocol::<fproc::LauncherMarker>().map_err(LaunchError::Launcher)?;
114 let (logger, stdout_handle, stderr_handle) =
115 create_std_combined_log_stream().map_err(LaunchError::Logger)?;
116 let (process, job) = launch_process_impl(args, launcher, stdout_handle, stderr_handle).await?;
117 Ok((process, job, logger))
118}
119
120pub async fn launch_process_with_separate_std_handles(
123 args: LaunchProcessArgs<'_>,
124) -> Result<(Process, ScopedJob, LoggerStream, LoggerStream), LaunchError> {
125 let launcher = connect_to_protocol::<fproc::LauncherMarker>().map_err(LaunchError::Launcher)?;
126 let (stdout_logger, stdout_handle) = create_log_stream().map_err(LaunchError::Logger)?;
127 let (stderr_logger, stderr_handle) = create_log_stream().map_err(LaunchError::Logger)?;
128 let (process, job) = launch_process_impl(args, launcher, stdout_handle, stderr_handle).await?;
129 Ok((process, job, stdout_logger, stderr_logger))
130}
131
132async fn launch_process_impl(
133 args: LaunchProcessArgs<'_>,
134 launcher: fproc::LauncherProxy,
135 stdout_handle: zx::NullableHandle,
136 stderr_handle: zx::NullableHandle,
137) -> Result<(Process, ScopedJob), LaunchError> {
138 const STDOUT: u16 = 1;
139 const STDERR: u16 = 2;
140
141 let mut handle_infos = args.handle_infos.unwrap_or(vec![]);
142
143 handle_infos.push(fproc::HandleInfo {
144 handle: stdout_handle,
145 id: HandleInfo::new(HandleType::FileDescriptor, STDOUT).as_raw(),
146 });
147
148 handle_infos.push(fproc::HandleInfo {
149 handle: stderr_handle,
150 id: HandleInfo::new(HandleType::FileDescriptor, STDERR).as_raw(),
151 });
152
153 handle_infos.push(fproc::HandleInfo {
154 handle: runtime::duplicate_utc_clock_handle(*UTC_CLOCK_BASIC_RIGHTS)
155 .map_err(LaunchError::UtcClock)?
156 .into_handle(),
157 id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
158 });
159
160 if let Some(config_vmo) = args.config_vmo {
161 handle_infos.push(fproc::HandleInfo {
162 handle: config_vmo.into_handle(),
163 id: HandleInfo::new(HandleType::ComponentConfigVmo, 0).as_raw(),
164 });
165 }
166
167 if let Some(svc_dir) = args.ns.get(&NamespacePath::new("/svc").unwrap()) {
168 let (client, server) = zx::Channel::create();
169 svc_dir
170 .as_ref_directory()
171 .open("fuchsia.logger.LogSink", fio::Flags::PROTOCOL_SERVICE, server.into())
172 .expect("open LogSink for test");
173 handle_infos.push(fproc::HandleInfo {
174 handle: client.into(),
175 id: runtime::HandleInfo::new(runtime::HandleType::LogSink, 0).as_raw(),
176 });
177 }
178
179 let LaunchProcessArgs {
180 bin_path,
181 process_name,
182 args,
183 options,
184 ns,
185 job,
186 name_infos,
187 environs,
188 loader_proxy_chan,
189 executable_vmo,
190 component_instance,
191 url,
192 ..
193 } = args;
194 let launch_info =
196 runner::component::configure_launcher(runner::component::LauncherConfigArgs {
197 bin_path,
198 name: process_name,
199 args,
200 options,
201 ns,
202 job,
203 handle_infos: Some(handle_infos),
204 name_infos,
205 environs,
206 launcher: &launcher,
207 loader_proxy_chan,
208 executable_vmo,
209 })
210 .await
211 .map_err(LaunchError::LoadInfo)?;
212
213 let component_job = launch_info
214 .job
215 .as_handle_ref()
216 .duplicate(zx::Rights::SAME_RIGHTS)
217 .expect("handle duplication failed!");
218
219 let (status, process) = launcher.launch(launch_info).await.map_err(LaunchError::LaunchCall)?;
220
221 let status = zx::Status::from_raw(status);
222 if status != zx::Status::OK {
223 return Err(LaunchError::ProcessLaunch(status));
224 }
225
226 let process = process.ok_or_else(|| LaunchError::UnExpectedError)?;
227
228 trace_component_start(&process, component_instance, url).await;
229
230 Ok((process, ScopedJob::new(zx::Job::from_handle(component_job))))
231}
232
233async fn trace_component_start(
237 process: &Process,
238 component_instance: Option<fidl::Event>,
239 url: Option<String>,
240) {
241 if fuchsia_trace::category_enabled(c"component:start") {
242 let pid = process.koid().unwrap().raw_koid();
243 let moniker = match component_instance {
244 None => "Missing component instance".to_string(),
245 Some(component_instance) => match connect_to_protocol::<IntrospectorMarker>() {
246 Ok(introspector) => {
247 let component_instance =
248 component_instance.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
249 match introspector.get_moniker(component_instance).await {
250 Ok(Ok(moniker)) => moniker,
251 Ok(Err(e)) => {
252 format!("Couldn't get moniker: {e:?}")
253 }
254 Err(e) => {
255 format!("Couldn't get the moniker: {e:?}")
256 }
257 }
258 }
259 Err(e) => {
260 format!("Couldn't get introspector: {e:?}")
261 }
262 },
263 };
264 let url = url.unwrap_or_else(|| "Missing URL".to_string());
265 fuchsia_trace::instant!(
266 c"component:start",
267 c"-test-",
270 fuchsia_trace::Scope::Thread,
271 "moniker" => format!("{}", moniker).as_str(),
272 "url" => url.as_str(),
273 "pid" => pid
274 );
275 }
276}
277
278pub struct ScopedJob {
280 pub object: Option<zx::Job>,
281}
282
283impl ScopedJob {
284 pub fn new(job: zx::Job) -> Self {
285 Self { object: Some(job) }
286 }
287
288 pub fn take(mut self) -> zx::Job {
290 self.object.take().unwrap()
291 }
292}
293
294impl Drop for ScopedJob {
295 fn drop(&mut self) {
296 if let Some(job) = self.object.take() {
297 job.kill().ok();
298 }
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use fidl::endpoints::{ClientEnd, Proxy, create_proxy_and_stream};
306 use fuchsia_runtime::{job_default, process_self, swap_utc_clock_handle};
307 use futures::prelude::*;
308 use {
309 fidl_fuchsia_component_runner as fcrunner, fidl_fuchsia_io as fio, fuchsia_async as fasync,
310 zx,
311 };
312
313 #[test]
314 fn scoped_job_works() {
315 let new_job = job_default().create_child_job().unwrap();
316 let job_dup = new_job.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
317
318 let _child_job = new_job.create_child_job().unwrap();
320
321 let info = job_dup.info().unwrap();
323 assert!(!info.exited);
324 {
325 let _job_about_to_die = ScopedJob::new(new_job);
326 }
327
328 let info = job_dup.info().unwrap();
330 assert!(info.exited);
331 }
332
333 #[test]
334 fn scoped_job_take_works() {
335 let new_job = job_default().create_child_job().unwrap();
336 let raw_handle = new_job.raw_handle();
337
338 let scoped = ScopedJob::new(new_job);
339
340 let ret_job = scoped.take();
341
342 assert_eq!(ret_job.raw_handle(), raw_handle);
344 }
345
346 #[fasync::run_singlethreaded(test)]
347 #[ignore] async fn utc_clock_is_cloned() {
349 let clock = fuchsia_runtime::UtcClock::create(zx::ClockOpts::MONOTONIC, None)
350 .expect("failed to create clock");
351 let expected_clock_koid = clock.koid().expect("failed to get clock koid");
352
353 let _ = swap_utc_clock_handle(clock).expect("failed to swap clocks");
356
357 let pkg = fuchsia_fs::directory::open_in_namespace(
360 "/pkg",
361 fio::PERM_READABLE | fio::PERM_EXECUTABLE,
362 )
363 .expect("failed to open pkg");
364 let args = LaunchProcessArgs {
365 bin_path: "bin/test_runners_lib_lib_test", environs: None,
367 args: None,
368 job: None,
369 process_name: "foo",
370 name_infos: None,
371 handle_infos: None,
372 ns: vec![fcrunner::ComponentNamespaceEntry {
373 path: Some("/pkg".into()),
374 directory: Some(ClientEnd::new(pkg.into_channel().unwrap().into_zx_channel())),
375 ..Default::default()
376 }]
377 .try_into()
378 .unwrap(),
379 loader_proxy_chan: None,
380 executable_vmo: None,
381 options: zx::ProcessOptions::empty(),
382 config_vmo: None,
383 url: None,
384 component_instance: None,
385 };
386 let (mock_proxy, mut mock_stream) = create_proxy_and_stream::<fproc::LauncherMarker>();
387 let mock_fut = async move {
388 let mut all_handles = vec![];
389 while let Some(request) =
390 mock_stream.try_next().await.expect("failed to get next message")
391 {
392 match request {
393 fproc::LauncherRequest::AddHandles { handles, .. } => {
394 all_handles.extend(handles);
395 }
396 fproc::LauncherRequest::Launch { responder, .. } => {
397 responder
398 .send(
399 zx::Status::OK.into_raw(),
400 Some(
401 process_self()
402 .duplicate(zx::Rights::SAME_RIGHTS)
403 .expect("failed to duplicate process handle"),
404 ),
405 )
406 .expect("failed to send reply");
407 }
408 _ => {}
409 }
410 }
411 return all_handles;
412 };
413 let (_logger, stdout_handle, stderr_handle) =
414 create_std_combined_log_stream().map_err(LaunchError::Logger).unwrap();
415 let client_fut = async move {
416 let _ = launch_process_impl(args, mock_proxy, stdout_handle, stderr_handle)
417 .await
418 .expect("failed to launch process");
419 };
420
421 let (all_handles, ()) = futures::future::join(mock_fut, client_fut).await;
422 let clock_id = HandleInfo::new(HandleType::ClockUtc, 0).as_raw();
423
424 let utc_clock_handle = all_handles
425 .into_iter()
426 .find_map(
427 |hi: fproc::HandleInfo| if hi.id == clock_id { Some(hi.handle) } else { None },
428 )
429 .expect("UTC clock handle");
430 let clock_koid = utc_clock_handle.koid().expect("failed to get koid");
431 assert_eq!(expected_clock_koid, clock_koid);
432 }
433}