* find: skip broken pipe errors in wasm executions
* find: report non-broken-pipe write errors instead of swallowing them
flush() only checked for BrokenPipe, silently ignoring other errors
(e.g. ENOSPC). Add regression tests for both cases.
locate compared the database age against --max-database-age using
`TimeDelta::days(config.max_age as i64)`. chrono's `TimeDelta::days` is
the panicking constructor, so a large but syntactically valid value
(e.g. --max-database-age 1000000000000) aborted the process with exit
101 instead of being handled.
The `as i64` cast was wrong for values above i64::MAX as well: usize::MAX
wrapped to -1, so a freshly built database was reported as "more than
18446744073709551615 days old (actual age is 0.0 days)".
Guard both conversions and treat an age that i64/chrono cannot represent
as never exceeded, which matches what GNU locate effectively does -- it
truncates the value into an unsigned int, so a huge age simply never
warns. The check moves into an `is_db_too_old` helper so it can be unit
tested.
The -newerXY parser used an unanchored regex, so tokens such as
-neweraBcmty were accepted as a truncated -neweraB. Require an exact
-newerXY match, report unknown predicates with GNU wording, and cover
the issue #782 cases in unit and integration tests.
Signed-off-by: Alex Chen <l46983284@gmail.com>
chrono's %.f drops the fraction (and the leading dot) entirely when the
sub-second part is zero, while GNU find always prints a dot followed by
ten fractional digits. Use an explicit .%f0 so whole-second timestamps
format the same way as GNU.
Sizing command lines all the way up to ARG_MAX made execvp() fail with
E2BIG: the kernel also charges the argv/envp pointer arrays against the
limit, which the character count does not account for. Follow GNU xargs
and use at most 128 KiB per command line by default, and use a
saturating subtraction so a tiny ARG_MAX cannot underflow.
The cap only applies when no explicit -s was given: like GNU, an
explicit -s may still raise the limit up to the system maximum.
A `-printf` field width could panic two ways: a width above `u16::MAX` reached
`core::fmt`'s `{:>width$}`, which caps the width argument at `u16::MAX` and
panics ("Formatting argument out of range"); and a width above `usize::MAX` was
`.unwrap()`-ed after a failed parse. Make `parse_format_width` fallible and
reject both — an unparseable (too-large-for-`usize`) width, and any width above
`u16::MAX` — with a graceful error instead of aborting.
In replace mode (-I/-i/--replace) xargs runs the command once per input item, so
with empty input it should run nothing. Instead the final builder was still
executed and `CommandBuilder::execute` indexed `extra_args[0]`, which is empty
for no input, panicking with index-out-of-bounds. Treat replace mode as implying
--no-run-if-empty (as GNU does) so empty input runs nothing and exits 0.
`find <file> -ls` panicked (exit 101) when the file's owning uid (or gid) had
no `/etc/passwd` (`/etc/group`) entry: it resolved the name with
`User::from_uid(...).unwrap().unwrap()`, and the inner unwrap aborts on the
`Ok(None)` that an unmapped id returns. Unmapped owners are routine (extracted
archives, NFS, deleted accounts, container images).
Resolve the uid/gid via `uucore::entries::uid2usr`/`gid2grp`, falling back to
the numeric id when there is no entry, matching GNU find (which prints the
number). These helpers already serialize the non-thread-safe getpwuid/getgrgid
behind a mutex, so reuse them rather than reimplementing the lookup.
Add a -ls integration test that chowns a file to an unmapped id and checks the
numeric fallback; it skips when the process lacks the privilege to do so.
Regression test for the panic fixed in #720/#723: a `\NNN` octal escape
immediately followed by a multibyte character (`\0€`) used to slice the
format string mid-char and abort. Assert `-printf` emits the escape byte
and the following character intact.
`locate -d <file> <pattern>` panicked (slice index underflow, exit 101) when
the database file is exactly one byte: `check_db` consumes that byte with
`read_exact`, then `read_until(b'\0', ...)` hits EOF and appends nothing,
leaving `buf` empty. `&buf[..buf.len() - 1]` then underflows `buf.len() - 1`
to `usize::MAX` and slices out of bounds.
Strip the trailing nul with `strip_suffix(b"\0")` (falling back to the whole
buffer when `read_until` stopped at EOF without one) instead of slicing
`buf.len() - 1`. An empty buffer now yields `None` (unrecognized format), so
the too-short database is skipped like any other invalid one and locate exits
1 instead of crashing. Adds a regression test.
Opening the output file with `?` surfaced a bare "No such file or
directory" with no indication of which file failed (easy to hit because the
default output path /usr/local/var/locatedb often doesn't exist).
Report it like GNU does, naming the path and the reason, e.g.
`cannot create '/usr/local/var/locatedb': No such file or directory`.
strip_errno() drops the trailing "(os error N)" noise.
locate and updatedb were Unix-only (gated behind cfg and stubbed out on
Windows). Make them cross-platform:
- locate: decode raw DB entries via a platform-specific bytes_to_path
(verbatim bytes on Unix, lossy UTF-8 elsewhere), use Metadata::len()
instead of the unix-only size(), and a cross-platform make_symlink in
tests that skips gracefully when symlink creation is unprivileged.
- drop the #[cfg(unix)] gate on the locate module and the
"unsupported on Windows" main stubs for locate/updatedb.
- db_tests: remove #[cfg(not(windows))] gates, write updatedb output to
a tempdir instead of /dev/null, and add a full updatedb->locate
roundtrip test that is platform-independent.
The test from #647 used {} only in an argument, so it passed on
unpatched code. Rewrite it to make the matched entry the
testing-commandline binary and pass {} as the utility name, so the
placeholder must resolve to the entry's path and execute.
* fix: replace {} placeholder in -exec utility name
The {} placeholder was only replaced in arguments to -exec, not in the
utility_name position itself. Running `find -exec {} \;` passed the
literal string "{}" to Command::new, causing "No such file or directory".
Extracted arg parsing into a shared `parse_arg` helper and changed the
`executable` field from String to the existing Arg enum so it undergoes
the same {} replacement as other arguments.
Fixes#614
* test: add test for {} placeholder in utility name
Verify that SingleExecMatcher resolves {} in arguments
when the executable is a known path. Covers the case
where -exec receives {} in argument positions.
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
The -ok/-okdir prompt rendered the full substituted command, but GNU
find prints a fixed, abbreviated form: "< executable ... pathname > ? "
(literal ..., a space before ?, and always the full entry path even for
-okdir). Match it exactly and update the prompt-format test.
Verified byte-for-byte against GNU findutils 4.10.0.
-ok is like -exec ... ; but prompts the user on stderr before each
invocation and only runs the command if the response is affirmative
(starts with 'y' or 'Y'). -okdir is the corresponding -execdir variant.
Only the ';' terminator is accepted: POSIX does not define -ok ... + and
GNU find rejects it.
Response source: GNU find reads from /dev/tty so that the user's answer
always comes from the real terminal even when stdin is occupied (e.g.
`find -files0-from - -ok rm {} \;` reads paths from stdin, so responses
cannot also come from there). /dev/tty is the POSIX name for a
process's controlling terminal and exists on all Unix-like systems. We
open /dev/tty only when stdin is itself a terminal
(std::io::stdin().is_terminal() == true). When stdin is a pipe or file,
we read from stdin directly — matching BSD find and making scripted use
(and integration tests via pipe_in()) work naturally without any special
environment variable. On Windows, or when /dev/tty cannot be opened, we
likewise fall back to stdin.
Implementation: rather than a separate OkMatcher that duplicated
SingleExecMatcher's fields, constructor, and exec logic, the interactive
prompt is folded into SingleExecMatcher behind an `interactive: bool`
field. `SingleExecMatcher::new_interactive()` constructs the -ok/-okdir
variant; `matches()` adds a guarded block that builds a GNU-find-style
prompt ("< executable arg... >? ") and calls `matcher_io.confirm()`
before executing. If the user declines, the expression is false and the
command is not run. This keeps the arg-parsing, path resolution,
current_dir logic, and error handling in one place so bug fixes apply to
both -exec and -ok.
Dependencies::confirm() trait method: abstracts prompt+read so matchers
remain testable without a real terminal; FakeDependencies uses a
VecDeque of preset responses.
Tests:
- Unit tests (exec_unit_tests.rs): confirmed executes command, declined
skips command and returns false, confirmed but command fails returns
false, -okdir runs in parent dir
- Parser unit tests (matchers/mod.rs): missing-arg errors, missing
semicolon, correct parse, confirm/decline via FakeDependencies
- Integration tests (test_find.rs): pipe_in() makes stdin a pipe so
is_terminal() returns false and responses are read from the pipe;
"y"/"Y"/"yes"/" y" run command, "n" and empty response skip command,
-okdir runs in parent dir, prompt format verified, missing-semicolon
error
Closes https://github.com/uutils/findutils/issues/8.
GNU xargs treats the arguments to -I, -E, and -e as values even when
they begin with a hyphen. Clap was parsing those strings as options
instead, so invocations like 'xargs -I -_' failed before xargs could
process input.
Allow hyphen-leading values for those options and add regression
coverage for replacement and EOF markers that start with '-'.