Skip to content

Commit f82b364

Browse files
committed
shred: implement and test feature --random-source
1 parent a5a9f7d commit f82b364

File tree

2 files changed

+95
-14
lines changed

2 files changed

+95
-14
lines changed

src/uu/shred/src/shred.rs

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use clap::{Arg, ArgAction, Command};
1010
use libc::S_IWUSR;
1111
use rand::{Rng, SeedableRng, rngs::StdRng, seq::SliceRandom};
1212
use std::fs::{self, File, OpenOptions};
13-
use std::io::{self, Seek, Write};
13+
use std::io::{self, Read, Seek, Write};
1414
#[cfg(unix)]
1515
use std::os::unix::prelude::PermissionsExt;
1616
use std::path::{Path, PathBuf};
@@ -34,6 +34,7 @@ pub mod options {
3434
pub const VERBOSE: &str = "verbose";
3535
pub const EXACT: &str = "exact";
3636
pub const ZERO: &str = "zero";
37+
pub const RANDOM_SOURCE: &str = "random-source";
3738

3839
pub mod remove {
3940
pub const UNLINK: &str = "unlink";
@@ -152,16 +153,25 @@ impl Iterator for FilenameIter {
152153
}
153154
}
154155

156+
enum RandomSource {
157+
System,
158+
Read(File),
159+
}
160+
155161
/// Used to generate blocks of bytes of size <= BLOCK_SIZE based on either a give pattern
156162
/// or randomness
157163
// The lint warns about a large difference because StdRng is big, but the buffers are much
158164
// larger anyway, so it's fine.
159165
#[allow(clippy::large_enum_variant)]
160-
enum BytesWriter {
166+
enum BytesWriter<'a> {
161167
Random {
162168
rng: StdRng,
163169
buffer: [u8; BLOCK_SIZE],
164170
},
171+
RandomFile {
172+
rng_file: &'a File,
173+
buffer: [u8; BLOCK_SIZE],
174+
},
165175
// To write patterns we only write to the buffer once. To be able to do
166176
// this, we need to extend the buffer with 2 bytes. We can then easily
167177
// obtain a buffer starting with any character of the pattern that we
@@ -177,12 +187,18 @@ enum BytesWriter {
177187
},
178188
}
179189

180-
impl BytesWriter {
181-
fn from_pass_type(pass: &PassType) -> Self {
190+
impl<'a> BytesWriter<'a> {
191+
fn from_pass_type(pass: &PassType, random_source: &'a RandomSource) -> Self {
182192
match pass {
183-
PassType::Random => Self::Random {
184-
rng: StdRng::from_os_rng(),
185-
buffer: [0; BLOCK_SIZE],
193+
PassType::Random => match random_source {
194+
RandomSource::System => Self::Random {
195+
rng: StdRng::from_os_rng(),
196+
buffer: [0; BLOCK_SIZE],
197+
},
198+
RandomSource::Read(file) => Self::RandomFile {
199+
rng_file: file,
200+
buffer: [0; BLOCK_SIZE],
201+
},
186202
},
187203
PassType::Pattern(pattern) => {
188204
// Copy the pattern in chunks rather than simply one byte at a time
@@ -203,17 +219,22 @@ impl BytesWriter {
203219
}
204220
}
205221

206-
fn bytes_for_pass(&mut self, size: usize) -> &[u8] {
222+
fn bytes_for_pass(&mut self, size: usize) -> Result<&[u8], io::Error> {
207223
match self {
208224
Self::Random { rng, buffer } => {
209225
let bytes = &mut buffer[..size];
210226
rng.fill(bytes);
211-
bytes
227+
Ok(bytes)
228+
}
229+
Self::RandomFile { rng_file, buffer } => {
230+
let bytes = &mut buffer[..size];
231+
rng_file.read_exact(bytes)?;
232+
Ok(bytes)
212233
}
213234
Self::Pattern { offset, buffer } => {
214235
let bytes = &buffer[*offset..size + *offset];
215236
*offset = (*offset + size) % PATTERN_LENGTH;
216-
bytes
237+
Ok(bytes)
217238
}
218239
}
219240
}
@@ -240,6 +261,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
240261
None => unreachable!(),
241262
};
242263

264+
let random_source = match matches.get_one::<String>(options::RANDOM_SOURCE) {
265+
Some(filepath) => RandomSource::Read(File::open(filepath).map_err(|_| {
266+
USimpleError::new(
267+
1,
268+
format!("cannot open random source: {}", filepath.quote()),
269+
)
270+
})?),
271+
None => RandomSource::System,
272+
};
243273
// TODO: implement --random-source
244274

245275
let remove_method = if matches.get_flag(options::WIPESYNC) {
@@ -275,6 +305,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
275305
size,
276306
exact,
277307
zero,
308+
&random_source,
278309
verbose,
279310
force,
280311
));
@@ -356,6 +387,13 @@ pub fn uu_app() -> Command {
356387
.help("add a final overwrite with zeros to hide shredding")
357388
.action(ArgAction::SetTrue),
358389
)
390+
.arg(
391+
Arg::new(options::RANDOM_SOURCE)
392+
.long(options::RANDOM_SOURCE)
393+
.help("take random bytes from FILE")
394+
.value_hint(clap::ValueHint::FilePath)
395+
.action(ArgAction::Set),
396+
)
359397
// Positional arguments
360398
.arg(
361399
Arg::new(options::FILE)
@@ -395,6 +433,7 @@ fn wipe_file(
395433
size: Option<u64>,
396434
exact: bool,
397435
zero: bool,
436+
random_source: &RandomSource,
398437
verbose: bool,
399438
force: bool,
400439
) -> UResult<()> {
@@ -501,7 +540,7 @@ fn wipe_file(
501540
// size is an optional argument for exactly how many bytes we want to shred
502541
// Ignore failed writes; just keep trying
503542
show_if_err!(
504-
do_pass(&mut file, &pass_type, exact, size)
543+
do_pass(&mut file, &pass_type, exact, random_source, size)
505544
.map_err_context(|| format!("{}: File write pass failed", path.maybe_quote()))
506545
);
507546
}
@@ -529,22 +568,23 @@ fn do_pass(
529568
file: &mut File,
530569
pass_type: &PassType,
531570
exact: bool,
571+
random_source: &RandomSource,
532572
file_size: u64,
533573
) -> Result<(), io::Error> {
534574
// We might be at the end of the file due to a previous iteration, so rewind.
535575
file.rewind()?;
536576

537-
let mut writer = BytesWriter::from_pass_type(pass_type);
577+
let mut writer = BytesWriter::from_pass_type(pass_type, random_source);
538578
let (number_of_blocks, bytes_left) = split_on_blocks(file_size, exact);
539579

540580
// We start by writing BLOCK_SIZE times as many time as possible.
541581
for _ in 0..number_of_blocks {
542-
let block = writer.bytes_for_pass(BLOCK_SIZE);
582+
let block = writer.bytes_for_pass(BLOCK_SIZE)?;
543583
file.write_all(block)?;
544584
}
545585

546586
// Then we write remaining data which is smaller than the BLOCK_SIZE
547-
let block = writer.bytes_for_pass(bytes_left as usize);
587+
let block = writer.bytes_for_pass(bytes_left as usize)?;
548588
file.write_all(block)?;
549589

550590
file.sync_data()?;

tests/by-util/test_shred.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,44 @@ fn test_all_patterns_present() {
251251
result.stderr_contains(pat);
252252
}
253253
}
254+
255+
#[test]
256+
fn test_random_source_regular_file() {
257+
let scene = TestScenario::new(util_name!());
258+
let at = &scene.fixtures;
259+
// Currently, our block size is 4096. If it changes, this test has to be adapted.
260+
let mut many_bytes = Vec::with_capacity(4096 * 4);
261+
for i in 0..4096u32 {
262+
many_bytes.extend(i.to_le_bytes());
263+
}
264+
assert_eq!(many_bytes.len(), 4096 * 4);
265+
at.write_bytes("source_long", &many_bytes);
266+
let file = "foo.txt";
267+
at.write(file, "a");
268+
scene
269+
.ucmd()
270+
.arg("-vn3")
271+
.arg("--random-source=source_long")
272+
.arg(file)
273+
.succeeds()
274+
.stderr_only("shred: foo.txt: pass 1/3 (random)...\nshred: foo.txt: pass 2/3 (random)...\nshred: foo.txt: pass 3/3 (random)...\n");
275+
// Should rewrite the file exactly three times
276+
assert_eq!(at.read_bytes(file), many_bytes[(4096 * 2)..(4096 * 3)]);
277+
}
278+
279+
#[test]
280+
#[ignore = "known issue #7947"]
281+
fn test_random_source_dir() {
282+
let scene = TestScenario::new(util_name!());
283+
let at = &scene.fixtures;
284+
at.mkdir("source");
285+
let file = "foo.txt";
286+
at.write(file, "a");
287+
scene
288+
.ucmd()
289+
.arg("-v")
290+
.arg("--random-source=source")
291+
.arg(file)
292+
.fails()
293+
.stderr_only("shred: foo.txt: pass 1/3 (random)...\nshred: foo.txt: File write pass failed: Is a directory\n");
294+
}

0 commit comments

Comments
 (0)