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