use agent_bridle_fdguard::GrantedRoot;
use rustix::fs::{fstat, mkdirat, openat, Mode, OFlags};
use std::path::{Path, PathBuf};
pub(super) struct Destination {
parent: GrantedRoot,
path: PathBuf,
object: Option<GrantedRoot>,
owner: Option<u32>,
created: bool,
}
impl Destination {
pub(super) fn capture(path: &Path) -> Result<Self, String> {
let parent = GrantedRoot::acquire(
path.parent()
.and_then(|p| p.ancestors().find(|p| p.is_dir()))
.ok_or("worktree destination has no existing parent")?,
)
.map_err(|e| e.to_string())?;
let mut out = Self {
parent,
path: path.into(),
object: None,
owner: None,
created: false,
};
if out.path.parent() == Some(out.parent.provenance()) {
match out.open_leaf() {
Ok(object) => out.bind(object)?,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(e.to_string()),
}
}
Ok(out)
}
fn open_leaf(&self) -> std::io::Result<GrantedRoot> {
let fd = openat(
self.parent.as_fd(),
Path::new(self.path.file_name().expect("destination leaf")),
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
Mode::empty(),
)?;
GrantedRoot::from_owned_fd(fd, &self.path)
}
fn bind(&mut self, object: GrantedRoot) -> Result<(), String> {
let stat = fstat(object.as_fd()).map_err(|e| e.to_string())?;
let owner = stat.st_uid;
if owner != unsafe { libc::geteuid() } {
return Err("worktree destination is owned by another user".into());
}
if stat.st_mode & 0o7777 != 0o700 {
return Err("worktree destination must have mode 0700".into());
}
if !Self::empty(&object)? {
return Err("worktree destination must be empty".into());
}
self.owner = Some(owner);
self.object = Some(object);
Ok(())
}
fn check_parent(&self) -> Result<(), String> {
if self.path.parent() != Some(self.parent.provenance()) {
return Err("worktree destination needs an existing immediate parent; create the missing intermediate directories separately".into());
}
let current = GrantedRoot::acquire(self.parent.provenance()).map_err(|e| e.to_string())?;
if current.identity() != self.parent.identity() {
return Err("worktree destination parent changed during approval or execution".into());
}
Ok(())
}
pub(super) fn prepare(&mut self) -> Result<(), String> {
self.check_parent()?;
self.materialize()?;
self.check()
}
fn materialize(&mut self) -> Result<(), String> {
if self.object.is_none() {
mkdirat(
self.parent.as_fd(),
Path::new(self.path.file_name().expect("destination leaf")),
Mode::from_raw_mode(0o700),
)
.map_err(|e| format!("cannot create approved worktree destination: {e}"))?;
let object = self.open_leaf().map_err(|e| e.to_string())?;
self.bind(object)?;
self.created = true;
}
Ok(())
}
pub(super) fn check(&self) -> Result<(), String> {
self.check_parent()?;
let expected = self
.object
.as_ref()
.ok_or("worktree destination was not prepared")?;
for current in [
self.open_leaf().map_err(|e| e.to_string())?,
GrantedRoot::acquire(&self.path).map_err(|e| e.to_string())?,
] {
if current.identity() != expected.identity()
|| Some(fstat(current.as_fd()).map_err(|e| e.to_string())?.st_uid) != self.owner
{
return Err(
"worktree destination identity or owner changed during approval or execution"
.into(),
);
}
}
Ok(())
}
fn empty(object: &GrantedRoot) -> Result<bool, String> {
let directory =
crate::fs_cap::WorkspaceDir::from_granted_root(object).map_err(|e| e.to_string())?;
Ok(directory
.read_dir(Path::new("."))
.map_err(|e| e.to_string())?
.is_empty())
}
pub(super) fn left_empty(&self) -> bool {
self.created
&& self.check().is_ok()
&& self
.object
.as_ref()
.is_some_and(|object| Self::empty(object) == Ok(true))
}
}
#[cfg(test)]
mod tests {
use super::*;
pub(super) fn canonical_tempdir() -> (tempfile::TempDir, PathBuf) {
let temp = tempfile::tempdir().unwrap();
let root = temp.path().canonicalize().unwrap();
(temp, root)
}
#[test]
fn sibling_round2_parent_swap_between_check_and_mkdir() {
let (_temp, root) = canonical_tempdir();
let parent = root.join("parent");
let held = root.join("held");
std::fs::create_dir(&parent).unwrap();
let mut guard = Destination::capture(&parent.join("task")).unwrap();
guard.check_parent().unwrap();
std::fs::rename(&parent, &held).unwrap();
std::fs::create_dir(&parent).unwrap();
std::fs::write(parent.join("sentinel"), "replacement").unwrap();
std::fs::create_dir(parent.join("task")).unwrap();
guard.materialize().unwrap();
assert!(guard.check().is_err());
assert!(held.join("task").exists());
drop(guard);
assert!(held.join("task").is_dir());
assert!(parent.join("task").is_dir());
assert_eq!(
std::fs::read_to_string(parent.join("sentinel")).unwrap(),
"replacement"
);
}
#[test]
fn sibling_round3_drop_preserves_replacement_and_contents() {
for replace in [false, true] {
let (_temp, root) = canonical_tempdir();
let path = root.join("task");
let mut guard = Destination::capture(&path).unwrap();
guard.prepare().unwrap();
if replace {
std::fs::rename(&path, root.join("held")).unwrap();
std::fs::create_dir(&path).unwrap();
assert!(guard.check().is_err());
}
if !replace {
std::fs::write(path.join("sentinel"), "keep").unwrap();
}
drop(guard);
assert!(path.is_dir(), "drop deleted an empty replacement");
if !replace {
assert_eq!(
std::fs::read_to_string(path.join("sentinel")).unwrap(),
"keep"
);
}
}
}
#[test]
fn sibling_round2_owner_change_refuses() {
let (_temp, root) = canonical_tempdir();
let mut guard = Destination::capture(&root.join("task")).unwrap();
guard.prepare().unwrap();
guard.owner = guard.owner.map(|uid| uid.wrapping_add(1));
assert!(guard.check().is_err());
}
}
#[cfg(test)]
mod round3_tests {
use super::tests::canonical_tempdir;
use super::*;
use std::os::unix::fs::PermissionsExt;
#[test]
fn sibling_round3_leaf_is_private_and_drop_preserves_it() {
let (_temp, root) = canonical_tempdir();
let path = root.join("task");
let mut guard = Destination::capture(&path).unwrap();
guard.prepare().unwrap();
assert_eq!(
std::fs::metadata(&path).unwrap().permissions().mode() & 0o7777,
0o700
);
drop(guard);
assert!(path.is_dir(), "failure/cancellation must not unlink a leaf");
}
#[test]
fn sibling_round3_bind_refuses_replacement_contents_or_mode() {
for nonempty in [false, true] {
let (_temp, root) = canonical_tempdir();
let path = root.join("task");
let held = root.join("held");
let mut guard = Destination::capture(&path).unwrap();
mkdirat(guard.parent.as_fd(), "task", Mode::from_raw_mode(0o700)).unwrap();
std::fs::rename(&path, &held).unwrap();
std::fs::create_dir(&path).unwrap();
std::fs::set_permissions(
&path,
std::fs::Permissions::from_mode(if nonempty { 0o700 } else { 0o755 }),
)
.unwrap();
if nonempty {
std::fs::write(path.join("sentinel"), "keep").unwrap();
}
let object = guard.open_leaf().unwrap();
assert!(guard.bind(object).is_err(), "replacement was bound");
drop(guard);
assert!(held.is_dir() && path.is_dir());
if nonempty {
assert_eq!(
std::fs::read_to_string(path.join("sentinel")).unwrap(),
"keep"
);
}
}
}
}