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