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::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 =
258 job.koid().map(|j| j.raw_koid().to_string()).unwrap_or_else(|_| "<unknown>".to_string());
259 let LogInfo { style, job_info, message } = describe_error(err, job.as_handle_ref().cast());
260
261 let level = match style {
262 LogStyle::JobKilled => log::Level::Info,
263 LogStyle::Warn => log::Level::Warn,
264 LogStyle::Error => log::Level::Error,
265 };
266 log::log!(level,
267 op:%, process_name:% = name, job_koid:%, job_info:%, error:% = err;
268 "{message}",
269 );
270}
271
272fn describe_error<'a>(err: &LauncherError, job: zx::Unowned<'a, zx::Job>) -> LogInfo {
279 let log_level: LogStyle;
280 let job_message: String;
281 match err {
282 LauncherError::BuilderError(err)
283 if err.as_zx_status() == zx::Status::BAD_STATE
284 || matches!(err, ProcessBuilderError::LoadDynamicLinkerTimeout()) =>
285 {
286 match job.info() {
287 Ok(job_info) => {
288 match job_info.exited {
289 true => {
290 log_level = match job_info.return_code {
291 sys::ZX_TASK_RETCODE_SYSCALL_KILL => LogStyle::JobKilled,
292 _ => LogStyle::Warn,
293 };
294 let return_code_str = match job_info.return_code {
295 sys::ZX_TASK_RETCODE_SYSCALL_KILL => "killed",
296 sys::ZX_TASK_RETCODE_OOM_KILL => "killed on oom",
297 sys::ZX_TASK_RETCODE_POLICY_KILL => {
298 "killed due to policy violation"
299 }
300 sys::ZX_TASK_RETCODE_VDSO_KILL => {
301 "killed due to breaking vdso API contract"
302 }
303 sys::ZX_TASK_RETCODE_EXCEPTION_KILL => "killed due to exception",
304 _ => "exited for unknown reason",
305 };
306 job_message = format!(
307 "job was {} (retcode {})",
308 return_code_str, job_info.return_code
309 );
310 }
311 false => {
312 log_level = LogStyle::Error;
315 job_message = "job is running".to_string();
316 }
317 }
318 }
319 Err(status) => {
320 log_level = LogStyle::Error;
321 job_message = format!(" (error {} getting job state)", status);
322 }
323 }
324 }
325 _ => {
326 log_level = LogStyle::Error;
328 job_message = "".to_string();
329 }
330 }
331
332 let message = match log_level {
333 LogStyle::JobKilled => {
334 "Process operation failed because the parent job was killed. This is expected."
335 }
336 LogStyle::Warn | LogStyle::Error => "Process operation failed",
337 };
338
339 LogInfo { style: log_level, job_info: job_message, message }
340}
341
342pub type Connector = Box<dyn Connect<Proxy = fproc::LauncherProxy> + Send + Sync>;
343
344#[cfg(test)]
347pub struct BuiltInConnector {}
348
349#[cfg(test)]
350impl Connect for BuiltInConnector {
351 type Proxy = fproc::LauncherProxy;
352
353 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
354 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fproc::LauncherMarker>();
355 fuchsia_async::Task::spawn(async move {
356 let result = ProcessLauncher::serve(stream).await;
357 if let Err(error) = result {
358 log::warn!(error:%; "ProcessLauncher.serve failed");
359 }
360 })
361 .detach();
362 Ok(proxy)
363 }
364}
365
366pub struct NamespaceConnector {
369 pub namespace: Arc<namespace::Namespace>,
370}
371
372#[derive(Error, Debug)]
373enum NamespaceConnectorError {
374 #[error("Missing /svc in namespace: {0:?}")]
375 MissingSvcInNamespace(Vec<NamespacePath>),
376}
377
378impl Connect for NamespaceConnector {
379 type Proxy = fproc::LauncherProxy;
380
381 fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
382 static PATH: LazyLock<NamespacePath> = LazyLock::new(|| "/svc".parse().unwrap());
383 let svc = self.namespace.get(&PATH).ok_or_else(|| {
384 NamespaceConnectorError::MissingSvcInNamespace(self.namespace.paths())
385 })?;
386 fuchsia_component::client::connect_to_protocol_at_dir_root::<fproc::LauncherMarker>(svc)
387 .context("failed to connect to external launcher service")
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use fuchsia_runtime::job_default;
394 use zx::Task;
395
396 use super::*;
397
398 #[test]
399 fn describe_expected_error_in_killed_job() {
400 let job = job_default().create_child_job().unwrap();
402 job.kill().unwrap();
403 let errors = [
404 LauncherError::BuilderError(ProcessBuilderError::CreateProcess(zx::Status::BAD_STATE)),
405 LauncherError::BuilderError(ProcessBuilderError::LoadDynamicLinkerTimeout()),
406 ];
407 for err in &errors {
408 let description = describe_error(err, job.as_handle_ref().cast());
409 assert_eq!(
410 description,
411 LogInfo {
412 style: LogStyle::JobKilled,
413 job_info: "job was killed (retcode -1024)".to_string(),
414 message: "Process operation failed because the parent job was killed. This is expected."
415 }
416 );
417 }
418 }
419
420 #[test]
421 fn describe_unexpected_error_in_killed_job() {
422 let job = job_default().create_child_job().unwrap();
424 job.kill().unwrap();
425 let expected_err = LauncherError::BuilderError(ProcessBuilderError::CreateProcess(
426 zx::Status::ACCESS_DENIED,
427 ));
428 let description = describe_error(&expected_err, job.as_handle_ref().cast());
429 assert_eq!(
430 description,
431 LogInfo {
432 style: LogStyle::Error,
433 job_info: "".to_string(),
434 message: "Process operation failed"
435 }
436 );
437 }
438
439 #[test]
440 fn describe_error_in_running_job() {
441 let job = job_default();
443 let err =
444 LauncherError::BuilderError(ProcessBuilderError::CreateProcess(zx::Status::BAD_STATE));
445 let description = describe_error(&err, job.clone());
446 assert_eq!(
447 description,
448 LogInfo {
449 style: LogStyle::Error,
450 job_info: "job is running".to_string(),
451 message: "Process operation failed"
452 }
453 );
454 }
455}