/* find -- search for files in a directory hierarchy (fts version)
Copyright (C) 1990-2019 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 .
*/
/* This file was written by James Youngman, based on oldfind.c.
GNU find was written by Eric Decker ,
with enhancements by David MacKenzie ,
Jay Plett ,
and Tim Wood .
The idea for -print0 and xargs -0 came from
Dan Bernstein .
*/
/* config.h must always be included first. */
#include
/* system headers. */
#include
#include
#include
#include
#include
#include
/* gnulib headers. */
#include "cloexec.h"
#include "closeout.h"
#include "error.h"
#include "fts_.h"
#include "intprops.h"
#include "progname.h"
#include "quotearg.h"
#include "save-cwd.h"
#include "xgetcwd.h"
/* find headers. */
#include "defs.h"
#include "die.h"
#include "dircallback.h"
#include "fdleak.h"
#include "unused-result.h"
#include "system.h"
#undef STAT_MOUNTPOINTS
/* FTS_TIGHT_CYCLE_CHECK tries to work around Savannah bug #17877
* (but actually using it doesn't fix the bug).
*/
static int ftsoptions = FTS_NOSTAT|FTS_TIGHT_CYCLE_CHECK|FTS_CWDFD|FTS_VERBATIM;
static int prev_depth = INT_MIN; /* fts_level can be < 0 */
static int curr_fd = -1;
static bool find (char *arg) __attribute_warn_unused_result__;
static bool process_all_startpoints (int argc, char *argv[]) __attribute_warn_unused_result__;
static void
left_dir (void)
{
if (ftsoptions & FTS_CWDFD)
{
if (curr_fd >= 0)
{
close (curr_fd);
curr_fd = -1;
}
}
else
{
/* do nothing. */
}
}
/*
* Signal that we are now inside a directory pointed to by dir_fd.
* The caller can't tell if this is the first time this happens, so
* we have to be careful not to call dup() more than once
*/
static void
inside_dir (int dir_fd)
{
if (ftsoptions & FTS_CWDFD)
{
assert (dir_fd == AT_FDCWD || dir_fd >= 0);
state.cwd_dir_fd = dir_fd;
if (curr_fd < 0)
{
if (AT_FDCWD == dir_fd)
{
curr_fd = AT_FDCWD;
}
else if (dir_fd >= 0)
{
curr_fd = dup_cloexec (dir_fd);
}
else
{
/* curr_fd is invalid, but dir_fd is also invalid.
* This should not have happened.
*/
assert (curr_fd >= 0 || dir_fd >= 0);
}
}
}
else
{
/* FTS_CWDFD is not in use. We can always assume that
* AT_FDCWD refers to the directory we are currentl searching.
*
* Therefore there is nothing to do.
*/
}
}
#ifdef STAT_MOUNTPOINTS
static void init_mounted_dev_list (void);
#endif
#define HANDLECASE(N) case N: return #N;
static const char *
get_fts_info_name (int info)
{
static char buf[1 + INT_BUFSIZE_BOUND (info) + 1];
switch (info)
{
HANDLECASE(FTS_D);
HANDLECASE(FTS_DC);
HANDLECASE(FTS_DEFAULT);
HANDLECASE(FTS_DNR);
HANDLECASE(FTS_DOT);
HANDLECASE(FTS_DP);
HANDLECASE(FTS_ERR);
HANDLECASE(FTS_F);
HANDLECASE(FTS_INIT);
HANDLECASE(FTS_NS);
HANDLECASE(FTS_NSOK);
HANDLECASE(FTS_SL);
HANDLECASE(FTS_SLNONE);
HANDLECASE(FTS_W);
default:
sprintf (buf, "[%d]", info);
return buf;
}
}
static void
visit (FTS *p, FTSENT *ent, struct stat *pstat)
{
struct predicate *eval_tree;
state.have_stat = (ent->fts_info != FTS_NS) && (ent->fts_info != FTS_NSOK);
state.rel_pathname = ent->fts_accpath;
state.cwd_dir_fd = p->fts_cwd_fd;
/* Apply the predicates to this path. */
eval_tree = get_eval_tree ();
apply_predicate (ent->fts_path, pstat, eval_tree);
/* Deal with any side effects of applying the predicates. */
if (state.stop_at_current_level)
{
fts_set (p, ent, FTS_SKIP);
}
}
static const char*
partial_quotearg_n (int n, char *s, size_t len, enum quoting_style style)
{
if (0 == len)
{
return quotearg_n_style (n, style, "");
}
else
{
char saved;
const char *result;
saved = s[len];
s[len] = 0;
result = quotearg_n_style (n, style, s);
s[len] = saved;
return result;
}
}
/* We've detected a file system loop. This is caused by one of
* two things:
*
* 1. Option -L is in effect and we've hit a symbolic link that
* points to an ancestor. This is harmless. We won't traverse the
* symbolic link.
*
* 2. We have hit a real cycle in the directory hierarchy. In this
* case, we issue a diagnostic message (POSIX requires this) and we
* skip that directory entry.
*/
static void
issue_loop_warning (FTSENT * ent)
{
if (S_ISLNK(ent->fts_statp->st_mode))
{
error (0, 0,
_("Symbolic link %s is part of a loop in the directory hierarchy; we have already visited the directory to which it points."),
safely_quote_err_filename (0, ent->fts_path));
}
else
{
/* We have found an infinite loop. POSIX requires us to
* issue a diagnostic. Usually we won't get to here
* because when the leaf optimisation is on, it will cause
* the subdirectory to be skipped. If /a/b/c/d is a hard
* link to /a/b, then the link count of /a/b/c is 2,
* because the ".." entry of /a/b/c/d points to /a, not
* to /a/b/c.
*/
error (0, 0,
_("File system loop detected; "
"%s is part of the same file system loop as %s."),
safely_quote_err_filename (0, ent->fts_path),
partial_quotearg_n (1,
ent->fts_cycle->fts_path,
ent->fts_cycle->fts_pathlen,
options.err_quoting_style));
}
}
/*
* Return true if NAME corresponds to a file which forms part of a
* symbolic link loop. The command
* rm -f a b; ln -s a b; ln -s b a
* produces such a loop.
*/
static bool
symlink_loop (const char *name)
{
struct stat stbuf;
const int rv = options.xstat (name, &stbuf);
return (0 != rv) && (ELOOP == errno);
}
static void
show_outstanding_execdirs (FILE *fp)
{
if (options.debug_options & DebugExec)
{
bool seen = false;
struct predicate *p;
p = get_eval_tree ();
fprintf (fp, "Outstanding execdirs:");
while (p)
{
const char *pfx;
if (pred_is (p, pred_execdir))
pfx = "-execdir";
else if (pred_is (p, pred_okdir))
pfx = "-okdir";
else
pfx = NULL;
if (pfx)
{
size_t i;
const struct exec_val *execp = &p->args.exec_vec;
seen = true;
fprintf (fp, "%s ", pfx);
if (execp->multiple)
fprintf (fp, "multiple ");
fprintf (fp, "%" PRIuMAX " args: ", (uintmax_t) execp->state.cmd_argc);
for (i=0; istate.cmd_argc; ++i)
{
fprintf (fp, "%s ", execp->state.cmd_argv[i]);
}
fprintf (fp, "\n");
}
p = p->pred_next;
}
if (!seen)
fprintf (fp, " none\n");
}
else
{
/* No debug output is wanted. */
}
}
static void
consider_visiting (FTS *p, FTSENT *ent)
{
struct stat statbuf;
mode_t mode;
int ignore, isdir;
if (options.debug_options & DebugSearch)
fprintf (stderr,
"consider_visiting (early): %s: "
"fts_info=%-6s, fts_level=%2d, prev_depth=%d "
"fts_path=%s, fts_accpath=%s\n",
quotearg_n_style (0, options.err_quoting_style, ent->fts_path),
get_fts_info_name (ent->fts_info),
(int)ent->fts_level, prev_depth,
quotearg_n_style (1, options.err_quoting_style, ent->fts_path),
quotearg_n_style (2, options.err_quoting_style, ent->fts_accpath));
if (ent->fts_info == FTS_DP)
{
left_dir ();
}
else if (ent->fts_level > prev_depth || ent->fts_level==0)
{
left_dir ();
}
inside_dir (p->fts_cwd_fd);
prev_depth = ent->fts_level;
statbuf.st_ino = ent->fts_statp->st_ino;
/* Cope with various error conditions. */
if (ent->fts_info == FTS_ERR)
{
nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
return;
}
if (ent->fts_info == FTS_DNR)
{
nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
if (options.do_dir_first)
{
/* Return for unreadable directories without -depth.
* With -depth, the directory itself has to be processed, yet the
* error message above has to be output.
*/
return;
}
}
else if (ent->fts_info == FTS_DC)
{
issue_loop_warning (ent);
error_severity (EXIT_FAILURE);
return;
}
else if (ent->fts_info == FTS_SLNONE)
{
/* fts_read() claims that ent->fts_accpath is a broken symbolic
* link. That would be fine, but if this is part of a symbolic
* link loop, we diagnose the problem and also ensure that the
* eventual return value is nonzero. Note that while the path
* we stat is local (fts_accpath), we print the full path name
* of the file (fts_path) in the error message.
*/
if (symlink_loop (ent->fts_accpath))
{
nonfatal_target_file_error (ELOOP, ent->fts_path);
return;
}
}
else if (ent->fts_info == FTS_NS)
{
if (ent->fts_level == 0)
{
/* e.g., nonexistent starting point */
nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
return;
}
else
{
/* The following if statement fixes Savannah bug #19605
* (failure to diagnose a symbolic link loop)
*/
if (symlink_loop (ent->fts_accpath))
{
nonfatal_target_file_error (ELOOP, ent->fts_path);
return;
}
else
{
nonfatal_target_file_error (ent->fts_errno, ent->fts_path);
/* Continue despite the error, as file name without stat info
* might be better than not even processing the file name. This
* can lead to repeated error messages later on, though, if a
* predicate requires stat information.
*
* Not printing an error message here would be even more wrong,
* though, as this could cause the contents of a directory to be
* silently ignored, as the directory wouldn't be identified as
* such.
*/
}
}
}
/* Cope with the usual cases. */
if (ent->fts_info == FTS_NSOK
|| ent->fts_info == FTS_NS /* e.g. symlink loop */)
{
assert (!state.have_stat);
assert (ent->fts_info == FTS_NSOK || state.type == 0);
mode = state.type;
}
else
{
state.have_stat = true;
state.have_type = true;
statbuf = *(ent->fts_statp);
state.type = mode = statbuf.st_mode;
if (00000 == mode)
{
/* Savannah bug #16378. */
error (0, 0, _("WARNING: file %s appears to have mode 0000"),
quotearg_n_style (0, options.err_quoting_style, ent->fts_path));
}
}
/* update state.curdepth before calling digest_mode(), because digest_mode
* may call following_links().
*/
state.curdepth = ent->fts_level;
if (mode)
{
if (!digest_mode (&mode, ent->fts_path, ent->fts_name, &statbuf, 0))
return;
}
/* examine this item. */
ignore = 0;
isdir = S_ISDIR(mode)
|| (FTS_D == ent->fts_info)
|| (FTS_DP == ent->fts_info)
|| (FTS_DC == ent->fts_info);
if (isdir && (ent->fts_info == FTS_NSOK))
{
/* This is a directory, but fts did not stat it, so
* presumably would not be planning to search its
* children. Force a stat of the file so that the
* children can be checked.
*/
fts_set (p, ent, FTS_AGAIN);
return;
}
if (options.maxdepth >= 0)
{
if (ent->fts_level >= options.maxdepth)
{
fts_set (p, ent, FTS_SKIP); /* descend no further */
if (ent->fts_level > options.maxdepth)
ignore = 1; /* don't even look at this one */
}
}
if ( (ent->fts_info == FTS_D) && !options.do_dir_first )
{
/* this is the preorder visit, but user said -depth */
ignore = 1;
}
else if ( (ent->fts_info == FTS_DP) && options.do_dir_first )
{
/* this is the postorder visit, but user didn't say -depth */
ignore = 1;
}
else if (ent->fts_level < options.mindepth)
{
ignore = 1;
}
if (options.debug_options & DebugSearch)
fprintf (stderr,
"consider_visiting (late): %s: "
"fts_info=%-6s, isdir=%d ignore=%d have_stat=%d have_type=%d \n",
quotearg_n_style (0, options.err_quoting_style, ent->fts_path),
get_fts_info_name (ent->fts_info),
isdir, ignore, state.have_stat, state.have_type);
if (!ignore)
{
visit (p, ent, &statbuf);
}
if (ent->fts_info == FTS_DP)
{
/* we're leaving a directory. */
state.stop_at_current_level = false;
}
}
static bool
find (char *arg)
{
char * arglist[2];
FTS *p;
FTSENT *ent;
state.starting_path_length = strlen (arg);
inside_dir (AT_FDCWD);
arglist[0] = arg;
arglist[1] = NULL;
switch (options.symlink_handling)
{
case SYMLINK_ALWAYS_DEREF:
ftsoptions |= FTS_COMFOLLOW|FTS_LOGICAL;
break;
case SYMLINK_DEREF_ARGSONLY:
ftsoptions |= FTS_COMFOLLOW|FTS_PHYSICAL;
break;
case SYMLINK_NEVER_DEREF:
ftsoptions |= FTS_PHYSICAL;
break;
}
if (options.stay_on_filesystem)
ftsoptions |= FTS_XDEV;
p = fts_open (arglist, ftsoptions, NULL);
if (NULL == p)
{
error (0, errno, _("cannot search %s"),
safely_quote_err_filename (0, arg));
error_severity (EXIT_FAILURE);
}
else
{
int level = INT_MIN;
while ( (errno=0, ent=fts_read (p)) != NULL )
{
if (state.execdirs_outstanding)
{
/* If we changed level, perform any outstanding
* execdirs. If we see a sequence of directory entries
* like this: fffdfffdfff, we could build a command line
* of 9 files, but this simple-minded implementation
* builds a command line for only 3 files at a time
* (since fts descends into the directories).
*/
if ((int)ent->fts_level != level)
{
show_outstanding_execdirs (stderr);
complete_pending_execdirs ();
}
}
level = (int)ent->fts_level;
state.already_issued_stat_error_msg = false;
state.have_stat = false;
state.have_type = !!ent->fts_statp->st_mode;
state.type = state.have_type ? ent->fts_statp->st_mode : 0;
consider_visiting (p, ent);
}
/* fts_read returned NULL; distinguish between "finished" and "error". */
if (errno)
{
error (0, errno,
"failed to read file names from file system at or below %s",
safely_quote_err_filename (0, arg));
error_severity (EXIT_FAILURE);
return false;
}
if (0 != fts_close (p))
{
/* Here we break the abstraction of fts_close a bit, because we
* are going to skip the rest of the start points, and return with
* nonzero exit status. Hence we need to issue a diagnostic on
* stderr. */
error (0, errno,
_("failed to restore working directory after searching %s"),
arg);
error_severity (EXIT_FAILURE);
return false;
}
p = NULL;
}
return true;
}
static bool
process_all_startpoints (int argc, char *argv[])
{
int i;
bool empty = true;
/* figure out how many start points there are */
for (i = 0; i < argc && !looks_like_expression (argv[i], true); i++)
{
empty = false;
state.starting_path_length = strlen (argv[i]); /* TODO: is this redundant? */
if (!find (argv[i]))
return false;
}
if (empty)
{
/*
* 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);
}
return true;
}
int
main (int argc, char **argv)
{
int end_of_leading_options = 0; /* First arg after any -H/-L etc. */
struct predicate *eval_tree;
if (argv[0])
set_program_name (argv[0]);
else
set_program_name ("find");
record_initial_cwd ();
state.already_issued_stat_error_msg = false;
state.exit_status = 0;
state.execdirs_outstanding = false;
state.cwd_dir_fd = AT_FDCWD;
if (fd_leak_check_is_enabled ())
{
remember_non_cloexec_fds ();
}
state.shared_files = sharefile_init ("w");
if (NULL == state.shared_files)
{
die (EXIT_FAILURE, errno,
_("Failed to initialize shared-file hash table"));
}
/* Set the option defaults before we do the locale initialisation as
* check_nofollow() needs to be executed in the POSIX locale.
*/
set_option_defaults (&options);
#ifdef HAVE_SETLOCALE
setlocale (LC_ALL, "");
#endif
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
if (atexit (close_stdout))
{
die (EXIT_FAILURE, errno, _("The atexit library function failed"));
}
/* Check for -P, -H or -L options. Also -D and -O, which are
* both GNU extensions.
*/
end_of_leading_options = process_leading_options (argc, argv);
if (options.debug_options & DebugStat)
options.xstat = debug_stat;
if (options.debug_options & DebugTime)
fprintf (stderr, "cur_day_start = %s", ctime (&options.cur_day_start.tv_sec));
/* We are now processing the part of the "find" command line
* after the -H/-L options (if any).
*/
eval_tree = build_expression_tree (argc, argv, end_of_leading_options);
/* safely_chdir() needs to check that it has ended up in the right place.
* To avoid bailing out when something gets automounted, it checks if
* the target directory appears to have had a directory mounted on it as
* we chdir()ed. The problem with this is that in order to notice that
* a file system was mounted, we would need to lstat() all the mount points.
* That strategy loses if our machine is a client of a dead NFS server.
*
* Hence if safely_chdir() and wd_sanity_check() can manage without needing
* to know the mounted device list, we do that.
*/
if (!options.open_nofollow_available)
{
#ifdef STAT_MOUNTPOINTS
init_mounted_dev_list ();
#endif
}
/* process_all_startpoints processes the starting points named on
* the command line. A false return value from it means that we
* failed to restore the original context. That means it would not
* be safe to call cleanup() since we might complete an execdir in
* the wrong directory for example.
*/
if (process_all_startpoints (argc-end_of_leading_options,
argv+end_of_leading_options))
{
/* If "-exec ... {} +" has been used, there may be some
* partially-full command lines which have been built,
* but which are not yet complete. Execute those now.
*/
show_success_rates (eval_tree);
cleanup ();
}
return state.exit_status;
}
bool
is_fts_enabled (int *fts_options)
{
/* this version of find (i.e. this main()) uses fts. */
*fts_options = ftsoptions;
return true;
}