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};
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::Arc;
14
15/// A specialized engine for performing secure cryptographic overwrites on a file.
16pub struct Overwriter<'a> {
17    file: &'a mut File,
18    buffer_size: usize,
19    verify: bool,
20    cancelled: Arc<AtomicBool>,
21}
22
23impl<'a> Overwriter<'a> {
24    /// Creates a new `Overwriter` for a specific file handle.
25    pub fn new(file: &'a mut File, verify: bool, cancelled: Arc<AtomicBool>) -> Self {
26        Self {
27            file,
28            buffer_size: 64 * 1024,
29            verify,
30            cancelled,
31        }
32    }
33
34    fn is_cancelled(&self) -> bool {
35        self.cancelled.load(Ordering::Relaxed)
36    }
37
38    /// Executes the full destruction sequence based on the selected method.
39    pub fn execute(&mut self, method: ShredMethod, passes: u32) -> ShredResult<()> {
40        match method {
41            ShredMethod::Zero => self.run_zero_pass()?,
42            ShredMethod::Random => {
43                for _ in 0..passes {
44                    if self.is_cancelled() {
45                        break;
46                    }
47                    self.run_random_pass()?;
48                }
49            }
50            ShredMethod::Dod => {
51                if !self.is_cancelled() {
52                    self.run_fixed_pass(0x00)?;
53                }
54                if !self.is_cancelled() {
55                    self.run_fixed_pass(0xFF)?;
56                }
57                if !self.is_cancelled() {
58                    self.run_random_pass()?;
59                }
60            }
61            ShredMethod::Gutmann => {
62                // Gutmann 35-pass sequence
63                for _ in 0..4 {
64                    if self.is_cancelled() {
65                        return Ok(());
66                    }
67                    self.run_random_pass()?;
68                }
69                let patterns = [
70                    0x55, 0xAA, 0x92, 0x49, 0x24, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
71                    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x92, 0x49, 0x24, 0x6D, 0xB6,
72                    0xDB,
73                ];
74                for &p in &patterns {
75                    if self.is_cancelled() {
76                        return Ok(());
77                    }
78                    self.run_fixed_pass(p)?;
79                }
80                for _ in 0..4 {
81                    if self.is_cancelled() {
82                        return Ok(());
83                    }
84                    self.run_random_pass()?;
85                }
86            }
87        }
88        Ok(())
89    }
90
91    fn run_zero_pass(&mut self) -> ShredResult<()> {
92        self.run_fixed_pass(0x00)
93    }
94
95    fn run_fixed_pass(&mut self, byte: u8) -> ShredResult<()> {
96        let file_size = self.file.metadata()?.len();
97        self.file.seek(SeekFrom::Start(0))?;
98
99        let buffer = vec![byte; self.buffer_size];
100        let mut total_written: u64 = 0;
101
102        while total_written < file_size {
103            if self.is_cancelled() {
104                return Ok(());
105            }
106            let remaining = file_size - total_written;
107            let current_chunk = std::cmp::min(self.buffer_size as u64, remaining) as usize;
108            self.file.write_all(&buffer[..current_chunk])?;
109            total_written += current_chunk as u64;
110        }
111
112        self.file.sync_all()?;
113
114        if self.verify {
115            self.verify_pass(byte)?;
116        }
117
118        Ok(())
119    }
120
121    fn run_random_pass(&mut self) -> ShredResult<()> {
122        let file_size = self.file.metadata()?.len();
123        self.file.seek(SeekFrom::Start(0))?;
124
125        let mut rng = StdRng::from_entropy();
126        let mut buffer = vec![0u8; self.buffer_size];
127        let mut total_written: u64 = 0;
128
129        while total_written < file_size {
130            if self.is_cancelled() {
131                return Ok(());
132            }
133            let remaining = file_size - total_written;
134            let current_chunk = std::cmp::min(self.buffer_size as u64, remaining) as usize;
135            rng.fill_bytes(&mut buffer[..current_chunk]);
136            self.file.write_all(&buffer[..current_chunk])?;
137            total_written += current_chunk as u64;
138        }
139
140        self.file.sync_all()?;
141
142        // For random, we just verify that it's readable and matches nothing specific?
143        // Actually, for random, verification is harder unless we keep the seed.
144        // We'll just skip detailed content verification for random for now,
145        // or just verify it's not all zeros if that's what we want.
146        // Most tools just verify fixed patterns.
147
148        Ok(())
149    }
150
151    fn verify_pass(&mut self, expected_byte: u8) -> ShredResult<()> {
152        let file_size = self.file.metadata()?.len();
153        self.file.seek(SeekFrom::Start(0))?;
154
155        let mut buffer = vec![0u8; self.buffer_size];
156        let mut total_read: u64 = 0;
157
158        while total_read < file_size {
159            if self.is_cancelled() {
160                return Ok(());
161            }
162            let remaining = file_size - total_read;
163            let current_chunk = std::cmp::min(self.buffer_size as u64, remaining) as usize;
164            self.file.read_exact(&mut buffer[..current_chunk])?;
165
166            for &byte in &buffer[..current_chunk] {
167                if byte != expected_byte {
168                    return Err(ShredError::Obfuscation(format!(
169                        "Verification failed: Expected 0x{:02X}, found 0x{:02X} at offset {}",
170                        expected_byte, byte, total_read
171                    )));
172                }
173            }
174            total_read += current_chunk as u64;
175        }
176        Ok(())
177    }
178}