convert_arg_to_number parsed the depth with Rust's usize parser, which accepts a
leading '+', so "-maxdepth +1" was silently accepted. GNU find rejects a signed
value there. Only accept plain decimal digits. Adds a test_find.rs test.
std::env::args panics on a non-UTF-8 argument. Read the arguments with args_os
and report the first invalid one with a clean error and exit 1, instead of
aborting (#816). Adds a helper unit test and a test_find.rs test.
improvements was "failing in the reference, not failing now", which is
also true of a test that produced no result because it hung. That is how
a hang in find got reported as "+ gnu/okdir_path_empty". List those
separately instead; they don't fail the job, since an upstream rename
looks the same.
The starting points and the answers to the prompts would both be read
from standard input, so they compete for the same bytes and the result
depends on buffering. GNU find rejects the combination up front; we
accepted it and prompted, consuming input meant for the caller. The bfs
tests gnu/files0_from_stdin_ok and gnu/ok_files0_from_stdin now pass, and
the former no longer hangs on the testsuite's stdin.
Reading the starting points from a file leaves stdin free for the
prompts, so that combination stays allowed.
Both actions run the command after changing into the directory being
scanned, so a relative $PATH entry -- including the empty string and ".",
which mean the current directory -- would be looked up inside a directory
somebody else may be able to write to. GNU find rejects this at parse
time; we ran the command anyway, and for -okdir we first prompted, which
meant reading the answer from whatever stdin happened to be. In the bfs
testsuite that is the harness's own stdin, so gnu/okdir_path_empty hung
and reported no result at all instead of failing (bfs gnu/okdir_path_*
and gnu/execdir_path_* now pass).
An unset $PATH is left alone: the command is then looked up in the
system default path.
The -size argument was parsed with an un-anchored regex, so "x5c" or "abc5c"
silently matched the "5c" in the middle. Anchor the regex and allow only the
one extra '+' GNU accepts after the comparison sign, so "++5c"/"-+5c" stay valid
while the junk-prefix and invalid-sign forms are rejected. Adds a test_find.rs
test.
Two divergences from GNU find:
- An unrecognized %X directive silently dropped the '%' and printed
only the following character.
- An unrecognized \X escape was a hard parse error (exit 1) instead of
a warning.
Both now print a "find: warning: ..." message to stderr and emit the
directive/escape literally (with its '%' or '\' prefix), matching GNU
find's behavior and exit code (0).
Also fixes advance_one(), which sliced the remaining format string at
a fixed 1-byte offset and panicked on multibyte characters (e.g. '€')
immediately after '%' or '\' — using char::len_utf8() instead.
Fixes#815
-files0-from read the whole input into memory with read_to_end before
splitting on NUL, so an endless source such as /dev/urandom or /dev/zero
exhausted memory and produced no output. Read the starting points
incrementally with read_until(0) and walk each one as it is parsed.
Fixes#779
Co-authored-by: Pablo Garcia <pablito@MacBook-Neo-de-Pablo.local>
Point cargo-binstall at the cargo-dist release artifacts
(findutils-{target}.tar.xz, zip on Windows) so 'cargo binstall
findutils' installs prebuilt binaries instead of building from source.
When input ended with a blank immediately before its final newline,
xargs passed one extra empty argument to the command:
$ printf 'aaa \nbbb \n' | xargs printf '[%s]'
[aaa][bbb][] # was
[aaa][bbb] # GNU findutils 4.10.0, and now
WhitespaceDelimitedArgumentReader::next used one `result.is_empty()`
check to answer two different questions. The whitespace branch broke out
of the loop only if `result` was non-empty, so a delimiter seen before
any token was skipped -- correct for blanks, but it left "we are
mid-token" indistinguishable from "we have not started one". The EOF
branch then used `i == 0` as a proxy for "nothing was consumed", which
is false for a final call that consumes only the trailing newline, so it
flushed a zero-length token. Input ending without a newline happened to
work because nothing was left to consume.
Track that state explicitly with an `in_argument` flag, mirroring
`seen_arg` in GNU's read_line, and test `result.is_empty()` at EOF. A
delimiter now ends an argument only when we are inside one, and EOF
flushes only a non-empty buffer.
This also fixes a second symptom of the same conflation: quoted empty
arguments were dropped mid-stream, since quotes start an argument
without contributing any bytes. `printf '"" x\n'` gave [x] and now gives
[][x], again matching GNU. An unterminated `x ''` at end of input still
yields just [x], as GNU's `if (p == linebuf) return -1` does.
-I/-i is unaffected: with a replace string and no explicit delimiter,
xargs uses the newline-delimited reader instead.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* xargs: strip `(os error N)` from --arg-file open failure
When --arg-file points at a missing path, xargs used to surface the raw
io::Error display, which appends " (os error N)" for OS errors:
Error: Failed to open sdf: No such file or directory (os error 2)
GNU xargs omits that detail. Reuse uucore::error::strip_errno (already
used by updatedb) so the message reads:
Error: Failed to open sdf: No such file or directory
Closes#811
* test: make xargs_arg_file_missing_strips_errno cross-platform
The assertion hardcoded the Unix errno wording ("No such file or
directory"), so the test failed on Windows where the OS reports
"The system cannot find the path specified." (reported by @sylvestre).
The contract this test guards is only that the trailing "(os error N)"
suffix is stripped — the OS wording is out of our control. Assert the
portable parts instead: our "Failed to open <path>:" wrapper names the
path and no "(os error" suffix leaks.