Merge pull request #9266 from sylvestre/sort-compress

sort: make compression program failures non-fatal, warn and fallback to plain files
This commit is contained in:
Daniel Hofstetter 2025-11-16 15:11:47 +01:00 committed by GitHub
commit a15ee0927b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 62 additions and 34 deletions

View file

@ -15,7 +15,7 @@ sort-open-failed = open failed: {$path}: {$error}
sort-parse-key-error = failed to parse key {$key}: {$msg}
sort-cannot-read = cannot read: {$path}: {$error}
sort-open-tmp-file-failed = failed to open temporary file: {$error}
sort-compress-prog-execution-failed = couldn't execute compress program: errno {$code}
sort-compress-prog-execution-failed = could not run compress program '{$prog}': {$error}
sort-compress-prog-terminated-abnormally = {$prog} terminated abnormally
sort-cannot-create-tmp-file = cannot create temporary file in '{$path}':
sort-file-operands-combined = extra operand '{$file}'

View file

@ -15,7 +15,7 @@ sort-open-failed = échec d'ouverture : {$path} : {$error}
sort-parse-key-error = échec d'analyse de la clé {$key} : {$msg}
sort-cannot-read = impossible de lire : {$path} : {$error}
sort-open-tmp-file-failed = échec d'ouverture du fichier temporaire : {$error}
sort-compress-prog-execution-failed = impossible d'exécuter le programme de compression : errno {$code}
sort-compress-prog-execution-failed = impossible d'exécuter le programme de compression '{$prog}' : {$error}
sort-compress-prog-terminated-abnormally = {$prog} s'est terminé anormalement
sort-cannot-create-tmp-file = impossible de créer un fichier temporaire dans '{$path}' :
sort-file-operands-combined = opérande supplémentaire '{$file}'

View file

@ -20,7 +20,7 @@ use std::{
};
use itertools::Itertools;
use uucore::error::UResult;
use uucore::error::{UResult, strip_errno};
use crate::Output;
use crate::chunks::RecycledChunk;
@ -52,10 +52,37 @@ pub fn ext_sort(
let settings = settings.clone();
move || sorter(&recycled_receiver, &sorted_sender, &settings)
});
if settings.compress_prog.is_some() {
// Test if compression program exists and works, disable if not
let mut effective_settings = settings.clone();
if let Some(ref prog) = settings.compress_prog {
// Test the compression program by trying to spawn it
match std::process::Command::new(prog)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
{
Ok(mut child) => {
// Kill the test process immediately
let _ = child.kill();
}
Err(err) => {
// Print the error and disable compression
eprintln!(
"sort: could not run compress program '{}': {}",
prog,
strip_errno(&err)
);
effective_settings.compress_prog = None;
}
}
}
if effective_settings.compress_prog.is_some() {
reader_writer::<_, WriteableCompressedTmpFile>(
files,
settings,
&effective_settings,
&sorted_receiver,
recycled_sender,
output,
@ -64,7 +91,7 @@ pub fn ext_sort(
} else {
reader_writer::<_, WriteablePlainTmpFile>(
files,
settings,
&effective_settings,
&sorted_receiver,
recycled_sender,
output,

View file

@ -488,7 +488,8 @@ impl WriteableTmpFile for WriteableCompressedTmpFile {
let mut child = command
.spawn()
.map_err(|err| SortError::CompressProgExecutionFailed {
code: err.raw_os_error().unwrap(),
prog: compress_prog.to_owned(),
error: err,
})?;
let child_stdin = child.stdin.take().unwrap();
Ok(Self {
@ -522,7 +523,8 @@ impl ClosedTmpFile for ClosedCompressedTmpFile {
let mut child = command
.spawn()
.map_err(|err| SortError::CompressProgExecutionFailed {
code: err.raw_os_error().unwrap(),
prog: self.compress_prog.clone(),
error: err,
})?;
let child_stdout = child.stdout.take().unwrap();
Ok(CompressedTmpMergeInput {

View file

@ -152,8 +152,8 @@ pub enum SortError {
#[error("{}", translate!("sort-open-tmp-file-failed", "error" => strip_errno(.error)))]
OpenTmpFileFailed { error: std::io::Error },
#[error("{}", translate!("sort-compress-prog-execution-failed", "code" => .code))]
CompressProgExecutionFailed { code: i32 },
#[error("{}", translate!("sort-compress-prog-execution-failed", "prog" => .prog, "error" => strip_errno(.error)))]
CompressProgExecutionFailed { prog: String, error: std::io::Error },
#[error("{}", translate!("sort-compress-prog-terminated-abnormally", "prog" => .prog.quote()))]
CompressProgTerminatedAbnormally { prog: String },

View file

@ -1002,32 +1002,31 @@ fn test_compress_merge() {
#[test]
#[cfg(not(target_os = "android"))]
fn test_compress_fail() {
let result = new_ucmd!()
.args(&[
"ext_sort.txt",
"-n",
"--compress-program",
"nonexistent-program",
"-S",
"10",
])
.succeeds();
#[cfg(not(windows))]
new_ucmd!()
.args(&[
"ext_sort.txt",
"-n",
"--compress-program",
"nonexistent-program",
"-S",
"10",
])
.fails()
.stderr_only("sort: couldn't execute compress program: errno 2\n");
// With coverage, it fails with a different error:
// "thread 'main' panicked at 'called `Option::unwrap()` on ...
// So, don't check the output
result.stderr_contains(
"sort: could not run compress program 'nonexistent-program': No such file or directory",
);
#[cfg(windows)]
new_ucmd!()
.args(&[
"ext_sort.txt",
"-n",
"--compress-program",
"nonexistent-program",
"-S",
"10",
])
.fails();
result.stderr_contains("could not run compress program");
// Check that it still produces correct sorted output to stdout
let expected = new_ucmd!()
.args(&["ext_sort.txt", "-n"])
.succeeds()
.stdout_move_str();
assert_eq!(result.stdout_str(), expected);
}
#[test]