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: Vec<u8>,
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 let buffer_size = 64 * 1024;
48 Self {
49 file,
50 buffer: vec![0u8; buffer_size],
51 verify,
52 cancelled,
53 }
54 }
55
56 fn is_cancelled(&self) -> bool {
57 self.cancelled.load(Ordering::Relaxed)
58 }
59
60 pub fn execute(&mut self, method: ShredMethod, passes: u32) -> ShredResult<()> {
62 match method {
63 ShredMethod::Zero => self.run_zero_pass()?,
64 ShredMethod::Random => {
65 for _ in 0..passes {
66 if self.is_cancelled() {
67 break;
68 }
69 self.run_random_pass()?;
70 }
71 }
72 ShredMethod::Dod => {
73 if !self.is_cancelled() {
74 self.run_fixed_pass(0x00)?;
75 }
76 if !self.is_cancelled() {
77 self.run_fixed_pass(0xFF)?;
78 }
79 if !self.is_cancelled() {
80 self.run_random_pass()?;
81 }
82 }
83 ShredMethod::Gutmann => {
84 for _ in 0..4 {
86 if self.is_cancelled() {
87 return Ok(());
88 }
89 self.run_random_pass()?;
90 }
91 let patterns = [
92 0x55, 0xAA, 0x92, 0x49, 0x24, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
93 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x92, 0x49, 0x24, 0x6D, 0xB6,
94 0xDB,
95 ];
96 for &p in &patterns {
97 if self.is_cancelled() {
98 return Ok(());
99 }
100 self.run_fixed_pass(p)?;
101 }
102 for _ in 0..4 {
103 if self.is_cancelled() {
104 return Ok(());
105 }
106 self.run_random_pass()?;
107 }
108 }
109 }
110 Ok(())
111 }
112
113 fn run_zero_pass(&mut self) -> ShredResult<()> {
114 self.run_fixed_pass(0x00)
115 }
116
117 fn run_fixed_pass(&mut self, byte: u8) -> ShredResult<()> {
118 let file_size = self.file.metadata()?.len();
119 self.file.seek(SeekFrom::Start(0))?;
120
121 self.buffer.fill(byte);
123 let mut total_written: u64 = 0;
124 let buffer_len = self.buffer.len();
125
126 while total_written < file_size {
127 if self.is_cancelled() {
128 return Ok(());
129 }
130 let remaining = file_size - total_written;
131 let current_chunk = std::cmp::min(buffer_len as u64, remaining) as usize;
132 self.file.write_all(&self.buffer[..current_chunk])?;
133 total_written += current_chunk as u64;
134 }
135
136 self.file.sync_all()?;
137
138 if self.verify {
139 self.verify_pass(byte)?;
140 }
141
142 Ok(())
143 }
144
145 fn run_random_pass(&mut self) -> ShredResult<()> {
146 let file_size = self.file.metadata()?.len();
147 self.file.seek(SeekFrom::Start(0))?;
148
149 let mut rng = StdRng::from_entropy();
150 let mut total_written: u64 = 0;
151 let buffer_len = self.buffer.len();
152
153 while total_written < file_size {
154 if self.is_cancelled() {
155 return Ok(());
156 }
157 let remaining = file_size - total_written;
158 let current_chunk = std::cmp::min(buffer_len as u64, remaining) as usize;
159 rng.fill_bytes(&mut self.buffer[..current_chunk]);
160 self.file.write_all(&self.buffer[..current_chunk])?;
161 total_written += current_chunk as u64;
162 }
163
164 self.file.sync_all()?;
165 Ok(())
166 }
167
168 fn verify_pass(&mut self, expected_byte: u8) -> ShredResult<()> {
169 let file_size = self.file.metadata()?.len();
170 self.file.seek(SeekFrom::Start(0))?;
171
172 let mut total_read: u64 = 0;
173 let buffer_len = self.buffer.len();
174
175 while total_read < file_size {
176 if self.is_cancelled() {
177 return Ok(());
178 }
179 let remaining = file_size - total_read;
180 let current_chunk = std::cmp::min(buffer_len as u64, remaining) as usize;
181 self.file.read_exact(&mut self.buffer[..current_chunk])?;
182
183 for &byte in &self.buffer[..current_chunk] {
184 if self.is_cancelled() {
185 return Ok(());
186 }
187 if byte != expected_byte {
188 return Err(ShredError::Obfuscation(format!(
189 "Verification failed: Expected 0x{:02X}, found 0x{:02X} at offset {}",
190 expected_byte, byte, total_read
191 )));
192 }
193 }
194 total_read += current_chunk as u64;
195 }
196 Ok(())
197 }
198}