Skip to main content

sys_shred/core/
overwrite.rs

1//! # Low-Level Overwrite Engine
2//!
3//! This module implements the core byte-level overwriting logic. It is designed
4//! to be hardware-aware, utilizing `sync_all` to bypass Operating System
5//! write buffers and ensure data reaches the physical storage medium.
6
7use crate::cli::args::ShredMethod;
8use crate::error::{ShredError, ShredResult};
9use rand::{rngs::StdRng, RngCore, SeedableRng};
10use std::fs::File;
11use std::io::{Read, Seek, SeekFrom, Write};
12#[cfg(unix)]
13use std::os::unix::io::AsRawFd;
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::Arc;
16
17/// A specialized engine for performing secure cryptographic overwrites on a file.
18pub struct Overwriter<'a> {
19    file: &'a mut File,
20    buffer_size: usize,
21    verify: bool,
22    cancelled: Arc<AtomicBool>,
23}
24
25impl<'a> Overwriter<'a> {
26    /// Creates a new `Overwriter` for a specific file handle.
27    pub fn new(file: &'a mut File, verify: bool, cancelled: Arc<AtomicBool>) -> Self {
28        // Apply OS-specific hints for aggressive cache bypassing
29        #[cfg(target_os = "macos")]
30        {
31            let fd = file.as_raw_fd();
32            unsafe {
33                // F_NOCACHE: Turns off caching for this file descriptor.
34                libc::fcntl(fd, libc::F_NOCACHE, 1);
35            }
36        }
37
38        #[cfg(target_os = "linux")]
39        {
40            let fd = file.as_raw_fd();
41            unsafe {
42                // POSIX_FADV_DONTNEED: Hint to the kernel that we don't need this data in cache.
43                libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_DONTNEED);
44            }
45        }
46
47        Self {
48            file,
49            buffer_size: 64 * 1024,
50            verify,
51            cancelled,
52        }
53    }
54
55    fn is_cancelled(&self) -> bool {
56        self.cancelled.load(Ordering::Relaxed)
57    }
58
59    /// Executes the full destruction sequence based on the selected method.
60    pub fn execute(&mut self, method: ShredMethod, passes: u32) -> ShredResult<()> {
61        match method {
62            ShredMethod::Zero => self.run_zero_pass()?,
63            ShredMethod::Random => {
64                for _ in 0..passes {
65                    if self.is_cancelled() {
66                        break;
67                    }
68                    self.run_random_pass()?;
69                }
70            }
71            ShredMethod::Dod => {
72                if !self.is_cancelled() {
73                    self.run_fixed_pass(0x00)?;
74                }
75                if !self.is_cancelled() {
76                    self.run_fixed_pass(0xFF)?;
77                }
78                if !self.is_cancelled() {
79                    self.run_random_pass()?;
80                }
81            }
82            ShredMethod::Gutmann => {
83                // Gutmann 35-pass sequence
84                for _ in 0..4 {
85                    if self.is_cancelled() {
86                        return Ok(());
87                    }
88                    self.run_random_pass()?;
89                }
90                let patterns = [
91                    0x55, 0xAA, 0x92, 0x49, 0x24, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
92                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x92, 0x49, 0x24, 0x6D, 0xB6,
93                    0xDB,
94                ];
95                for &p in &patterns {
96                    if self.is_cancelled() {
97                        return Ok(());
98                    }
99                    self.run_fixed_pass(p)?;
100                }
101                for _ in 0..4 {
102                    if self.is_cancelled() {
103                        return Ok(());
104                    }
105                    self.run_random_pass()?;
106                }
107            }
108        }
109        Ok(())
110    }
111
112    fn run_zero_pass(&mut self) -> ShredResult<()> {
113        self.run_fixed_pass(0x00)
114    }
115
116    fn run_fixed_pass(&mut self, byte: u8) -> ShredResult<()> {
117        let file_size = self.file.metadata()?.len();
118        self.file.seek(SeekFrom::Start(0))?;
119
120        let buffer = vec![byte; self.buffer_size];
121        let mut total_written: u64 = 0;
122
123        while total_written < file_size {
124            if self.is_cancelled() {
125                return Ok(());
126            }
127            let remaining = file_size - total_written;
128            let current_chunk = std::cmp::min(self.buffer_size as u64, remaining) as usize;
129            self.file.write_all(&buffer[..current_chunk])?;
130            total_written += current_chunk as u64;
131        }
132
133        self.file.sync_all()?;
134
135        if self.verify {
136            self.verify_pass(byte)?;
137        }
138
139        Ok(())
140    }
141
142    fn run_random_pass(&mut self) -> ShredResult<()> {
143        let file_size = self.file.metadata()?.len();
144        self.file.seek(SeekFrom::Start(0))?;
145
146        let mut rng = StdRng::from_entropy();
147        let mut buffer = vec![0u8; self.buffer_size];
148        let mut total_written: u64 = 0;
149
150        while total_written < file_size {
151            if self.is_cancelled() {
152                return Ok(());
153            }
154            let remaining = file_size - total_written;
155            let current_chunk = std::cmp::min(self.buffer_size as u64, remaining) as usize;
156            rng.fill_bytes(&mut buffer[..current_chunk]);
157            self.file.write_all(&buffer[..current_chunk])?;
158            total_written += current_chunk as u64;
159        }
160
161        self.file.sync_all()?;
162
163        // For random, we just verify that it's readable and matches nothing specific?
164        // Actually, for random, verification is harder unless we keep the seed.
165        // We'll just skip detailed content verification for random for now,
166        // or just verify it's not all zeros if that's what we want.
167        // Most tools just verify fixed patterns.
168
169        Ok(())
170    }
171
172    fn verify_pass(&mut self, expected_byte: u8) -> ShredResult<()> {
173        let file_size = self.file.metadata()?.len();
174        self.file.seek(SeekFrom::Start(0))?;
175
176        let mut buffer = vec![0u8; self.buffer_size];
177        let mut total_read: u64 = 0;
178
179        while total_read < file_size {
180            if self.is_cancelled() {
181                return Ok(());
182            }
183            let remaining = file_size - total_read;
184            let current_chunk = std::cmp::min(self.buffer_size as u64, remaining) as usize;
185            self.file.read_exact(&mut buffer[..current_chunk])?;
186
187            for &byte in &buffer[..current_chunk] {
188                if self.is_cancelled() {
189                    return Ok(());
190                }
191                if byte != expected_byte {
192                    return Err(ShredError::Obfuscation(format!(
193                        "Verification failed: Expected 0x{:02X}, found 0x{:02X} at offset {}",
194                        expected_byte, byte, total_read
195                    )));
196                }
197            }
198            total_read += current_chunk as u64;
199        }
200        Ok(())
201    }
202}