find: add -files0-from option

* bootstrap.conf (gnulib_modules): Add argv-iter and same-inode.
* find/defs.h (struct options): Add files0_from and ok_prompt_stdin
members.
* find/ftsfind.c (argv-iter.h, same-inode.h, xalloc.h): Add #include
for gnulib headers.
(process_all_startpoints): Change loop over starting point arguments
to a loop using the argv_iter gnulib module.
* find/parser.c (parse_table): Add option.
(parse_files0_from): Declare and define function.
(insert_exec_ok): Set options flag ok_prompt_stdin to true for
the -ok and -okdir action.
* find/util.c (usage): Add new option.
(set_option_defaults): Initialize new struct members.
* doc/find.texi (node Starting points): Add new section describing
the regular processing of starting points, and that with the new
option.  Also mention in the description of -ok and -okdir that they
conflict with the new option.
* find/find.1: Document the new option here as well.
* tests/find/files0-from.sh: Add test.
* tests/local.mk (all_tests): Reference it.
* NEWS (New features in find): Mention the new option.
This commit is contained in:
Bernhard Voelker 2021-05-06 23:24:43 +02:00
parent 9ec8970ce7
commit 11576f4e6a
10 changed files with 538 additions and 21 deletions

7
NEWS
View File

@ -2,6 +2,13 @@ GNU findutils NEWS - User visible changes. -*- outline -*- (allout)
* Noteworthy changes in release ?.? (????-??-??) [?]
** New features in find
find now supports the -files0-from option to be able to safely pass an
arbitrary number of starting points to the tool. The option requires a file
name as argument, or "-" to read from standard input. The entries in that
file have to be separated by NUL characters.
** Changes in locate / updatedb
update now skips (fuse-mounted) s3fs filesystems by default,

View File

@ -74,6 +74,7 @@ gnulib_modules="
alloca
areadlinkat
argmatch
argv-iter
assert
byteswap
c-strcasestr
@ -143,6 +144,7 @@ gnulib_modules="
regex
rpmatch
safe-read
same-inode
save-cwd
savedir
selinux-at

View File

@ -287,6 +287,7 @@ more information about the matching files.
@menu
* find Expressions::
* Starting points::
* Name::
* Links::
* Time::
@ -357,6 +358,90 @@ for exactly @var{n}.
@end table
@node Starting points
@section Starting points
GNU @code{find} searches the directory tree rooted at each given starting-point
by evaluating the given expression from left to right, according to the
rules of operator precedence, until the outcome is known (the left hand side
is false for @samp{and} operations, true for @samp{or}), at which point
@code{find} moves on to the next file name.
If no starting-point is specified, the current directory @samp{.} is assumed.
A double dash @samp{--} could theoretically be used to signal that any remaining
arguments are not options, but this does not really work due to the way
@code{find} determines the end of the list of starting point arguments:
it does that by reading until an expression argument comes (which also starts
with a @samp{-}).
Now, if a starting point argument would begin with a @samp{-}, then @code{find}
would treat it as expression argument instead.
Thus, to ensure that all start points are taken as such, and especially to
prevent that wildcard patterns expanded by the calling shell are not mistakenly
treated as expression arguments, it is generally safer to prefix wildcards
or dubious path names with either @samp{./}, or to use absolute path names
starting with @samp{/}.
Alternatively, it is generally safe though non-portable to use the GNU option
@samp{-files0-from} to pass arbitrary starting points to @code{find}.
@deffn Option -files0-from file
@deffnx Option -files0-from file
Read the starting points from @file{file} instead of getting them on the
command line.
In contrast to the known limitations of passing starting points via arguments
on the command line, namely the limitation of the amount of file names,
and the inherent ambiguity of file names clashing with option names,
using this option allows to safely pass an arbitrary number of starting points
to @code{find}.
Using this option and passing starting points on the command line is mutually
exclusive, and is therefore not allowed at the same time.
The @file{file} argument is mandatory.
One can use @samp{-files0-from -} to read the list of starting points from the
standard input stream, and e.g. from a pipe.
In this case, the actions @samp{-ok} and @samp{-okdir} are not allowed,
because they would obviously interfere with reading from standard input
in order to get a user confirmation.
The starting points in @file{file} have to be separated by ASCII NUL characters.
Two consecutive NUL characters, i.e., a starting point with a Zero-length
file name is not allowed and will lead to an error diagnostic followed by
a non-Zero exit code later.
The given @file{file} has to contain at least one starting point,
i.e., an empty input file will be diagnosed as well.
The processing of the starting points is otherwise as usual, e.g. @code{find}
will recurse into subdirectories unless otherwise prevented.
To process only the starting points, one can additionally pass @samp{-maxdepth 0}.
Further notes:
if a file is listed more than once in the input file, it is unspecified
whether it is visited more than once.
If the @file{file} is mutated during the operation of @code{find}, the result
is unspecified as well.
Finally, the seek position within the named @samp{file} at the time @code{find}
exits, be it with @samp{-quit} or in any other way, is also unspecified.
By "unspecified" here is meant that it may or may not work or do any specific
thing, and that the behavior may change from platform to platform, or from
findutils release to release.
Example:
Given that another program @code{proggy} pre-filters and creates a huge
NUL-separated list of files, process those as starting points, and find
all regular, empty files among them:
@example
$ proggy | find -files0-from - -maxdepth 0 -type f -empty
@end example
The use of @samp{-files0-from -} means to read the names of the starting points
from standard input, i.e., from the pipe; and @samp{-maxdepth 0} ensures that
only explicitly those entries are examined without recursing into directories
(in the case one of the starting points is one).
@end deffn
@node Name
@section Name
@ -2822,6 +2907,8 @@ If the user does not agree to run the command, just return false.
Otherwise, run it, with standard input redirected from
@file{/dev/null}.
This action may not be specified together with the @samp{-files0-from} option.
The response to the prompt is matched against a pair of regular
expressions to determine if it is a yes or no response. These regular
expressions are obtained from the system (@code{nl_langinfo} items
@ -2844,6 +2931,8 @@ is expanded to a relative path starting with the name of one of the
starting directories, rather than just the basename of the matched
file. If the command is run, its standard input is redirected from
@file{/dev/null}.
This action may not be specified together with the @samp{-files0-from} option.
@end deffn
When processing multiple files with a single command, to query the

View File

@ -636,6 +636,12 @@ struct options
/* How should we quote filenames in error messages and so forth?
*/
enum quoting_style err_quoting_style;
/* Read starting points from FILE (instead of argv). */
const char *files0_from;
/* True if actions like -ok, -okdir need a user confirmation via stdin. */
bool ok_prompt_stdin;
};

