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