Skip to content
Merged
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
1 change: 1 addition & 0 deletions .vscode/cspell.dictionaries/jargon.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ microbenchmarks
microbenchmarking
multibyte
multicall
nmerge
noatime
nocache
nocreat
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions src/uu/sort/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ tempfile = { workspace = true }
unicode-width = { workspace = true }
uucore = { workspace = true, features = ["fs", "version-cmp"] }

[target.'cfg(target_os = "linux")'.dependencies]
nix = { workspace = true }

[[bin]]
name = "sort"
path = "src/main.rs"
62 changes: 52 additions & 10 deletions src/uu/sort/src/sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html
// https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html

// spell-checker:ignore (misc) HFKJFK Mbdfhn
// spell-checker:ignore (misc) HFKJFK Mbdfhn getrlimit RLIMIT_NOFILE rlim

mod check;
mod chunks;
Expand All @@ -23,6 +23,8 @@ use clap::{crate_version, Arg, ArgAction, Command};
use custom_str_cmp::custom_str_cmp;
use ext_sort::ext_sort;
use fnv::FnvHasher;
#[cfg(target_os = "linux")]
use nix::libc::{getrlimit, rlimit, RLIMIT_NOFILE};
use numeric_str_cmp::{human_numeric_str_cmp, numeric_str_cmp, NumInfo, NumInfoParseSettings};
use rand::{thread_rng, Rng};
use rayon::prelude::*;
Expand All @@ -45,7 +47,7 @@ use uucore::line_ending::LineEnding;
use uucore::parse_size::{ParseSizeError, Parser};
use uucore::shortcut_value_parser::ShortcutValueParser;
use uucore::version_cmp::version_cmp;
use uucore::{format_usage, help_about, help_section, help_usage};
use uucore::{format_usage, help_about, help_section, help_usage, show_error};

use crate::tmp_dir::TmpDirWrapper;

Expand Down Expand Up @@ -146,7 +148,9 @@ enum SortError {
CompressProgTerminatedAbnormally {
prog: String,
},
TmpDirCreationFailed,
TmpFileCreationFailed {
path: PathBuf,
},
Uft8Error {
error: Utf8Error,
},
Expand Down Expand Up @@ -212,7 +216,9 @@ impl Display for SortError {
Self::CompressProgTerminatedAbnormally { prog } => {
write!(f, "{} terminated abnormally", prog.quote())
}
Self::TmpDirCreationFailed => write!(f, "could not create temporary directory"),
Self::TmpFileCreationFailed { path } => {
write!(f, "cannot create temporary file in '{}':", path.display())
}
Self::Uft8Error { error } => write!(f, "{error}"),
}
}
Expand Down Expand Up @@ -1030,6 +1036,18 @@ fn make_sort_mode_arg(mode: &'static str, short: char, help: &'static str) -> Ar
arg
}

#[cfg(target_os = "linux")]
fn get_rlimit() -> UResult<usize> {
let mut limit = rlimit {
rlim_cur: 0,
rlim_max: 0,
};
match unsafe { getrlimit(RLIMIT_NOFILE, &mut limit) } {
0 => Ok(limit.rlim_cur as usize),
_ => Err(UUsageError::new(2, "Failed to fetch rlimit")),
}
}

#[uucore::main]
#[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Expand Down Expand Up @@ -1158,12 +1176,36 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.map(String::from);

if let Some(n_merge) = matches.get_one::<String>(options::BATCH_SIZE) {
settings.merge_batch_size = n_merge.parse().map_err(|_| {
UUsageError::new(
2,
format!("invalid --batch-size argument {}", n_merge.quote()),
)
})?;
match n_merge.parse::<usize>() {
Ok(parsed_value) => {
if parsed_value < 2 {
show_error!("invalid --batch-size argument '{}'", n_merge);
return Err(UUsageError::new(2, "minimum --batch-size argument is '2'"));
}
settings.merge_batch_size = parsed_value;
}
Err(e) => {
let error_message = if *e.kind() == std::num::IntErrorKind::PosOverflow {
#[cfg(target_os = "linux")]
{
show_error!("--batch-size argument {} too large", n_merge.quote());

format!(
"maximum --batch-size argument with current rlimit is {}",
get_rlimit()?
)
}
#[cfg(not(target_os = "linux"))]
{
format!("--batch-size argument {} too large", n_merge.quote())
}
} else {
format!("invalid --batch-size argument {}", n_merge.quote())
};

return Err(UUsageError::new(2, error_message));
}
}
}

