Merge branch 'uutils:main' into uucore-memory

This commit is contained in:
mattsu
2025-10-18 21:04:23 +09:00
committed by GitHub
12 changed files with 429 additions and 207 deletions
+5
View File
@@ -379,6 +379,11 @@ jobs:
set -x
# Regression-test for https://github.com/uutils/coreutils/issues/8701
make UTILS="rm chmod chown chgrp mv du"
# Verifies that
# 1. there is no "error: none of the selected packages contains this
# feature: feat_external_libstdbuf"
# 2. the makefile doesn't try to install libstdbuf even though stdbuf is skipped
DESTDIR=/tmp/ make SKIP_UTILS="stdbuf" install
build_rust_stable:
name: Build/stable
+10 -10
View File
@@ -107,6 +107,7 @@ PROGS := \
dir \
dircolors \
dirname \
du \
echo \
env \
expand \
@@ -117,6 +118,7 @@ PROGS := \
fold \
hashsum \
head \
hostname \
join \
link \
ln \
@@ -145,17 +147,18 @@ PROGS := \
sleep \
sort \
split \
stty \
sum \
sync \
tac \
tail \
tee \
test \
touch \
tr \
true \
truncate \
tsort \
uname \
unexpand \
uniq \
vdir \
@@ -169,10 +172,8 @@ UNIX_PROGS := \
chmod \
chown \
chroot \
du \
groups \
hostid \
hostname \
id \
install \
kill \
@@ -185,10 +186,9 @@ UNIX_PROGS := \
pinky \
stat \
stdbuf \
stty \
timeout \
touch \
tty \
uname \
unlink \
uptime \
users \
@@ -228,7 +228,7 @@ ifneq ($(OS),Windows_NT)
PROGS := $(PROGS) $(SELINUX_PROGS)
endif
UTILS ?= $(PROGS)
UTILS ?= $(filter-out $(SKIP_UTILS),$(PROGS))
ifneq ($(findstring stdbuf,$(UTILS)),)
# Use external libstdbuf per default. It is more robust than embedding libstdbuf.
@@ -306,7 +306,7 @@ TEST_PROGS := \
who
TESTS := \
$(sort $(filter $(UTILS),$(filter-out $(SKIP_UTILS),$(TEST_PROGS))))
$(sort $(filter $(UTILS),$(TEST_PROGS)))
TEST_NO_FAIL_FAST :=
TEST_SPEC_FEATURE :=
@@ -326,7 +326,7 @@ endef
# Output names
EXES := \
$(sort $(filter $(UTILS),$(filter-out $(SKIP_UTILS),$(PROGS))))
$(sort $(UTILS))
INSTALLEES := ${EXES}
ifeq (${MULTICALL}, y)
@@ -352,7 +352,7 @@ build-coreutils:
build: build-coreutils build-pkgs locales
$(foreach test,$(filter-out $(SKIP_UTILS),$(PROGS)),$(eval $(call TEST_BUSYBOX,$(test))))
$(foreach test,$(UTILS),$(eval $(call TEST_BUSYBOX,$(test))))
test:
${CARGO} test ${CARGOFLAGS} --features "$(TESTS) $(TEST_SPEC_FEATURE)" --no-default-features $(TEST_NO_FAIL_FAST)
@@ -482,7 +482,7 @@ endif
install: build install-manpages install-completions install-locales
mkdir -p $(INSTALLDIR_BIN)
ifneq ($(OS),Windows_NT)
ifneq (,$(and $(findstring stdbuf,$(UTILS)),$(findstring feat_external_libstdbuf,$(CARGOFLAGS))))
mkdir -p $(DESTDIR)$(LIBSTDBUF_DIR)
$(INSTALL) -m 755 $(BUILDDIR)/deps/libstdbuf* $(DESTDIR)$(LIBSTDBUF_DIR)/
endif
+5
View File
@@ -18,6 +18,11 @@ license = "MIT"
[package.metadata]
cargo-fuzz = true
# Enable debug symbols in release builds for readable backtraces
# when fuzzing discovers crashes. This addresses issue #5343.
[profile.release]
debug = true
[dependencies]
libfuzzer-sys = "0.4.7"
rand = { version = "0.9.0", features = ["small_rng"] }
+2 -2
View File
@@ -9,7 +9,7 @@ use std::{env, fmt};
use uucore::{
display::Quotable,
parser::parse_size::{ParseSizeError, parse_size_u64},
parser::parse_size::{ParseSizeError, parse_size_non_zero_u64, parse_size_u64},
};
/// The first ten powers of 1024.
@@ -213,7 +213,7 @@ pub(crate) fn read_block_size(matches: &ArgMatches) -> Result<BlockSize, ParseSi
fn block_size_from_env() -> Option<u64> {
for env_var in ["DF_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
if let Ok(env_size) = env::var(env_var) {
return parse_size_u64(&env_size).ok();
return parse_size_non_zero_u64(&env_size).ok();
}
}
+2 -2
View File
@@ -68,7 +68,7 @@ use uucore::{
line_ending::LineEnding,
os_str_as_bytes_lossy,
parser::parse_glob,
parser::parse_size::parse_size_u64,
parser::parse_size::parse_size_non_zero_u64,
parser::shortcut_value_parser::ShortcutValueParser,
quoting_style::{QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name},
show, show_error, show_warning,
@@ -902,7 +902,7 @@ impl Config {
let (file_size_block_size, block_size) = if !opt_si && !opt_hr && !raw_block_size.is_empty()
{
match parse_size_u64(&raw_block_size.to_string_lossy()) {
match parse_size_non_zero_u64(&raw_block_size.to_string_lossy()) {
Ok(size) => match (is_env_var_blocksize, opt_kb) {
(true, true) => (DEFAULT_FILE_SIZE_BLOCK_SIZE, DEFAULT_BLOCK_SIZE),
(true, false) => (DEFAULT_FILE_SIZE_BLOCK_SIZE, size),
+15
View File
@@ -66,6 +66,7 @@ od-help-address-radix = Select the base in which file offsets are printed.
od-help-skip-bytes = Skip bytes input bytes before formatting and writing.
od-help-read-bytes = limit dump to BYTES input bytes
od-help-endian = byte order to use for multi-byte formats
od-help-strings = output strings of at least BYTES graphic chars. 3 is assumed when BYTES is not specified.
od-help-a = named characters, ignoring high-order bit
od-help-b = octal bytes
od-help-c = ASCII characters or backslash escapes
@@ -76,3 +77,17 @@ od-help-output-duplicates = do not use * to mark line suppression
od-help-width = output BYTES bytes per output line. 32 is implied when BYTES is not
specified.
od-help-traditional = compatibility mode with one input, offset and label.
od-help-o = octal 2-byte units
od-help-capital-i = decimal 8-byte units
od-help-capital-l = decimal 8-byte units
od-help-i = decimal 4-byte units
od-help-l = decimal 8-byte units
od-help-x = hexadecimal 2-byte units
od-help-h = hexadecimal 2-byte units
od-help-capital-o = octal 4-byte units
od-help-s = decimal 2-byte units
od-help-capital-x = hexadecimal 4-byte units
od-help-capital-h = hexadecimal 4-byte units
od-help-e = floating point double precision (64-bit) units
od-help-f = floating point double precision (32-bit) units
od-help-capital-f = floating point double precision (64-bit) units
+15
View File
@@ -67,6 +67,7 @@ od-help-address-radix = Sélectionner la base dans laquelle les décalages de fi
od-help-skip-bytes = Ignorer les octets d'entrée avant le formatage et l'écriture.
od-help-read-bytes = limiter le dump à OCTETS octets d'entrée
od-help-endian = ordre des octets à utiliser pour les formats multi-octets
od-help-strings = afficher les chaînes d'au moins OCTETS caractères graphiques. 3 est supposé quand OCTETS n'est pas spécifié.
od-help-a = caractères nommés, ignorant le bit d'ordre supérieur
od-help-b = octets octaux
od-help-c = caractères ASCII ou échappements antislash
@@ -77,3 +78,17 @@ od-help-output-duplicates = ne pas utiliser * pour marquer la suppression de lig
od-help-width = sortir OCTETS octets par ligne de sortie. 32 est impliqué quand OCTETS n'est pas
spécifié.
od-help-traditional = mode de compatibilité avec une entrée, décalage et étiquette.
od-help-o = unités octales 2-octets
od-help-capital-i = unités décimales 8-octets
od-help-capital-l = unités décimales 8-octets
od-help-i = unités décimales 4-octets
od-help-l = unités décimales 8-octets
od-help-x = unités hexadécimales 2-octets
od-help-h = unités hexadécimales 2-octets
od-help-capital-o = unités octales 4-octets
od-help-s = unités décimales 2-octets
od-help-capital-x = unités hexadécimales 4-octets
od-help-capital-h = unités hexadécimales 4-octets
od-help-e = unités virgule flottante double précision (64-bits)
od-help-f = unités virgule flottante double précision (32-bits)
od-help-capital-f = unités virgule flottante double précision (64-bits)
+186 -70
View File
@@ -26,7 +26,7 @@ mod prn_int;
use std::cmp;
use std::fmt::Write;
use std::io::BufReader;
use std::io::{BufReader, Read};
use crate::byteorder_io::ByteOrder;
use crate::formatter_item_info::FormatWriter;
@@ -76,6 +76,21 @@ struct OdOptions {
line_bytes: usize,
output_duplicates: bool,
radix: Radix,
string_min_length: Option<usize>,
}
/// Helper function to parse bytes with error handling
fn parse_bytes_option(matches: &ArgMatches, option_name: &str) -> UResult<Option<u64>> {
match matches.get_one::<String>(option_name) {
None => Ok(None),
Some(s) => match parse_number_of_bytes(s) {
Ok(n) => Ok(Some(n)),
Err(e) => Err(USimpleError::new(
1,
format_error_message(&e, s, option_name),
)),
},
}
}
impl OdOptions {
@@ -95,18 +110,7 @@ impl OdOptions {
ByteOrder::Native
};
let mut skip_bytes = match matches.get_one::<String>(options::SKIP_BYTES) {
None => 0,
Some(s) => match parse_number_of_bytes(s) {
Ok(n) => n,
Err(e) => {
return Err(USimpleError::new(
1,
format_error_message(&e, s, options::SKIP_BYTES),
));
}
},
};
let mut skip_bytes = parse_bytes_option(matches, options::SKIP_BYTES)?.unwrap_or(0);
let mut label: Option<u64> = None;
@@ -156,17 +160,16 @@ impl OdOptions {
let output_duplicates = matches.get_flag(options::OUTPUT_DUPLICATES);
let read_bytes = match matches.get_one::<String>(options::READ_BYTES) {
let read_bytes = parse_bytes_option(matches, options::READ_BYTES)?;
let string_min_length = match parse_bytes_option(matches, options::STRINGS)? {
None => None,
Some(s) => match parse_number_of_bytes(s) {
Ok(n) => Some(n),
Err(e) => {
return Err(USimpleError::new(
1,
format_error_message(&e, s, options::READ_BYTES),
));
}
},
Some(n) => Some(usize::try_from(n).map_err(|_| {
USimpleError::new(
1,
translate!("od-error-argument-too-large", "option" => "-S", "value" => n.to_string()),
)
})?),
};
let radix = match matches.get_one::<String>(options::ADDRESS_RADIX) {
@@ -208,6 +211,7 @@ impl OdOptions {
line_bytes,
output_duplicates,
radix,
string_min_length,
})
}
}
@@ -224,28 +228,39 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let od_options = OdOptions::new(&clap_matches, &args)?;
let mut input_offset =
InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label);
// Check if we're in strings mode
if let Some(min_length) = od_options.string_min_length {
extract_strings_from_input(
&od_options.input_strings,
od_options.skip_bytes,
od_options.read_bytes,
min_length,
od_options.radix,
)
} else {
let mut input_offset =
InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label);
let mut input = open_input_peek_reader(
&od_options.input_strings,
od_options.skip_bytes,
od_options.read_bytes,
);
let mut input_decoder = InputDecoder::new(
&mut input,
od_options.line_bytes,
PEEK_BUFFER_SIZE,
od_options.byte_order,
);
let mut input = open_input_peek_reader(
&od_options.input_strings,
od_options.skip_bytes,
od_options.read_bytes,
);
let mut input_decoder = InputDecoder::new(
&mut input,
od_options.line_bytes,
PEEK_BUFFER_SIZE,
od_options.byte_order,
);
let output_info = OutputInfo::new(
od_options.line_bytes,
&od_options.formats[..],
od_options.output_duplicates,
);
let output_info = OutputInfo::new(
od_options.line_bytes,
&od_options.formats[..],
od_options.output_duplicates,
);
odfunc(&mut input_offset, &mut input_decoder, &output_info)
odfunc(&mut input_offset, &mut input_decoder, &output_info)
}
}
pub fn uu_app() -> Command {
@@ -263,7 +278,7 @@ pub fn uu_app() -> Command {
Arg::new(options::HELP)
.long(options::HELP)
.help(translate!("od-help-help"))
.action(ArgAction::Help)
.action(ArgAction::Help),
)
.arg(
Arg::new(options::ADDRESS_RADIX)
@@ -297,10 +312,8 @@ pub fn uu_app() -> Command {
Arg::new(options::STRINGS)
.short('S')
.long(options::STRINGS)
.help(
"NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \
BYTES is not specified.",
)
.help(translate!("od-help-strings"))
.num_args(0..=1)
.default_missing_value("3")
.value_name("BYTES"),
)
@@ -337,85 +350,85 @@ pub fn uu_app() -> Command {
.arg(
Arg::new("o")
.short('o')
.help("octal 2-byte units")
.help(translate!("od-help-o"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("I")
.short('I')
.help("decimal 8-byte units")
.help(translate!("od-help-capital-i"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("L")
.short('L')
.help("decimal 8-byte units")
.help(translate!("od-help-capital-l"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("i")
.short('i')
.help("decimal 4-byte units")
.help(translate!("od-help-i"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("l")
.short('l')
.help("decimal 8-byte units")
.help(translate!("od-help-l"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("x")
.short('x')
.help("hexadecimal 2-byte units")
.help(translate!("od-help-x"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("h")
.short('h')
.help("hexadecimal 2-byte units")
.help(translate!("od-help-h"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("O")
.short('O')
.help("octal 4-byte units")
.action(ArgAction::SetTrue)
.help(translate!("od-help-capital-o"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("s")
.short('s')
.help("decimal 2-byte units")
.help(translate!("od-help-s"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("X")
.short('X')
.help("hexadecimal 4-byte units")
.help(translate!("od-help-capital-x"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("H")
.short('H')
.help("hexadecimal 4-byte units")
.help(translate!("od-help-capital-h"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("e")
.short('e')
.help("floating point double precision (64-bit) units")
.help(translate!("od-help-e"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("f")
.short('f')
.help("floating point double precision (32-bit) units")
.help(translate!("od-help-f"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("F")
.short('F')
.help("floating point double precision (64-bit) units")
.help(translate!("od-help-capital-f"))
.action(ArgAction::SetTrue),
)
.arg(
@@ -532,6 +545,105 @@ where
}
}
/// Extract and display printable strings from input (od -S option)
fn extract_strings_from_input(
input_strings: &[String],
skip_bytes: u64,
read_bytes: Option<u64>,
min_length: usize,
radix: Radix,
) -> UResult<()> {
let inputs = map_input_strings(input_strings);
let mut mf = MultifileReader::new(inputs);
// Apply skip_bytes by reading and discarding
let mut skipped = 0u64;
while skipped < skip_bytes {
let to_skip = std::cmp::min(8192, skip_bytes - skipped);
let mut skip_buf = vec![0u8; to_skip as usize];
match mf.read(&mut skip_buf) {
Ok(0) => break, // EOF reached
Ok(n) => skipped += n as u64,
Err(_) => break,
}
}
// Helper function to format and print a string
let print_string = |offset: u64, string: &[u8]| {
let string_content = String::from_utf8_lossy(string);
match radix {
Radix::NoPrefix => println!("{string_content}"),
Radix::Decimal => println!("{offset:07} {string_content}"),
Radix::Hexadecimal => println!("{offset:07x} {string_content}"),
Radix::Octal => println!("{offset:07o} {string_content}"),
}
};
let mut current_string = Vec::new();
let mut string_start_offset = 0u64;
let mut current_offset = skip_bytes;
let mut bytes_read = 0u64;
let mut buf = [0u8; 1];
loop {
// Check if we've reached the read_bytes limit
if let Some(limit) = read_bytes {
if bytes_read >= limit {
// Special case: when -N limit is reached with a pending string
// that meets min_length, output it even without null terminator
if current_string.len() >= min_length {
print_string(string_start_offset, &current_string);
}
break;
}
}
// Read one byte at a time
match mf.read(&mut buf) {
Ok(0) => break, // EOF
Ok(_) => {
bytes_read += 1;
let byte = buf[0];
// Check if it's a printable character (including space)
if (0x20..=0x7E).contains(&byte) {
if current_string.is_empty() {
string_start_offset = current_offset;
}
current_string.push(byte);
} else {
// Either null terminator or non-printable character
if byte == 0 && current_string.len() >= min_length {
// Null terminator found with valid string
print_string(string_start_offset, &current_string);
}
current_string.clear();
}
current_offset += 1;
}
Err(e) => {
// Note: GNU od does not output unterminated strings at EOF
// Strings must be null-terminated to be output
if mf.has_error() {
show_error!("{}", e);
return Err(1.into());
}
break;
}
}
}
// GNU od doesn't output an offset when strings mode finds no valid strings
// This includes cases with only unterminated or too-short strings
if mf.has_error() {
Err(1.into())
} else {
Ok(())
}
}
/// Outputs a single line of input, into one or more lines human readable output.
fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &OutputInfo) {
let mut first = true; // First line of a multi-format raster.
@@ -595,6 +707,17 @@ fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &Output
}
}
/// Helper function to convert input strings to InputSource
fn map_input_strings(input_strings: &[String]) -> Vec<InputSource<'_>> {
input_strings
.iter()
.map(|w| match w as &str {
"-" => InputSource::Stdin,
x => InputSource::FileName(x),
})
.collect()
}
/// returns a reader implementing `PeekRead + Read + HasError` providing the combined input
///
/// `skip_bytes` is the number of bytes skipped from the input
@@ -605,14 +728,7 @@ fn open_input_peek_reader(
read_bytes: Option<u64>,
) -> PeekReader<BufReader<PartialReader<MultifileReader<'_>>>> {
// should return "impl PeekRead + Read + HasError" when supported in (stable) rust
let inputs = input_strings
.iter()
.map(|w| match w as &str {
"-" => InputSource::Stdin,
x => InputSource::FileName(x),
})
.collect::<Vec<_>>();
let inputs = map_input_strings(input_strings);
let mf = MultifileReader::new(inputs);
let pr = PartialReader::new(mf, skip_bytes, read_bytes);
// Add a BufReader over the top of the PartialReader. This will have the
+28 -21
View File
@@ -14,16 +14,17 @@ use uucore::benchmark::{run_util_function, setup_test_file, text_data};
fn sort_ascii_c_locale(bencher: Bencher) {
let data = text_data::generate_ascii_data_simple(100_000);
let file_path = setup_test_file(&data);
// Reuse the same output file across iterations to reduce filesystem variance
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap().to_string();
bencher.bench(|| {
unsafe {
env::set_var("LC_ALL", "C");
}
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap();
black_box(run_util_function(
uumain,
&["-o", output_path, file_path.to_str().unwrap()],
&["-o", &output_path, file_path.to_str().unwrap()],
));
});
}
@@ -33,16 +34,17 @@ fn sort_ascii_c_locale(bencher: Bencher) {
fn sort_ascii_utf8_locale(bencher: Bencher) {
let data = text_data::generate_ascii_data_simple(200_000);
let file_path = setup_test_file(&data);
// Reuse the same output file across iterations to reduce filesystem variance
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap().to_string();
bencher.bench(|| {
unsafe {
env::set_var("LC_ALL", "en_US.UTF-8");
}
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap();
black_box(run_util_function(
uumain,
&["-o", output_path, file_path.to_str().unwrap()],
&["-o", &output_path, file_path.to_str().unwrap()],
));
});
}
@@ -52,16 +54,17 @@ fn sort_ascii_utf8_locale(bencher: Bencher) {
fn sort_mixed_c_locale(bencher: Bencher) {
let data = text_data::generate_mixed_locale_data(50_000);
let file_path = setup_test_file(&data);
// Reuse the same output file across iterations to reduce filesystem variance
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap().to_string();
bencher.bench(|| {
unsafe {
env::set_var("LC_ALL", "C");
}
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap();
black_box(run_util_function(
uumain,
&["-o", output_path, file_path.to_str().unwrap()],
&["-o", &output_path, file_path.to_str().unwrap()],
));
});
}
@@ -71,16 +74,17 @@ fn sort_mixed_c_locale(bencher: Bencher) {
fn sort_mixed_utf8_locale(bencher: Bencher) {
let data = text_data::generate_mixed_locale_data(50_000);
let file_path = setup_test_file(&data);
// Reuse the same output file across iterations to reduce filesystem variance
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap().to_string();
bencher.bench(|| {
unsafe {
env::set_var("LC_ALL", "en_US.UTF-8");
}
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap();
black_box(run_util_function(
uumain,
&["-o", output_path, file_path.to_str().unwrap()],
&["-o", &output_path, file_path.to_str().unwrap()],
));
});
}
@@ -90,16 +94,17 @@ fn sort_mixed_utf8_locale(bencher: Bencher) {
fn sort_german_c_locale(bencher: Bencher) {
let data = text_data::generate_german_locale_data(50_000);
let file_path = setup_test_file(&data);
// Reuse the same output file across iterations to reduce filesystem variance
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap().to_string();
bencher.bench(|| {
unsafe {
env::set_var("LC_ALL", "C");
}
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap();
black_box(run_util_function(
uumain,
&["-o", output_path, file_path.to_str().unwrap()],
&["-o", &output_path, file_path.to_str().unwrap()],
));
});
}
@@ -109,16 +114,17 @@ fn sort_german_c_locale(bencher: Bencher) {
fn sort_german_locale(bencher: Bencher) {
let data = text_data::generate_german_locale_data(50_000);
let file_path = setup_test_file(&data);
// Reuse the same output file across iterations to reduce filesystem variance
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap().to_string();
bencher.bench(|| {
unsafe {
env::set_var("LC_ALL", "de_DE.UTF-8");
}
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap();
black_box(run_util_function(
uumain,
&["-o", output_path, file_path.to_str().unwrap()],
&["-o", &output_path, file_path.to_str().unwrap()],
));
});
}
@@ -128,16 +134,17 @@ fn sort_german_locale(bencher: Bencher) {
fn sort_random_strings(bencher: Bencher) {
let data = text_data::generate_random_strings(50_000, 50);
let file_path = setup_test_file(&data);
// Reuse the same output file across iterations to reduce filesystem variance
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap().to_string();
bencher.bench(|| {
unsafe {
env::set_var("LC_ALL", "en_US.UTF-8");
}
let output_file = NamedTempFile::new().unwrap();
let output_path = output_file.path().to_str().unwrap();
black_box(run_util_function(
uumain,
&["-o", output_path, file_path.to_str().unwrap()],
&["-o", &output_path, file_path.to_str().unwrap()],
));
});
}
+28
View File
@@ -690,6 +690,24 @@ fn test_block_size_from_env() {
assert_eq!(get_header("BLOCKSIZE", "333"), "333B-blocks");
}
#[test]
fn test_block_size_from_env_zero() {
fn get_header(env_var: &str, env_value: &str) -> String {
let output = new_ucmd!()
.arg("--output=size")
.env(env_var, env_value)
.succeeds()
.stdout_str_lossy();
output.lines().next().unwrap().trim().to_string()
}
let default_block_size_header = "1K-blocks";
assert_eq!(get_header("DF_BLOCK_SIZE", "0"), default_block_size_header);
assert_eq!(get_header("BLOCK_SIZE", "0"), default_block_size_header);
assert_eq!(get_header("BLOCKSIZE", "0"), default_block_size_header);
}
#[test]
fn test_block_size_from_env_precedences() {
fn get_header(one: (&str, &str), two: (&str, &str)) -> String {
@@ -747,6 +765,16 @@ fn test_invalid_block_size_from_env() {
let header = output.lines().next().unwrap().trim().to_string();
assert_eq!(header, default_block_size_header);
let output = new_ucmd!()
.arg("--output=size")
.env("DF_BLOCK_SIZE", "0")
.env("BLOCK_SIZE", "222")
.succeeds()
.stdout_str_lossy();
let header = output.lines().next().unwrap().trim().to_string();
assert_eq!(header, default_block_size_header);
}
#[test]
+14
View File
@@ -5355,6 +5355,12 @@ fn test_ls_invalid_block_size() {
.fails_with_code(2)
.no_stdout()
.stderr_is("ls: invalid --block-size argument 'invalid'\n");
new_ucmd!()
.arg("--block-size=0")
.fails_with_code(2)
.no_stdout()
.stderr_is("ls: invalid --block-size argument '0'\n");
}
#[cfg(all(unix, feature = "dd"))]
@@ -5394,6 +5400,14 @@ fn test_ls_invalid_block_size_in_env_var() {
.succeeds()
.stdout_contains_line("total 4")
.stdout_contains(" 1024 ");
scene
.ucmd()
.arg("-og")
.env("BLOCKSIZE", "0")
.succeeds()
.stdout_contains_line("total 4")
.stdout_contains(" 1024 ");
}
#[cfg(all(unix, feature = "dd"))]
+119 -102
View File
@@ -3,16 +3,14 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore abcdefghijklmnopqrstuvwxyz Anone fdbb
// spell-checker:ignore abcdefghijklmnopqrstuvwxyz Anone fdbb littl
#[cfg(unix)]
use std::io::Read;
use unindent::unindent;
use uutests::at_and_ucmd;
use uutests::new_ucmd;
use uutests::util::TestScenario;
use uutests::util_name;
use uutests::{at_and_ucmd, new_ucmd, util_name};
// octal dump of 'abcdefghijklmnopqrstuvwxyz\n'
static ALPHA_OUT: &str = "
@@ -32,39 +30,30 @@ fn test_file() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("test", "abcdefghijklmnopqrstuvwxyz\n");
scene
.ucmd()
.arg("--endian=little")
.arg("test")
.succeeds()
.no_stderr()
.stdout_is(unindent(ALPHA_OUT));
scene
.ucmd()
.arg("--endian=littl") // spell-checker:disable-line
.arg("test")
.succeeds()
.no_stderr()
.stdout_is(unindent(ALPHA_OUT));
scene
.ucmd()
.arg("--endian=l")
.arg("test")
.succeeds()
.no_stderr()
.stdout_is(unindent(ALPHA_OUT));
for arg in ["--endian=little", "--endian=littl", "--endian=l"] {
scene
.ucmd()
.arg(arg)
.arg("test")
.succeeds()
.stdout_only(unindent(ALPHA_OUT));
}
// Ensure that default format matches `-t o2`, and that `-t` does not absorb file argument
scene
.ucmd()
.arg("--endian=little")
.arg("-t")
.arg("o2")
.arg("test");
.arg("test")
.succeeds()
.stdout_only(unindent(ALPHA_OUT));
}
// Test that od can read 2 files and concatenate the contents
#[test]
fn test_2files() {
fn test_two_files() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("test1", "abcdefghijklmnop");
at.write("test2", "qrstuvwxyz\n"); // spell-checker:disable-line
@@ -72,15 +61,13 @@ fn test_2files() {
.arg("test1")
.arg("test2")
.succeeds()
.no_stderr()
.stdout_is(unindent(ALPHA_OUT));
.stdout_only(unindent(ALPHA_OUT));
}
// Test that od gives non-0 exit val for filename that doesn't exist.
#[test]
fn test_no_file() {
let (_at, mut ucmd) = at_and_ucmd!();
ucmd.arg("}surely'none'would'thus'a'file'name").fails();
fn test_non_existing_file() {
new_ucmd!().arg("non_existing_file").fails();
}
// Test that od reads from stdin instead of a file
@@ -91,8 +78,7 @@ fn test_from_stdin() {
.arg("--endian=little")
.run_piped_stdin(input.as_bytes())
.success()
.no_stderr()
.stdout_is(unindent(ALPHA_OUT));
.stdout_only(unindent(ALPHA_OUT));
}
// Test that od reads from stdin and also from files
@@ -110,8 +96,7 @@ fn test_from_mixed() {
.arg("test-3")
.run_piped_stdin(data2.as_bytes())
.success()
.no_stderr()
.stdout_is(unindent(ALPHA_OUT));
.stdout_only(unindent(ALPHA_OUT));
}
#[test]
@@ -122,8 +107,7 @@ fn test_multiple_formats() {
.arg("-b")
.run_piped_stdin(input.as_bytes())
.success()
.no_stderr()
.stdout_is(unindent(
.stdout_only(unindent(
"
0000000 a b c d e f g h i j k l m n o p
141 142 143 144 145 146 147 150 151 152 153 154 155 156 157 160
@@ -151,8 +135,7 @@ fn test_dec() {
.arg("-s")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -171,8 +154,7 @@ fn test_hex16() {
.arg("-x")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -189,8 +171,7 @@ fn test_hex32() {
.arg("-X")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -217,8 +198,7 @@ fn test_f16() {
.arg("-w8")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -245,8 +225,7 @@ fn test_fh() {
.arg("-w8")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -273,8 +252,7 @@ fn test_fb() {
.arg("-w8")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -300,8 +278,7 @@ fn test_f32() {
.arg("-f")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -329,8 +306,7 @@ fn test_f64() {
.arg("-F")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -340,8 +316,7 @@ fn test_multibyte() {
.args(&["-t", "c"])
.run_piped_stdin(input.as_bytes())
.success()
.no_stderr()
.stdout_is(unindent(
.stdout_only(unindent(
r"
0000000 342 200 231 342 200 220 313 206 342 200 230 313 234 350 252 236
0000020 360 237 231 202 342 234 205 360 237 220 266 360 235 233 221 U
@@ -368,8 +343,7 @@ fn test_width() {
.arg("-v")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -425,8 +399,7 @@ fn test_width_without_value() {
.arg("-w")
.run_piped_stdin(&input[..])
.success()
.no_stderr()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -456,9 +429,8 @@ fn test_suppress_duplicates() {
.arg("-O")
.arg("-x")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -482,9 +454,8 @@ fn test_big_endian() {
.arg("-X")
.arg("-x")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(&expected_output);
.stdout_only(&expected_output);
new_ucmd!()
.arg("--endian=b")
.arg("-F")
@@ -492,9 +463,8 @@ fn test_big_endian() {
.arg("-X")
.arg("-x")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -518,9 +488,8 @@ fn test_alignment_Xxa() {
.arg("-x")
.arg("-a")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -542,9 +511,8 @@ fn test_alignment_Fx() {
.arg("-F")
.arg("-x")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -570,9 +538,8 @@ fn test_max_uint() {
.arg("-Dd")
.arg("--format=u1")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -593,9 +560,8 @@ fn test_hex_offset() {
.arg("-X")
.arg("-X")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -616,9 +582,8 @@ fn test_dec_offset() {
.arg("-X")
.arg("-X")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -632,9 +597,8 @@ fn test_no_offset() {
.arg("-X")
.arg("-X")
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(expected_output);
.stdout_only(expected_output);
}
#[test]
@@ -670,9 +634,8 @@ fn test_skip_bytes() {
.arg("-c")
.arg("--skip-bytes=5")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
"
0000005 f g h i j k l m n o p q
0000021
@@ -687,9 +650,8 @@ fn test_skip_bytes_hex() {
.arg("-c")
.arg("--skip-bytes=0xB")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
"
0000013 l m n o p q
0000021
@@ -699,9 +661,8 @@ fn test_skip_bytes_hex() {
.arg("-c")
.arg("--skip-bytes=0xE")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
"
0000016 o p q
0000021
@@ -765,9 +726,8 @@ fn test_ascii_dump() {
new_ucmd!()
.arg("-tx1zacz") // spell-checker:disable-line
.run_piped_stdin(&input[..])
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
r"
0000000 00 01 0a 0d 10 1f 20 61 62 63 7d 7e 7f 80 90 a0 >...... abc}~....<
nul soh nl cr dle us sp a b c } ~ del nul dle sp
@@ -793,8 +753,7 @@ fn test_filename_parsing() {
.arg("--")
.arg("-f")
.succeeds()
.no_stderr()
.stdout_is(unindent(
.stdout_only(unindent(
"
000000 m i n u s sp l o w e r c a s e sp
000010 f nl
@@ -810,9 +769,8 @@ fn test_stdin_offset() {
.arg("-c")
.arg("+5")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
"
0000005 f g h i j k l m n o p q
0000021
@@ -828,8 +786,7 @@ fn test_file_offset() {
.arg("-f")
.arg("10")
.succeeds()
.no_stderr()
.stdout_is(unindent(
.stdout_only(unindent(
r"
0000010 w e r c a s e f \n
0000022
@@ -849,9 +806,8 @@ fn test_traditional() {
.arg("10")
.arg("0")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
r"
0000010 (0000000) i j k l m n o p q
i j k l m n o p q
@@ -870,9 +826,8 @@ fn test_traditional_with_skip_bytes_override() {
.arg("-c")
.arg("0")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
r"
0000000 a b c d e f g h i j k l m n o p
0000020
@@ -889,9 +844,8 @@ fn test_traditional_with_skip_bytes_non_override() {
.arg("--skip-bytes=10")
.arg("-c")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
r"
0000012 k l m n o p
0000020
@@ -923,9 +877,8 @@ fn test_traditional_only_label() {
.arg("10")
.arg("0x10")
.run_piped_stdin(input.as_bytes())
.no_stderr()
.success()
.stdout_is(unindent(
.stdout_only(unindent(
r"
(0000020) i j k l m n o p q r s t u v w x
i j k l m n o p q r s t u v w x
@@ -989,6 +942,70 @@ fn test_od_options_after_filename() {
.arg("-t")
.arg("x2")
.succeeds()
.no_stderr()
.stdout_is(" 1c68 fdbb\n");
.stdout_only(" 1c68 fdbb\n");
}
#[test]
fn test_od_strings_option() {
// Test -S option: output strings of at least N graphic chars
// Test -S0: output all null-terminated strings regardless of length
new_ucmd!()
.arg("-S0")
.pipe_in(b"hello\x00world\x00")
.succeeds()
.stdout_is("0000000 hello\n0000006 world\n");
// Test -S0 with single character strings
new_ucmd!()
.arg("-S0")
.pipe_in(b"a\x00b\x00cd\x00")
.succeeds()
.stdout_is("0000000 a\n0000002 b\n0000004 cd\n");
// Test with null-terminated strings
new_ucmd!()
.arg("-S3")
.pipe_in(b"\x01hello\x00world\x00ab\x00")
.succeeds()
.stdout_is("0000001 hello\n0000007 world\n");
// Test with -S10 to show only strings with minimum length
new_ucmd!()
.arg("-S10")
.pipe_in(b"\x01 \x00 \x00")
.succeeds()
.stdout_is("0000001 \n0000014 \n");
// Test with unterminated string at EOF (should not output)
new_ucmd!()
.arg("-S10")
.pipe_in(b" ")
.succeeds()
.no_output();
// Test with -N limit and pending string (should output even without null)
let expected = "0000000 \n";
new_ucmd!()
.arg("-N2")
.arg("-S1")
.pipe_in(" ")
.succeeds()
.stdout_is(expected);
// Test with -N limit and string too short
new_ucmd!()
.arg("-N11")
.arg("-S11")
.pipe_in(b" \x00")
.succeeds()
.no_output();
// Test with no address prefix (-An)
new_ucmd!()
.arg("-S3")
.arg("-An")
.pipe_in(b"hello\x00world\x00")
.succeeds()
.stdout_is("hello\nworld\n");
}