Skip to content
Open
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
35 changes: 19 additions & 16 deletions src/bin/coreutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use itertools::Itertools as _;
use std::cmp;
use std::ffi::OsString;
use std::io::{self, Write};
use std::process::ExitCode;
use uucore::{Args, error::strip_errno};

const VERSION: &str = env!("CARGO_PKG_VERSION");
Expand Down Expand Up @@ -49,15 +50,15 @@ Currently defined functions:
}

#[allow(clippy::cognitive_complexity)]
fn main() {
fn main() -> ExitCode {
let utils = util_map();
let mut args = uucore::args_os();

let binary = validation::binary_path(&mut args);
let binary_as_util = validation::name(&binary).unwrap_or_else(|| {
let Some(binary_as_util) = validation::name(&binary) else {
usage(&utils, "<unknown binary name>");
exit(0);
});
return ExitCode::SUCCESS;
};
Comment on lines +58 to +61

// binary name ends with util name?
let is_coreutils = binary_as_util.ends_with("utils");
Expand All @@ -73,41 +74,41 @@ fn main() {
uucore::set_utility_is_second_arg();
args.next()
} else {
validation::not_found(&OsString::from(binary_as_util));
return validation::not_found(&OsString::from(binary_as_util));
};

// 0th argument equals util name?
if let Some(util_os) = util_name {
let Some(util) = util_os.to_str() else {
validation::not_found(&util_os)
return validation::not_found(&util_os);
};

match util {
"--list" => {
// we should fail with additional args https://github.com/uutils/coreutils/issues/11383#issuecomment-4082564058
if args.next().is_some() {
let _ = writeln!(io::stderr(), "coreutils: invalid argument");
exit(1);
return ExitCode::FAILURE;
}
let mut out = io::stdout().lock();
for util in utils.keys() {
if let Err(e) = writeln!(out, "{util}")
&& e.kind() != io::ErrorKind::BrokenPipe
{
let _ = writeln!(io::stderr(), "coreutils: {}", strip_errno(&e));
exit(1);
return ExitCode::FAILURE;
}
}
exit(0);
return ExitCode::SUCCESS;
}
"--version" | "-V" => {
if let Err(e) = writeln!(io::stdout(), "coreutils {VERSION} (multi-call binary)")
&& e.kind() != io::ErrorKind::BrokenPipe
{
let _ = writeln!(io::stderr(), "coreutils: {}", strip_errno(&e));
exit(1);
return ExitCode::FAILURE;
}
exit(0);
return ExitCode::SUCCESS;
}
// Not a special command: fallthrough to calling a util
_ => {}
Expand All @@ -120,17 +121,19 @@ fn main() {
// Could be something like:
// #[cfg(not(feature = "only_english"))]
validation::setup_localization_or_exit(util);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that this exit is left untouched?

exit(uumain(vec![util_os].into_iter().chain(args)));
let code = uumain(vec![util_os].into_iter().chain(args));
// ExitCode only holds a u8; larger codes (Windows) go through exit.
return u8::try_from(code).map_or_else(|_| exit(code), ExitCode::from);
Comment thread
Copilot marked this conversation as resolved.
}
// GNU coreutils --help string shows help for coreutils
if util == "-h" || (util.starts_with("--h") && "--help".starts_with(util)) {
usage(&utils, binary_as_util);
exit(0);
return ExitCode::SUCCESS;
} else if util.starts_with('-') {
// Argument looks like an option but wasn't recognized
validation::unrecognized_option(binary_as_util, &util_os);
return validation::unrecognized_option(binary_as_util, &util_os);
}
validation::not_found(&util_os);
return validation::not_found(&util_os);
}
// GNU just fails, but busybox tests needs usage
// todo: patch the test suite instead
Expand All @@ -139,5 +142,5 @@ fn main() {
} else {
let _ = writeln!(io::stderr(), "coreutils: missing argument");
}
exit(1);
ExitCode::FAILURE
}
14 changes: 7 additions & 7 deletions src/common/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use std::ffi::{OsStr, OsString};
use std::io::{Write, stderr};
use std::path::{Path, PathBuf};
use std::process;
use std::process::{self, ExitCode};

use uucore::Args;
use uucore::display::Quotable;
Expand Down Expand Up @@ -49,24 +49,24 @@ pub fn get_all_utilities<T: Args>(
.collect()
}

/// Prints a "utility not found" error and exits
pub fn not_found(util: &OsStr) -> ! {
/// Prints a "utility not found" error
pub fn not_found(util: &OsStr) -> ExitCode {
let _ = writeln!(
stderr(),
"coreutils: unknown program '{}'",
util.maybe_quote()
);
exit(1);
ExitCode::FAILURE
}

/// Prints an "unrecognized option" error and exits
pub fn unrecognized_option(binary_name: &str, option: &OsStr) -> ! {
/// Prints an "unrecognized option" error
pub fn unrecognized_option(binary_name: &str, option: &OsStr) -> ExitCode {
let _ = writeln!(
stderr(),
"{binary_name}: unrecognized option '{}'",
option.to_string_lossy()
);
exit(1);
ExitCode::FAILURE
}

/// Sets up localization for a utility with proper error handling
Expand Down
138 changes: 138 additions & 0 deletions util/valgrind.supp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# spell-checker:ignore supp jiff rayon crossbeam Memcheck stdin
#
# Valgrind suppressions for uutils coreutils.
#
# Everything below is allocated once, kept in a static (or a thread that lives
# until exit) and released by the OS when the process ends. Rust never drops
# statics, so valgrind reports these blocks as "still reachable" (or "possibly
# lost" when only an interior pointer is left). None of them grows with the
# input, so they are not leaks.
#
# Usage:
# valgrind --leak-check=full --show-reachable=yes \
# --suppressions=util/valgrind.supp coreutils cat file

# The parsed .ftl translations. Bundles need `&'static FluentResource`, so the
# resources are leaked on purpose and cached for the rest of the run.
{
uucore-locale-resources
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*uucore*mods*locale*
}
# Same allocations as above. Debug builds have deep parser stacks and the
# default --num-callers cuts them before the uucore frame, so match the parser.
{
fluent-resources
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*fluent_bundle*resource*FluentResource*try_new*
}
{
fluent-parser
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*fluent_syntax*parser*
}
# The command line, copied once into a LazyLock static by uucore::args_os().
{
uucore-argv
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*uucore*ARGV*
}
# Derived from ARGV and cached for error messages ("coreutils cat: ...").
{
uucore-execution-phrase
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*uucore*EXECUTION_PHRASE*
}
{
uucore-util-name
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*uucore*UTIL_NAME*
}
# jiff loads the system time zone database and the local zone into a global
# cache on first use (date, ls -l, pr, touch...). The cache holds Arcs, so
# valgrind only sees interior pointers and reports "possibly lost".
{
jiff-timezone-database
Memcheck:Leak
match-leak-kinds: possible,reachable
...
fun:*jiff*tz*
}
# sort spawns rayon's global thread pool. Its workers are still parked when
# main returns, so their stacks and registry show up as "possibly lost".
{
rayon-global-thread-pool
Memcheck:Leak
match-leak-kinds: possible,reachable
...
fun:*rayon_core*registry*
}
# The epoch-based garbage collector used by the rayon workers is a global.
{
crossbeam-epoch-collector
Memcheck:Leak
match-leak-kinds: possible,reachable
...
fun:*crossbeam_epoch*
}
# Thread handle created when a channel is first used inside a worker thread.
{
std-thread-handle-in-worker
Memcheck:Leak
match-leak-kinds: possible,reachable
...
fun:*std*thread*current*init_current*
}
# regex keeps a per-thread id for its matcher cache pool in a static.
{
regex-thread-pool-id
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*regex_automata*
}
# Utilities cache locale-dependent data (date format, decimal separator...) in
# OnceLock/LazyLock statics. Only "reachable" blocks are matched here, so a
# real leak inside such an initializer would still be reported.
{
once-lock-statics
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*OnceLock*initialize*
}
{
lazy-lock-statics
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*LazyLock*force*
}
# std's global stdin buffer; any Rust program reading stdin has it.
{
std-stdin-buffer
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*std*io*stdio*
}
# std records the main thread's stack bounds for its stack overflow handler.
{
std-main-thread-info
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*std*sys*pal*unix*stack_overflow*
}
Loading