Files
RedBear-OS/init/src/service.rs
T
Red Bear OS e30f24997c init: ConditionPathExists service gate (systemd-style, with ! negation)
init's Service parser used deny_unknown_fields, so the C0
driver-manager service files (which use ConditionPathExists) were
rejected wholesale — driver-manager stayed dormant by parser accident
instead of by design.

- service.rs: new optional ConditionPathExists field + condition_met()
  gate. A leading '!' negates: the service starts only when the path
  does NOT exist.
- scheduler.rs: skip services whose condition is not met (status_skip,
  same pattern as skip_cmd).
- 00_driver-manager.service: fix inverted polarity — the intent is
  'run unless /etc/driver-manager.d/disabled exists', i.e.
  ConditionPathExists = "!/etc/driver-manager.d/disabled".
- Drop two pre-existing unused imports (Write in service.rs,
  status_fail in main.rs).
2026-07-23 16:55:42 +09:00

175 lines
6.1 KiB
Rust

use std::collections::{BTreeMap, BTreeSet};
use std::ffi::OsString;
use std::fs::File;
use std::io::{self, Read};
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,
#[serde(default, rename = "ConditionPathExists")]
pub condition_path_exists: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ServiceType {
#[default]
Notify,
Scheme(String),
Oneshot,
OneshotAsync,
}
impl Service {
/// systemd-style ConditionPathExists gate. A leading `!` negates the
/// test: the service starts only when the path does NOT exist.
pub fn condition_met(&self) -> bool {
let Some(cond) = &self.condition_path_exists else {
return true;
};
let (negated, path) = match cond.strip_prefix('!') {
Some(rest) => (true, rest),
None => (false, cond.as_str()),
};
std::path::Path::new(path).exists() != negated
}
}
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(())
}
});
}
}