mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
Merge branch 'cp-preserve-strip-setuid-on-chown-fail' of github.com:Darius-Constantin/coreutils into cp-preserve-strip-setuid-on-chown-fail
This commit is contained in:
commit
25feb0475a
17 changed files with 231 additions and 164 deletions
18
.github/workflows/CICD.yml
vendored
18
.github/workflows/CICD.yml
vendored
|
|
@ -300,22 +300,22 @@ jobs:
|
|||
run: make nextest PROFILE=ci CARGOFLAGS="--hide-progress-bar"
|
||||
env:
|
||||
RUST_BACKTRACE: "1"
|
||||
|
||||
- name: "`make install PROFILE=release-fast COMPLETIONS=n MANPAGES=n LOCALES=n`"
|
||||
- name: "`make install PROG_PREFIX=uu- PROFILE=release-fast COMPLETIONS=n MANPAGES=n LOCALES=n`"
|
||||
shell: bash
|
||||
run: |
|
||||
set -x
|
||||
DESTDIR=/tmp/ make PROFILE=release-fast COMPLETIONS=n MANPAGES=n LOCALES=n install
|
||||
DESTDIR=/tmp/ make install PROG_PREFIX=uu- PROFILE=release-fast COMPLETIONS=n MANPAGES=n LOCALES=n
|
||||
# Check that utils are built with given profile
|
||||
./target/release-fast/true
|
||||
# Check that the utils are present
|
||||
test -f /tmp/usr/local/bin/tty
|
||||
# Check that the progs have prefix
|
||||
test -f /tmp/usr/local/bin/uu-tty
|
||||
test -f /tmp/usr/local/libexec/uu-coreutils/libstdbuf.*
|
||||
# Check that the manpage is not present
|
||||
! test -f /tmp/usr/local/share/man/man1/whoami.1
|
||||
! test -f /tmp/usr/local/share/man/man1/uu-whoami.1
|
||||
# Check that the completion is not present
|
||||
! test -f /tmp/usr/local/share/zsh/site-functions/_install
|
||||
! test -f /tmp/usr/local/share/bash-completion/completions/head.bash
|
||||
! test -f /tmp/usr/local/share/fish/vendor_completions.d/cat.fish
|
||||
! test -f /tmp/usr/local/share/zsh/site-functions/_uu-install
|
||||
! test -f /tmp/usr/local/share/bash-completion/completions/uu-head.bash
|
||||
! test -f /tmp/usr/local/share/fish/vendor_completions.d/uu-cat.fish
|
||||
env:
|
||||
RUST_BACKTRACE: "1"
|
||||
- name: "`make install`"
|
||||
|
|
|
|||
3
.github/workflows/fuzzing.yml
vendored
3
.github/workflows/fuzzing.yml
vendored
|
|
@ -79,8 +79,7 @@ jobs:
|
|||
matrix:
|
||||
test-target:
|
||||
- { name: fuzz_test, should_pass: true }
|
||||
# https://github.com/uutils/coreutils/issues/5311
|
||||
- { name: fuzz_date, should_pass: false }
|
||||
- { name: fuzz_date, should_pass: true }
|
||||
- { name: fuzz_expr, should_pass: true }
|
||||
- { name: fuzz_printf, should_pass: true }
|
||||
- { name: fuzz_echo, should_pass: true }
|
||||
|
|
|
|||
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3126,7 +3126,6 @@ dependencies = [
|
|||
"clap",
|
||||
"codspeed-divan-compat",
|
||||
"fluent",
|
||||
"hex",
|
||||
"tempfile",
|
||||
"uucore",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -27,20 +27,20 @@ CARGO ?= cargo
|
|||
CARGOFLAGS ?=
|
||||
RUSTC_ARCH ?= # should be empty except for cross-build, not --target $(shell rustc --print host-tuple)
|
||||
|
||||
#prefix prepended to all binaries and library dir
|
||||
PROG_PREFIX ?=
|
||||
|
||||
# Install directories
|
||||
PREFIX ?= /usr/local
|
||||
DESTDIR ?=
|
||||
BINDIR ?= $(PREFIX)/bin
|
||||
DATAROOTDIR ?= $(PREFIX)/share
|
||||
LIBSTDBUF_DIR ?= $(PREFIX)/libexec/coreutils
|
||||
LIBSTDBUF_DIR ?= $(PREFIX)/libexec/$(PROG_PREFIX)coreutils
|
||||
# Export variable so that it is used during the build
|
||||
export LIBSTDBUF_DIR
|
||||
|
||||
INSTALLDIR_BIN=$(DESTDIR)$(BINDIR)
|
||||
|
||||
#prefix to apply to coreutils binary and all tool binaries
|
||||
PROG_PREFIX ?=
|
||||
|
||||
# This won't support any directory with spaces in its name, but you can just
|
||||
# make a symlink without spaces that points to the directory.
|
||||
BASEDIR ?= $(shell pwd)
|
||||
|
|
|
|||
1
fuzz/Cargo.lock
generated
1
fuzz/Cargo.lock
generated
|
|
@ -1575,7 +1575,6 @@ version = "0.5.0"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"fluent",
|
||||
"hex",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,38 @@ use libfuzzer_sys::fuzz_target;
|
|||
|
||||
use std::ffi::OsString;
|
||||
use uu_date::uumain;
|
||||
use uufuzz::generate_and_run_uumain;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let delim: u8 = 0; // Null byte
|
||||
let args = data
|
||||
let fuzz_args: Vec<OsString> = data
|
||||
.split(|b| *b == delim)
|
||||
.filter_map(|e| std::str::from_utf8(e).ok())
|
||||
.map(OsString::from);
|
||||
uumain(args);
|
||||
.map(OsString::from)
|
||||
.collect();
|
||||
|
||||
// Skip test cases that would cause the program to read from stdin
|
||||
// These would hang the fuzzer waiting for input
|
||||
for i in 0..fuzz_args.len() {
|
||||
if let Some(arg) = fuzz_args.get(i) {
|
||||
let arg_str = arg.to_string_lossy();
|
||||
// Skip if -f- or --file=- (reads dates from stdin)
|
||||
if (arg_str == "-f"
|
||||
&& fuzz_args
|
||||
.get(i + 1)
|
||||
.map(|a| a.to_string_lossy() == "-")
|
||||
.unwrap_or(false))
|
||||
|| arg_str == "-f-"
|
||||
|| arg_str == "--file=-"
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add program name as first argument (required for proper argument parsing)
|
||||
let mut args = vec![OsString::from("date")];
|
||||
args.extend(fuzz_args);
|
||||
|
||||
let _ = generate_and_run_uumain(&args, uumain, None);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ uucore = { workspace = true, features = [
|
|||
"sum",
|
||||
"hardware",
|
||||
] }
|
||||
hex = { workspace = true }
|
||||
fluent = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use uucore::checksum::{
|
|||
use uucore::error::UResult;
|
||||
use uucore::hardware::{HasHardwareFeatures as _, SimdPolicy};
|
||||
use uucore::line_ending::LineEnding;
|
||||
use uucore::{format_usage, translate};
|
||||
use uucore::{format_usage, show_error, translate};
|
||||
|
||||
/// Print CPU hardware capability detection information to stderr
|
||||
/// This matches GNU cksum's --debug behavior
|
||||
|
|
@ -31,9 +31,9 @@ fn print_cpu_debug_info() {
|
|||
|
||||
fn print_feature(name: &str, available: bool) {
|
||||
if available {
|
||||
eprintln!("cksum: using {name} hardware support");
|
||||
show_error!("using {name} hardware support");
|
||||
} else {
|
||||
eprintln!("cksum: {name} support not detected");
|
||||
show_error!("{name} support not detected");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +140,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
let check = matches.get_flag(options::CHECK);
|
||||
|
||||
let check_flag = |flag| match (check, matches.get_flag(flag)) {
|
||||
(_, false) => Ok(false),
|
||||
(true, true) => Ok(true),
|
||||
(false, true) => Err(ChecksumError::CheckOnlyFlag(flag.into())),
|
||||
};
|
||||
|
||||
// Each of the following flags are only expected in --check mode.
|
||||
// If we encounter them otherwise, end with an error.
|
||||
let ignore_missing = check_flag(options::IGNORE_MISSING)?;
|
||||
let warn = check_flag(options::WARN)?;
|
||||
let quiet = check_flag(options::QUIET)?;
|
||||
let strict = check_flag(options::STRICT)?;
|
||||
let status = check_flag(options::STATUS)?;
|
||||
|
||||
let algo_cli = matches
|
||||
.get_one::<String>(options::ALGORITHM)
|
||||
.map(AlgoKind::from_cksum)
|
||||
|
|
@ -166,11 +180,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
let text_flag = matches.get_flag(options::TEXT);
|
||||
let binary_flag = matches.get_flag(options::BINARY);
|
||||
let strict = matches.get_flag(options::STRICT);
|
||||
let status = matches.get_flag(options::STATUS);
|
||||
let warn = matches.get_flag(options::WARN);
|
||||
let ignore_missing = matches.get_flag(options::IGNORE_MISSING);
|
||||
let quiet = matches.get_flag(options::QUIET);
|
||||
let tag = matches.get_flag(options::TAG);
|
||||
|
||||
if tag || binary_flag || text_flag {
|
||||
|
|
@ -191,6 +200,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
// Not --check
|
||||
|
||||
// Print hardware debug info if requested
|
||||
if matches.get_flag(options::DEBUG) {
|
||||
print_cpu_debug_info();
|
||||
}
|
||||
|
||||
// Set the default algorithm to CRC when not '--check'ing.
|
||||
let algo_kind = algo_cli.unwrap_or(AlgoKind::Crc);
|
||||
|
||||
|
|
@ -199,22 +213,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let algo = SizedAlgoKind::from_unsized(algo_kind, length)?;
|
||||
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
|
||||
|
||||
let output_format = figure_out_output_format(
|
||||
algo,
|
||||
tag,
|
||||
binary,
|
||||
matches.get_flag(options::RAW),
|
||||
matches.get_flag(options::BASE64),
|
||||
);
|
||||
|
||||
// Print hardware debug info if requested
|
||||
if matches.get_flag(options::DEBUG) {
|
||||
print_cpu_debug_info();
|
||||
}
|
||||
|
||||
let opts = ChecksumComputeOptions {
|
||||
algo_kind: algo,
|
||||
output_format,
|
||||
output_format: figure_out_output_format(
|
||||
algo,
|
||||
tag,
|
||||
binary,
|
||||
matches.get_flag(options::RAW),
|
||||
matches.get_flag(options::BASE64),
|
||||
),
|
||||
line_ending,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -104,3 +104,4 @@ date-error-date-overflow = date overflow '{$date}'
|
|||
date-error-setting-date-not-supported-macos = setting the date is not supported by macOS
|
||||
date-error-setting-date-not-supported-redox = setting the date is not supported by Redox
|
||||
date-error-cannot-set-date = cannot set date
|
||||
date-error-extra-operand = extra operand '{$operand}'
|
||||
|
|
|
|||
|
|
@ -99,3 +99,4 @@ date-error-date-overflow = débordement de date '{$date}'
|
|||
date-error-setting-date-not-supported-macos = la définition de la date n'est pas prise en charge par macOS
|
||||
date-error-setting-date-not-supported-redox = la définition de la date n'est pas prise en charge par Redox
|
||||
date-error-cannot-set-date = impossible de définir la date
|
||||
date-error-extra-operand = opérande supplémentaire '{$operand}'
|
||||
|
|
|
|||
|
|
@ -171,6 +171,17 @@ fn parse_military_timezone_with_offset(s: &str) -> Option<i32> {
|
|||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
|
||||
|
||||
// Check for extra operands (multiple positional arguments)
|
||||
if let Some(formats) = matches.get_many::<String>(OPT_FORMAT) {
|
||||
let format_args: Vec<&String> = formats.collect();
|
||||
if format_args.len() > 1 {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("date-error-extra-operand", "operand" => format_args[1]),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let format = if let Some(form) = matches.get_one::<String>(OPT_FORMAT) {
|
||||
if !form.starts_with('+') {
|
||||
return Err(USimpleError::new(
|
||||
|
|
@ -515,7 +526,7 @@ pub fn uu_app() -> Command {
|
|||
.help(translate!("date-help-universal"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(Arg::new(OPT_FORMAT))
|
||||
.arg(Arg::new(OPT_FORMAT).num_args(0..).trailing_var_arg(true))
|
||||
}
|
||||
|
||||
/// Return the appropriate format string for the given settings.
|
||||
|
|
|
|||
|
|
@ -164,16 +164,27 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
|
|||
binary_flag_default
|
||||
};
|
||||
let check = matches.get_flag("check");
|
||||
let status = matches.get_flag("status");
|
||||
let quiet = matches.get_flag("quiet");
|
||||
let strict = matches.get_flag("strict");
|
||||
let warn = matches.get_flag("warn");
|
||||
let ignore_missing = matches.get_flag("ignore-missing");
|
||||
|
||||
if ignore_missing && !check {
|
||||
// --ignore-missing needs -c
|
||||
return Err(ChecksumError::IgnoreNotCheck.into());
|
||||
}
|
||||
let check_flag = |flag| match (check, matches.get_flag(flag)) {
|
||||
(_, false) => Ok(false),
|
||||
(true, true) => Ok(true),
|
||||
(false, true) => Err(ChecksumError::CheckOnlyFlag(flag.into())),
|
||||
};
|
||||
|
||||
// Each of the following flags are only expected in --check mode.
|
||||
// If we encounter them otherwise, end with an error.
|
||||
let ignore_missing = check_flag("ignore-missing")?;
|
||||
let warn = check_flag("warn")?;
|
||||
let quiet = check_flag("quiet")?;
|
||||
let strict = check_flag("strict")?;
|
||||
let status = check_flag("status")?;
|
||||
|
||||
let files = matches.get_many::<OsString>(options::FILE).map_or_else(
|
||||
// No files given, read from stdin.
|
||||
|| Box::new(iter::once(OsStr::new("-"))) as Box<dyn Iterator<Item = &OsStr>>,
|
||||
// At least one file given, read from them.
|
||||
|files| Box::new(files.map(OsStr::new)) as Box<dyn Iterator<Item = &OsStr>>,
|
||||
);
|
||||
|
||||
if check {
|
||||
// on Windows, allow --binary/--text to be used with --check
|
||||
|
|
@ -188,13 +199,6 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
// Execute the checksum validation based on the presence of files or the use of stdin
|
||||
// Determine the source of input: a list of files or stdin.
|
||||
let input = matches.get_many::<OsString>(options::FILE).map_or_else(
|
||||
|| iter::once(OsStr::new("-")).collect::<Vec<_>>(),
|
||||
|files| files.map(OsStr::new).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let verbose = ChecksumVerbose::new(status, quiet, warn);
|
||||
|
||||
let opts = ChecksumValidateOptions {
|
||||
|
|
@ -204,16 +208,11 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
|
|||
};
|
||||
|
||||
// Execute the checksum validation
|
||||
return perform_checksum_validation(input.iter().copied(), Some(algo_kind), length, opts);
|
||||
} else if quiet {
|
||||
return Err(ChecksumError::QuietNotCheck.into());
|
||||
} else if strict {
|
||||
return Err(ChecksumError::StrictNotCheck.into());
|
||||
return perform_checksum_validation(files, Some(algo_kind), length, opts);
|
||||
}
|
||||
|
||||
let line_ending = LineEnding::from_zero_flag(matches.get_flag("zero"));
|
||||
|
||||
let algo = SizedAlgoKind::from_unsized(algo_kind, length)?;
|
||||
let line_ending = LineEnding::from_zero_flag(matches.get_flag("zero"));
|
||||
|
||||
let opts = ChecksumComputeOptions {
|
||||
algo_kind: algo,
|
||||
|
|
@ -227,13 +226,6 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
|
|||
line_ending,
|
||||
};
|
||||
|
||||
let files = matches.get_many::<OsString>(options::FILE).map_or_else(
|
||||
// No files given, read from stdin.
|
||||
|| Box::new(iter::once(OsStr::new("-"))) as Box<dyn Iterator<Item = &OsStr>>,
|
||||
// At least one file given, read from them.
|
||||
|files| Box::new(files.map(OsStr::new)) as Box<dyn Iterator<Item = &OsStr>>,
|
||||
);
|
||||
|
||||
// Show the hashsum of the input
|
||||
perform_checksum_computation(opts, files)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,14 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use std::env;
|
||||
use uucore::translate;
|
||||
use uucore::{error::UResult, format_usage};
|
||||
use std::io::Write;
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
|
||||
use uucore::error::UResult;
|
||||
use uucore::line_ending::LineEnding;
|
||||
use uucore::{format_usage, os_str_as_bytes, translate};
|
||||
|
||||
static OPT_NULL: &str = "null";
|
||||
|
||||
|
|
@ -21,15 +25,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let separator = if matches.get_flag(OPT_NULL) {
|
||||
"\x00"
|
||||
} else {
|
||||
"\n"
|
||||
};
|
||||
let separator = LineEnding::from_zero_flag(matches.get_flag(OPT_NULL));
|
||||
|
||||
if variables.is_empty() {
|
||||
for (env_var, value) in env::vars() {
|
||||
print!("{env_var}={value}{separator}");
|
||||
for (env_var, value) in env::vars_os() {
|
||||
let env_bytes = os_str_as_bytes(&env_var)?;
|
||||
let val_bytes = os_str_as_bytes(&value)?;
|
||||
std::io::stdout().lock().write_all(env_bytes)?;
|
||||
print!("=");
|
||||
std::io::stdout().lock().write_all(val_bytes)?;
|
||||
print!("{separator}");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -41,8 +46,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
error_found = true;
|
||||
continue;
|
||||
}
|
||||
if let Ok(var) = env::var(env_var) {
|
||||
print!("{var}{separator}");
|
||||
if let Some(var) = env::var_os(env_var) {
|
||||
let val_bytes = os_str_as_bytes(&var)?;
|
||||
std::io::stdout().lock().write_all(val_bytes)?;
|
||||
print!("{separator}");
|
||||
} else {
|
||||
error_found = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,12 +373,9 @@ impl SizedAlgoKind {
|
|||
pub enum ChecksumError {
|
||||
#[error("the --raw option is not supported with multiple files")]
|
||||
RawMultipleFiles,
|
||||
#[error("the --ignore-missing option is meaningful only when verifying checksums")]
|
||||
IgnoreNotCheck,
|
||||
#[error("the --strict option is meaningful only when verifying checksums")]
|
||||
StrictNotCheck,
|
||||
#[error("the --quiet option is meaningful only when verifying checksums")]
|
||||
QuietNotCheck,
|
||||
|
||||
#[error("the --{0} option is meaningful only when verifying checksums")]
|
||||
CheckOnlyFlag(String),
|
||||
|
||||
// --length sanitization errors
|
||||
#[error("--length required for {}", .0.quote())]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
//! instead of parsing error strings, providing a more robust solution.
|
||||
//!
|
||||
|
||||
use crate::error::UResult;
|
||||
use crate::error::{UResult, USimpleError};
|
||||
use crate::locale::translate;
|
||||
|
||||
use clap::error::{ContextKind, ErrorKind};
|
||||
|
|
@ -108,43 +108,37 @@ impl<'a> ErrorFormatter<'a> {
|
|||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
let code = self.print_error(err, exit_code);
|
||||
callback();
|
||||
std::process::exit(code);
|
||||
}
|
||||
|
||||
/// Print error and return exit code (no exit call)
|
||||
pub fn print_error(&self, err: &Error, exit_code: i32) -> i32 {
|
||||
match err.kind() {
|
||||
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => self.handle_display_errors(err),
|
||||
ErrorKind::UnknownArgument => {
|
||||
self.handle_unknown_argument_with_callback(err, exit_code, callback)
|
||||
}
|
||||
ErrorKind::UnknownArgument => self.handle_unknown_argument(err, exit_code),
|
||||
ErrorKind::InvalidValue | ErrorKind::ValueValidation => {
|
||||
self.handle_invalid_value_with_callback(err, exit_code, callback)
|
||||
}
|
||||
ErrorKind::MissingRequiredArgument => {
|
||||
self.handle_missing_required_with_callback(err, exit_code, callback)
|
||||
self.handle_invalid_value(err, exit_code)
|
||||
}
|
||||
ErrorKind::MissingRequiredArgument => self.handle_missing_required(err, exit_code),
|
||||
ErrorKind::TooFewValues | ErrorKind::TooManyValues | ErrorKind::WrongNumberOfValues => {
|
||||
// These need full clap formatting
|
||||
eprint!("{}", err.render());
|
||||
callback();
|
||||
std::process::exit(exit_code);
|
||||
exit_code
|
||||
}
|
||||
_ => self.handle_generic_error_with_callback(err, exit_code, callback),
|
||||
_ => self.handle_generic_error(err, exit_code),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle help and version display
|
||||
fn handle_display_errors(&self, err: &Error) -> ! {
|
||||
fn handle_display_errors(&self, err: &Error) -> i32 {
|
||||
print!("{}", err.render());
|
||||
std::process::exit(0);
|
||||
0
|
||||
}
|
||||
|
||||
/// Handle unknown argument errors with callback
|
||||
fn handle_unknown_argument_with_callback<F>(
|
||||
&self,
|
||||
err: &Error,
|
||||
exit_code: i32,
|
||||
callback: F,
|
||||
) -> !
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
/// Handle unknown argument errors
|
||||
fn handle_unknown_argument(&self, err: &Error, exit_code: i32) -> i32 {
|
||||
if let Some(invalid_arg) = err.get(ContextKind::InvalidArg) {
|
||||
let arg_str = invalid_arg.to_string();
|
||||
let error_word = translate!("common-error");
|
||||
|
|
@ -179,21 +173,13 @@ impl<'a> ErrorFormatter<'a> {
|
|||
|
||||
self.print_usage_and_help();
|
||||
} else {
|
||||
self.print_simple_error_with_callback(
|
||||
&translate!("clap-error-unexpected-argument-simple"),
|
||||
exit_code,
|
||||
|| {},
|
||||
);
|
||||
self.print_simple_error_msg(&translate!("clap-error-unexpected-argument-simple"));
|
||||
}
|
||||
callback();
|
||||
std::process::exit(exit_code);
|
||||
exit_code
|
||||
}
|
||||
|
||||
/// Handle invalid value errors with callback
|
||||
fn handle_invalid_value_with_callback<F>(&self, err: &Error, exit_code: i32, callback: F) -> !
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
/// Handle invalid value errors
|
||||
fn handle_invalid_value(&self, err: &Error, exit_code: i32) -> i32 {
|
||||
let invalid_arg = err.get(ContextKind::InvalidArg);
|
||||
let invalid_value = err.get(ContextKind::InvalidValue);
|
||||
|
||||
|
|
@ -245,32 +231,22 @@ impl<'a> ErrorFormatter<'a> {
|
|||
eprintln!();
|
||||
eprintln!("{}", translate!("common-help-suggestion"));
|
||||
} else {
|
||||
self.print_simple_error(&err.render().to_string(), exit_code);
|
||||
self.print_simple_error_msg(&err.render().to_string());
|
||||
}
|
||||
|
||||
// InvalidValue errors traditionally use exit code 1 for backward compatibility
|
||||
// But if a utility explicitly requests a high exit code (>= 125), respect it
|
||||
// This allows utilities like runcon (125) to override the default while preserving
|
||||
// the standard behavior for utilities using normal error codes (1, 2, etc.)
|
||||
let actual_exit_code = if matches!(err.kind(), ErrorKind::InvalidValue) && exit_code < 125 {
|
||||
if matches!(err.kind(), ErrorKind::InvalidValue) && exit_code < 125 {
|
||||
1 // Force exit code 1 for InvalidValue unless using special exit codes
|
||||
} else {
|
||||
exit_code // Respect the requested exit code for special cases
|
||||
};
|
||||
callback();
|
||||
std::process::exit(actual_exit_code);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle missing required argument errors with callback
|
||||
fn handle_missing_required_with_callback<F>(
|
||||
&self,
|
||||
err: &Error,
|
||||
exit_code: i32,
|
||||
callback: F,
|
||||
) -> !
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
/// Handle missing required argument errors
|
||||
fn handle_missing_required(&self, err: &Error, exit_code: i32) -> i32 {
|
||||
let rendered_str = err.render().to_string();
|
||||
let lines: Vec<&str> = rendered_str.lines().collect();
|
||||
|
||||
|
|
@ -313,15 +289,11 @@ impl<'a> ErrorFormatter<'a> {
|
|||
}
|
||||
_ => eprint!("{}", err.render()),
|
||||
}
|
||||
callback();
|
||||
std::process::exit(exit_code);
|
||||
exit_code
|
||||
}
|
||||
|
||||
/// Handle generic errors with callback
|
||||
fn handle_generic_error_with_callback<F>(&self, err: &Error, exit_code: i32, callback: F) -> !
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
/// Handle generic errors
|
||||
fn handle_generic_error(&self, err: &Error, exit_code: i32) -> i32 {
|
||||
let rendered_str = err.render().to_string();
|
||||
if let Some(main_error_line) = rendered_str.lines().next() {
|
||||
self.print_localized_error_line(main_error_line);
|
||||
|
|
@ -330,27 +302,16 @@ impl<'a> ErrorFormatter<'a> {
|
|||
} else {
|
||||
eprint!("{}", err.render());
|
||||
}
|
||||
callback();
|
||||
std::process::exit(exit_code);
|
||||
exit_code
|
||||
}
|
||||
|
||||
/// Print a simple error message
|
||||
fn print_simple_error(&self, message: &str, exit_code: i32) -> ! {
|
||||
self.print_simple_error_with_callback(message, exit_code, || {})
|
||||
}
|
||||
|
||||
/// Print a simple error message with callback
|
||||
fn print_simple_error_with_callback<F>(&self, message: &str, exit_code: i32, callback: F) -> !
|
||||
where
|
||||
F: FnOnce(),
|
||||
{
|
||||
/// Print a simple error message (no exit)
|
||||
fn print_simple_error_msg(&self, message: &str) {
|
||||
let error_word = translate!("common-error");
|
||||
eprintln!(
|
||||
"{}: {message}",
|
||||
self.color_mgr.colorize(&error_word, Color::Red)
|
||||
);
|
||||
callback();
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
/// Print error line with localized "error:" prefix
|
||||
|
|
@ -478,7 +439,9 @@ where
|
|||
if e.exit_code() == 0 {
|
||||
e.into() // Preserve help/version
|
||||
} else {
|
||||
handle_clap_error_with_exit_code(e, exit_code)
|
||||
let formatter = ErrorFormatter::new(crate::util_name());
|
||||
let code = formatter.print_error(&e, exit_code);
|
||||
USimpleError::new(code, "")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,45 @@ fn test_invalid_arg() {
|
|||
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_arguments() {
|
||||
new_ucmd!().arg("").fails_with_code(1);
|
||||
new_ucmd!().args(&["", ""]).fails_with_code(1);
|
||||
new_ucmd!().args(&["", "", ""]).fails_with_code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extra_operands() {
|
||||
new_ucmd!()
|
||||
.args(&["test", "extra"])
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("extra operand 'extra'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_long_option() {
|
||||
new_ucmd!()
|
||||
.arg("--fB")
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("unexpected argument '--fB'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_short_option() {
|
||||
new_ucmd!()
|
||||
.arg("-w")
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("unexpected argument '-w'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_dash_as_date() {
|
||||
new_ucmd!()
|
||||
.arg("-")
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("invalid date");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_email() {
|
||||
for param in ["--rfc-email", "--rfc-e", "-R", "--rfc-2822", "--rfc-822"] {
|
||||
|
|
|
|||
|
|
@ -90,3 +90,30 @@ fn test_null_separator() {
|
|||
.stdout_is("FOO\x00VALUE\x00");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[cfg(not(any(target_os = "freebsd", target_os = "android", target_os = "openbsd")))]
|
||||
fn test_non_utf8_value() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
// Environment variable values can contain non-UTF-8 bytes on Unix.
|
||||
// printenv should output them correctly, matching GNU behavior.
|
||||
// Reproduces: LD_PRELOAD=$'/tmp/lib.so\xff' printenv LD_PRELOAD
|
||||
let value_with_invalid_utf8 = OsStr::from_bytes(b"/tmp/lib.so\xff");
|
||||
|
||||
let result = new_ucmd!()
|
||||
.env("LD_PRELOAD", value_with_invalid_utf8)
|
||||
.arg("LD_PRELOAD")
|
||||
.run();
|
||||
|
||||
// Use byte-based assertions to avoid UTF-8 conversion issues
|
||||
// when the test framework tries to format error messages
|
||||
assert!(
|
||||
result.succeeded(),
|
||||
"Command failed with exit code: {:?}, stderr: {:?}",
|
||||
result.code(),
|
||||
String::from_utf8_lossy(result.stderr())
|
||||
);
|
||||
result.stdout_is_bytes(b"/tmp/lib.so\xff\n");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue