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