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