View File

@ -79,6 +79,11 @@ prevent that wildcard patterns expanded by the calling shell are not mistakenly
treated as expression arguments, it is generally safer to prefix wildcards or
dubious path names with either `./' or to use absolute path names starting
with '/'.
Alternatively, it is generally safe though non-portable to use the GNU option
.B \-files0\-from
to pass arbitrary starting points to
.BR find .
.IP \-P
Never follow symbolic links. This is the default behaviour. When
.B find
@ -506,6 +511,59 @@ Process each directory's contents before the directory itself. The
\-delete action also implies
.BR \-depth .
.IP "\-files0\-from \fIfile\fR"
Read the starting points from \fIfile\fR instead of getting them on the
command line.
In contrast to the known limitations of passing starting points via arguments
on the command line, namely the limitation of the amount of file names,
and the inherent ambiguity of file names clashing with option names,
using this option allows to safely pass an arbitrary number of starting points
to \fBfind\fR.
Using this option and passing starting points on the command line is mutually
exclusive, and is therefore not allowed at the same time.
The \fIfile\fR argument is mandatory.
One can use
.B \-files0\-from\ \-
to read the list of starting points from the \fIstandard input\fR stream,
and e.g. from a pipe.
In this case, the actions
.B \-ok
and
.B \-okdir
are not allowed, because they would obviously interfere with reading from
\fIstandard input\fR in order to get a user confirmation.
The starting points in \fIfile\fR have to be separated by ASCII NUL characters.
Two consecutive NUL characters, i.e., a starting point with a Zero-length
file name is not allowed and will lead to an error diagnostic followed by
a non-Zero exit code later.
The given \fIfile\fR has to contain at least one starting point,
i.e., an empty input file will be diagnosed as well.
The processing of the starting points is otherwise as usual, e.g.
.B find
will recurse into subdirectories unless otherwise prevented.
To process only the starting points, one can additionally pass
.BR \-maxdepth\ 0 .
Further notes:
if a file is listed more than once in the input file, it is unspecified
whether it is visited more than once.
If the \fIfile\fR is mutated during the operation of
.BR find ,
the result is unspecified as well.
Finally, the seek position within the named \fIfile\fR at the time
.B find
exits, be it with
.B \-quit
or in any other way, is also unspecified.
By "unspecified" here is meant that it may or may not work or do any specific
thing, and that the behavior may change from platform to platform, or from
.B findutils
release to release.
.IP "\-help, \-\-help"
Print a summary of the command-line usage of
.B find
@ -1324,6 +1382,9 @@ but ask the user first. If the user agrees, run the command. Otherwise
just return false. If the command is run, its standard input is redirected
from
.IR /dev/null .
This action may not be specified together with the
.B \-files0\-from
option.
.IP
The response to the prompt is matched against a pair of regular
@ -1353,6 +1414,10 @@ but ask the user first in the same way as for
If the user does not agree, just return false.
If the command is run, its standard input is redirected from
.IR /dev/null .
This action may not be specified together with the
.B \-files0\-from
option.
.IP \-print
True; print the full file name on the standard output, followed by a
@ -2159,7 +2224,27 @@ traverses the hierarchy printing the matching filenames, and the time the
process executed by
.B xargs
works with that file.
.
.SS Processing arbitrary starting points
.IP \[bu]
Given that another program \fIproggy\fR pre-filters and creates a huge
NUL-separated list of files, process those as starting points, and find
all regular, empty files among them:
.nf
\&
.in +4m
.B $ proggy | find \-files0\-from \- \-maxdepth 0 \-type f \-empty
.in
\&
.fi
The use of
.B `\-files0\-from\ \-`
means to read the names of the starting points from \fIstandard input\fR,
i.e., from the pipe; and
.B \-maxdepth\ 0
ensures that only explicitly those entries are examined without recursing
into directories (in the case one of the starting points is one).
.
.SS
Executing a command for each file
.IP \[bu]
@ -2524,6 +2609,7 @@ exit status was unaffected by the failure of
.TS
l l l .
Feature Added in Also occurs in
\-files0\-from 4.9.0
\-newerXY 4.3.3 BSD
\-D 4.3.1
\-O 4.3.1

View File

@ -38,6 +38,7 @@
#include <unistd.h>
/* gnulib headers. */
#include "argv-iter.h"
#include "cloexec.h"
#include "closeout.h"
#include "error.h"
@ -45,8 +46,10 @@
#include "intprops.h"
#include "progname.h"
#include "quotearg.h"
#include "same-inode.h"
#include "save-cwd.h"
#include "xgetcwd.h"
#include "xalloc.h"
/* find headers. */
#include "defs.h"
@ -563,30 +566,163 @@ find (char *arg)
static bool
process_all_startpoints (int argc, char *argv[])
{
int i;
bool empty = true;
/* Did the user pass starting points on the command line? */
bool argv_starting_points = 0 < argc && !looks_like_expression (argv[0], true);
/* figure out how many start points there are */
for (i = 0; i < argc && !looks_like_expression (argv[i], true); i++)
FILE *stream = NULL;
char const* files0_filename_quoted = NULL;
struct argv_iterator *ai;
if (options.files0_from)
{
empty = false;
state.starting_path_length = strlen (argv[i]); /* TODO: is this redundant? */
if (!find (argv[i]))
return false;
/* Option -files0-from must not be combined with passing starting points
* on the command line. */
if (argv_starting_points)
{
error (0, 0, _("extra operand %s"), safely_quote_err_filename (0, argv[0]));
die (EXIT_FAILURE, 0, "%s",
_("file operands cannot be combined with -files0-from"));
}
if (0 == strcmp (options.files0_from, "-"))
{
/* Option -files0-from with argument "-" (=stdin) must not be combined
* with the -ok, -okdir actions: getting the user confirmation would
* mess with stdin. */
if (options.ok_prompt_stdin)
{
die (EXIT_FAILURE, 0, "%s\n",
_("option -files0-from reading from standard input"
" cannot be combined with -ok, -okdir"));
}
files0_filename_quoted = safely_quote_err_filename (0, _("(standard input)"));
stream = stdin;
}
else
{
files0_filename_quoted = safely_quote_err_filename (0, options.files0_from);
stream = fopen (options.files0_from, "r");
if (stream == NULL)
die (EXIT_FAILURE, errno, _("cannot open %s for reading"),
files0_filename_quoted);
const int fd = fileno (stream);
assert (fd >= 0);
if (options.ok_prompt_stdin)
{
/* Check if the given file is associated to the same stream as
* standard input - which is not allowed with -ok, -okdir. This
* is the case with special device names symlinks for stdin like
* $ find -files0-from /dev/stdin -ok
* or when the given FILE is also associated to stdin:
* $ find -files0-from FILE -ok < FILE
*/
struct stat sb1, sb2;
if (fstat (fd, &sb1) == 0 && fstat (STDIN_FILENO, &sb2) == 0
&& SAME_INODE (sb1, sb2))
{
die (EXIT_FAILURE, 0, "%s: %s\n",
_("option -files0-from: standard input must not refer"
" to the same file when combined with -ok, -okdir"),
files0_filename_quoted);
}
}
set_cloexec_flag (fd, true);
}
ai = argv_iter_init_stream (stream);
}
else
{
if (!argv_starting_points)
{
/* If no starting points are given on the comman line, then
* fall back to processing the current directory, i.e., ".".
* We use a temporary variable here because some actions modify
* the path temporarily. Hence if we use a string constant,
* we get a coredump. The best example of this is if we say
* "find -printf %H" (note, not "find . -printf %H").
*/
char defaultpath[2] = ".";
return find (defaultpath);
}
/* Process the starting point(s) from the command line. */
ai = argv_iter_init_argv (argv);
}
if (empty)
if (!ai)
xalloc_die ();
bool ok = true;
while (true)
{
/*
* We use a temporary variable here because some actions modify
* the path temporarily. Hence if we use a string constant,
* we get a coredump. The best example of this is if we say
* "find -printf %H" (note, not "find . -printf %H").
*/
char defaultpath[2] = ".";
return find (defaultpath);
enum argv_iter_err ai_err;
char *file_name = argv_iter (ai, &ai_err);
if (!file_name)
{
switch (ai_err)
{
case AI_ERR_EOF:
goto argv_iter_done;
case AI_ERR_READ: /* may only happen with -files0-from */
error (0, errno, _("%s: read error"), files0_filename_quoted);
state.exit_status = 1;
ok = false;
goto argv_iter_done;
case AI_ERR_MEM:
xalloc_die ();
default:
assert (!"unexpected error code from argv_iter");
}
}
/* Report and skip any empty file names before invoking fts.
This works around a glitch in fts, which fails immediately
(without looking at the other file names) when given an empty
file name. */
if (!file_name[0])
{
/* Diagnose a zero-length file name. When it's one
among many, knowing the record number may help. */
if (options.files0_from == NULL)
error (0, ENOENT, "%s", safely_quote_err_filename (0, file_name));
else
{
/* Using the standard 'filename:line-number:' prefix here is
not totally appropriate, since NUL is the separator, not NL,
but it might be better than nothing. */
unsigned long int file_number = argv_iter_n_args (ai);
error (0, 0, "%s:%lu: %s", files0_filename_quoted, file_number,
_("invalid zero-length file name"));
}
state.exit_status = 1;
ok = false;
continue;
}
/* Terminate loop when processing the start points from command line,
and reaching the first expression. */
if (!options.files0_from && looks_like_expression (file_name, true))
break;
state.starting_path_length = strlen (file_name); /* TODO: is this redundant? */
if (!find (file_name))
{
ok = false;
goto argv_iter_done;
}
}
return true;
argv_iter_done:
if (ok && options.files0_from && argv_iter_n_args (ai) <= 0)
die (EXIT_FAILURE, 0, _("file with starting points is empty: %s"),
files0_filename_quoted);
argv_iter_free (ai);
if (ok && options.files0_from && (ferror (stream) || fclose (stream) != 0))
die (EXIT_FAILURE, 0, _("error reading %s"), files0_filename_quoted);
return ok;
}

View File

@ -90,6 +90,7 @@ static bool parse_empty (const struct parser_table*, char *argv[], int *
static bool parse_exec (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_execdir (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_false (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_files0_from (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fls (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fprintf (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_follow (const struct parser_table*, char *argv[], int *arg_ptr);
@ -241,6 +242,7 @@ static struct parser_table const parse_table[] =
{ARG_ACTION, "exec", parse_exec, pred_exec}, /* POSIX */
{ARG_TEST, "executable", parse_accesscheck, pred_executable}, /* GNU, 4.3.0+ */
PARSE_ACTION ("execdir", execdir), /* *BSD, GNU */
PARSE_OPTION ("files0-from", files0_from), /* GNU */
PARSE_ACTION ("fls", fls), /* GNU */
PARSE_POSOPT ("follow", follow), /* GNU, Unix */
PARSE_ACTION ("fprint", fprint), /* GNU */
@ -963,6 +965,18 @@ parse_false (const struct parser_table* entry, char **argv, int *arg_ptr)
return insert_false ();
}
static bool
parse_files0_from (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *filename;
if (collect_arg (argv, arg_ptr, &filename))
{
options.files0_from = filename;
return true;
}
return false;
}
static bool
insert_fls (const struct parser_table* entry, const char *filename)
{
@ -2903,6 +2917,8 @@ insert_exec_ok (const char *action,
else
{
allow_plus = false;
/* The -ok* family need user confirmations via stdin. */
options.ok_prompt_stdin = true;
/* If find reads stdin (i.e. for -ok and similar), close stdin
* in the child to prevent some script from consuming the output
* intended for find.

View File

@ -180,8 +180,9 @@ operators (decreasing precedence; -and is implicit where no others are given):\n
HTL (_("\
positional options (always true): -daystart -follow -regextype\n\n\
normal options (always true, specified before other expressions):\n\
-depth --help -maxdepth LEVELS -mindepth LEVELS -mount -noleaf\n\
--version -xdev -ignore_readdir_race -noignore_readdir_race\n"));
-depth --help -files0-from FILE -maxdepth LEVELS -mindepth LEVELS\n\
-mount -noleaf --version -xdev -ignore_readdir_race\n\
-noignore_readdir_race\n"));
HTL (_("\
tests (N can be +N or -N or N): -amin N -anewer FILE -atime N -cmin N\n\
-cnewer FILE -ctime N -empty -false -fstype TYPE -gid N -group NAME\n\
@ -1073,6 +1074,9 @@ set_option_defaults (struct options *p)
set_follow_state (SYMLINK_NEVER_DEREF); /* The default is equivalent to -P. */
p->err_quoting_style = locale_quoting_style;
p->files0_from = NULL;
p->ok_prompt_stdin = false;
}

170
tests/find/files0-from.sh Executable file
View File

@ -0,0 +1,170 @@
#!/bin/sh
# Exercise -files0-from option.
# Copyright (C) 2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; fu_path_prepend_
print_ver_ find
# Option -files0-from requires a file name argument.
returns_ 1 find -files0-from > out 2> err \
&& grep 'missing argument.*files0' err \
|| { grep . out err; fail=1; }
# Option -files0-from must not be combined with passing starting points on
# the command line.
returns_ 1 find OFFENDING -files0-from FILE > out 2> err \
&& grep 'extra operand .*OFFENDING' err \
&& grep 'file operands cannot be combined with -files0-from' err \
|| { grep . out err; fail=1; }
# Process "." as default when starting points neither passed via -files0-from
# nor on the command line.
printf "%s\n" '.' > exp || framework_failure_
find -maxdepth 0 > out 2> err || fail=1
compare exp out || fail=1
compare /dev/null err || fail=1
# Option -files0-from with argument "-" (=stdin) must not be combined with
# the -ok action: getting the user confirmation would mess with stdin.
returns_ 1 find -files0-from - -ok echo '{}' ';' < /dev/null > out 2> err \
&& grep 'files0.* standard input .*cannot be combined with .*ok' err \
|| { grep . out err; fail=1; }
# Option -files0-from with argument "-" (=stdin) must not be combined with
# the -okdir action: getting the user confirmation would mess with stdin.
returns_ 1 find -files0-from - -okdir echo '{}' ';' < /dev/null > out 2> err \
&& grep 'files0.* standard input .*cannot be combined with .*ok' err \
|| { grep . out err; fail=1; }
# File argument of -files0-from option must not refer to the same file as stdin.
printf '.' > in || framework_failure_
returns_ 1 find -files0-from in -ok echo '{}' ';' < in > out 2> err \
&& grep 'files0.* standard input .*same file .*with -ok, -okdir' err \
|| { grep . out err; fail=1; }
# Likewise via a symlink.
if ln -s in inlink; then
returns_ 1 find -files0-from inlink -ok echo '{}' ';' < in > out 2> err \
&& grep 'files0.* standard input .*same file .*with -ok, -okdir' err \
|| { grep . out err; fail=1; }
# ... and vice versa.
returns_ 1 find -files0-from in -ok echo '{}' ';' < inlink > out 2> err \
&& grep 'files0.* standard input .*same file .*with -ok, -okdir' err \
|| { grep . out err; fail=1; }
fi
# Likewise when the system provides the name '/dev/stdin'.
if ls /dev/stdin >/dev/null 2>&1; then
returns_ 1 find -files0-from /dev/stdin -ok echo '{}' ';' < in > out 2> err \
&& grep 'files0.* standard input .*same file .*with -ok, -okdir' err \
|| { grep . out err; fail=1; }
fi
# Exercise a non-existing file.
returns_ 1 find -files0-from ENOENT > out 2> err \
&& grep 'cannot open .ENOENT. for reading: No such' err \
|| { grep . out err; fail=1; }
# Exercise a file which cannot be opened.
# The shadow(5) file seems to be a good choice for reasonable coverage.
f='/etc/shadow'
if test -e $f && test '!' -r $f; then
returns_ 1 find -files0-from $f > out 2> err \
&& grep 'cannot open .* for reading: Permission denied' err \
|| { grep . out err; fail=1; }
fi
# Exercise a directory argument.
returns_ 1 find -files0-from / > out 2> err \
&& grep 'read error' err \
|| { grep . out err; fail=1; }
# Exercise an empty input file.
returns_ 1 find -files0-from /dev/null > out 2> err || fail=1
compare /dev/null out || fail=1
grep 'file with starting points is empty:' err || fail=1
# Likewise via stdin.
returns_ 1 find -files0-from - < /dev/null > out 2> err || fail=1
compare /dev/null out || fail=1
grep 'file with starting points is empty:.*standard input' err || fail=1
# Likewise via a pipe on stdin.
cat /dev/null | returns_ 1 find -files0-from - > out 2> err || fail=1
compare /dev/null out || fail=1
grep 'file with starting points is empty:.*standard input' err || fail=1
# Now a regular case: 2 files: expect the same output.
touch a b || framework_failure_
printf '%s\0' a b > in || framework_failure_
tr '\0' '\n' < in > exp || framework_failure_
find -files0-from in -print > out 2> err || fail=1
compare exp out || fail=1
compare /dev/null err || fail=1
# Demonstrate that -files0-from accepts file names which would otherwise be
# rejected because they are recognized as test or action.
touch ./-print ./-mtime ./-size || framework_failure_
printf '%s\0' _print _mtime _size | tr '_' '-' > in || framework_failure_
tr '\0' '\n' < in > exp || framework_failure_
find -files0-from in -printf '%p\n' > out 2> err || fail=1
compare exp out || fail=1
compare /dev/null err || fail=1
# Zero-length file name on position 2, once per stdin ...
printf '%s\n' a b > exp || framework_failure_
printf '%s\0' a '' b \
| tee file \
| returns_ 1 find -files0-from - -print > out 2> err || fail=1
compare exp out || fail=1
grep '(standard input).:2: invalid zero-length file name' err || fail=1
# ... and once per file.
returns_ 1 find -files0-from file -print > out 2> err || fail=1
compare exp out || fail=1
grep 'file.:2: invalid zero-length file name' err || fail=1
# Non-existing file name.
printf '%s\0' a ENOENT b \
| returns_ 1 find -files0-from - -print > out 2> err || fail=1
compare exp out || fail=1
grep 'ENOENT' err || fail=1
# Demonstrate (the usual!) recursion ...
mkdir d1 d1/d2 d1/d2/d3 && touch d1/d2/d3/file || framework_failure_
printf '%s\n' d1 d1/d2 d1/d2/d3 d1/d2/d3/file > exp || framework_failure_
printf 'd1' \
| find -files0-from - > out 2> err || fail=1
compare exp out || fail=1
compare /dev/null err || fail=1
# ... and how to avoid recursion with -maxdepth 0.
find d1 -print0 \
| find -files0-from - -maxdepth 0 > out 2> err || fail=1
compare exp out || fail=1
compare /dev/null err || fail=1
# Large input with files.
# Create file 'exp' with many times its own name as content, NUL-separated;
# and let find(1) check that it worked (4x in bytes).
yes exp | head -n 100000 | tr '\n' '\0' > exp \
&& find exp -size 400000c | grep . || framework_failure_
# Run the test.
find -files0-from exp > out 2> err || fail=1
# ... and verify.
test $( wc -l < out ) = 100000 || fail=1
find out -size 400000c | grep . || fail=1
Exit $fail

View File

@ -114,6 +114,7 @@ all_tests = \
tests/find/printf_inode.sh \
tests/find/execdir-fd-leak.sh \
tests/find/exec-plus-last-file.sh \
tests/find/files0-from.sh \
tests/find/refuse-noop.sh \
tests/find/debug-missing-arg.sh \
tests/find/used.sh \