file_shred/erase/
overwrite.rs

1use ::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)?; // 00000000
36                sync(&mut file)?;
37                count -= 1;
38            }
39            if count > 1 {
40                overwrite_constant(&mut file, file_size, verbose, 255)?; // 11111111
41                sync(&mut file)?;
42                count -= 1;
43            }
44            if count > 1 {
45                overwrite_constant(&mut file, file_size, verbose, 85)?; // 01010101
46                sync(&mut file)?;
47                count -= 1;
48            }
49            if count > 1 {
50                overwrite_constant(&mut file, file_size, verbose, 170)?; // 10101010
51                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
107/// Overwrite the data with garbage.
108/// It is recommended to sync the file after each step.
109fn 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    // Jump to start of file
116    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    // Overwrite the data in blocks. Might overwrite a bit at the end.
128    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}