use crate::unit::service;
use crate::{condition, ecs};
use bevy_ecs::prelude::*;
use calloop::generic;
use rustix::pipe;
use std::os::fd;
use std::os::fd::FromRawFd;
use std::os::linux::net::SocketAddrExt;
use std::os::unix::net;
use std::os::unix::process::CommandExt;
use std::{io, mem, process};
pub trait Notify {
fn configure_command(
&mut self,
loop_handle: &ecs::LoopHandle,
entity: Entity,
cmd: &mut process::Command,
) -> anyhow::Result>;
}
#[derive(Resource)]
pub struct Systemd {
pid: u32,
next_id: u32,
}
impl Default for Systemd {
fn default() -> Self {
Self {
pid: process::id(),
next_id: 1,
}
}
}
impl Notify for Systemd {
fn configure_command(
&mut self,
loop_handle: &ecs::LoopHandle,
entity: Entity,
cmd: &mut process::Command,
) -> anyhow::Result > {
let name = format!("run/apogee/notify/{}/{}", self.pid, self.next_id);
self.next_id += 1;
let address = net::SocketAddr::from_abstract_name(name.as_bytes())?;
let socket = net::UnixDatagram::bind_addr(&address)?;
socket.set_nonblocking(true)?;
cmd.env("NOTIFY_SOCKET", format!("@{name}"));
let fd_wrapper = unsafe { generic::FdWrapper::new(socket) };
let source = generic::Generic::new(
fd_wrapper,
calloop::Interest {
readable: true,
writable: false,
},
calloop::Mode::Level,
);
let token = loop_handle
.0
.insert_source(source, move |readiness, socket, state| {
if !readiness.readable {
return Ok(calloop::PostAction::Continue);
}
let mut buffer = [0_u8; 8192];
loop {
match socket.recv(&mut buffer) {
Ok(length) => {
let ready = buffer[..length]
.split(|byte| *byte == b'\n')
.any(|field| field == b"READY=1");
if !ready {
continue;
}
let Some(ready) = state.world.get::(entity) else {
return Ok(calloop::PostAction::Remove);
};
state.world.entity_mut(ready.0).insert(condition::Fulfilled);
return Ok(calloop::PostAction::Remove);
}
Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
return Ok(calloop::PostAction::Continue);
}
Err(error) => return Err(error),
}
}
})?;
Ok(Some(token))
}
}
pub struct Dummy;
impl Notify for Dummy {
fn configure_command(
&mut self,
loop_handle: &ecs::LoopHandle,
entity: Entity,
cmd: &mut process::Command,
) -> anyhow::Result> {
_ = loop_handle;
_ = entity;
_ = cmd;
Ok(None)
}
}
#[derive(Resource)]
pub struct S6;
impl Notify for S6 {
fn configure_command(
&mut self,
loop_handle: &ecs::LoopHandle,
entity: Entity,
cmd: &mut process::Command,
) -> anyhow::Result > {
let (notify_read, notify_write) = pipe::pipe_with(pipe::PipeFlags::CLOEXEC)?;
unsafe {
cmd.pre_exec(move || {
let mut target = fd::OwnedFd::from_raw_fd(3);
rustix::io::dup2(¬ify_write, &mut target).map_err(io::Error::from)?;
mem::forget(target);
Ok(())
});
}
let fd_wrapper = unsafe { generic::FdWrapper::new(notify_read) };
let source = generic::Generic::new(
fd_wrapper,
calloop::Interest {
readable: true,
writable: false,
},
calloop::Mode::Level,
);
let token = loop_handle.0.insert_source(source, move |_, fd, state| {
let mut buf = [0u8; 64];
match rustix::io::read(fd, &mut buf) {
Ok(0) => Ok(calloop::PostAction::Remove),
Ok(n) => {
if buf[..n].contains(&b'\n') {
let Some(ready) = state.world.get::(entity) else {
return Ok(calloop::PostAction::Remove);
};
state.world.entity_mut(ready.0).insert(condition::Fulfilled);
Ok(calloop::PostAction::Remove)
} else {
Ok(calloop::PostAction::Continue)
}
}
Err(rustix::io::Errno::AGAIN) => Ok(calloop::PostAction::Continue),
Err(err) => {
log::error!("failed to read notify fd: {err}");
Ok(calloop::PostAction::Remove)
}
}
})?;
Ok(Some(token))
}
}