use std::ffi::{OsStr, OsString};
use std::io;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::net::UnixListener;
use super::UdsSecurityError;
const TEMP_BIND_ATTEMPTS: usize = 8;
const NAME_ALPHABET: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
const MAX_TOKEN_LEN: usize = 12;
pub(crate) fn temp_socket_name(final_name: &OsStr, pid: u32, seq: u64) -> OsString {
let budget = final_name.as_encoded_bytes().len();
let mut x = splitmix64((u64::from(pid) << 32) ^ seq);
let mut token = String::with_capacity(MAX_TOKEN_LEN);
for _ in 0..MAX_TOKEN_LEN {
token.push(char::from(NAME_ALPHABET[(x % 36) as usize]));
x /= 36;
}
let name = if budget >= 2 {
format!(".{}", &token[..(budget - 1).min(MAX_TOKEN_LEN)])
} else {
token[..1].to_string()
};
OsString::from(name)
}
fn splitmix64(mut z: u64) -> u64 {
z = z.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub(crate) fn bind_temp_beside(
final_path: &Path,
parent: &Path,
) -> Result<(PathBuf, UnixListener), UdsSecurityError> {
static SEQ: AtomicU64 = AtomicU64::new(0);
let final_name = final_path
.file_name()
.ok_or_else(|| UdsSecurityError::Bind {
path: final_path.to_path_buf(),
source: io::Error::new(io::ErrorKind::InvalidInput, "socket path has no file name"),
})?;
let mut attempt = 0;
loop {
attempt += 1;
let name = temp_socket_name(
final_name,
std::process::id(),
SEQ.fetch_add(1, Ordering::Relaxed),
);
if name == final_name {
continue;
}
let temp = parent.join(name);
match UnixListener::bind(&temp) {
Ok(listener) => return Ok((temp, listener)),
Err(e) if e.kind() == io::ErrorKind::AddrInUse && attempt < TEMP_BIND_ATTEMPTS => {}
Err(source) => return Err(UdsSecurityError::Bind { path: temp, source }),
}
}
}
pub(crate) fn publish(temp: &Path, final_path: &Path) -> Result<(), UdsSecurityError> {
if let Err(e) = std::fs::hard_link(temp, final_path) {
let _ = std::fs::remove_file(temp);
let source = if e.kind() == io::ErrorKind::AlreadyExists {
io::Error::from_raw_os_error(libc::EADDRINUSE)
} else {
e
};
return Err(UdsSecurityError::Bind {
path: final_path.to_path_buf(),
source,
});
}
std::fs::remove_file(temp).map_err(|source| {
let _ = std::fs::remove_file(final_path);
UdsSecurityError::Bind {
path: temp.to_path_buf(),
source,
}
})
}