hive-priv: never leave an agent dir or the agents root root-owned on a fresh host
This commit is contained in:
parent
52cdfe84a0
commit
24c5c02942
1 changed files with 78 additions and 5 deletions
|
|
@ -244,17 +244,31 @@ async fn ensure_agent_subvolume(
|
|||
let root = env.state_root;
|
||||
let agent_root = root.join(agent_name);
|
||||
|
||||
// Progressive: existing agents (plain dir OR already a subvol) are left
|
||||
// untouched — never auto-migrated.
|
||||
// Progressive: existing agents (plain dir OR already a subvol) are never
|
||||
// auto-migrated. The one exception is an agent dir this op itself left
|
||||
// root-owned on a fresh host: hive-c0re cannot create below it.
|
||||
if agent_root.exists() {
|
||||
if let (Ok(agent), Ok(root_meta)) = (
|
||||
std::fs::symlink_metadata(&agent_root),
|
||||
std::fs::metadata(root),
|
||||
) && is_stranded_root_dir(agent.uid(), root_meta.uid())
|
||||
{
|
||||
std::os::unix::fs::lchown(&agent_root, Some(root_meta.uid()), Some(root_meta.gid()))
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"chown stranded {} to agents-root owner",
|
||||
agent_root.display()
|
||||
)
|
||||
})?;
|
||||
tracing::warn!(agent = %agent_name, path = %agent_root.display(), "handed a root-owned agent dir back to the agents-root owner");
|
||||
}
|
||||
return Ok((String::new(), String::new()));
|
||||
}
|
||||
|
||||
// Only btrfs supports subvolumes; on anything else hive-c0re's normal
|
||||
// `create_dir_all` makes a plain dir (the pre-subvolume behaviour). The
|
||||
// parent must exist for both statfs and `btrfs subvolume create`.
|
||||
std::fs::create_dir_all(root)
|
||||
.with_context(|| format!("create agents root {}", root.display()))?;
|
||||
ensure_agents_root(root)?;
|
||||
if !env.probes.on_btrfs(root)? {
|
||||
return Ok((String::new(), String::new()));
|
||||
}
|
||||
|
|
@ -306,6 +320,30 @@ async fn ensure_agent_subvolume(
|
|||
Ok((String::new(), String::new()))
|
||||
}
|
||||
|
||||
/// Make the agents root if it is missing. Made by root here, so it is handed
|
||||
/// to the owner of its parent (hive-core's state dir); the subvolume takes the
|
||||
/// root's owner, and a root-owned root would strand every agent dir under it.
|
||||
/// One made meanwhile by the tmpfiles rule is left as that rule made it.
|
||||
fn ensure_agents_root(root: &Path) -> Result<()> {
|
||||
use std::os::unix::fs::MetadataExt as _;
|
||||
|
||||
match std::fs::create_dir(root) {
|
||||
Ok(()) => {}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => return Ok(()),
|
||||
Err(e) => return Err(e).with_context(|| format!("create agents root {}", root.display())),
|
||||
}
|
||||
let parent = std::fs::metadata(root.parent().unwrap_or(root))
|
||||
.with_context(|| format!("stat parent of {}", root.display()))?;
|
||||
std::os::unix::fs::chown(root, Some(parent.uid()), Some(parent.gid()))
|
||||
.with_context(|| format!("chown agents root {} to its parent's owner", root.display()))
|
||||
}
|
||||
|
||||
/// An agent dir owned by root under an agents root that is not: hive-c0re
|
||||
/// (hive-core) cannot create `claude/` or `state/` in it.
|
||||
fn is_stranded_root_dir(agent_uid: u32, root_uid: u32) -> bool {
|
||||
agent_uid == 0 && root_uid != 0
|
||||
}
|
||||
|
||||
/// `DeleteAgentSubvolume` — delete the agent's state root iff it's a btrfs
|
||||
/// subvolume (purge path only). No-op for plain dirs / missing paths;
|
||||
/// hive-c0re's own `remove_dir_all` covers those. See the wire doc.
|
||||
|
|
@ -1506,6 +1544,37 @@ esac"#;
|
|||
assert!(!fx.agent("iris").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_agents_root_is_made_once_and_a_racing_creator_is_not_an_error() {
|
||||
use super::ensure_agents_root;
|
||||
use std::os::unix::fs::MetadataExt as _;
|
||||
let fx = Fx::new();
|
||||
let root = fx.state.join("made");
|
||||
ensure_agents_root(&root).unwrap();
|
||||
let (dir, parent) = (
|
||||
std::fs::metadata(&root).unwrap(),
|
||||
std::fs::metadata(&fx.state).unwrap(),
|
||||
);
|
||||
assert_eq!((dir.uid(), dir.gid()), (parent.uid(), parent.gid()));
|
||||
ensure_agents_root(&root).unwrap();
|
||||
assert!(ensure_agents_root(&root.join("no/parent")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_a_root_owned_dir_under_a_non_root_root_is_stranded() {
|
||||
use super::is_stranded_root_dir;
|
||||
assert!(is_stranded_root_dir(0, 998));
|
||||
assert!(
|
||||
!is_stranded_root_dir(0, 0),
|
||||
"a root-run host is not stranded"
|
||||
);
|
||||
assert!(!is_stranded_root_dir(998, 998));
|
||||
assert!(
|
||||
!is_stranded_root_dir(1000, 998),
|
||||
"never touch an agent-owned dir"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ensure_creates_the_subvolume_owned_like_the_root() {
|
||||
use std::os::unix::fs::MetadataExt as _;
|
||||
|
|
|
|||
Loading…
Reference in a new issue