Files
sausage/userland/box/src/retention.rs
T

94 lines
2.8 KiB
Rust

//! Append-only diagnostic files, independent of resumable state.
use std::io::{self, Write};
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT: AtomicU64 = AtomicU64::new(0);
pub fn date() -> String {
let days = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
/ 86400;
date_from_days(days as i64)
}
fn date_from_days(days: i64) -> String {
// Gregorian civil date from Unix days (400-year eras).
let z = days + 719468;
let era = z / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = mp + if mp < 10 { 3 } else { -9 };
format!("{:04}-{m:02}-{d:02}", y + i64::from(m <= 2))
}
pub fn create(name: &str, extension: &str) -> io::Result<(String, std::fs::File)> {
let dir = format!("/logs/box/{}", date());
std::fs::create_dir_all(&dir)?;
let name: String = name
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' {
c
} else {
'_'
}
})
.collect();
let mut path = format!("{dir}/{name}.{extension}");
loop {
match std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&path)
{
Ok(f) => return Ok((path, f)),
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
path = format!(
"{dir}/{name}-{}-{}.{extension}",
crate::pid(),
NEXT.fetch_add(1, Ordering::Relaxed)
);
}
Err(e) => return Err(e),
}
}
}
pub fn save(area: &str, name: &str, bytes: &[u8]) -> io::Result<String> {
let dir = format!("/logs/{area}/{}", date());
std::fs::create_dir_all(&dir)?;
loop {
let path = format!(
"{dir}/{name}-{}-{}.log",
crate::pid(),
NEXT.fetch_add(1, Ordering::Relaxed)
);
match std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&path)
{
Ok(mut f) => {
f.write_all(bytes)?;
return Ok(path);
}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
Err(e) => return Err(e),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn archive_calendar_dates() {
assert_eq!(date_from_days(0), "1970-01-01");
assert_eq!(date_from_days(19782), "2024-02-29");
assert_eq!(date_from_days(20635), "2026-07-01");
}
}