settings.line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED));
Expand Down
4 changes: 3 additions & 1 deletion src/uu/sort/src/tmp_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ impl TmpDirWrapper {
tempfile::Builder::new()
.prefix("uutils_sort")
.tempdir_in(&self.parent_path)
.map_err(|_| SortError::TmpDirCreationFailed)?,
.map_err(|_| SortError::TmpFileCreationFailed {
path: self.parent_path.clone(),
})?,
);

let path = self.temp_dir.as_ref().unwrap().path().to_owned();
Expand Down
32 changes: 32 additions & 0 deletions tests/by-util/test_sort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,38 @@ fn test_merge_batches() {
.stdout_only_fixture("ext_sort.expected");
}

#[test]
fn test_batch_size_invalid() {
TestScenario::new(util_name!())
.ucmd()
.arg("--batch-size=0")
.fails()
.code_is(2)
.stderr_contains("sort: invalid --batch-size argument '0'")
.stderr_contains("sort: minimum --batch-size argument is '2'");
}

#[test]
fn test_batch_size_too_large() {
let large_batch_size = "18446744073709551616";
TestScenario::new(util_name!())
.ucmd()
.arg(format!("--batch-size={}", large_batch_size))
.fails()
.code_is(2)
.stderr_contains(format!(
"--batch-size argument '{}' too large",
large_batch_size
));
#[cfg(target_os = "linux")]
TestScenario::new(util_name!())
.ucmd()
.arg(format!("--batch-size={}", large_batch_size))
.fails()
.code_is(2)
.stderr_contains("maximum --batch-size argument with current rlimit is");
}

#[test]
fn test_merge_batch_size() {
TestScenario::new(util_name!())
Expand Down
32 changes: 32 additions & 0 deletions util/gnu-patches/tests_sort_merge.pl.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
diff --git a/tests/sort/sort-merge.pl b/tests/sort/sort-merge.pl
index 89eed0c64..c2f5aa7e5 100755
--- a/tests/sort/sort-merge.pl
+++ b/tests/sort/sort-merge.pl
@@ -43,22 +43,22 @@ my @Tests =
# check validation of --batch-size option
['nmerge-0', "-m --batch-size=0", @inputs,
{ERR=>"$prog: invalid --batch-size argument '0'\n".
- "$prog: minimum --batch-size argument is '2'\n"}, {EXIT=>2}],
+ "$prog: minimum --batch-size argument is '2'\nTry 'sort --help' for more information.\n"}, {EXIT=>2}],

['nmerge-1', "-m --batch-size=1", @inputs,
{ERR=>"$prog: invalid --batch-size argument '1'\n".
- "$prog: minimum --batch-size argument is '2'\n"}, {EXIT=>2}],
+ "$prog: minimum --batch-size argument is '2'\nTry 'sort --help' for more information.\n"}, {EXIT=>2}],

['nmerge-neg', "-m --batch-size=-1", @inputs,
- {ERR=>"$prog: invalid --batch-size argument '-1'\n"}, {EXIT=>2}],
+ {ERR=>"$prog: invalid --batch-size argument '-1'\nTry 'sort --help' for more information.\n"}, {EXIT=>2}],

['nmerge-nan', "-m --batch-size=a", @inputs,
- {ERR=>"$prog: invalid --batch-size argument 'a'\n"}, {EXIT=>2}],
+ {ERR=>"$prog: invalid --batch-size argument 'a'\nTry 'sort --help' for more information.\n"}, {EXIT=>2}],

['nmerge-big', "-m --batch-size=$bigint", @inputs,
{ERR_SUBST=>'s/(current rlimit is) \d+/$1/'},
{ERR=>"$prog: --batch-size argument '$bigint' too large\n".
- "$prog: maximum --batch-size argument with current rlimit is\n"},
+ "$prog: maximum --batch-size argument with current rlimit is\nTry 'sort --help' for more information.\n"},
{EXIT=>2}],

# This should work since nmerge >= the number of input files