use crate::{Error, Store, StoredFile};
use decmpfs::Gate;
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
thread_local! {
static B3_HASHER: RefCell<blake3::Hasher> = RefCell::new(blake3::Hasher::new());
}
#[cfg(target_os = "macos")]
static FAST_PATH_SHARD_LOCKS: [std::sync::Mutex<()>; 256] =
[const { std::sync::Mutex::new(()) }; 256];
pub(crate) fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let file_type = entry.file_type()?;
let from = entry.path();
let to = dst.join(entry.file_name());
if file_type.is_dir() {
copy_dir_recursive(&from, &to)?;
} else if file_type.is_file() {
std::fs::copy(&from, &to)?;
}
}
Ok(())
}
pub(crate) fn blake3_hex(content: &[u8]) -> String {
B3_HASHER.with(|cell| {
let mut h = cell.borrow_mut();
h.reset();
h.update(content);
h.finalize().to_hex().to_string()
})
}
pub(crate) fn cas_file_matches_len(path: &Path, expected_len: u64) -> bool {
path.metadata()
.map(|metadata| metadata.len() == expected_len)
.unwrap_or(false)
}
fn wait_for_cas_file_len(path: &Path, expected_len: u64) {
let deadline = std::time::Instant::now() + std::time::Duration::from_millis(50);
while !cas_file_matches_len(path, expected_len) && std::time::Instant::now() < deadline {
std::thread::sleep(std::time::Duration::from_micros(250));
}
}
fn store_compression_gate() -> Option<&'static Gate> {
static GATE: std::sync::OnceLock<Option<Gate>> = std::sync::OnceLock::new();
GATE.get_or_init(|| {
let raw = aube_util::env::embedder_env("COMPRESS_STORE")?;
parse_compress_store_gate(&raw.to_string_lossy())
})
.as_ref()
}
pub(crate) fn parse_compress_store_gate(spec: &str) -> Option<Gate> {
let trimmed = spec.trim();
if trimmed.is_empty() || matches!(trimmed, "1" | "true" | "on" | "yes") {
return Some(Gate::default());
}
let mut glob: Option<&str> = None;
let mut size: Option<&str> = None;
for part in trimmed.split(';') {
let part = part.trim();
if let Some(rest) = part.strip_prefix("glob:") {
glob = Some(rest.trim());
} else if let Some(rest) = part.strip_prefix("size:") {
size = Some(rest.trim());
}
}
if glob.is_none() && size.is_none() {
return Some(Gate::default());
}
match Gate::new(glob.or(Some(decmpfs::DEFAULT_GLOB)), size) {
Ok(gate) => Some(gate),
Err(err) => {
warn!(
"AUBE_COMPRESS_STORE has an invalid size predicate ({err}); \
store compression disabled"
);
None
}
}
}
#[cfg(test)]
mod compress_gate_tests {
use super::parse_compress_store_gate;
#[test]
fn affirmative_and_directives_yield_a_gate() {
for spec in ["", "1", "true", "on", "yes", "whatever"] {
assert!(
parse_compress_store_gate(spec).is_some(),
"spec {spec:?} should produce a gate"
);
}
assert!(parse_compress_store_gate("glob:**/*.so").is_some());
assert!(parse_compress_store_gate("size:>= 1MB").is_some());
assert!(parse_compress_store_gate("glob:**/*.node;size:>= 512KB").is_some());
}
#[test]
fn malformed_size_predicate_fails_closed() {
assert!(parse_compress_store_gate("size:banana").is_none());
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CasWriteOutcome {
Created,
AlreadyExisted,
}
impl Store {
pub fn ensure_shards_exist(&self) -> Result<(), Error> {
std::fs::create_dir_all(&self.root).map_err(|e| Error::Io(self.root.clone(), e))?;
aube_util::fs::set_not_content_indexed(&self.root);
let mut buf = [0u8; 2];
for hi in 0u8..16 {
for lo in 0u8..16 {
buf[0] = hex_digit(hi);
buf[1] = hex_digit(lo);
let shard = std::str::from_utf8(&buf).unwrap();
let path = self.root.join(shard);
std::fs::create_dir_all(&path).map_err(|e| Error::Io(path, e))?;
}
}
Ok(())
}
fn create_cas_file(
&self,
path: &Path,
content: Option<&[u8]>,
) -> Result<CasWriteOutcome, Error> {
fn do_create_and_write(
this: &Store,
path: &Path,
content: Option<&[u8]>,
) -> Result<CasWriteOutcome, Error> {
if let Some(bytes) = content {
#[cfg(target_os = "linux")]
{
static O_TMPFILE_DISABLED: std::sync::OnceLock<bool> =
std::sync::OnceLock::new();
let disabled = *O_TMPFILE_DISABLED.get_or_init(|| {
aube_util::env::embedder_env("DISABLE_O_TMPFILE").is_some()
});
if !disabled {
match try_o_tmpfile_publish(path, bytes) {
Ok(outcome) => return Ok(outcome),
Err(OTmpfileFallback::Unsupported) => {}
Err(OTmpfileFallback::Hard(e)) => return Err(e),
}
}
}
#[cfg(target_os = "macos")]
if this.fast_path.load(Ordering::Acquire) {
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
let shard_idx = path
.parent()
.and_then(|p| p.file_name())
.and_then(|s| s.to_str())
.and_then(|s| u8::from_str_radix(s, 16).ok())
.map(|b| b as usize);
debug_assert!(
shard_idx.is_some(),
"fast-path CAS write to path without a valid hex shard parent: {}",
path.display()
);
if let Some(i) = shard_idx {
let _shard_guard = FAST_PATH_SHARD_LOCKS[i]
.lock()
.unwrap_or_else(|p| p.into_inner());
use std::os::unix::fs::PermissionsExt;
let force_mode = std::fs::Permissions::from_mode(0o644);
let open_result = std::fs::OpenOptions::new()
.mode(0o644)
.create_new(true)
.write(true)
.open(path);
match open_result {
Ok(mut f) => {
f.set_permissions(force_mode.clone())
.map_err(|e| Error::Io(path.to_path_buf(), e))?;
f.write_all(bytes)
.map_err(|e| Error::Io(path.to_path_buf(), e))?;
return Ok(CasWriteOutcome::Created);
}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
if cas_file_matches_len(path, bytes.len() as u64) {
return Ok(CasWriteOutcome::AlreadyExisted);
}
let _ = xx::file::remove_file(path);
match std::fs::OpenOptions::new()
.mode(0o644)
.create_new(true)
.write(true)
.open(path)
{
Ok(mut f) => {
f.set_permissions(force_mode)
.map_err(|e| Error::Io(path.to_path_buf(), e))?;
f.write_all(bytes)
.map_err(|e| Error::Io(path.to_path_buf(), e))?;
return Ok(CasWriteOutcome::Created);
}
Err(e) => {
return Err(Error::Io(path.to_path_buf(), e));
}
}
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
}
Err(e) => return Err(Error::Io(path.to_path_buf(), e)),
}
}
}
let _ = this; let parent = path.parent().ok_or_else(|| {
Error::Io(path.to_path_buf(), std::io::ErrorKind::NotFound.into())
})?;
let mut tmp = tempfile::Builder::new()
.prefix(".aube-cas-")
.tempfile_in(parent)
.map_err(|e| Error::Io(path.to_path_buf(), e))?;
use std::io::Write;
tmp.write_all(bytes)
.map_err(|e| Error::Io(path.to_path_buf(), e))?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
tmp.as_file()
.set_permissions(std::fs::Permissions::from_mode(0o644))
.map_err(|e| Error::Io(path.to_path_buf(), e))?;
}
return match tmp.persist_noclobber(path) {
Ok(_) => Ok(CasWriteOutcome::Created),
Err(e) if e.error.kind() == std::io::ErrorKind::AlreadyExists => {
Ok(CasWriteOutcome::AlreadyExisted)
}
Err(e) => Err(Error::Io(path.to_path_buf(), e.error)),
};
}
match std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
{
Ok(_) => Ok(CasWriteOutcome::Created),
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
Ok(CasWriteOutcome::AlreadyExisted)
}
Err(e) => Err(Error::Io(path.to_path_buf(), e)),
}
}
match do_create_and_write(self, path, content) {
Ok(outcome) => Ok(outcome),
Err(Error::Io(_, ref ioe)) if ioe.kind() == std::io::ErrorKind::NotFound => {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| Error::Io(parent.to_path_buf(), e))?;
}
do_create_and_write(self, path, content)
}
Err(e) => Err(e),
}
}
pub fn import_bytes(&self, content: &[u8], executable: bool) -> Result<StoredFile, Error> {
let hash_t0 = std::time::Instant::now();
let hex_hash = blake3_hex(content);
if aube_util::diag::enabled() {
aube_util::diag::event_lazy(
aube_util::diag::Category::Store,
"blake3_hash",
hash_t0.elapsed(),
|| format!(r#"{{"size":{}}}"#, content.len()),
);
}
let store_path = self.file_path_from_hex(&hex_hash);
let _diag_write =
aube_util::diag::Span::new(aube_util::diag::Category::Store, "import_bytes_write")
.with_meta_fn(|| format!(r#"{{"size":{}}}"#, content.len()));
let outcome = self.create_cas_file(&store_path, Some(content))?;
if aube_util::diag::enabled() {
let name = match outcome {
CasWriteOutcome::Created => "cas_miss",
CasWriteOutcome::AlreadyExisted => "cas_hit",
};
aube_util::diag::instant_lazy(aube_util::diag::Category::Store, name, || {
format!(r#"{{"size":{}}}"#, content.len())
});
}
let fast_path_handled_recovery =
cfg!(target_os = "macos") && self.fast_path.load(Ordering::Acquire);
if outcome == CasWriteOutcome::AlreadyExisted && !fast_path_handled_recovery {
if !cas_file_matches_len(&store_path, content.len() as u64) {
wait_for_cas_file_len(&store_path, content.len() as u64);
}
if !cas_file_matches_len(&store_path, content.len() as u64) {
let _ = xx::file::remove_file(&store_path);
self.create_cas_file(&store_path, Some(content))?;
if !cas_file_matches_len(&store_path, content.len() as u64) {
let actual_len = store_path.metadata().map(|metadata| metadata.len()).ok();
return Err(Error::Io(
store_path.clone(),
std::io::Error::other(format!(
"CAS entry has wrong size after import: expected {} bytes, got {}",
content.len(),
actual_len
.map(|len| format!("{len} bytes"))
.unwrap_or_else(|| "missing file".to_owned())
)),
));
}
}
}
if executable {
self.write_exec_marker(&store_path)?;
}
Ok(StoredFile {
hex_hash,
store_path,
executable,
size: Some(content.len() as u64),
})
}
pub fn import_bytes_gated(
&self,
rel_path: &str,
content: &[u8],
executable: bool,
) -> Result<StoredFile, Error> {
self.import_bytes_with_gate(rel_path, content, executable, store_compression_gate())
}
pub(crate) fn import_bytes_with_gate(
&self,
rel_path: &str,
content: &[u8],
executable: bool,
gate: Option<&Gate>,
) -> Result<StoredFile, Error> {
let Some(gate) = gate else {
return self.import_bytes(content, executable);
};
let unwrapped = decmpfs::addon::unwrap_if_hybrid(content);
let stored_bytes: &[u8] = unwrapped.as_deref().unwrap_or(content);
if !gate.matches(rel_path, stored_bytes.len() as u64) {
return self.import_bytes(stored_bytes, executable);
}
let hash_t0 = std::time::Instant::now();
let hex_hash = blake3_hex(stored_bytes);
if aube_util::diag::enabled() {
aube_util::diag::event_lazy(
aube_util::diag::Category::Store,
"blake3_hash",
hash_t0.elapsed(),
|| format!(r#"{{"size":{}}}"#, stored_bytes.len()),
);
}
let store_path = self.file_path_from_hex(&hex_hash);
if cas_file_matches_len(&store_path, stored_bytes.len() as u64) {
if aube_util::diag::enabled() {
aube_util::diag::instant_lazy(aube_util::diag::Category::Store, "cas_hit", || {
format!(r#"{{"size":{}}}"#, stored_bytes.len())
});
}
return self.finish_gated(hex_hash, store_path, executable, stored_bytes.len());
}
if let Some(parent) = store_path.parent()
&& !parent.exists()
{
std::fs::create_dir_all(parent).map_err(|e| Error::Io(parent.to_path_buf(), e))?;
}
match decmpfs::compress_bytes(&store_path, stored_bytes, &Gate::any()) {
Ok(_) => {}
Err(err) => {
warn!(
"decmpfs one-pass write failed for {} ({err}); \
falling back to the plain CAS path",
store_path.display()
);
return self.import_bytes(stored_bytes, executable);
}
}
if !cas_file_matches_len(&store_path, stored_bytes.len() as u64) {
wait_for_cas_file_len(&store_path, stored_bytes.len() as u64);
}
if !cas_file_matches_len(&store_path, stored_bytes.len() as u64) {
let _ = xx::file::remove_file(&store_path);
return self.import_bytes(stored_bytes, executable);
}
if aube_util::diag::enabled() {
aube_util::diag::instant_lazy(aube_util::diag::Category::Store, "cas_miss", || {
format!(r#"{{"size":{}}}"#, stored_bytes.len())
});
}
self.finish_gated(hex_hash, store_path, executable, stored_bytes.len())
}
fn write_exec_marker(&self, store_path: &Path) -> Result<(), Error> {
let exec_marker = PathBuf::from(format!("{}-exec", store_path.display()));
self.create_cas_file(&exec_marker, None)?;
Ok(())
}
fn finish_gated(
&self,
hex_hash: String,
store_path: PathBuf,
executable: bool,
len: usize,
) -> Result<StoredFile, Error> {
if executable {
self.write_exec_marker(&store_path)?;
}
Ok(StoredFile {
hex_hash,
store_path,
executable,
size: Some(len as u64),
})
}
}
#[cfg(target_os = "linux")]
fn posix_fallocate(file: &std::fs::File, len: libc::off_t) -> std::io::Result<()> {
use std::os::fd::AsRawFd;
if len <= 0 {
return Ok(());
}
let r = unsafe { libc::posix_fallocate(file.as_raw_fd(), 0, len) };
if r == 0 {
Ok(())
} else {
Err(std::io::Error::from_raw_os_error(r))
}
}
#[cfg(target_os = "linux")]
enum OTmpfileFallback {
Unsupported,
Hard(Error),
}
#[cfg(target_os = "linux")]
const CAS_SMALL_FILE_THRESHOLD_DEFAULT: usize = 64 * 1024;
#[cfg(target_os = "linux")]
fn cas_small_file_threshold() -> usize {
static THRESHOLD: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*THRESHOLD.get_or_init(|| {
match aube_util::env::embedder_env("CAS_SMALL_FILE_THRESHOLD")
.as_deref()
.map(|s| s.to_string_lossy().into_owned())
{
None => CAS_SMALL_FILE_THRESHOLD_DEFAULT,
Some(raw) => raw.parse::<usize>().unwrap_or_else(|_| {
warn!(
"CAS_SMALL_FILE_THRESHOLD={raw:?} is not a non-negative integer; \
falling back to default {CAS_SMALL_FILE_THRESHOLD_DEFAULT}"
);
CAS_SMALL_FILE_THRESHOLD_DEFAULT
}),
}
})
}
#[cfg(target_os = "linux")]
fn try_o_tmpfile_publish(path: &Path, bytes: &[u8]) -> Result<CasWriteOutcome, OTmpfileFallback> {
use std::ffi::CString;
use std::io::Write;
use std::os::fd::FromRawFd;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::PermissionsExt;
let parent = path.parent().ok_or(OTmpfileFallback::Hard(Error::Io(
path.to_path_buf(),
std::io::ErrorKind::NotFound.into(),
)))?;
let parent_c = CString::new(parent.as_os_str().as_bytes()).map_err(|_| {
OTmpfileFallback::Hard(Error::Io(
path.to_path_buf(),
std::io::Error::new(std::io::ErrorKind::InvalidInput, "parent path has nul"),
))
})?;
let raw_fd = unsafe {
libc::open(
parent_c.as_ptr(),
libc::O_TMPFILE | libc::O_RDWR | libc::O_CLOEXEC,
0o644 as libc::c_uint,
)
};
if raw_fd < 0 {
let err = std::io::Error::last_os_error();
return match err.raw_os_error() {
Some(libc::EOPNOTSUPP) | Some(libc::EISDIR) | Some(libc::EINVAL) => {
Err(OTmpfileFallback::Unsupported)
}
_ => Err(OTmpfileFallback::Hard(Error::Io(path.to_path_buf(), err))),
};
}
let owned = unsafe { std::os::fd::OwnedFd::from_raw_fd(raw_fd) };
let mut file = std::fs::File::from(owned);
let small_threshold = cas_small_file_threshold();
let is_large = bytes.len() >= small_threshold;
if is_large {
let _ = posix_fallocate(&file, bytes.len() as libc::off_t);
}
file.write_all(bytes)
.map_err(|e| OTmpfileFallback::Hard(Error::Io(path.to_path_buf(), e)))?;
file.set_permissions(std::fs::Permissions::from_mode(0o644))
.map_err(|e| OTmpfileFallback::Hard(Error::Io(path.to_path_buf(), e)))?;
let proc_link = format!("/proc/self/fd/{}", std::os::fd::AsRawFd::as_raw_fd(&file));
let proc_c = CString::new(proc_link.as_bytes()).map_err(|_| {
OTmpfileFallback::Hard(Error::Io(
path.to_path_buf(),
std::io::Error::other("fd path has nul"),
))
})?;
let final_c = CString::new(path.as_os_str().as_bytes()).map_err(|_| {
OTmpfileFallback::Hard(Error::Io(
path.to_path_buf(),
std::io::Error::new(std::io::ErrorKind::InvalidInput, "path has nul"),
))
})?;
let r = unsafe {
libc::linkat(
libc::AT_FDCWD,
proc_c.as_ptr(),
libc::AT_FDCWD,
final_c.as_ptr(),
libc::AT_SYMLINK_FOLLOW,
)
};
if r == 0 {
if is_large {
use std::os::fd::AsRawFd;
let fd = file.as_raw_fd();
unsafe {
libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_DONTNEED);
}
}
return Ok(CasWriteOutcome::Created);
}
let err = std::io::Error::last_os_error();
match err.raw_os_error() {
Some(libc::EEXIST) => Ok(CasWriteOutcome::AlreadyExisted),
Some(libc::ENOENT) => Err(OTmpfileFallback::Unsupported),
Some(libc::EOPNOTSUPP) | Some(libc::EXDEV) => Err(OTmpfileFallback::Unsupported),
Some(libc::EPERM) | Some(libc::EACCES) => Err(OTmpfileFallback::Unsupported),
_ => Err(OTmpfileFallback::Hard(Error::Io(path.to_path_buf(), err))),
}
}
fn hex_digit(n: u8) -> u8 {
match n {
0..=9 => b'0' + n,
10..=15 => b'a' + n - 10,
_ => unreachable!(),
}
}