mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
Merge pull request #4918 from shinhs0506/core-size
shred: add support for octal and hex size
This commit is contained in:
commit
160952a42c
6 changed files with 143 additions and 63 deletions
|
@ -114,7 +114,13 @@ pub fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> {
|
||||||
return Err(ParseSizeError::ParseFailure(src.to_string()));
|
return Err(ParseSizeError::ParseFailure(src.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_size(size_string).map(|n| (n, all_but_last))
|
// remove leading zeros so that size is interpreted as decimal, not octal
|
||||||
|
let trimmed_string = size_string.trim_start_matches('0');
|
||||||
|
if trimmed_string.is_empty() {
|
||||||
|
Ok((0, all_but_last))
|
||||||
|
} else {
|
||||||
|
parse_size(trimmed_string).map(|n| (n, all_but_last))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -19,6 +19,7 @@ use std::os::unix::prelude::PermissionsExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||||
|
use uucore::parse_size::parse_size;
|
||||||
use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_if_err};
|
use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_if_err};
|
||||||
|
|
||||||
const ABOUT: &str = help_about!("shred.md");
|
const ABOUT: &str = help_about!("shred.md");
|
||||||
|
@ -238,7 +239,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
.get_one::<String>(options::SIZE)
|
.get_one::<String>(options::SIZE)
|
||||||
.map(|s| s.to_string());
|
.map(|s| s.to_string());
|
||||||
let size = get_size(size_arg);
|
let size = get_size(size_arg);
|
||||||
let exact = matches.get_flag(options::EXACT) && size.is_none(); // if -s is given, ignore -x
|
let exact = matches.get_flag(options::EXACT) || size.is_some();
|
||||||
let zero = matches.get_flag(options::ZERO);
|
let zero = matches.get_flag(options::ZERO);
|
||||||
let verbose = matches.get_flag(options::VERBOSE);
|
let verbose = matches.get_flag(options::VERBOSE);
|
||||||
|
|
||||||
|
@ -318,38 +319,17 @@ pub fn uu_app() -> Command {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add support for all postfixes here up to and including EiB
|
|
||||||
// http://www.gnu.org/software/coreutils/manual/coreutils.html#Block-size
|
|
||||||
fn get_size(size_str_opt: Option<String>) -> Option<u64> {
|
fn get_size(size_str_opt: Option<String>) -> Option<u64> {
|
||||||
size_str_opt.as_ref()?;
|
size_str_opt
|
||||||
|
.as_ref()
|
||||||
let mut size_str = size_str_opt.as_ref().unwrap().clone();
|
.and_then(|size| parse_size(size.as_str()).ok())
|
||||||
// Immutably look at last character of size string
|
.or_else(|| {
|
||||||
let unit = match size_str.chars().last().unwrap() {
|
if let Some(size) = size_str_opt {
|
||||||
'K' => {
|
show_error!("invalid file size: {}", size.quote());
|
||||||
size_str.pop();
|
std::process::exit(1);
|
||||||
1024u64
|
}
|
||||||
}
|
None
|
||||||
'M' => {
|
})
|
||||||
size_str.pop();
|
|
||||||
(1024 * 1024) as u64
|
|
||||||
}
|
|
||||||
'G' => {
|
|
||||||
size_str.pop();
|
|
||||||
(1024 * 1024 * 1024) as u64
|
|
||||||
}
|
|
||||||
_ => 1u64,
|
|
||||||
};
|
|
||||||
|
|
||||||
let coefficient = match size_str.parse::<u64>() {
|
|
||||||
Ok(u) => u,
|
|
||||||
Err(_) => {
|
|
||||||
show_error!("{}: Invalid file size", size_str_opt.unwrap().maybe_quote());
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(coefficient * unit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pass_name(pass_type: &PassType) -> String {
|
fn pass_name(pass_type: &PassType) -> String {
|
||||||
|
|
|
@ -80,7 +80,7 @@ impl FilterMode {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(USimpleError::new(
|
return Err(USimpleError::new(
|
||||||
1,
|
1,
|
||||||
format!("invalid number of bytes: {e}"),
|
format!("invalid number of bytes: '{e}'"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -415,16 +415,17 @@ fn parse_num(src: &str) -> Result<Signum, ParseSizeError> {
|
||||||
starting_with = true;
|
starting_with = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return Err(ParseSizeError::ParseFailure(src.to_string()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_size(size_string).map(|n| match (n, starting_with) {
|
match parse_size(size_string) {
|
||||||
(0, true) => Signum::PlusZero,
|
Ok(n) => match (n, starting_with) {
|
||||||
(0, false) => Signum::MinusZero,
|
(0, true) => Ok(Signum::PlusZero),
|
||||||
(n, true) => Signum::Positive(n),
|
(0, false) => Ok(Signum::MinusZero),
|
||||||
(n, false) => Signum::Negative(n),
|
(n, true) => Ok(Signum::Positive(n)),
|
||||||
})
|
(n, false) => Ok(Signum::Negative(n)),
|
||||||
|
},
|
||||||
|
Err(_) => Err(ParseSizeError::ParseFailure(size_string.to_string())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_args(args: impl uucore::Args) -> UResult<Settings> {
|
pub fn parse_args(args: impl uucore::Args) -> UResult<Settings> {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) hdsf ghead gtail
|
// spell-checker:ignore (ToDO) hdsf ghead gtail ACDBK hexdigit
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -25,6 +25,12 @@ pub struct Parser<'parser> {
|
||||||
pub default_unit: Option<&'parser str>,
|
pub default_unit: Option<&'parser str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum NumberSystem {
|
||||||
|
Decimal,
|
||||||
|
Octal,
|
||||||
|
Hexadecimal,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'parser> Parser<'parser> {
|
impl<'parser> Parser<'parser> {
|
||||||
pub fn with_allow_list(&mut self, allow_list: &'parser [&str]) -> &mut Self {
|
pub fn with_allow_list(&mut self, allow_list: &'parser [&str]) -> &mut Self {
|
||||||
self.allow_list = Some(allow_list);
|
self.allow_list = Some(allow_list);
|
||||||
|
@ -62,31 +68,26 @@ impl<'parser> Parser<'parser> {
|
||||||
/// assert_eq!(Ok(123), parse_size("123"));
|
/// assert_eq!(Ok(123), parse_size("123"));
|
||||||
/// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000
|
/// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000
|
||||||
/// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024
|
/// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024
|
||||||
|
/// assert_eq!(Ok(44251 * 1024), parse_size("0xACDBK"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn parse(&self, size: &str) -> Result<u64, ParseSizeError> {
|
pub fn parse(&self, size: &str) -> Result<u64, ParseSizeError> {
|
||||||
if size.is_empty() {
|
if size.is_empty() {
|
||||||
return Err(ParseSizeError::parse_failure(size));
|
return Err(ParseSizeError::parse_failure(size));
|
||||||
}
|
}
|
||||||
// Get the numeric part of the size argument. For example, if the
|
|
||||||
// argument is "123K", then the numeric part is "123".
|
|
||||||
let numeric_string: String = size.chars().take_while(|c| c.is_ascii_digit()).collect();
|
|
||||||
let number: u64 = if numeric_string.is_empty() {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
match numeric_string.parse() {
|
|
||||||
Ok(n) => n,
|
|
||||||
Err(_) => return Err(ParseSizeError::parse_failure(size)),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the alphabetic units part of the size argument and compute
|
let number_system: NumberSystem = self.determine_number_system(size);
|
||||||
// the factor it represents. For example, if the argument is "123K",
|
|
||||||
// then the unit part is "K" and the factor is 1024. This may be the
|
// Split the size argument into numeric and unit parts
|
||||||
// empty string, in which case, the factor is 1.
|
// For example, if the argument is "123K", the numeric part is "123", and
|
||||||
//
|
// the unit is "K"
|
||||||
// The lowercase "b" (used by `od`, `head`, `tail`, etc.) means
|
let numeric_string: String = match number_system {
|
||||||
// "block" and the Posix block size is 512. The uppercase "B"
|
NumberSystem::Hexadecimal => size
|
||||||
// means "byte".
|
.chars()
|
||||||
|
.take(2)
|
||||||
|
.chain(size.chars().skip(2).take_while(|c| c.is_ascii_hexdigit()))
|
||||||
|
.collect(),
|
||||||
|
_ => size.chars().take_while(|c| c.is_ascii_digit()).collect(),
|
||||||
|
};
|
||||||
let mut unit: &str = &size[numeric_string.len()..];
|
let mut unit: &str = &size[numeric_string.len()..];
|
||||||
|
|
||||||
if let Some(default_unit) = self.default_unit {
|
if let Some(default_unit) = self.default_unit {
|
||||||
|
@ -115,6 +116,12 @@ impl<'parser> Parser<'parser> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute the factor the unit represents.
|
||||||
|
// empty string means the factor is 1.
|
||||||
|
//
|
||||||
|
// The lowercase "b" (used by `od`, `head`, `tail`, etc.) means
|
||||||
|
// "block" and the Posix block size is 512. The uppercase "B"
|
||||||
|
// means "byte".
|
||||||
let (base, exponent): (u128, u32) = match unit {
|
let (base, exponent): (u128, u32) = match unit {
|
||||||
"" => (1, 0),
|
"" => (1, 0),
|
||||||
"B" if self.capital_b_bytes => (1, 0),
|
"B" if self.capital_b_bytes => (1, 0),
|
||||||
|
@ -142,10 +149,62 @@ impl<'parser> Parser<'parser> {
|
||||||
Ok(n) => n,
|
Ok(n) => n,
|
||||||
Err(_) => return Err(ParseSizeError::size_too_big(size)),
|
Err(_) => return Err(ParseSizeError::size_too_big(size)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// parse string into u64
|
||||||
|
let number: u64 = match number_system {
|
||||||
|
NumberSystem::Decimal => {
|
||||||
|
if numeric_string.is_empty() {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
self.parse_number(&numeric_string, 10, size)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NumberSystem::Octal => {
|
||||||
|
let trimmed_string = numeric_string.trim_start_matches('0');
|
||||||
|
self.parse_number(trimmed_string, 8, size)?
|
||||||
|
}
|
||||||
|
NumberSystem::Hexadecimal => {
|
||||||
|
let trimmed_string = numeric_string.trim_start_matches("0x");
|
||||||
|
self.parse_number(trimmed_string, 16, size)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
number
|
number
|
||||||
.checked_mul(factor)
|
.checked_mul(factor)
|
||||||
.ok_or_else(|| ParseSizeError::size_too_big(size))
|
.ok_or_else(|| ParseSizeError::size_too_big(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn determine_number_system(&self, size: &str) -> NumberSystem {
|
||||||
|
if size.len() <= 1 {
|
||||||
|
return NumberSystem::Decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
if size.starts_with("0x") {
|
||||||
|
return NumberSystem::Hexadecimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_digits: usize = size
|
||||||
|
.chars()
|
||||||
|
.take_while(|c| c.is_ascii_digit())
|
||||||
|
.collect::<String>()
|
||||||
|
.len();
|
||||||
|
let all_zeros = size.chars().all(|c| c == '0');
|
||||||
|
if size.starts_with('0') && num_digits > 1 && !all_zeros {
|
||||||
|
return NumberSystem::Octal;
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberSystem::Decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_number(
|
||||||
|
&self,
|
||||||
|
numeric_string: &str,
|
||||||
|
radix: u32,
|
||||||
|
original_size: &str,
|
||||||
|
) -> Result<u64, ParseSizeError> {
|
||||||
|
u64::from_str_radix(numeric_string, radix)
|
||||||
|
.map_err(|_| ParseSizeError::ParseFailure(original_size.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a size string into a number of bytes.
|
/// Parse a size string into a number of bytes.
|
||||||
|
@ -336,7 +395,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_suffix() {
|
fn invalid_suffix() {
|
||||||
let test_strings = ["328hdsf3290", "5mib", "1e2", "1H", "1.2"];
|
let test_strings = ["5mib", "1eb", "1H"];
|
||||||
for &test_string in &test_strings {
|
for &test_string in &test_strings {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_size(test_string).unwrap_err(),
|
parse_size(test_string).unwrap_err(),
|
||||||
|
@ -450,4 +509,18 @@ mod tests {
|
||||||
assert!(parser.parse("1B").is_err());
|
assert!(parser.parse("1B").is_err());
|
||||||
assert!(parser.parse("B").is_err());
|
assert!(parser.parse("B").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_octal_size() {
|
||||||
|
assert_eq!(Ok(63), parse_size("077"));
|
||||||
|
assert_eq!(Ok(528), parse_size("01020"));
|
||||||
|
assert_eq!(Ok(668 * 1024), parse_size("01234K"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_hex_size() {
|
||||||
|
assert_eq!(Ok(10), parse_size("0xA"));
|
||||||
|
assert_eq!(Ok(94722), parse_size("0x17202"));
|
||||||
|
assert_eq!(Ok(44251 * 1024), parse_size("0xACDBK"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,6 +189,15 @@ fn test_no_such_file_or_directory() {
|
||||||
.stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory");
|
.stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lines_leading_zeros() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--lines=010")
|
||||||
|
.pipe_in("\n\n\n\n\n\n\n\n\n\n\n\n")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("\n\n\n\n\n\n\n\n\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
/// Test that each non-existent files gets its own error message printed.
|
/// Test that each non-existent files gets its own error message printed.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_nonexistent_files() {
|
fn test_multiple_nonexistent_files() {
|
||||||
|
|
|
@ -51,3 +51,14 @@ fn test_shred_force() {
|
||||||
// file_a was deleted.
|
// file_a was deleted.
|
||||||
assert!(!at.file_exists(file));
|
assert!(!at.file_exists(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hex() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
let file = "test_hex";
|
||||||
|
|
||||||
|
at.touch(file);
|
||||||
|
|
||||||
|
ucmd.arg("--size=0x10").arg(file).succeeds();
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue