circus/crates/common/src/gc_roots.rs
NotAShelf a52c61556a
crates/common: enhance notifications and GC roots handling
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I8a19eeda480672f8f22adbafb3039db66a6a6964
2026-02-02 01:49:29 +03:00

103 lines
3.1 KiB
Rust

//! GC root management - prevents nix-store --gc from deleting build outputs
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::time::Duration;
use tracing::{info, warn};
/// Remove GC root symlinks with mtime older than max_age. Returns count removed.
pub fn cleanup_old_roots(roots_dir: &Path, max_age: Duration) -> std::io::Result<u64> {
if !roots_dir.exists() {
return Ok(0);
}
let mut count = 0u64;
let now = std::time::SystemTime::now();
for entry in std::fs::read_dir(roots_dir)? {
let entry = entry?;
let metadata = match entry.metadata() {
Ok(m) => m,
Err(_) => continue,
};
let modified = match metadata.modified() {
Ok(t) => t,
Err(_) => continue,
};
if let Ok(age) = now.duration_since(modified)
&& age > max_age {
if let Err(e) = std::fs::remove_file(entry.path()) {
warn!(
"Failed to remove old GC root {}: {e}",
entry.path().display()
);
} else {
count += 1;
}
}
}
Ok(count)
}
pub struct GcRoots {
roots_dir: PathBuf,
enabled: bool,
}
impl GcRoots {
pub fn new(roots_dir: PathBuf, enabled: bool) -> std::io::Result<Self> {
if enabled {
std::fs::create_dir_all(&roots_dir)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&roots_dir, std::fs::Permissions::from_mode(0o700))?;
}
}
Ok(Self { roots_dir, enabled })
}
/// Register a GC root for a build output. Returns the symlink path.
pub fn register(
&self,
build_id: &uuid::Uuid,
output_path: &str,
) -> std::io::Result<Option<PathBuf>> {
if !self.enabled {
return Ok(None);
}
if !crate::validate::is_valid_store_path(output_path) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Invalid store path: {output_path}"),
));
}
let link_path = self.roots_dir.join(build_id.to_string());
// Remove existing symlink if present
if link_path.exists() || link_path.symlink_metadata().is_ok() {
std::fs::remove_file(&link_path)?;
}
symlink(output_path, &link_path)?;
info!(build_id = %build_id, output = output_path, "Registered GC root");
Ok(Some(link_path))
}
/// Remove a GC root for a build.
pub fn remove(&self, build_id: &uuid::Uuid) {
if !self.enabled {
return;
}
let link_path = self.roots_dir.join(build_id.to_string());
if let Err(e) = std::fs::remove_file(&link_path) {
if e.kind() != std::io::ErrorKind::NotFound {
warn!(build_id = %build_id, "Failed to remove GC root: {e}");
}
} else {
info!(build_id = %build_id, "Removed GC root");
}
}
}