sys_shred/core/
overwrite.rs1use 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
17pub 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 pub fn new(file: &'a mut File, verify: bool, cancelled: Arc<AtomicBool>) -> Self {
28 #[cfg(target_os = "macos")]
30 {
31 let fd = file.as_raw_fd();
32 unsafe {
33 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 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 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 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 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}