use std::os::unix::io::{FromRawFd, RawFd};
use std::os::unix::net::UnixListener as StdUnixListener;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use anyhow::{bail, Context, Result};
use tokio::net::UnixListener;
use super::paths;
pub const SERVICE_UNIT: &str = "omni-dev-daemon.service";
pub const SOCKET_UNIT: &str = "omni-dev-daemon.socket";
const DISABLE_ENV: &str = "OMNI_DEV_DAEMON_DISABLE_SYSTEMD";
fn unit_dir() -> Result<PathBuf> {
let base = dirs::config_dir().context("could not determine the user config directory")?;
Ok(base.join("systemd").join("user"))
}
fn systemd_disabled() -> bool {
std::env::var(DISABLE_ENV).is_ok_and(|v| flag_is_truthy(&v))
}
fn flag_is_truthy(v: &str) -> bool {
matches!(
v.trim().to_ascii_lowercase().as_str(),
"1" | "true" | "yes" | "on"
)
}
pub fn is_available() -> bool {
if systemd_disabled() {
return false;
}
if !Path::new("/run/systemd/system").is_dir() {
return false;
}
let runtime = std::env::var_os("XDG_RUNTIME_DIR").map_or_else(
|| PathBuf::from(format!("/run/user/{}", nix::unistd::getuid())),
PathBuf::from,
);
runtime.join("systemd").join("private").exists()
}
fn validate_path<'a>(path: &'a Path, what: &str) -> Result<&'a str> {
let s = path
.to_str()
.with_context(|| format!("{what} path is not valid UTF-8: {}", path.display()))?;
if s.contains(['\n', '\r']) {
bail!("{what} path contains a newline, which cannot be placed in a systemd unit: {s}");
}
Ok(s)
}
fn exec_arg(s: &str) -> String {
let escaped = s
.replace('\\', "\\\\") .replace('"', "\\\"") .replace('%', "%%") .replace('$', "$$"); format!("\"{escaped}\"")
}
fn escape_value(s: &str) -> String {
s.replace('%', "%%")
}
fn render_service(exe: &Path, socket: &Path) -> Result<String> {
let exe = validate_path(exe, "executable")?;
let socket = validate_path(socket, "socket")?;
Ok(format!(
r"[Unit]
Description=omni-dev daemon
Requires={socket_unit}
After={socket_unit}
[Service]
ExecStart={exe} daemon run --socket {socket}
",
socket_unit = SOCKET_UNIT,
exe = exec_arg(exe),
socket = exec_arg(socket),
))
}
fn render_socket(socket: &Path) -> Result<String> {
let socket = validate_path(socket, "socket")?;
Ok(format!(
r"[Unit]
Description=omni-dev daemon control socket
[Socket]
ListenStream={socket}
SocketMode=0600
[Install]
WantedBy=sockets.target
",
socket = escape_value(socket),
))
}
fn systemctl(args: &[&str]) -> Command {
let mut command = Command::new("systemctl");
command.arg("--user").args(args);
command
}
type Systemctl<'a> = dyn Fn(&[&str]) -> std::io::Result<std::process::Output> + 'a;
fn spawn_systemctl(args: &[&str]) -> std::io::Result<std::process::Output> {
systemctl(args).output()
}
fn daemon_reload(run: &Systemctl) -> Result<()> {
let output =
run(&["daemon-reload"]).context("failed to run `systemctl --user daemon-reload`")?;
if !output.status.success() {
bail!(
"`systemctl --user daemon-reload` failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
);
}
Ok(())
}
fn enable_now(run: &Systemctl) -> Result<()> {
let mut last_err = String::new();
for attempt in 0..5 {
if attempt > 0 {
std::thread::sleep(Duration::from_millis(200));
}
let output = run(&["enable", "--now", SOCKET_UNIT])
.context("failed to run `systemctl --user enable --now`")?;
if output.status.success() {
return Ok(());
}
last_err = String::from_utf8_lossy(&output.stderr).trim().to_string();
}
bail!("`systemctl --user enable --now {SOCKET_UNIT}` failed: {last_err}");
}
fn write_units(dir: &Path, exe: &Path, socket: &Path) -> Result<()> {
std::fs::create_dir_all(dir).with_context(|| format!("failed to create {}", dir.display()))?;
let service_unit = dir.join(SERVICE_UNIT);
let socket_unit = dir.join(SOCKET_UNIT);
std::fs::write(&service_unit, render_service(exe, socket)?)
.with_context(|| format!("failed to write {}", service_unit.display()))?;
std::fs::write(&socket_unit, render_socket(socket)?)
.with_context(|| format!("failed to write {}", socket_unit.display()))?;
Ok(())
}
pub fn install_and_load(socket: &Path) -> Result<()> {
let exe = std::env::current_exe().context("could not resolve the current executable")?;
write_units(&unit_dir()?, &exe, socket)?;
if let Some(parent) = socket.parent() {
paths::ensure_dir_0700(parent)?;
}
daemon_reload(&spawn_systemctl)?;
enable_now(&spawn_systemctl)
}
fn run_best_effort(run: &Systemctl, args: &[&str]) {
if let Err(e) = run(args) {
tracing::warn!("failed to run `systemctl --user {}`: {e}", args.join(" "));
}
}
pub fn unload() -> Result<()> {
unload_with(&spawn_systemctl)
}
fn unload_with(run: &Systemctl) -> Result<()> {
run_best_effort(run, &["stop", SOCKET_UNIT, SERVICE_UNIT]);
run_best_effort(run, &["disable", SOCKET_UNIT]);
Ok(())
}
#[allow(unsafe_code)]
fn set_cloexec(fd: RawFd) -> Result<()> {
use nix::libc;
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if flags == -1 {
return Err(std::io::Error::last_os_error())
.context("F_GETFD on the systemd-activated socket failed");
}
let rc = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
if rc == -1 {
return Err(std::io::Error::last_os_error())
.context("setting FD_CLOEXEC on the systemd-activated socket failed");
}
Ok(())
}
#[allow(unsafe_code)]
pub(crate) fn systemd_listener() -> Result<Option<UnixListener>> {
const SD_LISTEN_FDS_START: RawFd = 3;
let for_us = std::env::var("LISTEN_PID")
.ok()
.and_then(|pid| pid.parse::<u32>().ok())
.is_some_and(|pid| pid == std::process::id());
if !for_us {
return Ok(None);
}
let count = std::env::var("LISTEN_FDS")
.ok()
.and_then(|n| n.parse::<i32>().ok())
.unwrap_or(0);
if count < 1 {
return Ok(None);
}
let raw = SD_LISTEN_FDS_START;
set_cloexec(raw)?;
let std_listener = unsafe { StdUnixListener::from_raw_fd(raw) };
std_listener
.set_nonblocking(true)
.context("failed to set the systemd socket non-blocking")?;
let listener = UnixListener::from_std(std_listener)
.context("failed to adopt the systemd socket into the Tokio runtime")?;
Ok(Some(listener))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn assert_well_formed(unit: &str) {
for line in unit.lines() {
if line.is_empty() {
continue;
}
let is_section = line.starts_with('[') && line.ends_with(']');
let is_directive = line.contains('=');
assert!(
is_section || is_directive,
"malformed systemd unit line: {line:?}\n---\n{unit}"
);
}
}
#[test]
fn renders_plain_paths_verbatim() {
let service = render_service(
Path::new("/usr/local/bin/omni-dev"),
Path::new("/tmp/omni-dev/daemon.sock"),
)
.expect("ASCII paths render");
assert!(
service.contains(
"ExecStart=\"/usr/local/bin/omni-dev\" daemon run --socket \"/tmp/omni-dev/daemon.sock\""
),
"{service}"
);
assert!(
service.contains(&format!("Requires={SOCKET_UNIT}")),
"{service}"
);
assert_well_formed(&service);
}
#[test]
fn service_has_no_install_or_restart() {
let service = render_service(
Path::new("/usr/local/bin/omni-dev"),
Path::new("/tmp/omni-dev/daemon.sock"),
)
.expect("ASCII paths render");
assert!(!service.contains("Restart"), "{service}");
assert!(!service.contains("KeepAlive"), "{service}");
assert!(!service.contains("[Install]"), "{service}");
}
#[test]
fn exec_start_quotes_paths_with_spaces() {
let service = render_service(
Path::new("/opt/A B/omni-dev"),
Path::new("/tmp/omni-dev/daemon.sock"),
)
.expect("spaced path renders");
assert!(
service.contains("ExecStart=\"/opt/A B/omni-dev\" daemon run"),
"{service}"
);
assert_well_formed(&service);
}
#[test]
fn escapes_percent_specifier() {
let service = render_service(
Path::new("/usr/local/bin/omni-dev"),
Path::new("/tmp/50%/daemon.sock"),
)
.expect("percent path renders");
assert!(
service.contains("--socket \"/tmp/50%%/daemon.sock\""),
"{service}"
);
let socket = render_socket(Path::new("/tmp/50%/daemon.sock")).expect("renders");
assert!(
socket.contains("ListenStream=/tmp/50%%/daemon.sock"),
"{socket}"
);
assert!(!socket.contains("50%/"), "{socket}");
assert_well_formed(&socket);
}
#[test]
fn passes_xml_metacharacters_through_literally() {
let service = render_service(
Path::new("/usr/local/bin/omni-dev"),
Path::new("/tmp/a&b<c>d/daemon.sock"),
)
.expect("renders");
assert!(service.contains("/tmp/a&b<c>d/daemon.sock"), "{service}");
assert!(!service.contains("&"), "{service}");
assert_well_formed(&service);
}
#[test]
fn renders_a_socket_activated_unit() {
let socket = render_socket(Path::new("/tmp/omni-dev/daemon.sock")).expect("renders");
assert!(socket.contains("[Socket]"), "{socket}");
assert!(
socket.contains("ListenStream=/tmp/omni-dev/daemon.sock"),
"{socket}"
);
assert!(socket.contains("SocketMode=0600"), "{socket}");
assert!(socket.contains("WantedBy=sockets.target"), "{socket}");
assert_well_formed(&socket);
}
#[test]
fn rejects_non_utf8_paths() {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
let bad = Path::new(OsStr::from_bytes(b"/tmp/\xFF/omni-dev"));
assert!(render_service(bad, Path::new("/tmp/daemon.sock")).is_err());
assert!(render_service(Path::new("/usr/bin/omni-dev"), bad).is_err());
assert!(render_socket(bad).is_err());
}
#[test]
fn rejects_newline_in_path() {
let bad = Path::new("/tmp/a\nb/daemon.sock");
assert!(render_service(Path::new("/usr/bin/omni-dev"), bad).is_err());
assert!(render_socket(bad).is_err());
}
#[test]
fn systemd_listener_is_none_when_not_activated() {
let result = systemd_listener();
assert!(
matches!(result, Ok(None)),
"expected Ok(None) outside systemd activation, got {result:?}"
);
}
#[test]
fn unit_dir_sits_under_systemd_user() {
let dir = unit_dir().expect("config dir resolves in test");
assert!(dir.ends_with("systemd/user"), "{}", dir.display());
}
#[test]
fn write_units_lays_down_both_unit_files() {
let tmp = tempfile::tempdir().unwrap();
let unit_dir = tmp.path().join("systemd").join("user");
write_units(
&unit_dir,
Path::new("/usr/local/bin/omni-dev"),
Path::new("/tmp/omni-dev/daemon.sock"),
)
.expect("units are written");
let service = std::fs::read_to_string(unit_dir.join(SERVICE_UNIT)).unwrap();
let socket = std::fs::read_to_string(unit_dir.join(SOCKET_UNIT)).unwrap();
assert!(
service.contains("ExecStart=\"/usr/local/bin/omni-dev\" daemon run --socket \"/tmp/omni-dev/daemon.sock\""),
"{service}"
);
assert!(
socket.contains("ListenStream=/tmp/omni-dev/daemon.sock"),
"{socket}"
);
assert!(socket.contains("WantedBy=sockets.target"), "{socket}");
}
#[test]
fn systemctl_targets_the_user_manager() {
let cmd = systemctl(&["daemon-reload"]);
assert_eq!(cmd.get_program().to_str(), Some("systemctl"));
let args: Vec<_> = cmd.get_args().map(|a| a.to_str().unwrap()).collect();
assert_eq!(args, ["--user", "daemon-reload"]);
}
fn fake_output(raw: i32, stderr: &str) -> std::process::Output {
use std::os::unix::process::ExitStatusExt;
std::process::Output {
status: std::process::ExitStatus::from_raw(raw),
stdout: Vec::new(),
stderr: stderr.as_bytes().to_vec(),
}
}
#[test]
fn daemon_reload_reports_success_and_failure() {
daemon_reload(&|args| {
assert_eq!(args, ["daemon-reload"]);
Ok(fake_output(0, ""))
})
.expect("a zero-exit reload succeeds");
let err =
daemon_reload(&|_| Ok(fake_output(256, "boom"))).expect_err("a non-zero reload fails");
assert!(err.to_string().contains("boom"), "{err}");
assert!(
daemon_reload(&|_| Err(std::io::Error::other("no systemctl"))).is_err(),
"a spawn error must surface"
);
}
#[test]
fn enable_now_succeeds_on_the_first_attempt() {
let calls = std::cell::Cell::new(0);
enable_now(&|args| {
calls.set(calls.get() + 1);
assert_eq!(args, ["enable", "--now", SOCKET_UNIT]);
Ok(fake_output(0, ""))
})
.expect("first-attempt success");
assert_eq!(calls.get(), 1, "no retries on success");
}
#[test]
fn enable_now_retries_then_bails() {
let calls = std::cell::Cell::new(0);
let err = enable_now(&|_| {
calls.set(calls.get() + 1);
Ok(fake_output(256, "still failing"))
})
.expect_err("all attempts fail");
assert_eq!(calls.get(), 5, "retries the full budget");
assert!(err.to_string().contains("still failing"), "{err}");
}
#[test]
fn unload_stops_then_disables_best_effort() {
let seen: std::cell::RefCell<Vec<Vec<String>>> = std::cell::RefCell::new(Vec::new());
unload_with(&|args| {
seen.borrow_mut()
.push(args.iter().map(|s| (*s).to_string()).collect());
Ok(fake_output(0, ""))
})
.expect("unload is infallible");
let seen = seen.into_inner();
assert_eq!(seen[0], ["stop", SOCKET_UNIT, SERVICE_UNIT]);
assert_eq!(seen[1], ["disable", SOCKET_UNIT]);
}
#[test]
fn unload_swallows_a_missing_systemctl() {
unload_with(&|_| Err(std::io::Error::other("no systemctl")))
.expect("best-effort teardown never fails");
}
#[test]
fn flag_is_truthy_recognises_on_and_off_values() {
for on in ["1", "true", "TRUE", " yes ", "On"] {
assert!(flag_is_truthy(on), "{on:?} should be truthy");
}
for off in ["0", "false", "no", "", "off", "disable"] {
assert!(!flag_is_truthy(off), "{off:?} should be falsey");
}
}
#[test]
fn is_available_is_a_total_bool() {
let _ = is_available();
}
#[allow(unsafe_code)]
#[test]
fn set_cloexec_sets_the_flag() {
use std::os::unix::io::AsRawFd;
use nix::libc;
let dir = tempfile::tempdir().unwrap();
let listener = StdUnixListener::bind(dir.path().join("s.sock")).unwrap();
let fd = listener.as_raw_fd();
let cleared = unsafe { libc::fcntl(fd, libc::F_SETFD, 0) };
assert_eq!(cleared, 0, "clearing FD_CLOEXEC failed");
assert_eq!(
unsafe { libc::fcntl(fd, libc::F_GETFD) } & libc::FD_CLOEXEC,
0,
"precondition: FD_CLOEXEC should be cleared"
);
set_cloexec(fd).expect("set_cloexec succeeds on a valid fd");
assert_ne!(
unsafe { libc::fcntl(fd, libc::F_GETFD) } & libc::FD_CLOEXEC,
0,
"FD_CLOEXEC should be set after set_cloexec"
);
}
#[test]
fn set_cloexec_errors_on_a_bad_fd() {
assert!(set_cloexec(-1).is_err());
}
}