521c6ffe5b
Commit 99e08650 temporarily removed the post-switch_root chdir (which
points init's working directory at a valid dir in the new root) to test a
spawn hang, and added init-diag SPAWNING/SPAWNED/WAITING eprintlns. The
chdir was never restored: init and every daemon/getty/login/shell it
spawns inherit an invalid CWD (AT_REDOX_CWD_FD), so getcwd() fails in
/usr binaries and interactive shells (zsh/brush) hang instead of reading
input. Restore the chdir and drop the diagnostic eprintlns (which also
spammed the login console).
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(())
|
|
}
|
|
});
|
|
}
|
|
}
|