From b151e039ae08b1816549be257ef11373989826f8 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Thu, 24 Apr 2025 03:15:36 -0400 Subject: [PATCH] shred: fix random passes* (#7830) * shred: fix random passes, update documentation, add test * shred: update tests --- src/uu/shred/src/shred.rs | 27 +++++++++++++++------------ tests/by-util/test_shred.rs | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 8a9a755a1..3ad5d713f 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -51,8 +51,7 @@ const PATTERN_BUFFER_SIZE: usize = BLOCK_SIZE + PATTERN_LENGTH - 1; /// Patterns that appear in order for the passes /// -/// They are all extended to 3 bytes for consistency, even though some could be -/// expressed as single bytes. +/// A single-byte pattern is equivalent to a multi-byte pattern of that byte three times. const PATTERNS: [Pattern; 22] = [ Pattern::Single(b'\x00'), Pattern::Single(b'\xFF'), @@ -440,9 +439,13 @@ fn wipe_file( pass_sequence.push(PassType::Random); } } else { - // First fill it with Patterns, shuffle it, then evenly distribute Random - let n_full_arrays = n_passes / PATTERNS.len(); // How many times can we go through all the patterns? - let remainder = n_passes % PATTERNS.len(); // How many do we get through on our last time through? + // Add initial random to avoid O(n) operation later + pass_sequence.push(PassType::Random); + let n_random = (n_passes / 10).max(3); // Minimum 3 random passes; ratio of 10 after + let n_fixed = n_passes - n_random; + // Fill it with Patterns and all but the first and last random, then shuffle it + let n_full_arrays = n_fixed / PATTERNS.len(); // How many times can we go through all the patterns? + let remainder = n_fixed % PATTERNS.len(); // How many do we get through on our last time through, excluding randoms? for _ in 0..n_full_arrays { for p in PATTERNS { @@ -452,14 +455,14 @@ fn wipe_file( for pattern in PATTERNS.into_iter().take(remainder) { pass_sequence.push(PassType::Pattern(pattern)); } - let mut rng = rand::rng(); - pass_sequence.shuffle(&mut rng); // randomize the order of application - - let n_random = 3 + n_passes / 10; // Minimum 3 random passes; ratio of 10 after - // Evenly space random passes; ensures one at the beginning and end - for i in 0..n_random { - pass_sequence[i * (n_passes - 1) / (n_random - 1)] = PassType::Random; + // add random passes except one each at the beginning and end + for _ in 0..n_random - 2 { + pass_sequence.push(PassType::Random); } + + let mut rng = rand::rng(); + pass_sequence[1..].shuffle(&mut rng); // randomize the order of application + pass_sequence.push(PassType::Random); // add the last random pass } // --zero specifies whether we want one final pass of 0x00 on our file diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index e73471117..a9c40324a 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -10,6 +10,12 @@ use uutests::new_ucmd; use uutests::util::TestScenario; use uutests::util_name; +const PATTERNS: [&str; 22] = [ + "000000", "ffffff", "555555", "aaaaaa", "249249", "492492", "6db6db", "924924", "b6db6d", + "db6db6", "111111", "222222", "333333", "444444", "666666", "777777", "888888", "999999", + "bbbbbb", "cccccc", "dddddd", "eeeeee", +]; + #[test] fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails_with_code(1); @@ -241,3 +247,18 @@ fn test_shred_verbose_no_padding_10() { .succeeds() .stderr_contains("shred: foo: pass 1/10 (random)...\n"); } + +#[test] +fn test_all_patterns_present() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "foo.txt"; + at.write(file, "bar"); + + let result = scene.ucmd().arg("-vn25").arg(file).succeeds(); + + for pat in PATTERNS { + result.stderr_contains(pat); + } +}