1use crate::{MountAction, run_component_features};
6use anyhow::{Context, Error, anyhow, bail};
7use fidl::AsyncChannel;
8use fidl::endpoints::{ControlHandle, RequestStream, ServerEnd};
9use fidl_fuchsia_component_runner::{
10 ComponentControllerMarker, ComponentControllerRequest, ComponentControllerRequestStream,
11 ComponentStartInfo,
12};
13use fuchsia_runtime::{HandleInfo, HandleType};
14use futures::channel::oneshot;
15use futures::{FutureExt, StreamExt};
16use rand::distr::Alphanumeric;
17use rand::{Rng, rng};
18use serde::Deserialize;
19use serde::de::Error as _;
20use starnix_core::execution::{create_init_child_process, execute_task_with_prerun_result};
21use starnix_core::fs::fuchsia::{RemoteFs, SyslogFile, create_file_from_handle};
22use starnix_core::task::{CurrentTask, ExitStatus, Task};
23use starnix_core::vfs::fs_args::MountParams;
24use starnix_core::vfs::{
25 FdNumber, FdTable, FileSystemOptions, FsString, LookupContext, NamespaceNode, WhatToMount,
26};
27use starnix_core::{security, signals};
28use starnix_logging::{log_debug, log_error, log_info, log_warn};
29use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked};
30use starnix_task_command::TaskCommand;
31use starnix_types::ownership::WeakRef;
32use starnix_uapi::auth::{Capabilities, Credentials};
33use starnix_uapi::device_type::DeviceType;
34use starnix_uapi::errno;
35use starnix_uapi::errors::{EEXIST, ENOTDIR, Errno};
36use starnix_uapi::file_mode::mode;
37use starnix_uapi::mount_flags::MountFlags;
38use starnix_uapi::open_flags::OpenFlags;
39use starnix_uapi::signals::{SIGINT, SIGKILL};
40use starnix_uapi::unmount_flags::UnmountFlags;
41use std::ffi::CString;
42use std::ops::DerefMut;
43use std::os::unix::ffi::OsStrExt;
44use std::path::Path;
45use std::sync::Arc;
46use {
47 fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess,
48 fuchsia_async as fasync,
49};
50
51const COMPONENT_EXIT_CODE_BASE: i32 = 1024;
57
58#[derive(Debug, Deserialize)]
59#[serde(deny_unknown_fields)]
60struct ComponentProgram {
61 binary: CString,
62
63 #[serde(default)]
64 args: Vec<String>,
65
66 #[serde(default)]
67 environ: Vec<String>,
68
69 #[serde(default)]
70 cwd: Option<String>,
71
72 #[serde(default)]
73 uid: Option<runner::serde::StoreAsString<u32>>,
74
75 #[serde(default)]
76 component_mounts: Vec<String>,
77
78 #[serde(default)]
79 features: Vec<String>,
80
81 #[serde(default, deserialize_with = "parse_capabilities")]
82 capabilities: Option<Capabilities>,
83
84 #[serde(default)]
85 seclabel: Option<CString>,
86
87 #[serde(default, rename(deserialize = "test_target_kernel"))]
88 _test_target_kernel: Option<String>,
89}
90
91impl ComponentProgram {
92 fn resolve_templates(&mut self, component_path: &str, pkg_path: &str) {
93 let resolve_template = |values: &mut Vec<String>| {
94 for val in values {
95 *val = val
96 .replace("{pkg_path}", &pkg_path)
97 .replace("{component_path}", &component_path);
98 }
99 };
100
101 resolve_template(&mut self.args);
102 resolve_template(&mut self.environ);
103 }
104}
105
106fn parse_capabilities<'de, D>(deserializer: D) -> Result<Option<Capabilities>, D::Error>
107where
108 D: serde::Deserializer<'de>,
109{
110 let mut capabilities = Capabilities::empty();
111 for cap in Vec::<String>::deserialize(deserializer)? {
112 capabilities |= cap.parse().map_err(D::Error::custom)?;
113 }
114 Ok(Some(capabilities))
115}
116
117pub async fn start_component(
127 mut start_info: ComponentStartInfo,
128 controller: ServerEnd<ComponentControllerMarker>,
129 system_task: &CurrentTask,
130) -> Result<(), Error> {
131 let url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
132
133 let (task_complete_sender, task_complete) = oneshot::channel::<TaskResult>();
134
135 let weak_task = system_task.override_creds(
136 security::creds_start_internal_operation(system_task),
137 || {
138 let component_path = generate_component_path(
140 system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
141 system_task,
142 )?;
143 let pkg_path = format!("{component_path}/pkg");
144
145 let mount_record = Arc::new(Mutex::new(MountRecord::default()));
146
147 let ns = start_info.ns.take().ok_or_else(|| anyhow!("Missing namespace"))?;
148
149 let program = start_info.program.as_ref().context("reading program block")?;
150 let mut program: ComponentProgram =
151 runner::serde::deserialize_program(program).context("parsing program block")?;
152 program.resolve_templates(&component_path, &pkg_path);
153 log_debug!("start_component: {}\n{:#?}", url, program);
154
155 let ns_mount_options = system_task.kernel().features.default_ns_mount_options.as_ref();
156 let mut maybe_pkg = None;
157 let mut maybe_svc = None;
158 for entry in ns {
159 if let (Some(dir_path), Some(dir_handle)) = (entry.path, entry.directory) {
160 let dir_path_str = dir_path.as_str();
161 let mount_options = ns_mount_options
162 .and_then(|mount_options| mount_options.get(dir_path_str).cloned());
163
164 match dir_path_str {
165 "/svc" => {
166 maybe_svc = Some(fio::DirectoryProxy::new(AsyncChannel::from_channel(
167 dir_handle.into_channel(),
168 )));
169 }
170 "/custom_artifacts" => {
171 let dir_proxy =
174 fio::DirectorySynchronousProxy::new(dir_handle.into_channel());
175 mount_record
176 .lock()
177 .mount_remote(
178 system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
179 system_task,
180 &dir_proxy,
181 &dir_path,
182 mount_options.as_ref(),
183 )
184 .with_context(|| {
185 format!("failed to mount_remote on path {}", &dir_path)
186 })?;
187 }
188 _ => {
189 let dir_proxy =
190 fio::DirectorySynchronousProxy::new(dir_handle.into_channel());
191 mount_record
192 .lock()
193 .mount_remote(
194 system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
195 system_task,
196 &dir_proxy,
197 &format!("{component_path}/{dir_path}"),
198 mount_options.as_ref(),
199 )
200 .with_context(|| {
201 format!(
202 "failed to mount_remote on path {component_path}/{dir_path}"
203 )
204 })?;
205 if dir_path == "/pkg" {
206 maybe_pkg = Some(dir_proxy);
207 }
208 }
209 }
210 }
211 }
212
213 let pkg = maybe_pkg.ok_or_else(|| anyhow!("Missing /pkg entry in namespace"))?;
214
215 let uid = program
216 .uid
217 .map(|uid| uid.0)
218 .unwrap_or_else(|| system_task.kernel().features.default_uid);
219
220 let mut credentials = Credentials::with_ids(uid, uid);
221 if let Some(capabilities) = program.capabilities {
222 credentials.cap_permitted = capabilities;
223 credentials.cap_effective = capabilities;
224 credentials.cap_inheritable = capabilities;
225 credentials.cap_ambient = capabilities;
226 }
227
228 run_component_features(system_task.kernel(), &program.features, maybe_svc)
229 .unwrap_or_else(|e| {
230 log_error!("failed to set component features for {} - {:?}", url, e);
231 });
232
233 let current_task = create_init_child_process(
234 system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
235 system_task.kernel(),
236 TaskCommand::new(program.binary.as_bytes()),
237 program.seclabel.as_ref(),
238 )?;
239
240 execute_task_with_prerun_result(
241 system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
242 current_task,
243 {
244 let mount_record = mount_record.clone();
245 move |locked, current_task| {
246 let cwd_path = FsString::from(program.cwd.unwrap_or(pkg_path));
247 let cwd = current_task.lookup_path(
248 locked,
249 &mut LookupContext::default(),
250 current_task.fs().root(),
251 cwd_path.as_ref(),
252 )?;
253 current_task.fs().chdir(locked, current_task, cwd)?;
254
255 current_task.set_creds(credentials);
256
257 for mount in &program.component_mounts {
258 let action = MountAction::from_spec(locked, current_task, &pkg, mount)
259 .map_err(|e| {
260 log_error!("Error while mounting the filesystems: {e:?}");
261 errno!(EINVAL)
262 })?;
263 let mount_point =
264 current_task.lookup_path_from_root(locked, action.path.as_ref())?;
265 mount_record.lock().mount(
266 mount_point,
267 WhatToMount::Fs(action.fs),
268 action.flags,
269 )?;
270 }
271
272 parse_numbered_handles(
273 locked,
274 current_task,
275 start_info.numbered_handles,
276 ¤t_task.files,
277 )
278 .map_err(|e| {
279 log_error!("Error while parsing the numbered handles: {e:?}");
280 errno!(EINVAL)
281 })?;
282
283 let mut argv = vec![program.binary.clone()];
284 for arg in program.args {
285 argv.push(CString::new(arg).map_err(|_| errno!(EINVAL))?);
286 }
287
288 let mut environ = vec![];
289 for env in program.environ {
290 environ.push(CString::new(env).map_err(|_| errno!(EINVAL))?);
291 }
292
293 let executable = current_task.open_file(
294 locked,
295 program.binary.as_bytes().into(),
296 OpenFlags::RDONLY,
297 )?;
298 current_task.exec(locked, executable, program.binary, argv, environ)?;
299
300 Ok(WeakRef::from(¤t_task.task))
301 }
302 },
303 move |result| {
304 std::mem::drop(mount_record);
306
307 let _ = task_complete_sender.send(result);
310 },
311 None,
312 )
313 .map_err(anyhow::Error::from)
314 },
315 )?;
316
317 let controller = controller.into_stream();
318 fasync::Task::local(serve_component_controller(controller, weak_task, task_complete)).detach();
319
320 Ok(())
321}
322
323type TaskResult = Result<ExitStatus, Error>;
324
325async fn serve_component_controller(
334 controller: ComponentControllerRequestStream,
335 task: WeakRef<Task>,
336 task_complete: oneshot::Receiver<TaskResult>,
337) {
338 let controller_handle = controller.control_handle();
339
340 enum Event<T, U> {
341 Controller(T),
342 Completion(U),
343 }
344
345 let mut stream = futures::stream::select(
346 controller.map(Event::Controller),
347 task_complete.into_stream().map(Event::Completion),
348 );
349
350 while let Some(event) = stream.next().await {
351 match event {
352 Event::Controller(request) => match request {
353 Ok(ComponentControllerRequest::Stop { .. }) => {
354 if let Some(task) = task.upgrade() {
355 signals::send_standard_signal(
356 task.kernel().kthreads.unlocked_for_async().deref_mut(),
357 task.as_ref(),
358 signals::SignalInfo::kernel(SIGINT),
359 );
360 log_info!("Sent SIGINT to program {}", task.command());
361 }
362 }
363 Ok(ComponentControllerRequest::Kill { .. }) => {
364 if let Some(task) = task.upgrade() {
365 signals::send_standard_signal(
366 task.kernel().kthreads.unlocked_for_async().deref_mut(),
367 &task,
368 signals::SignalInfo::kernel(SIGKILL),
369 );
370 log_info!("Sent SIGKILL to program {}", task.command());
371 controller_handle.shutdown_with_epitaph(zx::Status::from_raw(
372 fcomponent::Error::InstanceDied.into_primitive() as i32,
373 ));
374 }
375 return;
376 }
377 Ok(ComponentControllerRequest::_UnknownMethod { ordinal, .. }) => {
378 log_warn!("Unknown ComponentController request: {ordinal}");
379 }
380 Err(_) => {
381 return;
382 }
383 },
384 Event::Completion(result) => match result {
385 Ok(Ok(ExitStatus::Exit(0))) => {
386 controller_handle.shutdown_with_epitaph(zx::Status::OK)
387 }
388 Ok(Ok(ExitStatus::Exit(n))) => controller_handle.shutdown_with_epitaph(
389 zx::Status::from_raw(COMPONENT_EXIT_CODE_BASE + n as i32),
390 ),
391 _ => controller_handle.shutdown_with_epitaph(zx::Status::from_raw(
392 fcomponent::Error::InstanceDied.into_primitive() as i32,
393 )),
394 },
395 }
396 }
397}
398
399fn generate_component_path<L>(
401 locked: &mut Locked<L>,
402 system_task: &CurrentTask,
403) -> Result<String, Error>
404where
405 L: LockEqualOrBefore<FileOpsCore>,
406{
407 let mount_point = system_task.lookup_path_from_root(locked, "/container/component/".into())?;
410
411 let component_path = loop {
413 let random_string: String =
414 rng().sample_iter(&Alphanumeric).take(10).map(char::from).collect();
415
416 match mount_point.create_node(
419 locked,
420 system_task,
421 random_string.as_str().into(),
422 mode!(IFDIR, 0o755),
423 DeviceType::NONE,
424 ) {
425 Ok(_) => break format!("/container/component/{random_string}"),
426 Err(errno) if errno == EEXIST => {}
427 Err(e) => bail!(e),
428 };
429 };
430
431 Ok(component_path)
432}
433
434pub fn parse_numbered_handles(
442 locked: &mut Locked<Unlocked>,
443 current_task: &CurrentTask,
444 numbered_handles: Option<Vec<fprocess::HandleInfo>>,
445 files: &FdTable,
446) -> Result<(), Error> {
447 if let Some(numbered_handles) = numbered_handles {
448 for numbered_handle in numbered_handles {
449 let info = HandleInfo::try_from(numbered_handle.id)?;
450 if info.handle_type() == HandleType::FileDescriptor {
451 let file = create_file_from_handle(locked, current_task, numbered_handle.handle)?;
452 files.insert(locked, current_task, FdNumber::from_raw(info.arg().into()), file)?;
453 }
454 }
455 }
456
457 let stdio = SyslogFile::new_file(locked, current_task);
458 for i in [0, 1, 2] {
460 if files.get(FdNumber::from_raw(i)).is_err() {
461 files.insert(locked, current_task, FdNumber::from_raw(i), stdio.clone())?;
462 }
463 }
464
465 Ok(())
466}
467
468#[derive(Default)]
472struct MountRecord {
473 mounts: Vec<NamespaceNode>,
475}
476
477impl MountRecord {
478 fn mount(
479 &mut self,
480 mount_point: NamespaceNode,
481 what: WhatToMount,
482 flags: MountFlags,
483 ) -> Result<(), Errno> {
484 mount_point.mount(what, flags)?;
485 self.mounts.push(mount_point);
486 Ok(())
487 }
488
489 fn mount_remote<L>(
490 &mut self,
491 locked: &mut Locked<L>,
492 system_task: &CurrentTask,
493 directory: &fio::DirectorySynchronousProxy,
494 path: &str,
495 mount_options: Option<&String>,
496 ) -> Result<(), Error>
497 where
498 L: LockEqualOrBefore<FileOpsCore>,
499 {
500 let mut current_node =
504 system_task.lookup_path_from_root(locked, ".".into()).context("looking up '.'")?;
505 let mut context = LookupContext::default();
506
507 let path = if let Some(path) = path.strip_prefix('/') { path } else { path };
511
512 for sub_dir in Path::new(path).components() {
513 let sub_dir_bytes = sub_dir.as_os_str().as_bytes();
514 current_node = match current_node.create_node(
515 locked,
516 system_task,
517 sub_dir_bytes.into(),
518 mode!(IFDIR, 0o755),
519 DeviceType::NONE,
520 ) {
521 Ok(node) => node,
522 Err(errno) if errno == EEXIST || errno == ENOTDIR => current_node
523 .lookup_child(locked, system_task, &mut context, sub_dir_bytes.into())
524 .with_context(|| format!("looking up {sub_dir:?}"))?,
525 Err(e) => bail!(e),
526 };
527 }
528
529 let flags = directory
530 .get_flags(zx::MonotonicInstant::INFINITE)
531 .context("transport error")?
532 .map_err(zx::Status::from_raw)
533 .context("get_flags")?;
534 let rights = flags.intersection(fio::MASK_KNOWN_PERMISSIONS);
535
536 let (client_end, server_end) = zx::Channel::create();
537 directory.clone(ServerEnd::new(server_end)).context("cloning directory")?;
538
539 let params = if let Some(mount_options) = mount_options {
542 MountParams::parse(mount_options.as_str().into())
543 .expect("failed to parse default_ns_mount_options")
544 } else {
545 MountParams::default()
546 };
547
548 let fs = RemoteFs::new_fs(
549 locked,
550 system_task.kernel(),
551 client_end,
552 FileSystemOptions { source: path.into(), params, ..Default::default() },
553 rights,
554 )
555 .context("making remote fs")?;
556
557 security::file_system_resolve_security(locked, system_task, &fs)
558 .context("resolving security")?;
559
560 let flags = MountFlags::NOSUID | MountFlags::NODEV | MountFlags::RELATIME;
563 current_node.mount(WhatToMount::Fs(fs), flags).context("mounting fs")?;
564 self.mounts.push(current_node);
565
566 Ok(())
567 }
568
569 fn unmount(&mut self) -> Result<(), Errno> {
570 while let Some(node) = self.mounts.pop() {
571 node.unmount(UnmountFlags::DETACH)?;
572 }
573 Ok(())
574 }
575}
576
577impl Drop for MountRecord {
578 fn drop(&mut self) {
579 match self.unmount() {
580 Ok(()) => {}
581 Err(e) => log_error!("failed to unmount during component exit: {:?}", e),
582 }
583 }
584}