use crate::error::{BallError, Result};
use std::fs::OpenOptions;
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
pub struct CtxFile {
path: PathBuf,
path_str: String,
}
impl CtxFile {
pub fn new(contents: &str) -> Result<Self> {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before unix epoch")
.as_nanos();
let path = std::env::temp_dir().join(format!(
"balls-eventctx-{}-{}.json",
std::process::id(),
nanos
));
let mut f = OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(&path)
.map_err(BallError::Io)?;
f.write_all(contents.as_bytes()).map_err(BallError::Io)?;
let path_str = path.to_string_lossy().into_owned();
Ok(Self { path, path_str })
}
pub fn path_str(&self) -> &str {
&self.path_str
}
}
impl Drop for CtxFile {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::os::unix::fs::PermissionsExt;
#[test]
fn writes_at_0600_then_removes_on_drop() {
let p;
{
let cf = CtxFile::new(r#"{"schema_version":1}"#).unwrap();
p = cf.path_str().to_string();
assert_eq!(
std::fs::read_to_string(&p).unwrap(),
r#"{"schema_version":1}"#
);
let mode = std::fs::metadata(&p).unwrap().permissions().mode();
assert_eq!(mode & 0o777, 0o600, "ctx file must not be world-readable");
}
assert!(
!std::path::Path::new(&p).exists(),
"the file must be gone once the CtxFile is dropped"
);
}
}