use std::io;
use std::os::fd::OwnedFd;
use std::path::{Component, Path};
use rustix::fs::{Mode, OFlags};
use rustix::io::Errno;
const DIRECTORY: OFlags = OFlags::RDONLY
.union(OFlags::DIRECTORY)
.union(OFlags::NOFOLLOW)
.union(OFlags::CLOEXEC);
pub(super) fn link_within(release: &Path, relative: &Path, target: &Path) -> io::Result<bool> {
let names: Vec<_> = relative
.components()
.filter_map(|component| match component {
Component::Normal(name) => Some(name),
_ => None,
})
.collect();
let Some((leaf, parents)) = names.split_last() else {
return Err(io::Error::from(io::ErrorKind::InvalidInput));
};
let mut dir: OwnedFd = rustix::fs::open(release, DIRECTORY, Mode::empty())?;
for name in parents {
dir = match rustix::fs::openat(&dir, *name, DIRECTORY, Mode::empty()) {
Ok(child) => child,
Err(Errno::NOENT) => {
match rustix::fs::mkdirat(&dir, *name, Mode::from_bits_truncate(0o755)) {
Ok(()) | Err(Errno::EXIST) => {}
Err(errno) => return Err(errno.into()),
}
rustix::fs::openat(&dir, *name, DIRECTORY, Mode::empty())?
}
Err(errno) => return Err(errno.into()),
};
}
match rustix::fs::symlinkat(target, &dir, *leaf) {
Ok(()) => Ok(true),
Err(Errno::EXIST) => Ok(rustix::fs::readlinkat(&dir, *leaf, Vec::new())
.is_ok_and(|existing| existing.as_bytes() == target.as_os_str().as_encoded_bytes())),
Err(errno) => Err(errno.into()),
}
}