use crate::{RuntimeError, modules::c_path::c_path};
use std::{
ffi::OsString,
mem,
os::unix::ffi::{OsStrExt, OsStringExt},
path::{self, Path, PathBuf},
};
const PATH_OFFSET: usize = mem::offset_of!(libc::sockaddr_un, sun_path);
pub(crate) fn to_raw(path: &Path) -> Result<(libc::sockaddr_un, libc::socklen_t), RuntimeError> {
let bytes = path.as_os_str().as_bytes();
let mut raw: libc::sockaddr_un = unsafe { mem::zeroed() };
if bytes.is_empty() || bytes.contains(&0) || bytes.len() >= raw.sun_path.len() {
return Err(RuntimeError::BadPath);
}
for (slot, byte) in raw.sun_path.iter_mut().zip(bytes) {
*slot = *byte as libc::c_char;
}
let len = PATH_OFFSET + bytes.len() + 1;
raw.sun_len = len as u8;
raw.sun_family = libc::AF_UNIX as libc::sa_family_t;
Ok((raw, len as libc::socklen_t))
}
pub(crate) fn from_raw(storage: &libc::sockaddr_storage, len: libc::socklen_t) -> Option<PathBuf> {
let len = len as usize;
if len <= PATH_OFFSET || storage.ss_family as libc::c_int != libc::AF_UNIX {
return None;
}
let raw = unsafe { &*(storage as *const libc::sockaddr_storage).cast::<libc::sockaddr_un>() };
let room = (len - PATH_OFFSET).min(raw.sun_path.len());
let bytes: Vec<u8> = raw.sun_path[..room]
.iter()
.take_while(|byte| **byte != 0)
.map(|byte| *byte as u8)
.collect();
if bytes.is_empty() {
return None;
}
Some(PathBuf::from(OsString::from_vec(bytes)))
}
pub(crate) struct Bound {
path: PathBuf,
absolute: PathBuf,
identity: Option<(libc::dev_t, libc::ino_t)>,
}
impl Bound {
pub(crate) fn new(path: &Path) -> Self {
let absolute = path::absolute(path).unwrap_or_else(|_| path.to_path_buf());
let identity = identity(&absolute);
Self {
path: path.to_path_buf(),
absolute,
identity,
}
}
pub(crate) fn unbound() -> Self {
Self {
path: PathBuf::new(),
absolute: PathBuf::new(),
identity: None,
}
}
#[inline(always)]
pub(crate) fn path(&self) -> &Path {
&self.path
}
}
impl Drop for Bound {
fn drop(&mut self) {
let Some(made) = self.identity else {
return;
};
if identity(&self.absolute) != Some(made) {
return;
}
if let Some(path) = c_path(&self.absolute) {
unsafe { libc::unlink(path.as_ptr()) };
}
}
}
fn identity(path: &Path) -> Option<(libc::dev_t, libc::ino_t)> {
let path = c_path(path)?;
let mut stat: libc::stat = unsafe { mem::zeroed() };
if unsafe { libc::lstat(path.as_ptr(), &mut stat) } != 0 {
return None;
}
Some((stat.st_dev, stat.st_ino))
}
#[cfg(test)]
mod tests {
use super::*;
fn stored(raw: &libc::sockaddr_un) -> libc::sockaddr_storage {
let mut storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
unsafe {
std::ptr::write(
(&mut storage as *mut libc::sockaddr_storage).cast::<libc::sockaddr_un>(),
*raw,
)
};
storage
}
#[test]
fn a_path_round_trips() {
let path = Path::new("/tmp/atap.sock");
let (raw, len) = to_raw(path).unwrap();
assert_eq!(len as usize, PATH_OFFSET + path.as_os_str().len() + 1);
assert_eq!(from_raw(&stored(&raw), len), Some(path.to_path_buf()));
}
#[test]
fn an_unbound_sender_has_no_path() {
let storage: libc::sockaddr_storage = unsafe { mem::zeroed() };
assert_eq!(from_raw(&storage, PATH_OFFSET as libc::socklen_t), None);
}
#[test]
fn an_unusable_path_is_a_bad_path() {
let long = "/".repeat(104);
assert!(matches!(to_raw(Path::new("")), Err(RuntimeError::BadPath)));
assert!(matches!(
to_raw(Path::new("a\0b")),
Err(RuntimeError::BadPath)
));
assert!(matches!(
to_raw(Path::new(&long)),
Err(RuntimeError::BadPath)
));
assert!(to_raw(Path::new(&long[..103])).is_ok());
}
}