use std::fs::{self, OpenOptions};
use std::io::{self, Seek, SeekFrom, Write};
use std::path::Path;
pub fn shred_file(path: &Path) -> io::Result<()> {
let len = fs::metadata(path)?.len();
if len > 0 {
let mut f = OpenOptions::new().write(true).open(path)?;
f.seek(SeekFrom::Start(0))?;
const BUF_SIZE: usize = 65536;
let buf = vec![0u8; BUF_SIZE];
let mut remaining = len as usize;
while remaining > 0 {
let n = remaining.min(BUF_SIZE);
f.write_all(&buf[..n])?;
remaining -= n;
}
f.sync_all()?;
}
fs::remove_file(path)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::NamedTempFile;
#[test]
fn shred_removes_file() {
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(b"secret data").unwrap();
let (_, path) = tmp.keep().unwrap();
shred_file(&path).unwrap();
assert!(!path.exists(), "file must be removed after shredding");
}
#[test]
fn shred_empty_file_succeeds() {
let tmp = NamedTempFile::new().unwrap();
let (_, path) = tmp.keep().unwrap();
shred_file(&path).unwrap();
assert!(!path.exists());
}
#[test]
fn shred_nonexistent_file_returns_error() {
let result = shred_file(Path::new("/nonexistent/path/to/file.txt"));
assert!(result.is_err());
}
#[test]
fn shred_large_file_succeeds() {
let mut tmp = NamedTempFile::new().unwrap();
tmp.write_all(&vec![0xABu8; 200 * 1024]).unwrap();
let (_, path) = tmp.keep().unwrap();
shred_file(&path).unwrap();
assert!(!path.exists());
}
}