use std::fs::{self, File, OpenOptions};
use std::io;
use std::os::unix::fs::{FileTypeExt, MetadataExt, OpenOptionsExt, PermissionsExt};
use std::os::unix::net::UnixStream;
use std::path::Path;
pub(super) fn check_parent(path: &Path) -> io::Result<()> {
let parent = path
.parent()
.ok_or_else(|| io::Error::other("socket needs a parent directory"))?;
let metadata = fs::metadata(parent)?;
if !metadata.is_dir() || metadata.permissions().mode() & 0o777 != 0o700 {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"socket directory must be private (mode 0700)",
));
}
Ok(())
}
pub(super) fn acquire(path: &Path) -> io::Result<File> {
check_parent(path)?;
let mut anchor = path.as_os_str().to_os_string();
anchor.push(".lock");
let anchor = Path::new(&anchor);
match fs::symlink_metadata(anchor) {
Ok(metadata) if !metadata.file_type().is_file() => {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"socket lock anchor is not a regular file",
));
}
Ok(_) => {}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => return Err(error),
}
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.mode(0o600)
.open(anchor)?;
file.try_lock().map_err(io::Error::from)?;
let opened = file.metadata()?;
let named = fs::symlink_metadata(anchor)?;
if !named.file_type().is_file() || (opened.dev(), opened.ino()) != (named.dev(), named.ino()) {
return Err(io::Error::new(
io::ErrorKind::AddrInUse,
"socket lock anchor changed",
));
}
Ok(file)
}
pub(super) fn clear_stale(path: &Path) -> io::Result<()> {
let metadata = match fs::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(()),
Err(error) => return Err(error),
};
if !metadata.file_type().is_socket() {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"socket path contains another file",
));
}
match UnixStream::connect(path) {
Ok(_) => Err(io::Error::new(
io::ErrorKind::AddrInUse,
"socket is already serving",
)),
Err(error) if error.kind() == io::ErrorKind::ConnectionRefused => {
let now = fs::symlink_metadata(path)?;
if (metadata.dev(), metadata.ino()) != (now.dev(), now.ino()) {
return Err(io::Error::new(
io::ErrorKind::AddrInUse,
"socket changed during recovery",
));
}
fs::remove_file(path)
}
Err(error) => Err(error),
}
}
fn name_anchor(endpoint: &Path) -> io::Result<std::path::PathBuf> {
if !endpoint.is_absolute()
|| endpoint.file_name().is_none()
|| endpoint.components().any(|part| {
!matches!(
part,
std::path::Component::RootDir | std::path::Component::Normal(_)
)
})
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"name endpoint must be an absolute file path",
));
}
let mut anchor = endpoint.as_os_str().to_os_string();
anchor.push(".owner.lock");
Ok(anchor.into())
}
fn name_directory(anchor: &Path, uid: u32) -> io::Result<fs::Metadata> {
let parent = anchor
.parent()
.ok_or_else(|| io::Error::other("name anchor needs a parent"))?;
let metadata = fs::symlink_metadata(parent)?;
if !metadata.is_dir()
|| metadata.uid() != uid
|| metadata.permissions().mode() & 0o7777 != 0o700
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"name anchor directory must be owned and private (mode 0700)",
));
}
Ok(metadata)
}
fn valid_name_anchor(metadata: &fs::Metadata, uid: u32) -> io::Result<()> {
if !metadata.is_file()
|| metadata.uid() != uid
|| metadata.permissions().mode() & 0o7777 != 0o600
|| metadata.len() != 0
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"name anchor must be an owned empty regular file (mode 0600)",
));
}
Ok(())
}
fn check_name_identity(
anchor: &Path,
file: &File,
directory: &fs::Metadata,
prior: Option<&fs::Metadata>,
uid: u32,
) -> io::Result<()> {
let parent = name_directory(anchor, uid)?;
let opened = file.metadata()?;
let named = fs::symlink_metadata(anchor)?;
valid_name_anchor(&opened, uid)?;
valid_name_anchor(&named, uid)?;
if (parent.dev(), parent.ino()) != (directory.dev(), directory.ino())
|| (opened.dev(), opened.ino()) != (named.dev(), named.ino())
|| prior.is_some_and(|prior| (prior.dev(), prior.ino()) != (opened.dev(), opened.ino()))
{
return Err(io::Error::new(
io::ErrorKind::AddrInUse,
"name anchor or directory changed",
));
}
Ok(())
}
pub(super) fn acquire_name(endpoint: &Path, uid: u32) -> io::Result<File> {
let anchor = name_anchor(endpoint)?;
let directory = name_directory(&anchor, uid)?;
let prior = match fs::symlink_metadata(&anchor) {
Ok(metadata) => {
valid_name_anchor(&metadata, uid)?;
Some(metadata)
}
Err(error) if error.kind() == io::ErrorKind::NotFound => None,
Err(error) => return Err(error),
};
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.mode(0o600)
.open(&anchor)?;
check_name_identity(&anchor, &file, &directory, prior.as_ref(), uid)?;
file.try_lock().map_err(io::Error::from)?;
check_name_identity(&anchor, &file, &directory, prior.as_ref(), uid)?;
Ok(file)
}
pub(super) fn name_is_live(endpoint: &Path, uid: u32) -> io::Result<bool> {
let anchor = name_anchor(endpoint)?;
let directory = name_directory(&anchor, uid)?;
let named = match fs::symlink_metadata(&anchor) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => {
let now = name_directory(&anchor, uid)?;
if (directory.dev(), directory.ino()) != (now.dev(), now.ino()) {
return Err(io::Error::new(
io::ErrorKind::AddrInUse,
"name anchor directory changed",
));
}
return Ok(false);
}
Err(error) => return Err(error),
};
valid_name_anchor(&named, uid)?;
let file = File::open(&anchor)?;
check_name_identity(&anchor, &file, &directory, Some(&named), uid)?;
let live = match file.try_lock().map_err(io::Error::from) {
Ok(()) => false,
Err(error) if error.kind() == io::ErrorKind::WouldBlock => true,
Err(error) => return Err(error),
};
let checked = check_name_identity(&anchor, &file, &directory, Some(&named), uid);
if live {
checked?;
} else {
let released = file.unlock();
match (checked, released) {
(Ok(()), Ok(())) => {}
(Err(error), Ok(())) | (Ok(()), Err(error)) => return Err(error),
(Err(check), Err(release)) => {
return Err(io::Error::other(format!(
"name anchor validation: {check}; probe lock release: {release}"
)));
}
}
}
Ok(live)
}