Skip to main content

sys_shred/core/
metadata.rs

1//! # Metadata & Filename Obfuscation
2//!
3//! Handles the transformation of file metadata to prevent path-based recovery
4//! and information leakage through filenames.
5
6use crate::error::{ShredError, ShredResult};
7use rand::{distributions::Alphanumeric, Rng};
8use std::fs;
9use std::path::{Path, PathBuf};
10
11#[cfg(unix)]
12use std::os::unix::io::AsRawFd;
13
14/// A utility for scrubbing and randomizing file metadata.
15///
16/// This component focuses on the anti-forensic aspects of shredding that go
17/// beyond raw data destruction.
18pub struct MetadataHandler;
19
20impl MetadataHandler {
21    /// Renames a file to a randomly generated alphanumeric string.
22    pub fn obfuscate_filename(path: &Path) -> ShredResult<PathBuf> {
23        let parent = path.parent().unwrap_or_else(|| Path::new("."));
24
25        // Retry logic to handle rare filename collisions
26        for _ in 0..5 {
27            let random_name: String = rand::thread_rng()
28                .sample_iter(&Alphanumeric)
29                .take(16)
30                .map(char::from)
31                .collect();
32
33            let new_path = parent.join(random_name);
34            if !new_path.exists() {
35                fs::rename(path, &new_path)?;
36                return Ok(new_path);
37            }
38        }
39
40        Err(ShredError::Obfuscation(
41            "Failed to generate a unique random filename after multiple attempts".to_string(),
42        ))
43    }
44
45    /// Truncates a file to zero bytes and flushes the change.
46    pub fn truncate(path: &Path) -> ShredResult<()> {
47        let file = fs::OpenOptions::new().write(true).open(path)?;
48        file.set_len(0)?;
49        file.sync_all()?;
50        Ok(())
51    }
52
53    /// Informs the OS/Hardware to discard the file's blocks (TRIM).
54    ///
55    /// On Linux, this uses `fallocate` with `FALLOC_FL_PUNCH_HOLE`.
56    /// On Windows, it uses `FSCTL_SET_ZERO_DATA`.
57    pub fn trim(path: &Path) -> ShredResult<()> {
58        let file = fs::OpenOptions::new().write(true).open(path)?;
59        let len = file.metadata()?.len();
60        if len == 0 {
61            return Ok(());
62        }
63
64        #[cfg(target_os = "linux")]
65        {
66            let fd = file.as_raw_fd();
67            let res = unsafe {
68                // FALLOC_FL_PUNCH_HOLE (0x02) | FALLOC_FL_KEEP_SIZE (0x01)
69                libc::fallocate(fd, 0x01 | 0x02, 0, len as libc::off_t)
70            };
71            if res != 0 {
72                return Err(ShredError::Io(std::io::Error::last_os_error()));
73            }
74        }
75
76        #[cfg(windows)]
77        {
78            use std::os::windows::io::AsRawHandle;
79            use windows_sys::Win32::Foundation::HANDLE;
80            use windows_sys::Win32::Storage::FileSystem::{
81                FILE_SET_ZERO_DATA_INFORMATION, FSCTL_SET_ZERO_DATA,
82            };
83            use windows_sys::Win32::System::IO::DeviceIoControl;
84
85            let handle = file.as_raw_handle() as HANDLE;
86            let mut info = FILE_SET_ZERO_DATA_INFORMATION {
87                FileOffset: 0,
88                BeyondFinalZero: len as i64,
89            };
90            let mut bytes_returned = 0;
91
92            unsafe {
93                DeviceIoControl(
94                    handle,
95                    FSCTL_SET_ZERO_DATA,
96                    &mut info as *mut _ as *mut _,
97                    std::mem::size_of::<FILE_SET_ZERO_DATA_INFORMATION>() as u32,
98                    std::ptr::null_mut(),
99                    0,
100                    &mut bytes_returned,
101                    std::ptr::null_mut(),
102                );
103            }
104        }
105
106        file.sync_all()?;
107        Ok(())
108    }
109}