file_shred/erase/
overwrite.rs1use ::std::fs::File;
2use ::std::fs::OpenOptions;
3use ::std::io::Seek;
4use ::std::io::SeekFrom;
5use ::std::io::Write;
6use ::std::path::Path;
7use ::std::rc::Rc;
8
9use ::rand::RngCore;
10
11use crate::util::errors::add_err;
12use crate::util::errors::wrap_io;
13use crate::util::ShredResult;
14
15fn sync(file: &mut File) -> ShredResult<()> {
16 wrap_io(
17 || "could not persist file while shredding",
18 file.sync_data(),
19 )
20}
21
22pub fn repeatedly_overwrite(path: &Path, overwrite_count: u32, verbose: bool) -> ShredResult<()> {
23 match OpenOptions::new()
24 .read(false)
25 .write(true)
26 .append(false)
27 .open(path)
28 {
29 Ok(mut file) => {
30 let mut count = overwrite_count;
31 let file_meta = wrap_io(|| "could not inspect file", file.metadata())?;
32 assert!(file_meta.is_file());
33 let file_size = file_meta.len();
34 if count > 1 {
35 overwrite_constant(&mut file, file_size, verbose, 0)?; sync(&mut file)?;
37 count -= 1;
38 }
39 if count > 1 {
40 overwrite_constant(&mut file, file_size, verbose, 255)?; sync(&mut file)?;
42 count -= 1;
43 }
44 if count > 1 {
45 overwrite_constant(&mut file, file_size, verbose, 85)?; sync(&mut file)?;
47 count -= 1;
48 }
49 if count > 1 {
50 overwrite_constant(&mut file, file_size, verbose, 170)?; sync(&mut file)?;
52 count -= 1;
53 }
54 for _ in 0..count {
55 overwrite_random_data(&mut file, file_size, verbose)?;
56 sync(&mut file)?;
57 }
58 Ok(())
59 }
60 Err(err) => {
61 if path.exists() {
62 Err(add_err(
63 format!(
64 "could not remove file '{}' because it could not be opened in write mode",
65 path.to_string_lossy()
66 ),
67 verbose,
68 err,
69 ))
70 } else {
71 Err(add_err(
72 format!(
73 "could not remove file '{}' because it does not exist",
74 path.to_string_lossy()
75 ),
76 verbose,
77 err,
78 ))
79 }
80 }
81 }
82}
83
84pub fn overwrite_constant<F: Write + Seek>(
85 file: &mut F,
86 file_size: u64,
87 verbose: bool,
88 value: u8,
89) -> ShredResult<()> {
90 let data = Rc::new([value; 512]);
91 overwrite_data(file, file_size, verbose, || data.clone())
92}
93
94pub fn overwrite_random_data<F: Write + Seek>(
95 file: &mut F,
96 file_size: u64,
97 verbose: bool,
98) -> ShredResult<()> {
99 let mut rng = rand::rng();
100 overwrite_data(file, file_size, verbose, || {
101 let mut data = [0u8; 512];
102 rng.fill_bytes(&mut data);
103 Rc::new(data)
104 })
105}
106
107fn overwrite_data<F: Write + Seek>(
110 file: &mut F,
111 file_size: u64,
112 verbose: bool,
113 mut value_gen: impl FnMut() -> Rc<[u8; 512]>,
114) -> ShredResult<()> {
115 match file.seek(SeekFrom::Start(0)) {
117 Ok(size) => assert_eq!(size, 0),
118 Err(err) => {
119 return Err(add_err(
120 "could not just to start of file during shredding",
121 verbose,
122 err,
123 ))
124 }
125 }
126
127 let steps = (file_size + 511) / 512;
129 for _ in 0..steps {
130 match file.write(&*value_gen()) {
131 Ok(size) => assert_eq!(size, 512),
132 Err(err) => {
133 return Err(add_err(
134 "could not overwrite file during shredding",
135 verbose,
136 err,
137 ))
138 }
139 }
140 }
141
142 Ok(())
143}
144
145#[cfg(test)]
146mod tests {
147 use ::std::io::Cursor;
148
149 use super::*;
150
151 #[test]
152 fn overwrite_long() {
153 let mut mock_file = Cursor::new(vec![0u8; 65_536 + 1]);
154 overwrite_constant(&mut mock_file, 65_536 + 1, false, b'm').unwrap();
155 let data = mock_file.get_ref();
156 assert!(data.starts_with(b"mmmmmm"));
157 assert!(data.ends_with(b"mmmmmm"));
158 assert_eq!(data.len(), 65_536 + 512);
159 }
160
161 #[test]
162 fn overwrite_fixed() {
163 let mut mock_file = Cursor::new(b"hello world".to_vec());
164 overwrite_constant(&mut mock_file, 11, true, 85).unwrap();
165 let data = mock_file.get_ref();
166 assert!(!data.starts_with(b"hello world"));
167 assert!(data.starts_with(b"UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"));
168 assert_eq!(data.len(), 512);
169 }
170
171 #[test]
172 fn overwrite_random() {
173 let initial = b"hello world this is an unlikely message that shouldn't happen by chance!";
174 let mut mock_file = Cursor::new(initial.to_vec());
175 overwrite_constant(&mut mock_file, 11, true, 85).unwrap();
176 let data = mock_file.get_ref();
177 assert!(!data.starts_with(initial));
178 assert_eq!(data.len(), 512);
179 }
180}