Files
RedBear-OS/init/src/service.rs
T

158 lines
5.5 KiB
Rust

use std::collections::{BTreeMap, BTreeSet};
use std::ffi::OsString;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::env;
use serde::Deserialize;
use crate::color::{init_error, init_warn, status_fail};
use crate::script::subst_env;
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Service {
pub cmd: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub envs: BTreeMap<String, String>,
#[serde(default)]
pub inherit_envs: BTreeSet<String>,
#[serde(rename = "type")]
pub type_: ServiceType,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ServiceType {
#[default]
Notify,
Scheme(String),
Oneshot,
OneshotAsync,
}
fn create_pipe() -> io::Result<(File, OwnedFd)> {
let mut fds = [0i32; 2];
// Use libc::pipe2() which goes through relibc's FILETABLE-managed path
// (FdGuard::open → SYS_OPENAT_INTO), keeping the userspace file table in
// sync with the kernel's file table. Using raw syscall::openat()
// (SYS_OPENAT) here would bypass the FILETABLE and create phantom fds
// that later cause EEXIST collisions when relibc's own pipe2() (called
// during Command::spawn fork+exec) tries to insert at a position the
// FILETABLE incorrectly thinks is free.
let ret = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) };
if ret != 0 {
return Err(io::Error::last_os_error());
}
let rd = unsafe { File::from_raw_fd(fds[0]) };
let wr = unsafe { OwnedFd::from_raw_fd(fds[1]) };
Ok((rd, wr))
}
impl Service {
pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) {
let mut command = Command::new(&self.cmd);
command.args(self.args.iter().map(|arg| subst_env(arg)));
command.env_clear();
for env in &self.inherit_envs {
if let Some(value) = env::var_os(env) {
command.env(env, value);
}
}
command.envs(base_envs).envs(&self.envs);
let (mut read_pipe, write_pipe) = create_pipe()
.unwrap_or_else(|e| panic!("pipe() failed in service.spawn for {}: {}", self.cmd, e));
unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_pipe.into()) };
let mut child = match command.spawn() {
Ok(child) => child,
Err(err) => {
status_fail(&format!("failed to execute {:?}: {}", command, err));
return;
}
};
match &self.type_ {
ServiceType::Notify => {
match read_pipe.read_exact(&mut [0]) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
init_warn(&format!("{:?} exited without notifying readiness", command));
}
Err(err) => {
init_error(&format!("failed to wait for {:?}: {}", command, err));
}
}
}
ServiceType::Scheme(scheme) => {
let mut new_fd = usize::MAX;
loop {
match syscall::call_ro(
read_pipe.as_raw_fd() as usize,
unsafe { plain::as_mut_bytes(&mut new_fd) },
syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER,
&[],
) {
Err(syscall::Error {
errno: syscall::EINTR,
}) => continue,
Ok(0) => {
init_warn(&format!("{:?} exited without notifying readiness", command));
return;
}
Ok(1) => {
break;
}
Ok(n) => {
init_error(&format!("incorrect amount of fds {} returned", n));
return;
}
Err(err) => {
init_error(&format!("failed to wait for {:?}: {}", command, err));
return;
}
}
}
let current_namespace_fd = libredox::call::getns().expect("TODO");
libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
.expect("TODO");
}
ServiceType::Oneshot => {
drop(read_pipe);
match child.wait() {
Ok(exit_status) => {
if !exit_status.success() {
status_fail(&format!("{:?} failed with {}", command, exit_status));
}
}
Err(err) => {
init_error(&format!("failed to wait for {:?}: {}", command, err))
}
}
}
ServiceType::OneshotAsync => {}
}
}
}
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
cmd.env(env, format!("{}", fd.as_raw_fd()));
unsafe {
cmd.pre_exec(move || {
// Pass notify pipe to child
if libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
});
}
}