From 8b2713510fea2d7db3f9b65a5d622cfe01a16b7c Mon Sep 17 00:00:00 2001 From: wtcpython <1762993226@qq.com> Date: Thu, 30 Jul 2026 21:50:15 +0800 Subject: [PATCH] sort: defer compress program errors until needed --- src/uu/sort/src/ext_sort/threaded.rs | 31 +++++++++++++++++++++------- tests/by-util/test_sort.rs | 17 ++++++++++++++- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/uu/sort/src/ext_sort/threaded.rs b/src/uu/sort/src/ext_sort/threaded.rs index 3f6f927129..0b925eb3b9 100644 --- a/src/uu/sort/src/ext_sort/threaded.rs +++ b/src/uu/sort/src/ext_sort/threaded.rs @@ -8,7 +8,7 @@ use std::cmp::Ordering; use std::fs::File; -use std::io::{Read, Write, stderr}; +use std::io::{Read, Write}; use std::iter; use std::path::PathBuf; use std::sync::mpsc::{Receiver, SyncSender}; @@ -16,6 +16,8 @@ use std::thread; use itertools::Itertools; use uucore::error::{UResult, strip_errno}; +use uucore::show_error; +use uucore::translate; use crate::Output; use crate::chunks::RecycledChunk; @@ -53,6 +55,8 @@ pub fn ext_sort( // Test if compression program exists and works, disable if not let mut effective_settings = settings.clone(); + // Keep the error until we know compression is actually needed. + let mut compress_prog_error = None; if let Some(ref prog) = settings.compress_prog { // Test the compression program by trying to spawn it match std::process::Command::new(prog) @@ -66,12 +70,7 @@ pub fn ext_sort( let _ = child.kill(); } Err(err) => { - // Print the error and disable compression - let _ = writeln!( - stderr(), - "sort: could not run compress program '{prog}': {}", - strip_errno(&err) - ); + compress_prog_error = Some((prog.clone(), err)); effective_settings.compress_prog = None; } } @@ -81,6 +80,7 @@ pub fn ext_sort( reader_writer::<_, WriteableCompressedTmpFile>( files, &effective_settings, + None, &sorted_receiver, recycled_sender, output, @@ -90,6 +90,7 @@ pub fn ext_sort( reader_writer::<_, WriteablePlainTmpFile>( files, &effective_settings, + compress_prog_error, &sorted_receiver, recycled_sender, output, @@ -104,6 +105,7 @@ fn reader_writer< >( files: F, settings: &GlobalSettings, + compress_prog_error: Option<(String, std::io::Error)>, receiver: &Receiver, sender: SyncSender, output: Output, @@ -126,6 +128,7 @@ fn reader_writer< separator, buffer_size, settings, + compress_prog_error, receiver, sender, )?; @@ -209,12 +212,14 @@ enum ReadResult { WroteChunksToFile { tmp_files: Vec }, } /// The function that is executed on the reader/writer thread. +#[allow(clippy::too_many_arguments)] fn read_write_loop( mut files: impl Iterator>>, tmp_dir: &mut TmpDirWrapper, separator: u8, buffer_size: usize, settings: &GlobalSettings, + compress_prog_error: Option<(String, std::io::Error)>, receiver: &Receiver, sender: SyncSender, ) -> UResult> { @@ -251,6 +256,18 @@ fn read_write_loop( } } + // The input did not fit into the first two in-memory chunks. + if let Some((prog, err)) = compress_prog_error { + show_error!( + "{}", + translate!( + "sort-compress-prog-execution-failed", + "prog" => prog, + "error" => strip_errno(&err) + ) + ); + } + let mut sender_option = Some(sender); let mut tmp_files = vec![]; loop { diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 2d5cb0a55d..8b9b962b6d 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -1705,8 +1705,9 @@ fn test_compress_fail() { "-n", "--compress-program", "nonexistent-program", + // Force external sorting by using 1K buffer size so ext_sort.txt spills to disk "-S", - "10", + "1K", ]) .succeeds(); @@ -1726,6 +1727,20 @@ fn test_compress_fail() { assert_eq!(result.stdout_str(), expected); } +#[test] +#[cfg(unix)] +fn test_input_error_before_compression_is_needed() { + let (at, mut ucmd) = at_and_ucmd!(); + at.write("input", "b\na\n"); + at.mkdir("directory"); + + ucmd.args(&["--compress-program=nonexistent", "input", "directory"]) + .fails_with_code(2) + .no_stdout() + .stderr_contains("Is a directory") + .stderr_does_not_contain("compress program"); +} + #[test] fn test_merge_batches() { new_ucmd!()