use std::{
collections::{HashMap, HashSet},
env, io,
};
use tokio::sync::Mutex;
use crate::utils::set_cloexec;
pub(crate) struct SystemdSockets(Mutex<HashMap<String, i32>>);
pub(crate) const LISTEN_PID: &str = "LISTEN_PID";
pub(crate) const LISTEN_FDNAMES: &str = "LISTEN_FDNAMES";
pub(crate) const LISTEN_FDS: &str = "LISTEN_FDS";
const SD_LISTEN_FDS_START: i32 = 3;
#[derive(Debug)]
pub enum SystemdSocketsReadError {
EnvironmentVariableError(String),
DoesNotMatchListenPid,
DuplicateSystemdSocketsRead,
}
#[derive(Debug)]
pub enum SystemdSocketError {
SystemdSocketsNotInitialized,
SocketNotFoundInParent(String),
SocketNotFoundInChildRegistry(String),
IoError(io::Error),
SockInfoIncorrect(String),
}
impl From<io::Error> for SystemdSocketError {
fn from(e: io::Error) -> Self {
Self::IoError(e)
}
}
impl SystemdSockets {
pub(crate) fn new() -> Result<SystemdSockets, SystemdSocketsReadError> {
let special_socket_names: HashSet<&str> =
HashSet::from(["unknown", "stored", "connection"]);
let listen_pid_str: String = env::var(LISTEN_PID).map_err(|e| {
SystemdSocketsReadError::EnvironmentVariableError(format!(
"env LISTEN_PID missing {:?}",
e
))
})?;
let listen_pid: i32 = listen_pid_str.parse::<i32>().map_err(|e| {
SystemdSocketsReadError::EnvironmentVariableError(format!(
"parsing pid from string failed {:?}",
e
))
})?;
let listen_fd_names: String = env::var(LISTEN_FDNAMES).map_err(|e| {
SystemdSocketsReadError::EnvironmentVariableError(format!(
"env LISTEN_FDNAMES missing {:?}",
e
))
})?;
log::debug!("LISTEN_PID: {}", listen_pid);
log::debug!("LISTEN_FDNAMES: {}", listen_fd_names);
if listen_pid != std::process::id() as i32 {
return Err(SystemdSocketsReadError::DoesNotMatchListenPid);
}
let names: Vec<&str> = listen_fd_names.split(':').collect();
let mut map: HashMap<String, i32> = HashMap::new();
for (i, &fd_name) in names.iter().enumerate() {
let fd = SD_LISTEN_FDS_START + i as i32;
set_cloexec(fd);
if special_socket_names.contains(fd_name) {
log::warn!(
"socket name not supported. FDNAME \"{}\", fd {} ignored",
fd_name,
fd
);
} else if map.contains_key(fd_name) {
log::warn!("duplicate FDNAME \"{}\", fd {} ignored", fd_name, fd);
} else {
map.insert(fd_name.to_string(), fd);
}
}
Ok(SystemdSockets(Mutex::new(map)))
}
pub(crate) async fn find(&self, name: String) -> Result<i32, SystemdSocketError> {
let locked_map = self.0.lock().await;
match locked_map.get(&name) {
Some(&fd) => Ok(fd),
None => Err(SystemdSocketError::SocketNotFoundInParent(format!(
"fd {:?} not found LISTEN_FDNAMES",
name
))),
}
}
}