The %s arm resolved a `*` precision without the check_precision call
the integer and float arms make, so a value above i32::MAX was used as
is instead of failing with "invalid precision". Check it there, for a
`*` precision only; a precision written in the format is still accepted.
Closes#15663
with a bare mode like -w, only the operand itself was checked for bits the umask kept. Entries found while recursing were changed silently. Chmod also exited with 0. Moved the check into a helper function and call it safe_chmod_file()
The source was checked with is_dir(), which follows symlinks, so a
symlink to a directory removed an empty destination directory and was
then moved into its place. Check the source entry itself, without its
trailing slashes, which would also make lstat follow the link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GNU quotes `{` and `}` when they are the whole name in the shell
quoting styles, while braces inside a longer name stay bare. This
affects `printf %q` and `ls --quoting-style=shell*`.
GNU accepts a flag or option given more than once, and the last value
wins. These utilities rejected that with "cannot be used multiple
times". Set args_override_self on their commands, as done for chown and
chgrp in #15669.
numfmt keeps the case GNU still rejects: a repeated --field fails with
"multiple field specifications".
--ignore-missing dropped every file whose open failed, not only the ones
that were absent, so a file that exists but cannot be read was skipped
without a word and the run exited 0.
Classify the error in get_file_to_check: only NotFound stays on the
missing path, anything else is printed, counted through failed_open and
returned as CantOpenFile. Drop the matching !ignore_missing guard on the
final failed_open_file check, which ignored missing files never reach.
Base58 text is one big number, but the decoder flushed every 1024
digits, so longer input decoded to the wrong bytes and basenc could not
read back its own output.
Fixes#15776
* uniq: only treat blanks as field separators for -f
skip_fields_offset used is_ascii_whitespace, which also ends a field at
\n, \r and \f. Only space and tab separate fields, so check for those.
Closes#15659
* tests: uniq: cover -z with a newline inside a record
* uniq: keep newline as a field separator with -z
GNU uniq 9.12 still ends a field at a newline with -z; only \r and \f
stop being separators.
With -D, the destination was removed before the same-file check ran, so
`install -D a a` deleted `a`. Run the check before the removal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
write_new_line() recorded a blank line as already printed for every
newline, including one that only finishes a line begun in an earlier
file or read. The real blank line right after it was then squeezed away.
Only a newline at the start of a line counts as a blank line now.
Closes#15655
A line too short to have the join field was ordered before a line whose
join field is empty, while `sort -k` gives both the same empty key. So
`join --check-order` rejected input that `sort -k` had just sorted, and
such lines never paired with each other.
Closes#15741
Passwd::locate(&str) falls back to getpwuid() when no account has the
given name and it parses as a number, so `groups 1000` or `groups +0`
answered for that uid while GNU reports "no such user". Add a name-only
locate_name() to uucore::entries, keep locate(&str) built on it with the
existing numeric fallback for id, chown and others, and use it in
groups and pinky.
Fixes#15813
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The message formatted the path with Path::display(), so control
characters in a symlink name were written to stderr unescaped. Use
quote() like the other rm diagnostics and drop the literal quotes from
the en-US and fr-FR strings.
Fixes#15811
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--sort overrides earlier occurrences of itself in clap, so ModeFlags
only sees the last --sort=<WORD> value. Passing conflicting modes such
as --sort=general-numeric --sort=human-numeric, or -h followed by
--sort=general-numeric --sort=human-numeric, drops the earlier value
and skips the incompatibility check.
Append --sort values in clap and collect every --sort occurrence in
ModeFlags::from_matches. Repeated identical --sort values remain
accepted, and conflicting modes now fail with "options '-...' are
incompatible".
Closes#15596
A file operand named `-d=` after `--` was rewritten to `--delimiter==`
before clap parsing, so cut opened the wrong file. Stop the rewrite once
`--` has been seen.
Fixes#15806
In a UTF-8 locale the whitespace matcher splits fields on Unicode blanks
such as U+3000, but -w=trimmed only stripped SPACE and TAB from the ends
of a line. A leading Unicode blank then emptied field 1 and shifted every
word, a trailing one added an empty last field, and -s kept one-word
lines. Trim with the same matcher that splits the fields, and update the
help text, which still said Space/Tab only.
Fixes#15819
The -t argument kept the default set-action together with
args_override_self, so a later -t silently replaced an earlier one.
Collect every occurrence and fail with "incompatible tabs" (exit 2)
when they differ, the same way repeated -o is handled. Repeating the
same separator keeps working.
Fixes#15598
install -d and -D passed 0755 to mkdir(2), so the caller's umask still
applied to the ancestors they created. Run the Directory/Standard
dispatch under a umask of 0, as GNU does, so new ancestors get exactly
0755 and the strip program sees a umask of 0.
extract_negative_modes only took an argument starting with '-' as a mode when its second character was a permission letter or a digit. Modes such as "-=r", "-+x" and "-,u+x" went on to clap, which has no such option and rejected them. Also accept '=', '+' and ',' there; no option starts with those.
Closes#15665
-J only turns off truncation and column alignment, as in GNU pr. The separator between columns and merged files was dropped too, so pr -2 -J -S, printed 116 instead of 1,16. The joined.log fixture recorded the glued lines; it now has the tab GNU pr writes between the merged files.
change_file printed its own error and returned a bare exit code, which
the recursive walk told apart from a real error by its empty message.
Return the error instead and let one helper print it, unless -f, and
set the exit status, for the operand loop as well as for the walk: every
failing operand is reported, as GNU does. The umask diagnostic is shown
where it is found, since GNU prints it even under -f.
The mode was parsed again for every file, so under -R a bad one was
reported once per entry, after the walk had started. Whether it parses
does not depend on the file: check it up front, as GNU does.
Failures met while walking a tree were returned up through
`Result::and`, so only one of them reached stderr, after all the other
output, and `-f` could not hide it. An unreadable directory was also
reported as "cannot access", where GNU says "cannot read directory",
and on Hurd the `read_dir` error came back without its path.
Report each failure where it happens and keep the exit status, let
`-f` hide the messages, report an unreadable directory as
"cannot read directory", and with `-v` add the "could not be accessed"
line that GNU prints for it.
%3N stripped the leading zeros of the nanosecond field before truncating
it, so 0.005123456 printed 512 instead of 005, and %s%3N timestamps
could go backwards. Handle %N as a fraction: a width keeps the first
digits and pads on the right, trailing zeros become the pad character,
and the - flag drops them. This matches GNU for the flag and width
combinations covered by the new test.
Fixes#15697
round_with_precision turns a small negative value into -0.0, and
format_float prints the sign of a negative zero, so `-0.3` with
`--round=up --format=%.0f` came out as `-0` (and `-0000` when
zero-padded). Drop the sign of a zero produced by rounding on the
plain path. The --to path keeps its existing `-0` output.
Closes#15661