158 lines
5.5 KiB
Rust
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(())
|
|
}
|
|
});
|
|
}
|
|
}
|