diff --git a/proxmox-systemd/src/notify.rs b/proxmox-systemd/src/notify.rs index 6053d250..1c2651b3 100644 --- a/proxmox-systemd/src/notify.rs +++ b/proxmox-systemd/src/notify.rs @@ -1,5 +1,6 @@ -use std::ffi::CString; +use std::ffi::{c_char, c_void, CStr, CString}; use std::io; +use std::os::fd::{AsFd, AsRawFd, RawFd}; use crate::sys; @@ -41,3 +42,86 @@ impl SystemdNotify { pub fn barrier(timeout: u64) -> Result<(), io::Error> { sys::check_call(unsafe { sys::sd_notify_barrier(0, timeout) }).map(drop) } + +/// Store a set of file descriptors in systemd's file descriptor store for this service. +pub fn store_fds(name: &str, fds: &[RawFd]) -> Result<(), io::Error> { + validate_name(name)?; + + let message = CString::new(format!("FDSTORE=1\nFDNAME={name}"))?; + + sys::check_call(unsafe { + sys::sd_pid_notify_with_fds(0, 0, message.as_ptr(), fds.as_ptr(), fds.len() as _) + }) + .map(drop) +} + +/// Store a file descriptor in systemd's file descriptor store for this service. +pub fn store_fd(name: &str, fds: &F) -> Result<(), io::Error> { + store_fds(name, &[fds.as_fd().as_raw_fd()]) +} + +/// Validate a name for the `FDNAME=` argument (see `man sd_pid_notify_with_fds`). +fn validate_name(name: &str) -> Result<(), io::Error> { + for b in name.as_bytes() { + if *b == b':' || b.is_ascii_control() { + return Err(io::Error::new( + io::ErrorKind::Other, + "invalid file descriptor name", + )); + } + } + Ok(()) +} + +/// An iterator over the file descriptor names in the systemd file descriptor store. +pub struct ListenFdNames { + ptr: *mut *mut c_char, + cur: usize, + len: usize, +} + +impl ListenFdNames { + /// Query the file descriptor names and numbers stored in the systemd file descriptor store. + pub fn query() -> Result { + let mut names = std::ptr::null_mut(); + let count = sys::check_call(unsafe { sys::sd_listen_fds_with_names(0, &mut names) })?; + Ok(Self { + ptr: names, + cur: 0, + len: count as usize, + }) + } + + fn as_array(&self) -> &[*mut c_char] { + if self.ptr.is_null() { + &[] + } else { + unsafe { std::slice::from_raw_parts(self.ptr, self.len) } + } + } +} + +impl Drop for ListenFdNames { + fn drop(&mut self) { + for name in self.as_array() { + unsafe { libc::free(*name as *mut c_void) } + } + } +} + +impl Iterator for ListenFdNames { + type Item = (RawFd, Result); + + fn next(&mut self) -> Option { + let names = self.as_array(); + if self.cur >= names.len() { + return None; + } + + let ptr = names[self.cur] as *const c_char; + let fd_num = (self.cur as RawFd) + sys::LISTEN_FDS_START; + self.cur += 1; + let name = String::from_utf8(unsafe { CStr::from_ptr(ptr) }.to_bytes().to_vec()); + Some((fd_num, name)) + } +} diff --git a/proxmox-systemd/src/sys.rs b/proxmox-systemd/src/sys.rs index eb0ccd88..64075929 100644 --- a/proxmox-systemd/src/sys.rs +++ b/proxmox-systemd/src/sys.rs @@ -1,5 +1,8 @@ -use std::ffi::{c_char, c_int, c_uchar}; +use std::ffi::{c_char, c_int, c_uchar, c_uint}; use std::io; +use std::os::fd::RawFd; + +pub const LISTEN_FDS_START: RawFd = 3; #[link(name = "systemd")] extern "C" { @@ -10,6 +13,17 @@ extern "C" { ) -> c_int; pub fn sd_notify(unset_environment: c_int, state: *const c_char) -> c_int; pub fn sd_notify_barrier(unset_environment: c_int, timeout: u64) -> c_int; + pub fn sd_pid_notify_with_fds( + pid: libc::pid_t, + unset_environment: c_int, + state: *const c_char, + fds: *const c_int, + n_fds: c_uint, + ) -> c_int; + pub fn sd_listen_fds_with_names( + unset_environment: c_int, + names: *mut *mut *mut c_char, + ) -> c_int; } pub fn check_call(ret: c_int) -> Result {