Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement shred --random-source #7005

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/uu/shred/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ path = "src/shred.rs"
[dependencies]
clap = { workspace = true }
rand = { workspace = true }
uucore = { workspace = true }
uucore = { workspace = true, features = ["rand-read"] }
libc = { workspace = true }

[[bin]]
Expand Down
88 changes: 78 additions & 10 deletions src/uu/shred/src/shred.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::path::{Path, PathBuf};
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::parse_size::parse_size_u64;
use uucore::rand_read::{ReadRng, WrappedRng};
use uucore::shortcut_value_parser::ShortcutValueParser;
use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_if_err};

Expand All @@ -34,6 +35,7 @@ pub mod options {
pub const VERBOSE: &str = "verbose";
pub const EXACT: &str = "exact";
pub const ZERO: &str = "zero";
pub const RANDOM_SOURCE: &str = "random-source";

pub mod remove {
pub const UNLINK: &str = "unlink";
Expand Down Expand Up @@ -198,17 +200,22 @@ impl BytesWriter {
}
}

fn bytes_for_pass(&mut self, size: usize) -> &[u8] {
fn bytes_for_pass(&mut self, size: usize) -> Result<&[u8], std::io::Error> {
match self {
Self::Random { rng, buffer } => {
let bytes = &mut buffer[..size];
rng.fill(bytes);
bytes
match rng.try_fill(bytes) {
Ok(()) => Ok(bytes),
Err(err) => Err(std::io::Error::new(
std::io::ErrorKind::Other,
err.to_string(),
)),
}
}
Self::Pattern { offset, buffer } => {
let bytes = &buffer[*offset..size + *offset];
*offset = (*offset + size) % PATTERN_LENGTH;
bytes
Ok(bytes)
}
}
}
Expand All @@ -235,8 +242,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
None => unreachable!(),
};

// TODO: implement --random-source

let remove_method = if matches.get_flag(options::WIPESYNC) {
RemoveMethod::WipeSync
} else if matches.contains_id(options::REMOVE) {
Expand All @@ -261,6 +266,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let exact = matches.get_flag(options::EXACT) || size.is_some();
let zero = matches.get_flag(options::ZERO);
let verbose = matches.get_flag(options::VERBOSE);
let random_source = matches
.get_one::<String>(options::RANDOM_SOURCE)
.map(String::from);

for path_str in matches.get_many::<String>(options::FILE).unwrap() {
show_if_err!(wipe_file(
Expand All @@ -272,6 +280,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
zero,
verbose,
force,
&random_source
));
}
Ok(())
Expand Down Expand Up @@ -357,6 +366,13 @@ pub fn uu_app() -> Command {
.action(ArgAction::Append)
.value_hint(clap::ValueHint::FilePath),
)
.arg(
Arg::new(options::RANDOM_SOURCE)
.long(options::RANDOM_SOURCE)
.value_name("FILE")
.help("get random bytes from FILE")
.value_hint(clap::ValueHint::FilePath),
)
}

fn get_size(size_str_opt: Option<String>) -> Option<u64> {
Expand Down Expand Up @@ -392,6 +408,7 @@ fn wipe_file(
zero: bool,
verbose: bool,
force: bool,
random_source: &Option<String>,
) -> UResult<()> {
// Get these potential errors out of the way first
let path = Path::new(path_str);
Expand Down Expand Up @@ -452,7 +469,17 @@ fn wipe_file(
for pattern in PATTERNS.into_iter().take(remainder) {
pass_sequence.push(PassType::Pattern(pattern));
}
let mut rng = rand::thread_rng();

let mut rng = match random_source {
Some(r) => {
let file = File::open(&r[..]).map_err_context(|| {
format!("failed to open random source {}", r.quote())
})?;
WrappedRng::RngFile(ReadRng::new(file))
}
None => WrappedRng::RngDefault(rand::thread_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
Expand Down Expand Up @@ -480,6 +507,29 @@ fn wipe_file(
None => metadata.len(),
};

// Random source errors
if let Some(random_source_str) = random_source {
let random_source_path = Path::new(random_source_str.as_str());
if !random_source_path.exists() {
return Err(USimpleError::new(
1,
format!(
"{}: No such file or directory",
random_source_path.maybe_quote()
),
));
}
if !random_source_path.is_file() {
return Err(USimpleError::new(
1,
format!(
"{}: read error: Is a directory",
random_source_path.maybe_quote()
),
));
}
}

for (i, pass_type) in pass_sequence.into_iter().enumerate() {
if verbose {
let pass_name = pass_name(&pass_type);
Expand All @@ -493,14 +543,15 @@ fn wipe_file(
}
// size is an optional argument for exactly how many bytes we want to shred
// Ignore failed writes; just keep trying
show_if_err!(do_pass(&mut file, &pass_type, exact, size)
show_if_err!(do_pass(&mut file, &pass_type, exact, size, random_source)
.map_err_context(|| format!("{}: File write pass failed", path.maybe_quote())));
}

if remove_method != RemoveMethod::None {
do_remove(path, path_str, verbose, remove_method)
.map_err_context(|| format!("{}: failed to remove file", path.maybe_quote()))?;
}

Ok(())
}

Expand All @@ -509,6 +560,7 @@ fn do_pass(
pass_type: &PassType,
exact: bool,
file_size: u64,
random_source: &Option<String>,
) -> Result<(), io::Error> {
// We might be at the end of the file due to a previous iteration, so rewind.
file.rewind()?;
Expand All @@ -517,7 +569,15 @@ fn do_pass(

// We start by writing BLOCK_SIZE times as many time as possible.
for _ in 0..(file_size / BLOCK_SIZE as u64) {
let block = writer.bytes_for_pass(BLOCK_SIZE);
let data = writer.bytes_for_pass(BLOCK_SIZE);
let Ok(block) = data else {
let random_source_path = random_source
.clone()
.expect("random_source should be Some is None");
let path = Path::new(&random_source_path);
show_error!("{}: end of file", path.maybe_quote());
std::process::exit(1);
};
file.write_all(block)?;
}

Expand All @@ -526,7 +586,15 @@ fn do_pass(
let bytes_left = (file_size % BLOCK_SIZE as u64) as usize;
if bytes_left > 0 {
let size = if exact { bytes_left } else { BLOCK_SIZE };
let block = writer.bytes_for_pass(size);
let data = writer.bytes_for_pass(size);
let Ok(block) = data else {
let random_source_path = random_source
.clone()
.expect("random_source should be Some is None");
let path = Path::new(&random_source_path);
show_error!("{}: end of file", path.maybe_quote());
std::process::exit(1);
};
file.write_all(block)?;
}

Expand Down
2 changes: 1 addition & 1 deletion src/uu/shuf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ clap = { workspace = true }
memchr = { workspace = true }
rand = { workspace = true }
rand_core = { workspace = true }
uucore = { workspace = true }
uucore = { workspace = true, features = ["rand-read"] }

[[bin]]
name = "shuf"
Expand Down
42 changes: 3 additions & 39 deletions src/uu/shuf/src/shuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@
use clap::{crate_version, Arg, ArgAction, Command};
use memchr::memchr_iter;
use rand::prelude::SliceRandom;
use rand::{Rng, RngCore};
use rand::Rng;
use std::collections::HashSet;
use std::fs::File;
use std::io::{stdin, stdout, BufReader, BufWriter, Error, Read, Write};
use std::ops::RangeInclusive;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::rand_read::{ReadRng, WrappedRng};
use uucore::{format_usage, help_about, help_usage};

mod rand_read_adapter;

enum Mode {
Default(String),
Echo(Vec<String>),
Expand Down Expand Up @@ -433,7 +432,7 @@ fn shuf_exec(input: &mut impl Shufable, opts: Options) -> UResult<()> {
Some(r) => {
let file = File::open(&r[..])
.map_err_context(|| format!("failed to open random source {}", r.quote()))?;
WrappedRng::RngFile(rand_read_adapter::ReadRng::new(file))
WrappedRng::RngFile(ReadRng::new(file))
}
None => WrappedRng::RngDefault(rand::thread_rng()),
};
Expand Down Expand Up @@ -494,41 +493,6 @@ fn parse_head_count(headcounts: Vec<String>) -> Result<usize, String> {
Ok(result)
}

enum WrappedRng {
RngFile(rand_read_adapter::ReadRng<File>),
RngDefault(rand::rngs::ThreadRng),
}

impl RngCore for WrappedRng {
fn next_u32(&mut self) -> u32 {
match self {
Self::RngFile(r) => r.next_u32(),
Self::RngDefault(r) => r.next_u32(),
}
}

fn next_u64(&mut self) -> u64 {
match self {
Self::RngFile(r) => r.next_u64(),
Self::RngDefault(r) => r.next_u64(),
}
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
match self {
Self::RngFile(r) => r.fill_bytes(dest),
Self::RngDefault(r) => r.fill_bytes(dest),
}
}

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
match self {
Self::RngFile(r) => r.try_fill_bytes(dest),
Self::RngDefault(r) => r.try_fill_bytes(dest),
}
}
}

#[cfg(test)]
// Since the computed value is a bool, it is more readable to write the expected value out:
#[allow(clippy::bool_assert_comparison)]
Expand Down
5 changes: 4 additions & 1 deletion src/uu/stdbuf/src/libstdbuf/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
use cpp_build::Config;

fn main() {
Config::new().pic(true).build("src/libstdbuf.rs");
Config::new()
.compiler("clang++")
.pic(true)
.build("src/libstdbuf.rs");
}
3 changes: 3 additions & 0 deletions src/uucore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ dunce = { version = "1.0.4", optional = true }
wild = "2.2.1"
glob = { workspace = true }
lazy_static = "1.4.0"
rand = { workspace = true }
rand_core = { workspace = true }
# * optional
itertools = { workspace = true, optional = true }
thiserror = { workspace = true, optional = true }
Expand Down Expand Up @@ -92,6 +94,7 @@ pipes = []
process = ["libc"]
proc-info = ["tty", "walkdir"]
quoting-style = []
rand-read = []
ranges = []
ringbuffer = []
signals = []
Expand Down
2 changes: 2 additions & 0 deletions src/uucore/src/lib/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub mod fsext;
pub mod lines;
#[cfg(feature = "quoting-style")]
pub mod quoting_style;
#[cfg(feature = "rand-read")]
pub mod rand_read;
#[cfg(feature = "ranges")]
pub mod ranges;
#[cfg(feature = "ringbuffer")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

//! A wrapper around any Read to treat it as an RNG.

use std::fmt;
use std::io::Read;
use std::{fmt, fs::File};

use rand_core::{impls, Error, RngCore};

Expand Down Expand Up @@ -89,6 +89,42 @@ impl std::error::Error for ReadError {
}
}

/// Generic interface for both ThreadRng and ReadRng
pub enum WrappedRng {
f8ith marked this conversation as resolved.
Show resolved Hide resolved
RngFile(ReadRng<File>),
RngDefault(rand::rngs::ThreadRng),
}

impl RngCore for WrappedRng {
fn next_u32(&mut self) -> u32 {
match self {
Self::RngFile(r) => r.next_u32(),
Self::RngDefault(r) => r.next_u32(),
}
}

fn next_u64(&mut self) -> u64 {
match self {
Self::RngFile(r) => r.next_u64(),
Self::RngDefault(r) => r.next_u64(),
}
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
match self {
Self::RngFile(r) => r.fill_bytes(dest),
Self::RngDefault(r) => r.fill_bytes(dest),
}
}

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
match self {
Self::RngFile(r) => r.try_fill_bytes(dest),
Self::RngDefault(r) => r.try_fill_bytes(dest),
}
}
}

#[cfg(test)]
mod test {
use std::println;
Expand Down
2 changes: 2 additions & 0 deletions src/uucore/src/lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ pub use crate::features::fs;
pub use crate::features::lines;
#[cfg(feature = "quoting-style")]
pub use crate::features::quoting_style;
#[cfg(feature = "rand-read")]
pub use crate::features::rand_read;
#[cfg(feature = "ranges")]
pub use crate::features::ranges;
#[cfg(feature = "ringbuffer")]
Expand Down
Loading
Loading