1use crate::vdso_vmo::get_stable_vdso_vmo;
6use anyhow::Context;
7use cm_types::NamespacePath;
8use fidl_connector::Connect;
9use fidl_fuchsia_process as fproc;
10use fuchsia_runtime::{HandleInfo, HandleInfoError};
11use futures::prelude::*;
12use process_builder::{
13 BuiltProcess, NamespaceEntry, ProcessBuilder, ProcessBuilderError, StartupHandle,
14};
15use std::ffi::CString;
16use std::fmt::Debug;
17use std::sync::{Arc, LazyLock};
18use thiserror::Error;
19use zx::{self as zx, AsHandleRef, sys};
20
21#[derive(Error, Debug)]
25enum LauncherError {
26 #[error("Invalid arg: {}", _0)]
27 InvalidArg(&'static str),
28 #[error("Failed to build new process: {}", _0)]
29 BuilderError(ProcessBuilderError),
30 #[error("Invalid handle info: {}", _0)]
31 HandleInfoError(HandleInfoError),
32}
33
34impl LauncherError {
35 pub fn as_zx_status(&self) -> zx::Status {
36 match self {
37 LauncherError::InvalidArg(_) => zx::Status::INVALID_ARGS,
38 LauncherError::BuilderError(e) => e.as_zx_status(),
39 LauncherError::HandleInfoError(_) => zx::Status::INVALID_ARGS,
40 }
41 }
42}
43
44impl From<ProcessBuilderError> for LauncherError {
45 fn from(err: ProcessBuilderError) -> Self {
46 LauncherError::BuilderError(err)
47 }
48}
49
50impl From<HandleInfoError> for LauncherError {
51 fn from(err: HandleInfoError) -> Self {
52 LauncherError::HandleInfoError(err)
53 }
54}
55
56#[derive(Default, Debug)]
57struct ProcessLauncherState {
58 args: Vec<Vec<u8>>,
59 environ: Vec<Vec<u8>>,
60 name_info: Vec<fproc::NameInfo>,
61 handles: Vec<fproc::HandleInfo>,
62 options: zx::ProcessOptions,
63}
64
65#[derive(Debug)]
68struct LaunchInfo {
69 executable: zx::Vmo,
70 job: Arc<zx::Job>,
71 name: String,
72}
73
74impl From<fproc::LaunchInfo> for LaunchInfo {
75 fn from(info: fproc::LaunchInfo) -> Self {
76 LaunchInfo { executable: info.executable, job: Arc::new(info.job), name: info.name }
77 }
78}
79
80pub struct ProcessLauncher;
82
83impl ProcessLauncher {
84 pub async fn serve(mut stream: fproc::LauncherRequestStream) -> Result<(), fidl::Error> {
88 let mut state = ProcessLauncherState::default();
91
92 while let Some(req) = stream.try_next().await? {
93 match req {
94 fproc::LauncherRequest::Launch { info, responder } => {
95 let info = LaunchInfo::from(info);
96 let job = info.job.clone();
97 let name = info.name.clone();
98
99 match Self::launch_process(info, state).await {
100 Ok(process) => {
101 responder.send(zx::Status::OK.into_raw(), Some(process))?;
102 }
103 Err(err) => {
104 log_launcher_error(&err, "launch", job, name);
105 responder.send(err.as_zx_status().into_raw(), None)?;
106 }
107 }
108
109 state = ProcessLauncherState::default();
111 }
112 fproc::LauncherRequest::CreateWithoutStarting { info, responder } => {
113 let info = LaunchInfo::from(info);
114 let job = info.job.clone();
115 let name = info.name.clone();
116
117 match Self::create_process(info, state).await {
118 Ok(built) => {
119 let process_data = fproc::ProcessStartData {
120 process: built.process,
121 root_vmar: built.root_vmar,
122 thread: built.thread,
123 entry: built.entry as u64,
124 stack: built.stack as u64,
125 bootstrap: built.bootstrap,
126 vdso_base: built.vdso_base as u64,
127 base: built.elf_base as u64,
128 };
129 responder.send(zx::Status::OK.into_raw(), Some(process_data))?;
130 }
131 Err(err) => {
132 log_launcher_error(&err, "create", job, name);
133 responder.send(err.as_zx_status().into_raw(), None)?;
134 }
135 }
136
137 state = ProcessLauncherState::default();
139 }
140 fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
141 state.args.append(&mut args);
142 }
143 fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
144 state.environ.append(&mut environ);
145 }
146 fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
147 state.name_info.append(&mut names);
148 }
149 fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
150 state.handles.append(&mut handles);
151 }
152 fproc::LauncherRequest::SetOptions { options, .. } => {
153 state.options = zx::ProcessOptions::from_bits_retain(options);
156 }
157 }
158 }
159 Ok(())
160 }
161
162 async fn launch_process(
163 info: LaunchInfo,
164 state: ProcessLauncherState,
165 ) -> Result<zx::Process, LauncherError> {
166 Ok(Self::create_process(info, state).await?.start()?)
167 }
168
169 async fn create_process(
170 info: LaunchInfo,
171 state: ProcessLauncherState,
172 ) -> Result<BuiltProcess, LauncherError> {
173 Ok(Self::create_process_builder(info, state)?.build().await?)
174 }
175
176 fn create_process_builder(
177 info: LaunchInfo,
178 state: ProcessLauncherState,
179 ) -> Result<ProcessBuilder, LauncherError> {
180 let proc_name = CString::new(info.name)
181 .map_err(|_| LauncherError::InvalidArg("Process name contained null byte"))?;
182 let stable_vdso_vmo = get_stable_vdso_vmo().map_err(|_| {
183 LauncherError::BuilderError(ProcessBuilderError::BadHandle("Failed to get stable vDSO"))
184 })?;
185 let mut b = ProcessBuilder::new(
186 &proc_name,
187 &info.job,
188 state.options,
189 info.executable,
190 stable_vdso_vmo,
191 )?;
192
193 let arg_cstr = state
194 .args
195 .into_iter()
196 .map(|a| CString::new(a))
197 .collect::<Result<_, _>>()
198 .map_err(|_| LauncherError::InvalidArg("Argument contained null byte"))?;
199 b.add_arguments(arg_cstr);
200
201 let env_cstr = state
202 .environ
203 .into_iter()
204 .map(|e| CString::new(e))
205 .collect::<Result<_, _>>()
206 .map_err(|_| LauncherError::InvalidArg("Environment string contained null byte"))?;
207 b.add_environment_variables(env_cstr);
208
209 let entries = state
210 .name_info
211 .into_iter()
212 .map(|n| Self::new_namespace_entry(n))
213 .collect::<Result<_, _>>()?;
214 b.add_namespace_entries(entries)?;
215
216 let handles = state
220 .handles
221 .into_iter()
222 .map(|h| Self::new_startup_handle(h))
223 .collect::<Result<_, _>>()?;
224 b.add_handles(handles)?;
225
226 Ok(b)
227 }
228
229 fn new_namespace_entry(info: fproc::NameInfo) -> Result<NamespaceEntry, LauncherError> {
232 let cstr = CString::new(info.path)
233 .map_err(|_| LauncherError::InvalidArg("Namespace path contained null byte"))?;
234 Ok(NamespaceEntry { path: cstr, directory: info.directory })
235 }
236
237 fn new_startup_handle(info: fproc::HandleInfo) -> Result<StartupHandle, LauncherError> {
238 Ok(StartupHandle { handle: info.handle, info: HandleInfo::try_from(info.id)? })
239 }
240}
241
242#[derive(Debug, PartialEq)]
243enum LogStyle {
244 JobKilled,
245 Warn,
246 Error,
247}
248
249#[derive(Debug, PartialEq)]
250struct LogInfo {
251 style: LogStyle,
252 job_info: String,
253 message: &'static str,
254}
255
256fn log_launcher_error(err: &LauncherError, op: &str, job: Arc<zx::Job>, name: String) {
257 let job_koid = job
258 .get_koid()
259 .map(|j| j.raw_koid().to_string())
260 .unwrap_or_else(|_| "<unknown>".to_string());
261 let LogInfo { style, job_info, message } = describe_error(err, job.as_handle_ref().cast());
262
263 let level = match style {
264 LogStyle::JobKilled => log::Level::Info,
265 LogStyle::Warn => log::Level::Warn,
266 LogStyle::Error => log::Level::Error,
267 };
268 log::log!(level,
269 op:%, process_name:% = name, job_koid:%, job_info:%, error:% = err;
270 "{message}",
271 );
272}
273
274fn describe_error<'a>(err: &LauncherError, job: zx::Unowned<'a, zx::Job>) -> LogInfo {
281 let log_level: LogStyle;
282 let job_message: String;
283 match err {
284 LauncherError::BuilderError(err)
285 if err.as_zx_status() == zx::Status::BAD_STATE
286 || matches!(err, ProcessBuilderError::LoadDynamicLinkerTimeout()) =>
287 {
288 match job.info() {
289 Ok(job_info) => {
290 match job_info.exited {
291 true => {
292 log_level = match job_info.return_code {
293 sys::ZX_TASK_RETCODE_SYSCALL_KILL => LogStyle::JobKilled,
294 _ => LogStyle::Warn,
295 };
296 let return_code_str = match job_info.return_code {
297 sys::ZX_TASK_RETCODE_SYSCALL_KILL => "killed",
298 sys::ZX_TASK_RETCODE_OOM_KILL => "killed on oom",
299 sys::ZX_TASK_RETCODE_POLICY_KILL => {
300 "killed due to policy violation"
301 }
302 sys::ZX_TASK_RETCODE_VDSO_KILL => {
303 "killed due to breaking vdso API contract"
304 }
305 sys::ZX_TASK_RETCODE_EXCEPTION_KILL => "killed due to exception",
306 _ => "exited for unknown reason",
307 };
308 job_message = format!(
309 "job was {} (retcode {})",
310 return_code_str, job_info.return_code
311 );
312 }
313 false => {
314 log_level = LogStyle::Error;
317 job_message = "job is running".to_string();
318 }
319 }
320 }
321 Err(status) => {
322 log_level = LogStyle::Error;
323 job_message = format!(" (error {} getting job state)", status);
324 }
325 }
326 }
327 _ => {
328 log_level = LogStyle::Error;
330 job_message = "".to_string();
331 }
332 }
333
334 let message = match log_level {
335 LogStyle::JobKilled => {
336 "Process operation failed because the parent job was killed. This is expected."
337 }
338 LogStyle::Warn | LogStyle::Error => "Process operation failed",
339 };
340
341 LogInfo { style: log_level, job_info: job_message, message }
342}
343
344pub type Connector = Box<dyn Connect<Proxy = fproc::LauncherProxy> + Send + Sync>;
345
346#[cfg(test)]
349pub struct BuiltInConnector {}
350
351#[cfg(test)]
352impl Connect for BuiltInConnector {
353 type Proxy = fproc::LauncherProxy;
354
355 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
356 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fproc::LauncherMarker>();
357 fuchsia_async::Task::spawn(async move {
358 let result = ProcessLauncher::serve(stream).await;
359 if let Err(error) = result {
360 log::warn!(error:%; "ProcessLauncher.serve failed");
361 }
362 })
363 .detach();
364 Ok(proxy)
365 }
366}
367
368pub struct NamespaceConnector {
371 pub namespace: Arc<namespace::Namespace>,
372}
373
374#[derive(Error, Debug)]
375enum NamespaceConnectorError {
376 #[error("Missing /svc in namespace: {0:?}")]
377 MissingSvcInNamespace(Vec<NamespacePath>),
378}
379
380impl Connect for NamespaceConnector {
381 type Proxy = fproc::LauncherProxy;
382
383 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
384 static PATH: LazyLock<NamespacePath> = LazyLock::new(|| "/svc".parse().unwrap());
385 let svc = self.namespace.get(&PATH).ok_or_else(|| {
386 NamespaceConnectorError::MissingSvcInNamespace(self.namespace.paths())
387 })?;
388 fuchsia_component::client::connect_to_protocol_at_dir_root::<fproc::LauncherMarker>(svc)
389 .context("failed to connect to external launcher service")
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use fuchsia_runtime::job_default;
396 use zx::Task;
397
398 use super::*;
399
400 #[test]
401 fn describe_expected_error_in_killed_job() {
402 let job = job_default().create_child_job().unwrap();
404 job.kill().unwrap();
405 let errors = [
406 LauncherError::BuilderError(ProcessBuilderError::CreateProcess(zx::Status::BAD_STATE)),
407 LauncherError::BuilderError(ProcessBuilderError::LoadDynamicLinkerTimeout()),
408 ];
409 for err in &errors {
410 let description = describe_error(err, job.as_handle_ref().cast());
411 assert_eq!(
412 description,
413 LogInfo {
414 style: LogStyle::JobKilled,
415 job_info: "job was killed (retcode -1024)".to_string(),
416 message: "Process operation failed because the parent job was killed. This is expected."
417 }
418 );
419 }
420 }
421
422 #[test]
423 fn describe_unexpected_error_in_killed_job() {
424 let job = job_default().create_child_job().unwrap();
426 job.kill().unwrap();
427 let expected_err = LauncherError::BuilderError(ProcessBuilderError::CreateProcess(
428 zx::Status::ACCESS_DENIED,
429 ));
430 let description = describe_error(&expected_err, job.as_handle_ref().cast());
431 assert_eq!(
432 description,
433 LogInfo {
434 style: LogStyle::Error,
435 job_info: "".to_string(),
436 message: "Process operation failed"
437 }
438 );
439 }
440
441 #[test]
442 fn describe_error_in_running_job() {
443 let job = job_default();
445 let err =
446 LauncherError::BuilderError(ProcessBuilderError::CreateProcess(zx::Status::BAD_STATE));
447 let description = describe_error(&err, job.clone());
448 assert_eq!(
449 description,
450 LogInfo {
451 style: LogStyle::Error,
452 job_info: "job is running".to_string(),
453 message: "Process operation failed"
454 }
455 );
456 }
457}