/* parser.c -- convert the command line args into an expression tree.
Copyright (C) 1990-2023 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 .
*/
/* config.h must always come first. */
#include
/* system headers. */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* gnulib headers. */
#include "error.h"
#include "fnmatch.h"
#include "fts_.h"
#include "modechange.h"
#include "mountlist.h"
#include "parse-datetime.h"
#include "print.h"
#include "progname.h"
#include "quotearg.h"
#include "regextype.h"
#include "safe-atoi.h"
#include "selinux-at.h"
#include "splitstring.h"
#include "stat-time.h"
#include "xalloc.h"
#include "xstrtod.h"
#include "xstrtol.h"
/* At the moment, we include this after gnulib headers, since it uses
some of the same names for function attribute macros as gnulib does,
since I plan to make gcc-sttrigbutes a gnulib module. However, for
now, I haven't made the wholesale edits to gnulib that this would
require. Including this file last simply minimises the number of
compiler warnings about macro redefinition (in gnulib headers).
*/
#include "gcc-function-attributes.h"
/* find headers. */
#include "buildcmd.h"
#include "defs.h"
#include "die.h"
#include "fdleak.h"
#include "findutils-version.h"
#include "system.h"
#if ! HAVE_ENDGRENT
# define endgrent() ((void) 0)
#endif
#if ! HAVE_ENDPWENT
# define endpwent() ((void) 0)
#endif
static bool parse_accesscheck (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_amin (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_and (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_anewer (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_cmin (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_cnewer (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_comma (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_daystart (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_delete (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_d (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_depth (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_empty (const struct parser_table*, char *argv[], int *arg_ptr);
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);
static bool parse_fprint (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fprint0 (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_fstype (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_gid (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_group (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ilname (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_iname (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_inum (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ipath (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_iregex (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_iwholename (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_links (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_lname (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ls (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_maxdepth (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_mindepth (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_mmin (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_name (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_negate (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_newer (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_newerXY (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_noleaf (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_nogroup (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_nouser (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_nowarn (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ok (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_okdir (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_or (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_path (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_perm (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_print0 (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_printf (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_prune (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_regex (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_regextype (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_samefile (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_size (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_time (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_true (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_type (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_uid (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_used (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_user (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_wholename (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_xdev (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_ignore_race (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_noignore_race (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_warn (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_xtype (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_quit (const struct parser_table*, char *argv[], int *arg_ptr);
static bool parse_context (const struct parser_table*, char *argv[], int *arg_ptr);
#if 0
static bool parse_show_control_chars (const struct parser_table*, char *argv[], int *arg_ptr);
#endif
static bool parse_help (const struct parser_table* entry, char **argv, int *arg_ptr)
_GL_ATTRIBUTE_NORETURN;
static bool parse_version (const struct parser_table*, char *argv[], int *arg_ptr)
_GL_ATTRIBUTE_NORETURN;
static bool insert_type (char **argv, int *arg_ptr,
const struct parser_table *entry,
PRED_FUNC which_pred);
static bool insert_regex (char *argv[], int *arg_ptr,
const struct parser_table *entry,
int regex_options);
static bool insert_exec_ok (const char *action,
const struct parser_table *entry,
char *argv[],
int *arg_ptr);
static bool get_comp_type (const char **str,
enum comparison_type *comp_type);
static bool get_relative_timestamp (const char *str,
struct time_val *tval,
struct timespec origin,
double sec_per_unit,
const char *overflowmessage);
static bool get_num (const char *str,
uintmax_t *num,
enum comparison_type *comp_type);
static struct predicate* insert_num (char *argv[], int *arg_ptr,
const struct parser_table *entry);
static void open_output_file (const char *path, struct format_val *p);
static void open_stdout (struct format_val *p);
static bool stream_is_tty(FILE *fp);
static bool parse_noop (const struct parser_table* entry,
char **argv, int *arg_ptr);
#define PASTE(x,y) x##y
#define PARSE_OPTION(what,suffix) \
{ (ARG_OPTION), (what), PASTE(parse_,suffix), NULL }
#define PARSE_POSOPT(what,suffix) \
{ (ARG_POSITIONAL_OPTION), (what), PASTE(parse_,suffix), NULL }
#define PARSE_TEST(what,suffix) \
{ (ARG_TEST), (what), PASTE(parse_,suffix), PASTE(pred_,suffix) }
#define PARSE_TEST_NP(what,suffix) \
{ (ARG_TEST), (what), PASTE(parse_,suffix), NULL }
#define PARSE_ACTION(what,suffix) \
{ (ARG_ACTION), (what), PASTE(parse_,suffix), PASTE(pred_,suffix) }
#define PARSE_PUNCTUATION(what,suffix) \
{ (ARG_PUNCTUATION), (what), PASTE(parse_,suffix), PASTE(pred_,suffix) }
/* Predicates we cannot handle in the usual way. If you add an entry
* to this table, double-check the switch statement in
* pred_sanity_check() to make sure that the new case is being
* correctly handled.
*/
static struct parser_table const parse_entry_newerXY =
{
ARG_SPECIAL_PARSE, "newerXY", parse_newerXY, pred_newerXY /* BSD */
};
/* GNU find predicates that are not mentioned in POSIX.2 are marked `GNU'.
If they are in some Unix versions of find, they are marked `Unix'. */
static struct parser_table const parse_table[] =
{
PARSE_PUNCTUATION("!", negate), /* POSIX */
PARSE_PUNCTUATION("not", negate), /* GNU */
PARSE_PUNCTUATION("(", openparen), /* POSIX */
PARSE_PUNCTUATION(")", closeparen), /* POSIX */
PARSE_PUNCTUATION(",", comma), /* GNU */
PARSE_PUNCTUATION("a", and), /* POSIX */
PARSE_TEST ("amin", amin), /* GNU */
PARSE_PUNCTUATION("and", and), /* GNU */
PARSE_TEST ("anewer", anewer), /* GNU */
{ARG_TEST, "atime", parse_time, pred_atime}, /* POSIX */
PARSE_TEST ("cmin", cmin), /* GNU */
PARSE_TEST ("cnewer", cnewer), /* GNU */
{ARG_TEST, "ctime", parse_time, pred_ctime}, /* POSIX */
PARSE_TEST ("context", context), /* GNU */
PARSE_POSOPT ("daystart", daystart), /* GNU */
PARSE_ACTION ("delete", delete), /* GNU, Mac OS, FreeBSD */
PARSE_OPTION ("d", d), /* Mac OS X, FreeBSD, NetBSD, OpenBSD, but deprecated in favour of -depth */
PARSE_OPTION ("depth", depth), /* POSIX */
PARSE_TEST ("empty", empty), /* GNU */
{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 */
PARSE_ACTION ("fprint0", fprint0), /* GNU */
{ARG_ACTION, "fprintf", parse_fprintf, pred_fprintf}, /* GNU */
PARSE_TEST ("fstype", fstype), /* GNU, Unix */
PARSE_TEST ("gid", gid), /* GNU */
PARSE_TEST ("group", group), /* POSIX */
PARSE_OPTION ("ignore_readdir_race", ignore_race), /* GNU */
PARSE_TEST ("ilname", ilname), /* GNU */
PARSE_TEST ("iname", iname), /* GNU */
PARSE_TEST ("inum", inum), /* GNU, Unix */
PARSE_TEST ("ipath", ipath), /* GNU, deprecated in favour of iwholename */
PARSE_TEST_NP ("iregex", iregex), /* GNU */
PARSE_TEST_NP ("iwholename", iwholename), /* GNU */
PARSE_TEST ("links", links), /* POSIX */
PARSE_TEST ("lname", lname), /* GNU */
PARSE_ACTION ("ls", ls), /* GNU, Unix */
PARSE_OPTION ("maxdepth", maxdepth), /* GNU */
PARSE_OPTION ("mindepth", mindepth), /* GNU */
PARSE_TEST ("mmin", mmin), /* GNU */
PARSE_OPTION ("mount", xdev), /* Unix */
{ARG_TEST, "mtime", parse_time, pred_mtime}, /* POSIX */
PARSE_TEST ("name", name),
#ifdef UNIMPLEMENTED_UNIX
PARSE(ARG_UNIMPLEMENTED, "ncpio", ncpio), /* Unix */
#endif
PARSE_TEST ("newer", newer), /* POSIX */
{ARG_TEST, "atime", parse_time, pred_atime}, /* POSIX */
PARSE_OPTION ("noleaf", noleaf), /* GNU */
PARSE_TEST ("nogroup", nogroup), /* POSIX */
PARSE_TEST ("nouser", nouser), /* POSIX */
PARSE_OPTION ("noignore_readdir_race", noignore_race), /* GNU */
PARSE_POSOPT ("nowarn", nowarn), /* GNU */
PARSE_POSOPT ("warn", warn), /* GNU */
PARSE_PUNCTUATION("o", or), /* POSIX */
PARSE_PUNCTUATION("or", or), /* GNU */
PARSE_ACTION ("ok", ok), /* POSIX */
PARSE_ACTION ("okdir", okdir), /* GNU (-execdir is BSD) */
PARSE_TEST ("path", path), /* POSIX */
PARSE_TEST ("perm", perm), /* POSIX */
PARSE_ACTION ("print", print), /* POSIX */
PARSE_ACTION ("print0", print0), /* GNU */
{ARG_ACTION, "printf", parse_printf, NULL}, /* GNU */
PARSE_ACTION ("prune", prune), /* POSIX */
PARSE_ACTION ("quit", quit), /* GNU */
{ARG_TEST, "readable", parse_accesscheck, pred_readable}, /* GNU, 4.3.0+ */
PARSE_TEST ("regex", regex), /* GNU */
PARSE_POSOPT ("regextype", regextype), /* GNU */
PARSE_TEST ("samefile", samefile), /* GNU */
#if 0
PARSE_OPTION ("show-control-chars", show_control_chars), /* GNU, 4.3.0+ */
#endif
PARSE_TEST ("size", size), /* POSIX */
PARSE_TEST ("type", type), /* POSIX */
PARSE_TEST ("uid", uid), /* GNU */
PARSE_TEST ("used", used), /* GNU */
PARSE_TEST ("user", user), /* POSIX */
PARSE_TEST_NP ("wholename", wholename), /* GNU, replaced -path, but now -path is standardized since POSIX 2008 */
{ARG_TEST, "writable", parse_accesscheck, pred_writable}, /* GNU, 4.3.0+ */
PARSE_OPTION ("xdev", xdev), /* POSIX */
PARSE_TEST ("xtype", xtype), /* GNU */
#ifdef UNIMPLEMENTED_UNIX
/* It's pretty ugly for find to know about archive formats.
Plus what it could do with cpio archives is very limited.
Better to leave it out. */
PARSE(ARG_UNIMPLEMENTED, "cpio", cpio), /* Unix */
#endif
/* gnulib's stdbool.h might have made true and false into macros,
* so we can't leave named 'true' and 'false' tokens, so we have
* to expeant the relevant entries longhand.
*/
{ARG_TEST, "false", parse_false, pred_false}, /* GNU */
{ARG_TEST, "true", parse_true, pred_true }, /* GNU */
/* Internal pseudo-option, therefore 3 minus: ---noop. */
{ARG_NOOP, "--noop", NULL, pred_true }, /* GNU, internal use only */
/* Various other cases that don't fit neatly into our macro scheme. */
{ARG_TEST, "help", parse_help, NULL}, /* GNU */
{ARG_TEST, "-help", parse_help, NULL}, /* GNU */
{ARG_TEST, "version", parse_version, NULL}, /* GNU */
{ARG_TEST, "-version", parse_version, NULL}, /* GNU */
{0, NULL, NULL, NULL}
};
static const char *first_nonoption_arg = NULL;
static const struct parser_table *noop = NULL;
static int
fallback_getfilecon (int fd, const char *name, char **p, int prev_rv)
{
/* Our original getfilecon () call failed. Perhaps we can't follow a
* symbolic link. If that might be the problem, lgetfilecon () the link.
* Otherwise, admit defeat. */
switch (errno)
{
case ENOENT:
case ENOTDIR:
if (options.debug_options & DebugStat)
{
fprintf (stderr, "fallback_getfilecon(): getfilecon(%s) failed; falling "
"back on lgetfilecon()\n", name);
}
return lgetfileconat (fd, name, p);
case EACCES:
case EIO:
case ELOOP:
case ENAMETOOLONG:
#ifdef EOVERFLOW
case EOVERFLOW: /* EOVERFLOW is not #defined on UNICOS. */
#endif
default:
return prev_rv;
}
}
/* optionh_getfilecon () implements the getfilecon operation when the
* -H option is in effect.
*
* If the item to be examined is a command-line argument, we follow
* symbolic links. If the getfilecon () call fails on the command-line
* item, we fall back on the properties of the symbolic link.
*
* If the item to be examined is not a command-line argument, we
* examine the link itself. */
static int
optionh_getfilecon (int fd, const char *name, char **p)
{
int rv;
if (0 == state.curdepth)
{
/* This file is from the command line; dereference the link (if it is
a link). */
rv = getfileconat (fd, name, p);
if (0 == rv)
return 0; /* success */
else
return fallback_getfilecon (fd, name, p, rv);
}
else
{
/* Not a file on the command line; do not dereference the link. */
return lgetfileconat (fd, name, p);
}
}
/* optionl_getfilecon () implements the getfilecon operation when the
* -L option is in effect. That option makes us examine the thing the
* symbolic link points to, not the symbolic link itself. */
static int
optionl_getfilecon (int fd, const char *name, char **p)
{
int rv = getfileconat (fd, name, p);
if (0 == rv)
return 0; /* normal case. */
else
return fallback_getfilecon (fd, name, p, rv);
}
/* optionp_getfilecon () implements the stat operation when the -P
* option is in effect (this is also the default). That option makes
* us examine the symbolic link itself, not the thing it points to. */
static int
optionp_getfilecon (int fd, const char *name, char **p)
{
return lgetfileconat (fd, name, p);
}
void
check_option_combinations (const struct predicate *p)
{
enum { seen_delete=1u, seen_prune=2u };
unsigned int predicates = 0u;
while (p)
{
if (p->pred_func == pred_delete)
predicates |= seen_delete;
else if (p->pred_func == pred_prune)
predicates |= seen_prune;
p = p->pred_next;
}
if ((predicates & seen_prune) && (predicates & seen_delete))
{
/* The user specified both -delete and -prune. One might test
* this by first doing
* find dirs .... -prune ..... -print
* to find out what's going to get deleted, and then switch to
* find dirs .... -prune ..... -delete
* once we are happy. Unfortunately, the -delete action also
* implicitly turns on -depth, which will affect the behaviour
* of -prune (in fact, it makes it a no-op). In this case we
* would like to prevent unfortunate accidents, so we require
* the user to have explicitly used -depth.
*
* We only get away with this because the -delete predicate is not
* in POSIX. If it was, we couldn't issue a fatal error here.
*/
if (!options.explicit_depth)
{
/* This fixes Savannah bug #20865. */
die (EXIT_FAILURE, 0,
_("The -delete action automatically turns on -depth, "
"but -prune does nothing when -depth is in effect. "
"If you want to carry on anyway, just explicitly use "
"the -depth option."));
}
}
}
static const struct parser_table*
get_noop (void)
{
int i;
if (NULL == noop)
{
for (i = 0; parse_table[i].parser_name != NULL; i++)
{
if (ARG_NOOP ==parse_table[i].type)
{
noop = &(parse_table[i]);
break;
}
}
}
return noop;
}
static int
get_stat_Ytime (const struct stat *p,
char what,
struct timespec *ret)
{
switch (what)
{
case 'a':
*ret = get_stat_atime (p);
return 1;
case 'B':
*ret = get_stat_birthtime (p);
return (ret->tv_nsec >= 0);
case 'c':
*ret = get_stat_ctime (p);
return 1;
case 'm':
*ret = get_stat_mtime (p);
return 1;
default:
assert (0);
abort ();
}
}
void
set_follow_state (enum SymlinkOption opt)
{
switch (opt)
{
case SYMLINK_ALWAYS_DEREF: /* -L */
options.xstat = optionl_stat;
options.x_getfilecon = optionl_getfilecon;
options.no_leaf_check = true;
break;
case SYMLINK_NEVER_DEREF: /* -P (default) */
options.xstat = optionp_stat;
options.x_getfilecon = optionp_getfilecon;
/* Can't turn no_leaf_check off because the user might have specified
* -noleaf anyway
*/
break;
case SYMLINK_DEREF_ARGSONLY: /* -H */
options.xstat = optionh_stat;
options.x_getfilecon = optionh_getfilecon;
options.no_leaf_check = true;
}
options.symlink_handling = opt;
if (options.debug_options & DebugStat)
{
/* For DebugStat, the choice is made at runtime within debug_stat()
* by checking the contents of the symlink_handling variable.
*/
options.xstat = debug_stat;
}
}
void
parse_begin_user_args (char **args, int argno,
const struct predicate *last,
const struct predicate *predicates)
{
(void) args;
(void) argno;
(void) last;
(void) predicates;
first_nonoption_arg = NULL;
}
void
parse_end_user_args (char **args, int argno,
const struct predicate *last,
const struct predicate *predicates)
{
/* does nothing */
(void) args;
(void) argno;
(void) last;
(void) predicates;
}
static bool
should_issue_warnings (void)
{
if (options.posixly_correct)
return false;
else
return options.warnings;
}
/* Check that it is legal to find the given primary in its
* position and return it.
*/
static const struct parser_table*
found_parser (const char *original_arg, const struct parser_table *entry)
{
/* If this is an option, but we have already had a
* non-option argument, the user may be under the
* impression that the behaviour of the option
* argument is conditional on some preceding
* tests. This might typically be the case with,
* for example, -maxdepth.
*
* The options -daystart and -follow are exempt
* from this treatment, since their positioning
* in the command line does have an effect on
* subsequent tests but not previous ones. That
* might be intentional on the part of the user.
*/
if (entry->type != ARG_POSITIONAL_OPTION)
{
if (entry->type == ARG_NOOP)
return NULL; /* internal use only, trap -noop here. */
/* Something other than -follow/-daystart.
* If this is an option, check if it followed
* a non-option and if so, issue a warning.
*/
if (entry->type == ARG_OPTION)
{
if ((first_nonoption_arg != NULL)
&& should_issue_warnings ())
{
/* option which follows a non-option */
error (0, 0,
_("warning: you have specified the global option %s "
"after the argument %s, but global options are not "
"positional, i.e., %s affects tests specified before it "
"as well as those specified after it. "
"Please specify global options before other arguments."),
original_arg,
first_nonoption_arg,
original_arg);
}
}
else
{
/* Not an option or a positional option,
* so remember we've seen it in order to
* use it in a possible future warning message.
*/
if (first_nonoption_arg == NULL)
{
first_nonoption_arg = original_arg;
}
}
}
return entry;
}
/* Return a pointer to the parser function to invoke for predicate
SEARCH_NAME.
Return NULL if SEARCH_NAME is not a valid predicate name. */
const struct parser_table*
find_parser (const char *search_name)
{
int i;
const char *original_arg = search_name;
/* Ugh. Special case -newerXY. */
if (0 == strncmp ("-newer", search_name, 6)
&& (8 == strlen (search_name)))
{
return found_parser (original_arg, &parse_entry_newerXY);
}
if (*search_name == '-')
search_name++;
for (i = 0; parse_table[i].parser_name != NULL; i++)
{
if (strcmp (parse_table[i].parser_name, search_name) == 0)
{
return found_parser (original_arg, &parse_table[i]);
}
}
return NULL;
}
static float
estimate_file_age_success_rate (float num_days)
{
if (num_days < 0.1f)
{
/* Assume 1% of files have timestamps in the future */
return 0.01f;
}
else if (num_days < 1.0f)
{
/* Assume 30% of files have timestamps today */
return 0.3f;
}
else if (num_days > 100.0f)
{
/* Assume 30% of files are very old */
return 0.3f;
}
else
{
/* Assume 39% of files are between 1 and 100 days old. */
return 0.39f;
}
}
static float
estimate_timestamp_success_rate (time_t when)
{
/* This calculation ignores the nanoseconds field of the
* origin, but I don't think that makes much difference
* to our estimate.
*/
int num_days = (options.cur_day_start.tv_sec - when) / 86400;
return estimate_file_age_success_rate (num_days);
}
/* Collect an argument from the argument list, or
* return false.
*/
static bool
collect_arg_nonconst (char **argv, int *arg_ptr, char **collected_arg)
{
if ((argv == NULL) || (argv[*arg_ptr] == NULL))
{
*collected_arg = NULL;
return false;
}
else
{
*collected_arg = argv[*arg_ptr];
(*arg_ptr)++;
return true;
}
}
static bool
collect_arg (char **argv, int *arg_ptr, const char **collected_arg)
{
char *arg;
const bool result = collect_arg_nonconst (argv, arg_ptr, &arg);
*collected_arg = arg;
return result;
}
static bool
collect_arg_stat_info (char **argv, int *arg_ptr, struct stat *p,
const char **argument)
{
const char *filename;
if (collect_arg (argv, arg_ptr, &filename))
{
*argument = filename;
if (0 != (options.xstat)(filename, p))
{
fatal_target_file_error (errno, filename);
}
return true;
}
else
{
*argument = NULL;
return false;
}
}
/* The parsers are responsible to continue scanning ARGV for
their arguments. Each parser knows what is and isn't
allowed for itself.
ARGV is the argument array.
*ARG_PTR is the index to start at in ARGV,
updated to point beyond the last element consumed.
The predicate structure is updated with the new information. */
static bool
parse_and (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = get_new_pred_noarg (entry);
our_pred->pred_func = pred_and;
our_pred->p_type = BI_OP;
our_pred->p_prec = AND_PREC;
our_pred->need_stat = our_pred->need_type = false;
return true;
}
static bool
parse_anewer (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct stat stat_newer;
const char *arg;
set_stat_placeholders (&stat_newer);
if (collect_arg_stat_info (argv, arg_ptr, &stat_newer, &arg))
{
struct predicate *our_pred = insert_primary (entry, arg);
our_pred->args.reftime.xval = XVAL_ATIME;
our_pred->args.reftime.ts = get_stat_mtime (&stat_newer);
our_pred->args.reftime.kind = COMP_GT;
our_pred->est_success_rate = estimate_timestamp_success_rate (stat_newer.st_mtime);
return true;
}
return false;
}
bool
parse_closeparen (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = get_new_pred_noarg (entry);
our_pred->pred_func = pred_closeparen;
our_pred->p_type = CLOSE_PAREN;
our_pred->p_prec = NO_PREC;
our_pred->need_stat = our_pred->need_type = false;
return true;
}
static bool
parse_cnewer (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct stat stat_newer;
const char *arg;
set_stat_placeholders (&stat_newer);
if (collect_arg_stat_info (argv, arg_ptr, &stat_newer, &arg))
{
struct predicate *our_pred = insert_primary (entry, arg);
our_pred->args.reftime.xval = XVAL_CTIME; /* like -newercm */
our_pred->args.reftime.ts = get_stat_mtime (&stat_newer);
our_pred->args.reftime.kind = COMP_GT;
our_pred->est_success_rate = estimate_timestamp_success_rate (stat_newer.st_mtime);
return true;
}
return false;
}
static bool
parse_comma (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = get_new_pred_noarg (entry);
our_pred->pred_func = pred_comma;
our_pred->p_type = BI_OP;
our_pred->p_prec = COMMA_PREC;
our_pred->need_stat = our_pred->need_type = false;
our_pred->est_success_rate = 1.0f;
return true;
}
static bool
parse_daystart (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct tm *local;
(void) entry;
(void) argv;
(void) arg_ptr;
if (options.full_days == false)
{
options.cur_day_start.tv_sec += DAYSECS;
options.cur_day_start.tv_nsec = 0;
local = localtime (&options.cur_day_start.tv_sec);
options.cur_day_start.tv_sec -= (local
? (local->tm_sec + local->tm_min * 60
+ local->tm_hour * 3600)
: options.cur_day_start.tv_sec % DAYSECS);
options.full_days = true;
}
return true;
}
static bool
parse_delete (const struct parser_table* entry, char *argv[], int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = insert_primary_noarg (entry);
our_pred->side_effects = our_pred->no_default_print = true;
/* -delete implies -depth */
options.do_dir_first = false;
/* We do not need stat information because we check for the case
* (errno==EISDIR) in pred_delete.
*/
our_pred->need_stat = our_pred->need_type = false;
our_pred->est_success_rate = 1.0f;
return true;
}
static bool
parse_depth (const struct parser_table* entry, char **argv, int *arg_ptr)
{
(void) entry;
(void) argv;
options.do_dir_first = false;
options.explicit_depth = true;
return parse_noop (entry, argv, arg_ptr);
}
static bool
parse_d (const struct parser_table* entry, char **argv, int *arg_ptr)
{
if (should_issue_warnings ())
{
error (0, 0,
_("warning: the -d option is deprecated; please use "
"-depth instead, because the latter is a "
"POSIX-compliant feature."));
}
return parse_depth (entry, argv, arg_ptr);
}
static bool
parse_empty (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = insert_primary_noarg (entry);
our_pred->est_success_rate = 0.01f; /* assume 1% of files are empty. */
return true;
}
static bool
parse_exec (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_exec_ok ("-exec", entry, argv, arg_ptr);
}
static bool
parse_execdir (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_exec_ok ("-execdir", entry, argv, arg_ptr);
}
static bool
insert_false(void)
{
struct predicate *our_pred;
const struct parser_table *entry_false;
entry_false = find_parser("false");
our_pred = insert_primary_noarg (entry_false);
our_pred->need_stat = our_pred->need_type = false;
our_pred->side_effects = our_pred->no_default_print = false;
our_pred->est_success_rate = 0.0f;
return true;
}
static bool
parse_false (const struct parser_table* entry, char **argv, int *arg_ptr)
{
(void) entry;
(void) argv;
(void) 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)
{
struct predicate *our_pred = insert_primary_noarg (entry);
if (filename)
open_output_file (filename, &our_pred->args.printf_vec);
else
open_stdout (&our_pred->args.printf_vec);
our_pred->side_effects = our_pred->no_default_print = true;
our_pred->est_success_rate = 1.0f;
return true;
}
static bool
parse_fls (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *filename;
if (collect_arg (argv, arg_ptr, &filename))
{
if (insert_fls (entry, filename))
return true;
else
--*arg_ptr; /* don't consume the invalid arg. */
}
return false;
}
static bool
parse_follow (const struct parser_table* entry, char **argv, int *arg_ptr)
{
set_follow_state (SYMLINK_ALWAYS_DEREF);
return parse_noop (entry, argv, arg_ptr);
}
static bool
parse_fprint (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
const char *filename;
if (collect_arg (argv, arg_ptr, &filename))
{
our_pred = insert_primary (entry, filename);
open_output_file (filename, &our_pred->args.printf_vec);
our_pred->side_effects = our_pred->no_default_print = true;
our_pred->need_stat = our_pred->need_type = false;
our_pred->est_success_rate = 1.0f;
return true;
}
else
{
return false;
}
}
static bool
insert_fprint (const struct parser_table* entry, const char *filename)
{
struct predicate *our_pred = insert_primary (entry, filename);
if (filename)
open_output_file (filename, &our_pred->args.printf_vec);
else
open_stdout (&our_pred->args.printf_vec);
our_pred->side_effects = our_pred->no_default_print = true;
our_pred->need_stat = our_pred->need_type = false;
our_pred->est_success_rate = 1.0f;
return true;
}
static bool
parse_fprint0 (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *filename;
if (collect_arg (argv, arg_ptr, &filename))
{
if (insert_fprint (entry, filename))
return true;
else
--*arg_ptr; /* don't consume the bad arg. */
}
return false;
}
static float estimate_fstype_success_rate (const char *fsname)
{
struct stat dir_stat;
const char *the_root_dir = "/";
if (0 == stat (the_root_dir, &dir_stat)) /* it's an absolute path anyway */
{
const char *fstype = filesystem_type (&dir_stat, the_root_dir);
/* Assume most files are on the same file system type as the root fs. */
if (0 == strcmp (fsname, fstype))
return 0.7f;
else
return 0.3f;
}
return 1.0f;
}
static bool
parse_fstype (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *typename;
if (collect_arg (argv, arg_ptr, &typename))
{
if (options.optimisation_level < 2 || is_used_fs_type (typename))
{
struct predicate *our_pred = insert_primary (entry, typename);
our_pred->args.str = typename;
/* This is an expensive operation, so although there are
* circumstances where it is selective, we ignore this fact
* because we probably don't want to promote this test to the
* front anyway.
*/
our_pred->est_success_rate = estimate_fstype_success_rate (typename);
return true;
}
else
{
/* This filesystem type is not listed in the mount table.
* Hence this predicate will always return false (with this argument).
* Substitute a predicate with the same effect as -false.
*/
if (options.debug_options & DebugTreeOpt)
{
fprintf (stderr,
"-fstype %s can never succeed, substituting -false\n",
typename);
}
return insert_false ();
}
}
else
{
return false;
}
}
static bool
parse_gid (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *p = insert_num (argv, arg_ptr, entry);
if (p)
{
p->est_success_rate = (p->args.numinfo.l_val < 100) ? 0.99 : 0.2;
return true;
}
return false;
}
static bool
parse_group (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *groupname;
const int saved_argc = *arg_ptr;
if (collect_arg (argv, arg_ptr, &groupname))
{
gid_t gid;
struct predicate *our_pred;
struct group *cur_gr = getgrnam (groupname);
endgrent ();
if (cur_gr)
{
gid = cur_gr->gr_gid;
}
else
{
const int gid_len = strspn (groupname, "0123456789");
if (gid_len)
{
if (groupname[gid_len] == 0)
{
gid = safe_atoi (groupname, options.err_quoting_style);
}
else
{
/* XXX: no test in test suite for this */
die (EXIT_FAILURE, 0,
_("%s is not the name of an existing group and"
" it does not look like a numeric group ID "
"because it has the unexpected suffix %s"),
quotearg_n_style (0, options.err_quoting_style, groupname),
quotearg_n_style (1, options.err_quoting_style, groupname+gid_len));
*arg_ptr = saved_argc; /* don't consume the invalid argument. */
return false;
}
}
else
{
if (*groupname)
{
/* XXX: no test in test suite for this */
die (EXIT_FAILURE, 0,
_("%s is not the name of an existing group"),
quotearg_n_style (0, options.err_quoting_style, groupname));
}
else
{
die (EXIT_FAILURE, 0,
_("argument to -group is empty, but should be a group name"));
}
*arg_ptr = saved_argc; /* don't consume the invalid argument. */
return false;
}
}
our_pred = insert_primary (entry, groupname);
our_pred->args.gid = gid;
our_pred->est_success_rate = (our_pred->args.numinfo.l_val < 100) ? 0.99 : 0.2;
return true;
}
return false;
}
static bool
parse_help (const struct parser_table* entry, char **argv, int *arg_ptr)
{
(void) entry;
(void) argv;
(void) arg_ptr;
usage (EXIT_SUCCESS);
}
static float
estimate_pattern_match_rate (const char *pattern, int is_regex)
{
if (strpbrk (pattern, "*?[") || (is_regex && strpbrk(pattern, ".")))
{
/* A wildcard; assume the pattern matches most files. */
return 0.8f;
}
else
{
return 0.1f;
}
}
static bool
parse_ilname (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *name;
if (collect_arg (argv, arg_ptr, &name))
{
struct predicate *our_pred = insert_primary (entry, name);
our_pred->args.str = name;
/* Use the generic glob pattern estimator to figure out how many
* links will match, but bear in mind that most files won't be links.
*/
our_pred->est_success_rate = 0.1f * estimate_pattern_match_rate (name, 0);
return true;
}
else
{
return false;
}
}
/* sanity check the fnmatch() function to make sure that case folding
* is supported (as opposed to just having the flag ignored).
*/
static bool
fnmatch_sanitycheck (void)
{
static bool checked = false;
if (!checked)
{
if (0 != fnmatch ("foo", "foo", 0)
|| 0 == fnmatch ("Foo", "foo", 0)
|| 0 != fnmatch ("Foo", "foo", FNM_CASEFOLD))
{
die (EXIT_FAILURE, 0,
_("sanity check of the fnmatch() library function failed."));
return false;
}
checked = true;
}
return checked;
}
static void
check_name_arg (const char *pred, const char *alt, const char *arg)
{
if (should_issue_warnings () && strchr (arg, '/') && (0 != strcmp ("/", arg)))
{
error (0, 0,
_("warning: %s matches against basenames only, "
"but the given pattern contains a directory separator (%s), "
"thus the expression will evaluate to false all the time. "
"Did you mean %s?"),
safely_quote_err_filename (0, pred),
safely_quote_err_filename (1, "/"),
safely_quote_err_filename (2, alt));
}
}
static bool
parse_iname (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *name;
fnmatch_sanitycheck ();
if (collect_arg (argv, arg_ptr, &name))
{
struct predicate *our_pred;
check_name_arg ("-iname", "-iwholename", name);
our_pred = insert_primary (entry, name);
our_pred->need_stat = our_pred->need_type = false;
our_pred->args.str = name;
our_pred->est_success_rate = estimate_pattern_match_rate (name, 0);
return true;
}
return false;
}
static bool
parse_inum (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *p = insert_num (argv, arg_ptr, entry);
if (p)
{
/* inode number is exact match only, so very low proportions of
* files match
*/
p->est_success_rate = 1e-6;
p->need_inum = true;
p->need_stat = false;
p->need_type = false;
return true;
}
return false;
}
static bool
parse_iregex (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_regex (argv, arg_ptr, entry, RE_ICASE|options.regex_options);
}
static bool
parse_links (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *p = insert_num (argv, arg_ptr, entry);
if (p)
{
if (p->args.numinfo.l_val == 1)
p->est_success_rate = 0.99;
else if (p->args.numinfo.l_val == 2)
p->est_success_rate = 0.01;
else
p->est_success_rate = 1e-3;
return true;
}
return false;
}
static bool
parse_lname (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *name;
fnmatch_sanitycheck ();
if (collect_arg (argv, arg_ptr, &name))
{
struct predicate *our_pred = insert_primary (entry, name);
our_pred->args.str = name;
our_pred->est_success_rate = 0.1f * estimate_pattern_match_rate (name, 0);
return true;
}
return false;
}
static bool
parse_ls (const struct parser_table* entry, char **argv, int *arg_ptr)
{
(void) &argv;
(void) &arg_ptr;
return insert_fls (entry, NULL);
}
static bool
insert_depthspec (const struct parser_table* entry, char **argv, int *arg_ptr,
int *limitptr)
{
const char *depthstr;
int depth_len;
const char *predicate = argv[(*arg_ptr)-1];
if (collect_arg (argv, arg_ptr, &depthstr))
{
depth_len = strspn (depthstr, "0123456789");
if ((depth_len > 0) && (depthstr[depth_len] == 0))
{
(*limitptr) = safe_atoi (depthstr, options.err_quoting_style);
if (*limitptr >= 0)
{
return parse_noop (entry, argv, arg_ptr);
}
}
die (EXIT_FAILURE, 0,
_("Expected a positive decimal integer argument to %s, but got %s"),
predicate,
quotearg_n_style (0, options.err_quoting_style, depthstr));
/* NOTREACHED */
return false;
}
/* missing argument */
return false;
}
static bool
parse_maxdepth (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_depthspec (entry, argv, arg_ptr, &options.maxdepth);
}
static bool
parse_mindepth (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_depthspec (entry, argv, arg_ptr, &options.mindepth);
}
static bool
do_parse_xmin (const struct parser_table* entry,
char **argv,
int *arg_ptr,
enum xval xv)
{
const char *minutes;
const int saved_argc = *arg_ptr;
if (collect_arg (argv, arg_ptr, &minutes))
{
struct time_val tval;
struct timespec origin = options.cur_day_start;
tval.xval = xv;
origin.tv_sec += DAYSECS;
if (get_relative_timestamp (minutes, &tval, origin, 60,
"arithmetic overflow while converting %s "
"minutes to a number of seconds"))
{
struct predicate *our_pred = insert_primary (entry, minutes);
our_pred->args.reftime = tval;
our_pred->est_success_rate = estimate_timestamp_success_rate (tval.ts.tv_sec);
return true;
}
else
{
/* Don't consume the invalid argument. */
*arg_ptr = saved_argc;
}
}
return false;
}
static bool
parse_amin (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return do_parse_xmin (entry, argv, arg_ptr, XVAL_ATIME);
}
static bool
parse_cmin (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return do_parse_xmin (entry, argv, arg_ptr, XVAL_CTIME);
}
static bool
parse_mmin (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return do_parse_xmin (entry, argv, arg_ptr, XVAL_MTIME);
}
static bool
parse_name (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *name;
fnmatch_sanitycheck ();
if (collect_arg (argv, arg_ptr, &name))
{
struct predicate *our_pred;
check_name_arg ("-name", "-wholename", name);
our_pred = insert_primary (entry, name);
our_pred->need_stat = our_pred->need_type = false;
our_pred->args.str = name;
our_pred->est_success_rate = estimate_pattern_match_rate (name, 0);
return true;
}
return false;
}
static bool
parse_negate (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) &argv;
(void) &arg_ptr;
our_pred = get_new_pred_chk_op (entry, NULL);
our_pred->pred_func = pred_negate;
our_pred->p_type = UNI_OP;
our_pred->p_prec = NEGATE_PREC;
our_pred->need_stat = our_pred->need_type = false;
return true;
}
static bool
parse_newer (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
struct stat stat_newer;
const char *arg;
set_stat_placeholders (&stat_newer);
if (collect_arg_stat_info (argv, arg_ptr, &stat_newer, &arg))
{
our_pred = insert_primary (entry, arg);
our_pred->args.reftime.ts = get_stat_mtime (&stat_newer);
our_pred->args.reftime.xval = XVAL_MTIME;
our_pred->args.reftime.kind = COMP_GT;
our_pred->est_success_rate = estimate_timestamp_success_rate (stat_newer.st_mtime);
return true;
}
return false;
}
static bool
parse_newerXY (const struct parser_table* entry, char **argv, int *arg_ptr)
{
(void) argv;
(void) arg_ptr;
if ((argv == NULL) || (argv[*arg_ptr] == NULL))
{
return false;
}
else if (8u != strlen (argv[*arg_ptr]))
{
return false;
}
else
{
char x, y;
const char validchars[] = "aBcmt";
assert (0 == strncmp ("-newer", argv[*arg_ptr], 6));
x = argv[*arg_ptr][6];
y = argv[*arg_ptr][7];
#if !defined HAVE_STRUCT_STAT_ST_BIRTHTIME && !defined HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC && !defined HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC && !defined HAVE_STRUCT_STAT_ST_BIRTHTIM_TV_NSEC
if ('B' == x || 'B' == y)
{
error (0, 0,
_("This system does not provide a way to find the birth time of a file."));
return false;
}
#endif
/* -newertY (for any Y) is invalid. */
if (x == 't'
|| (NULL == strchr (validchars, x))
|| (NULL == strchr ( validchars, y)))
{
return false;
}
else
{
struct predicate *our_pred;
/* Because this item is ARG_SPECIAL_PARSE, we have to advance arg_ptr
* past the test name (for most other tests, this is already done)
*/
if (argv[1+*arg_ptr] == NULL)
{
die (EXIT_FAILURE, 0, _("The %s test needs an argument"),
quotearg_n_style (0, options.err_quoting_style, argv[*arg_ptr]));
}
else
{
(*arg_ptr)++;
}
our_pred = insert_primary (entry, argv[*arg_ptr]);
switch (x)
{
case 'a':
our_pred->args.reftime.xval = XVAL_ATIME;
break;
case 'B':
our_pred->args.reftime.xval = XVAL_BIRTHTIME;
break;
case 'c':
our_pred->args.reftime.xval = XVAL_CTIME;
break;
case 'm':
our_pred->args.reftime.xval = XVAL_MTIME;
break;
default:
assert (strchr (validchars, x));
assert (0);
}
if ('t' == y)
{
if (!parse_datetime (&our_pred->args.reftime.ts,
argv[*arg_ptr],
&options.start_time))
{
die (EXIT_FAILURE, 0,
_("I cannot figure out how to interpret %s as a date or time"),
quotearg_n_style (0, options.err_quoting_style, argv[*arg_ptr]));
}
}
else
{
struct stat stat_newer;
/* Stat the named file. */
set_stat_placeholders (&stat_newer);
if ((*options.xstat) (argv[*arg_ptr], &stat_newer))
fatal_target_file_error (errno, argv[*arg_ptr]);
if (!get_stat_Ytime (&stat_newer, y, &our_pred->args.reftime.ts))
{
/* We cannot extract a timestamp from the struct stat. */
die (EXIT_FAILURE, 0,
_("Cannot obtain birth time of file %s"),
safely_quote_err_filename (0, argv[*arg_ptr]));
}
}
our_pred->args.reftime.kind = COMP_GT;
our_pred->est_success_rate = estimate_timestamp_success_rate (our_pred->args.reftime.ts.tv_sec);
(*arg_ptr)++;
assert (our_pred->pred_func != NULL);
assert (our_pred->pred_func == pred_newerXY);
assert (our_pred->need_stat);
return true;
}
}
}
static bool
parse_noleaf (const struct parser_table* entry, char **argv, int *arg_ptr)
{
options.no_leaf_check = true;
return parse_noop (entry, argv, arg_ptr);
}
static bool
parse_nogroup (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) &argv;
(void) &arg_ptr;
our_pred = insert_primary (entry, NULL);
our_pred->est_success_rate = 1e-4;
return true;
}
static bool
parse_nouser (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = insert_primary_noarg (entry);
our_pred->est_success_rate = 1e-3;
return true;
}
static bool
parse_nowarn (const struct parser_table* entry, char **argv, int *arg_ptr)
{
options.warnings = false;
return parse_noop (entry, argv, arg_ptr);
}
static bool
parse_ok (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_exec_ok ("-ok", entry, argv, arg_ptr);
}
static bool
parse_okdir (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_exec_ok ("-okdir", entry, argv, arg_ptr);
}
bool
parse_openparen (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = get_new_pred_chk_op (entry, NULL);
our_pred->pred_func = pred_openparen;
our_pred->p_type = OPEN_PAREN;
our_pred->p_prec = NO_PREC;
our_pred->need_stat = our_pred->need_type = false;
return true;
}
static bool
parse_or (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = get_new_pred_noarg (entry);
our_pred->pred_func = pred_or;
our_pred->p_type = BI_OP;
our_pred->p_prec = OR_PREC;
our_pred->need_stat = our_pred->need_type = false;
return true;
}
static bool
is_feasible_path_argument (const char *arg, bool foldcase)
{
const char *last = strrchr (arg, '/');
if (last && !last[1])
{
/* The name ends with "/". */
if (matches_start_point (arg, foldcase))
{
/* "-path foo/" can succeed if one of the start points is "foo/". */
return true;
}
else
{
return false;
}
}
return true;
}
static bool
insert_path_check (const struct parser_table* entry, char **argv, int *arg_ptr,
const char *predicate_name, PREDICATEFUNCTION pred)
{
const char *name;
bool foldcase = false;
if (pred == pred_ipath)
foldcase = true;
fnmatch_sanitycheck ();
if (collect_arg (argv, arg_ptr, &name))
{
struct predicate *our_pred = insert_primary_withpred (entry, pred, name);
our_pred->need_stat = our_pred->need_type = false;
our_pred->args.str = name;
our_pred->est_success_rate = estimate_pattern_match_rate (name, 0);
if (!options.posixly_correct
&& !is_feasible_path_argument (name, foldcase))
{
error (0, 0, _("warning: -%s %s will not match anything "
"because it ends with /."),
predicate_name, name);
our_pred->est_success_rate = 1.0e-8;
}
return true;
}
return false;
}
/* For some time, -path was deprecated (at RMS's request) in favour of
* -iwholename. See the node "GNU Manuals" in standards.texi for the
* rationale for this (basically, GNU prefers the use of the phrase
* "file name" to "path name".
*
* We do not issue a warning that this usage is deprecated
* since it is standardized since POSIX 2008.
*/
static bool
parse_path (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_path_check (entry, argv, arg_ptr, "path", pred_path);
}
static bool
parse_wholename (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_path_check (entry, argv, arg_ptr, "wholename", pred_path);
}
/* -ipath was deprecated (at RMS's request) in favour of
* -iwholename. See the node "GNU Manuals" in standards.texi
* for the rationale for this (basically, GNU prefers the use
* of the phrase "file name" to "path name".
* However, -path is now standardised so I un-deprecated -ipath.
*/
static bool
parse_ipath (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_path_check (entry, argv, arg_ptr, "ipath", pred_ipath);
}
static bool
parse_iwholename (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_path_check (entry, argv, arg_ptr, "iwholename", pred_ipath);
}
static bool
parse_perm (const struct parser_table* entry, char **argv, int *arg_ptr)
{
mode_t perm_val[2];
float rate;
int mode_start = 0;
enum permissions_type kind = PERM_EXACT;
struct mode_change *change;
struct predicate *our_pred;
const char *perm_expr;
if (!collect_arg (argv, arg_ptr, &perm_expr))
return false;
switch (perm_expr[0])
{
case '-':
mode_start = 1;
kind = PERM_AT_LEAST;
rate = 0.2;
break;
case '/': /* GNU extension */
mode_start = 1;
kind = PERM_ANY;
rate = 0.3;
break;
default:
/* For example, '-perm 0644', which is valid and matches
* only files whose mode is exactly 0644.
*/
mode_start = 0;
kind = PERM_EXACT;
rate = 0.01;
break;
}
change = mode_compile (perm_expr + mode_start);
/* Reject invalid modes, or modes of the form +NUMERICMODE.
The latter were formerly accepted as a GNU extension, but that
extension was incompatible with how GNU 'chmod' treats these modes now,
and it would be confusing if 'find' continued to support it. */
if (NULL == change
|| (perm_expr[0] == '+' && '0' <= perm_expr[1] && perm_expr[1] < '8'))
die (EXIT_FAILURE, 0, _("invalid mode %s"),
quotearg_n_style (0, options.err_quoting_style, perm_expr));
perm_val[0] = mode_adjust (0, false, 0, change, NULL);
perm_val[1] = mode_adjust (0, true, 0, change, NULL);
free (change);
if (('/' == perm_expr[0]) && (0 == perm_val[0]) && (0 == perm_val[1]))
{
/* The meaning of -perm /000 will change in the future. It
* currently matches no files, but like -perm -000 it should
* match all files.
*
* Starting in 2005, we used to issue a warning message
* informing the user that the behaviour would change in the
* future. We have now changed the behaviour and issue a
* warning message that the behaviour recently changed.
*/
error (0, 0,
_("warning: you have specified a mode pattern %s (which is "
"equivalent to /000). The meaning of -perm /000 has now been "
"changed to be consistent with -perm -000; that is, while it "
"used to match no files, it now matches all files."),
perm_expr);
kind = PERM_AT_LEAST;
/* The "magic" number below is just the fraction of files on my
* own system that "-type l -xtype l" fails for (i.e. unbroken symlinks).
* Actual totals are 1472 and 1073833.
*/
rate = 0.9986; /* probably matches anything but a broken symlink */
}
our_pred = insert_primary (entry, perm_expr);
our_pred->est_success_rate = rate;
our_pred->args.perm.kind = kind;
memcpy (our_pred->args.perm.val, perm_val, sizeof perm_val);
return true;
}
bool
parse_print (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = insert_primary_noarg (entry);
/* -print has the side effect of printing. This prevents us
from doing undesired multiple printing when the user has
already specified -print. */
our_pred->side_effects = our_pred->no_default_print = true;
our_pred->need_stat = our_pred->need_type = false;
open_stdout (&our_pred->args.printf_vec);
return true;
}
static bool
parse_print0 (const struct parser_table* entry, char **argv, int *arg_ptr)
{
(void) entry;
(void) argv;
(void) arg_ptr;
return insert_fprint (entry, NULL);
}
static bool
parse_printf (const struct parser_table* entry, char **argv, int *arg_ptr)
{
char *format;
const int saved_argc = *arg_ptr;
if (collect_arg_nonconst (argv, arg_ptr, &format))
{
struct format_val fmt;
open_stdout (&fmt);
if (insert_fprintf (&fmt, entry, format))
{
return true;
}
else
{
*arg_ptr = saved_argc; /* don't consume the invalid argument. */
return false;
}
}
return false;
}
static bool
parse_fprintf (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *filename;
char *format;
int saved_argc = *arg_ptr;
if (collect_arg (argv, arg_ptr, &filename))
{
if (collect_arg_nonconst (argv, arg_ptr, &format))
{
struct format_val fmt;
open_output_file (filename, &fmt);
saved_argc = *arg_ptr;
if (insert_fprintf (&fmt, entry, format))
return true;
}
}
*arg_ptr = saved_argc; /* don't consume the invalid argument. */
return false;
}
static bool
parse_prune (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = insert_primary_noarg (entry);
if (options.do_dir_first == false)
our_pred->need_stat = our_pred->need_type = false;
/* -prune has a side effect that it does not descend into
the current directory. */
our_pred->side_effects = true;
our_pred->no_default_print = false;
return true;
}
static bool
parse_quit (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred = insert_primary_noarg (entry);
(void) argv;
(void) arg_ptr;
our_pred->need_stat = our_pred->need_type = false;
our_pred->side_effects = true; /* Exiting is a side effect... */
our_pred->no_default_print = false; /* Don't inhibit the default print, though. */
our_pred->est_success_rate = 1.0f;
return true;
}
static bool
parse_regextype (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *type_name;
if (collect_arg (argv, arg_ptr, &type_name))
{
/* collect the regex type name */
options.regex_options = get_regex_type (type_name);
return parse_noop (entry, argv, arg_ptr);
}
return false;
}
static bool
parse_regex (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_regex (argv, arg_ptr, entry, options.regex_options);
}
static bool
insert_regex (char **argv,
int *arg_ptr,
const struct parser_table *entry,
int regex_options)
{
const char *rx;
if (collect_arg (argv, arg_ptr, &rx))
{
struct re_pattern_buffer *re;
const char *error_message;
struct predicate *our_pred = insert_primary_withpred (entry, pred_regex, rx);
our_pred->need_stat = our_pred->need_type = false;
re = xmalloc (sizeof (struct re_pattern_buffer));
our_pred->args.regex = re;
re->allocated = 100;
re->buffer = xmalloc (re->allocated);
re->fastmap = NULL;
re_set_syntax (regex_options);
re->syntax = regex_options;
re->translate = NULL;
error_message = re_compile_pattern (rx, strlen (rx), re);
if (error_message)
die (EXIT_FAILURE, 0,
_("failed to compile regular expression '%s': %s"),
rx, error_message);
our_pred->est_success_rate = estimate_pattern_match_rate (rx, 1);
return true;
}
return false;
}
static bool
parse_size (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
char *arg;
uintmax_t num;
char suffix;
enum comparison_type c_type;
int blksize = 512;
int len;
/* XXX: cannot (yet) convert to ue collect_arg() as this
* function modifies the args in-place.
*/
if ((argv == NULL) || (argv[*arg_ptr] == NULL))
return false;
arg = argv[*arg_ptr];
len = strlen (arg);
if (len == 0)
die (EXIT_FAILURE, 0, _("invalid null argument to -size"));
suffix = arg[len - 1];
switch (suffix)
{
case 'b':
blksize = 512;
arg[len - 1] = '\0';
break;
case 'c':
blksize = 1;
arg[len - 1] = '\0';
break;
case 'k':
blksize = 1024;
arg[len - 1] = '\0';
break;
case 'M': /* Mebibytes */
blksize = 1024*1024;
arg[len - 1] = '\0';
break;
case 'G': /* Gibibytes */
blksize = 1024*1024*1024;
arg[len - 1] = '\0';
break;
case 'w':
blksize = 2;
arg[len - 1] = '\0';
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
suffix = 0;
break;
default:
die (EXIT_FAILURE, 0,
_("invalid -size type `%c'"), argv[*arg_ptr][len - 1]);
}
/* TODO: accept fractional mebibytes etc. ? */
if (!get_num (arg, &num, &c_type))
{
char tail[2];
tail[0] = suffix;
tail[1] = 0;
die (EXIT_FAILURE, 0,
_("Invalid argument `%s%s' to -size"),
arg, tail);
return false;
}
our_pred = insert_primary (entry, arg);
our_pred->args.size.kind = c_type;
our_pred->args.size.blocksize = blksize;
our_pred->args.size.size = num;
our_pred->need_stat = true;
our_pred->need_type = false;
if (COMP_GT == c_type)
our_pred->est_success_rate = (num*blksize > 20480) ? 0.1 : 0.9;
else if (COMP_LT == c_type)
our_pred->est_success_rate = (num*blksize > 20480) ? 0.9 : 0.1;
else
our_pred->est_success_rate = 0.01;
(*arg_ptr)++;
return true;
}
static bool
parse_samefile (const struct parser_table* entry, char **argv, int *arg_ptr)
{
/* General idea: stat the file, remember device and inode numbers.
* If a candidate file matches those, it's the same file.
*/
struct predicate *our_pred;
struct stat st, fst;
int fd, openflags;
const char *filename;
set_stat_placeholders (&st);
if (!collect_arg_stat_info (argv, arg_ptr, &st, &filename))
return false;
set_stat_placeholders (&fst);
/* POSIX systems are free to re-use the inode number of a deleted
* file. To ensure that we are not fooled by inode reuse, we hold
* the file open if we can. This would prevent the system reusing
* the file.
*/
fd = -3; /* -3 means uninitialized */
openflags = O_RDONLY;
if (options.symlink_handling == SYMLINK_NEVER_DEREF)
{
if (options.open_nofollow_available)
{
assert (O_NOFOLLOW != 0);
openflags |= O_NOFOLLOW;
fd = -1; /* safe to open it. */
}
else
{
if (S_ISLNK(st.st_mode))
{
/* no way to ensure that a symlink will not be followed
* by open(2), so fall back on using lstat(). Accept
* the risk that the named file will be deleted and
* replaced with another having the same inode.
*
* Avoid opening the file.
*/
fd = -2; /* Do not open it */
}
else
{
fd = -1;
/* Race condition here: the file might become a symlink here. */
}
}
}
else
{
/* We want to dereference the symlink anyway */
fd = -1; /* safe to open it without O_NOFOLLOW */
}
assert (fd != -3); /* check we made a decision */
if (fd == -1)
{
/* Race condition here. The file might become a
* symbolic link in between our call to stat and
* the call to open_cloexec.
*/
fd = open_cloexec (filename, openflags);
if (fd >= 0)
{
/* We stat the file again here to prevent a race condition
* between the first stat and the call to open(2).
*/
if (0 != fstat (fd, &fst))
{
fatal_target_file_error (errno, filename);
}
else
{
/* Worry about the race condition. If the file became a
* symlink after our first stat and before our call to
* open, fst may contain the stat information for the
* destination of the link, not the link itself.
*/
if ((*options.xstat) (filename, &st))
fatal_target_file_error (errno, filename);
if ((options.symlink_handling == SYMLINK_NEVER_DEREF)
&& (!options.open_nofollow_available))
{
if (S_ISLNK(st.st_mode))
{
/* We lost the race. Leave the data in st. The
* file descriptor points to the wrong thing.
*/
close (fd);
fd = -1;
}
else
{
/* Several possibilities here:
* 1. There was no race
* 2. The file changed into a symlink after the stat and
* before the open, and then back into a non-symlink
* before the second stat.
*
* In case (1) there is no problem. In case (2),
* the stat() and fstat() calls will have returned
* different data. O_NOFOLLOW was not available,
* so the open() call may have followed a symlink
* even if the -P option is in effect.
*/
if ((st.st_dev == fst.st_dev)
&& (st.st_ino == fst.st_ino))
{
/* No race. No need to copy fst to st,
* since they should be identical (modulo
* differences in padding bytes).
*/
}
else
{
/* We lost the race. Leave the data in st. The
* file descriptor points to the wrong thing.
*/
close (fd);
fd = -1;
}
}
}
else
{
st = fst;
}
}
}
}
our_pred = insert_primary (entry, filename);
our_pred->args.samefileid.ino = st.st_ino;
our_pred->args.samefileid.dev = st.st_dev;
our_pred->args.samefileid.fd = fd;
our_pred->need_type = false;
/* smarter way: compare type and inode number first. */
/* TODO: maybe optimise this away by being optimistic */
our_pred->need_stat = true;
our_pred->est_success_rate = 0.01f;
return true;
}
#if 0
/* This function is commented out partly because support for it is
* uneven.
*/
static bool
parse_show_control_chars (const struct parser_table* entry,
char **argv,
int *arg_ptr)
{
const char *arg;
const char *errmsg = _("The -show-control-chars option takes "
"a single argument which "
"must be 'literal' or 'safe'");
if ((argv == NULL) || (argv[*arg_ptr] == NULL))
{
die (EXIT_FAILURE, errno, "%s", errmsg);
return false;
}
else
{
arg = argv[*arg_ptr];
if (0 == strcmp ("literal", arg))
{
options.literal_control_chars = true;
}
else if (0 == strcmp ("safe", arg))
{
options.literal_control_chars = false;
}
else
{
die (EXIT_FAILURE, errno, "%s", errmsg);
return false;
}
(*arg_ptr)++; /* consume the argument. */
return true;
}
}
#endif
static bool
parse_true (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = insert_primary_noarg (entry);
our_pred->need_stat = our_pred->need_type = false;
our_pred->est_success_rate = 1.0f;
return true;
}
static bool
parse_noop (const struct parser_table* entry, char **argv, int *arg_ptr)
{
(void) entry;
return parse_true (get_noop (), argv, arg_ptr);
}
static bool
parse_accesscheck (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
(void) argv;
(void) arg_ptr;
our_pred = insert_primary_noarg (entry);
our_pred->need_stat = our_pred->need_type = false;
our_pred->side_effects = our_pred->no_default_print = false;
if (pred_is(our_pred, pred_executable))
our_pred->est_success_rate = 0.2;
else
our_pred->est_success_rate = 0.9;
return true;
}
static bool
parse_type (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_type (argv, arg_ptr, entry, pred_type);
}
static bool
parse_uid (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *p = insert_num (argv, arg_ptr, entry);
if (p)
{
p->est_success_rate = (p->args.numinfo.l_val < 100) ? 0.99 : 0.2;
return true;
}
return false;
}
static bool
parse_used (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
struct time_val tval;
const char *offset_str;
const char *errmsg = "arithmetic overflow while converting %s days to a number of seconds";
if (collect_arg (argv, arg_ptr, &offset_str))
{
/* The timespec is actually a delta value, so we use an origin of 0. */
struct timespec zero = {0,0};
if (get_relative_timestamp (offset_str, &tval, zero, DAYSECS, errmsg))
{
our_pred = insert_primary (entry, offset_str);
our_pred->args.reftime = tval;
our_pred->est_success_rate = estimate_file_age_success_rate (tval.ts.tv_sec / DAYSECS);
return true;
}
else
{
die (EXIT_FAILURE, 0,
_("Invalid argument %s to -used"), offset_str);
/*NOTREACHED*/
return false;
}
}
else
{
return false; /* missing argument */
}
}
static bool
parse_user (const struct parser_table* entry, char **argv, int *arg_ptr)
{
const char *username;
if (collect_arg (argv, arg_ptr, &username))
{
struct predicate *our_pred;
uid_t uid;
struct passwd *cur_pwd = getpwnam (username);
endpwent ();
if (cur_pwd != NULL)
{
uid = cur_pwd->pw_uid;
}
else
{
const size_t uid_len = strspn (username, "0123456789");
if (uid_len && (username[uid_len]==0))
{
uid = safe_atoi (username, options.err_quoting_style);
}
else
{
/* This is a fatal error (if we just return false, the caller
* will say "invalid argument `username' to -user", which is
* not as helpful). */
if (username[0])
{
die (EXIT_FAILURE, 0,
_("%s is not the name of a known user"),
quotearg_n_style (0, options.err_quoting_style,
username));
}
else
{
die (EXIT_FAILURE, 0,
_("The argument to -user should not be empty"));
}
/*NOTREACHED*/
return false;
}
}
our_pred = insert_primary (entry, username);
our_pred->args.uid = uid;
our_pred->est_success_rate = (our_pred->args.uid < 100) ? 0.99 : 0.2;
return true;
}
return false;
}
static bool
parse_version (const struct parser_table* entry, char **argv, int *arg_ptr)
{
bool has_features = false;
int flags;
(void) argv;
(void) arg_ptr;
(void) entry;
display_findutils_version ("find");
printf (_("Features enabled: "));
#if CACHE_IDS
printf ("CACHE_IDS(ignored) ");
has_features = true;
#endif
#if defined HAVE_STRUCT_DIRENT_D_TYPE
printf ("D_TYPE ");
has_features = true;
#endif
#if defined O_NOFOLLOW
printf ("O_NOFOLLOW(%s) ",
(options.open_nofollow_available ? "enabled" : "disabled"));
has_features = true;
#endif
#if defined LEAF_OPTIMISATION
printf ("LEAF_OPTIMISATION ");
has_features = true;
#endif
if (0 < is_selinux_enabled ())
{
printf ("SELINUX ");
has_features = true;
}
flags = 0;
if (is_fts_enabled (&flags))
{
printf ("FTS(");
has_features = true;
if (flags & FTS_CWDFD)
printf ("FTS_CWDFD");
printf (") ");
}
printf ("CBO(level=%d) ", (int)(options.optimisation_level));
has_features = true;
if (!has_features)
{
/* For the moment, leave this as English in case someone wants
to parse these strings. */
printf ("none");
}
printf ("\n");
exit (EXIT_SUCCESS);
}
static bool
parse_context (const struct parser_table* entry, char **argv, int *arg_ptr)
{
struct predicate *our_pred;
if ((argv == NULL) || (argv[*arg_ptr] == NULL))
return false;
if (is_selinux_enabled () <= 0)
{
die (EXIT_FAILURE, 0,
_("invalid predicate -context: SELinux is not enabled."));
return false;
}
our_pred = insert_primary (entry, NULL);
our_pred->est_success_rate = 0.01f;
our_pred->need_stat = false;
#ifdef DEBUG
our_pred->p_name = find_pred_name (pred_context);
#endif /*DEBUG*/
our_pred->args.scontext = argv[*arg_ptr];
(*arg_ptr)++;
return true;
}
static bool
parse_xdev (const struct parser_table* entry, char **argv, int *arg_ptr)
{
options.stay_on_filesystem = true;
return parse_noop (entry, argv, arg_ptr);
}
static bool
parse_ignore_race (const struct parser_table* entry, char **argv, int *arg_ptr)
{
options.ignore_readdir_race = true;
return parse_noop (entry, argv, arg_ptr);
}
static bool
parse_noignore_race (const struct parser_table* entry, char **argv, int *arg_ptr)
{
options.ignore_readdir_race = false;
return parse_noop (entry, argv, arg_ptr);
}
static bool
parse_warn (const struct parser_table* entry, char **argv, int *arg_ptr)
{
options.warnings = true;
return parse_noop (entry, argv, arg_ptr);
}
static bool
parse_xtype (const struct parser_table* entry, char **argv, int *arg_ptr)
{
return insert_type (argv, arg_ptr, entry, pred_xtype);
}
static bool
insert_type (char **argv, int *arg_ptr,
const struct parser_table *entry,
PRED_FUNC which_pred)
{
struct predicate *our_pred;
const char *typeletter;
const char *pred_string = which_pred == pred_xtype ? "-xtype" : "-type";
if (! collect_arg (argv, arg_ptr, &typeletter))
return false;
if (!*typeletter)
{
die (EXIT_FAILURE, 0,
_("Arguments to %s should contain at least one letter"),
pred_string);
/*NOTREACHED*/
return false;
}
our_pred = insert_primary_withpred (entry, which_pred, typeletter);
our_pred->est_success_rate = 0.0;
/* Figure out if we will need to stat the file, because if we don't
* need to follow symlinks, we can avoid a stat call by using
* struct dirent.d_type.
*/
if (which_pred == pred_xtype)
{
our_pred->need_stat = true;
our_pred->need_type = false;
}
else
{
our_pred->need_stat = false; /* struct dirent is enough */
our_pred->need_type = true;
}
/* From a real system here are the counts of files by type:
Type Count Fraction
f 4410884 0.875
d 464722 0.0922
l 156662 0.0311
b 4476 0.000888
c 2233 0.000443
s 80 1.59e-05
p 38 7.54e-06
*/
for (; *typeletter; )
{
unsigned int type_cell;
float rate = 0.01;
switch (*typeletter)
{
case 'b': /* block special */
type_cell = FTYPE_BLK;
rate = 0.000888f;
break;
case 'c': /* character special */
type_cell = FTYPE_CHR;
rate = 0.000443f;
break;
case 'd': /* directory */
type_cell = FTYPE_DIR;
rate = 0.0922f;
break;
case 'f': /* regular file */
type_cell = FTYPE_REG;
rate = 0.875f;
break;
case 'l': /* symbolic link */
#ifdef S_IFLNK
type_cell = FTYPE_LNK;
rate = 0.0311f;
#else
type_cell = 0;
die (EXIT_FAILURE, 0,
_("%s %c is not supported because symbolic links "
"are not supported on the platform find was compiled on."),
pred_string, (*typeletter));
#endif
break;
case 'p': /* pipe */
#ifdef S_IFIFO
type_cell = FTYPE_FIFO;
rate = 7.554e-6f;
#else
type_cell = 0;
die (EXIT_FAILURE, 0,
_("%s %c is not supported because FIFOs "
"are not supported on the platform find was compiled on."),
pred_string, (*typeletter));
#endif
break;
case 's': /* socket */
#ifdef S_IFSOCK
type_cell = FTYPE_SOCK;
rate = 1.59e-5f;
#else
type_cell = 0;
die (EXIT_FAILURE, 0,
_("%s %c is not supported because named sockets "
"are not supported on the platform find was compiled on."),
pred_string, (*typeletter));
#endif
break;
case 'D': /* Solaris door */
#ifdef S_IFDOOR
type_cell = FTYPE_DOOR;
/* There are no Solaris doors on the example system surveyed
* above, but if someone uses -type D, they are presumably
* expecting to find a non-zero number. We guess at a
* rate. */
rate = 1.0e-5f;
#else
type_cell = 0;
die (EXIT_FAILURE, 0,
_("%s %c is not supported because Solaris doors "
"are not supported on the platform find was compiled on."),
pred_string, (*typeletter));
#endif
break;
default: /* None of the above ... nuke 'em. */
type_cell = 0;
die (EXIT_FAILURE, 0,
_("Unknown argument to %s: %c"), pred_string, (*typeletter));
/*NOTREACHED*/
return false;
}
if (our_pred->args.types[type_cell])
{
die (EXIT_FAILURE, 0,
_("Duplicate file type '%c' in the argument list to %s."),
(*typeletter), pred_string);
}
our_pred->est_success_rate += rate;
our_pred->args.types[type_cell] = true;
/* Advance.
* Currently, only 1-character file types separated by ',' are supported.
*/
typeletter++;
if (*typeletter)
{
if (*typeletter != ',')
{
die (EXIT_FAILURE, 0,
_("Must separate multiple arguments to %s using: ','"),
pred_string);
/*NOTREACHED*/
return false;
}
typeletter++;
if (!*typeletter)
{
die (EXIT_FAILURE, 0,
_("Last file type in list argument to %s "
"is missing, i.e., list is ending on: ','"),
pred_string);
/*NOTREACHED*/
return false;
}
}
}
return true;
}
/* Return true if the file accessed via FP is a terminal.
*/
static bool
stream_is_tty (FILE *fp)
{
int fd = fileno (fp);
if (-1 == fd)
{
return false; /* not a valid stream */
}
else
{
return isatty (fd) ? true : false;
}
}
static void
check_path_safety (const char *action)
{
const char *path = getenv ("PATH");
const char *path_separators = ":";
size_t pos, len;
if (NULL == path)
{
/* $PATH is not set. Assume the OS default is safe.
* That may not be true on Windows, but I'm not aware
* of a way to get Windows to avoid searching the
* current directory anyway.
*/
return;
}
splitstring (path, path_separators, true, &pos, &len);
do
{
if (0 == len || (1 == len && path[pos] == '.'))
{
/* empty field signifies . */
die (EXIT_FAILURE, 0,
_("The current directory is included in the PATH "
"environment variable, which is insecure in "
"combination with the %s action of find. "
"Please remove the current directory from your "
"$PATH (that is, remove \".\", doubled colons, "
"or leading or trailing colons)"),
action);
}
else if (path[pos] != '/')
{
char *relpath = strndup (&path[pos], len);
die (EXIT_FAILURE, 0,
_("The relative path %s is included in the PATH "
"environment variable, which is insecure in "
"combination with the %s action of find. "
"Please remove that entry from $PATH"),
safely_quote_err_filename (0, relpath ? relpath : &path[pos]),
action);
/*NOTREACHED*/
free (relpath);
}
} while (splitstring (path, path_separators, false, &pos, &len));
}
/* handles both exec and ok predicate */
static bool
insert_exec_ok (const char *action,
const struct parser_table *entry,
char **argv,
int *arg_ptr)
{
int start, end; /* Indexes in ARGV of start & end of cmd. */
int i; /* Index into cmd args */
int saw_braces; /* True if previous arg was '{}'. */
bool allow_plus; /* True if + is a valid terminator */
int brace_count; /* Number of instances of {}. */
const char *brace_arg; /* Which arg did {} appear in? */
PRED_FUNC func = entry->pred_func;
enum BC_INIT_STATUS bcstatus;
struct predicate *our_pred;
struct exec_val *execp; /* Pointer for efficiency. */
if ((argv == NULL) || (argv[*arg_ptr] == NULL))
return false;
our_pred = insert_primary_withpred (entry, func, "(some -exec* arguments)");
our_pred->side_effects = our_pred->no_default_print = true;
our_pred->need_type = our_pred->need_stat = false;
execp = &our_pred->args.exec_vec;
execp->wd_for_exec = NULL;
if ((func != pred_okdir) && (func != pred_ok))
{
allow_plus = true;
execp->close_stdin = false;
}
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.
*/
execp->close_stdin = true;
}
if ((func == pred_execdir) || (func == pred_okdir))
{
execp->wd_for_exec = NULL;
options.ignore_readdir_race = false;
check_path_safety (action);
}
else
{
assert (NULL != initial_wd);
execp->wd_for_exec = initial_wd;
}
our_pred->args.exec_vec.multiple = 0;
/* Count the number of args with path replacements, up until the ';'.
* Also figure out if the command is terminated by ";" or by "+".
*/
start = *arg_ptr;
for (end = start, saw_braces=0, brace_count=0, brace_arg=NULL;
(argv[end] != NULL)
&& ((argv[end][0] != ';') || (argv[end][1] != '\0'));
end++)
{
/* For -exec and -execdir, "{} +" can terminate the command. */
if ( allow_plus
&& argv[end][0] == '+' && argv[end][1] == 0
&& saw_braces)
{
our_pred->args.exec_vec.multiple = 1;
break;
}
saw_braces = 0;
if (mbsstr (argv[end], "{}"))
{
saw_braces = 1;
brace_arg = argv[end];
++brace_count;
if (0 == end && (func == pred_execdir || func == pred_okdir))
{
/* The POSIX standard says that {} replacement should
* occur even in the utility name. This is insecure
* since it means we will be executing a command whose
* name is chosen according to whatever find finds in
* the file system. That can be influenced by an
* attacker. Hence for -execdir and -okdir this is not
* allowed. We can specify this as those options are
* not defined by POSIX.
*/
die (EXIT_FAILURE, 0,
_("You may not use {} within the utility name for "
"-execdir and -okdir, because this is a potential "
"security problem."));
}
}
}
/* Fail if no command given or no semicolon found. */
if ((end == start) || (argv[end] == NULL))
{
*arg_ptr = end;
free (our_pred);
return false;
}
if (our_pred->args.exec_vec.multiple)
{
const char *suffix;
if (func == pred_execdir)
suffix = "dir";
else
suffix = "";
if (brace_count > 1)
{
die (EXIT_FAILURE, 0,
_("Only one instance of {} is supported with -exec%s ... +"),
suffix);
}
else if (strlen (brace_arg) != 2u)
{
enum { MsgBufSize = 19 };
char buf[MsgBufSize];
const size_t needed = snprintf (buf, MsgBufSize, "-exec%s ... {} +", suffix);
assert (needed <= MsgBufSize); /* If this assertion fails, correct the value of MsgBufSize. */
die (EXIT_FAILURE, 0,
_("In %s the %s must appear by itself, but you specified %s"),
quotearg_n_style (0, options.err_quoting_style, buf),
quotearg_n_style (1, options.err_quoting_style, "{}"),
quotearg_n_style (2, options.err_quoting_style, brace_arg));
}
}
/* We use a switch statement here so that the compiler warns us when
* we forget to handle a newly invented enum value.
*
* Like xargs, we allow 2KiB of headroom for the launched utility to
* export its own environment variables before calling something
* else.
*/
bcstatus = bc_init_controlinfo (&execp->ctl, 2048u);
switch (bcstatus)
{
case BC_INIT_ENV_TOO_BIG:
case BC_INIT_CANNOT_ACCOMODATE_HEADROOM:
die (EXIT_FAILURE, 0, _("The environment is too large for exec()."));
break;
case BC_INIT_OK:
/* Good news. Carry on. */
break;
}
bc_use_sensible_arg_max (&execp->ctl);
execp->ctl.exec_callback = launch;
if (our_pred->args.exec_vec.multiple)
{
/* "+" terminator, so we can just append our arguments after the
* command and initial arguments.
*/
execp->replace_vec = NULL;
execp->ctl.replace_pat = NULL;
execp->ctl.rplen = 0;
execp->ctl.lines_per_exec = 0; /* no limit */
execp->ctl.args_per_exec = 0; /* no limit */
/* remember how many arguments there are */
execp->ctl.initial_argc = (end-start) - 1;
/* execp->state = xmalloc(sizeof struct buildcmd_state); */
bc_init_state (&execp->ctl, &execp->state, execp);
/* Gather the initial arguments. Skip the {}. */
for (i=start; ictl, &execp->state,
argv[i], strlen (argv[i])+1,
NULL, 0,
1);
}
}
else
{
/* Semicolon terminator - more than one {} is supported, so we
* have to do brace-replacement.
*/
execp->num_args = end - start;
execp->ctl.replace_pat = "{}";
execp->ctl.rplen = strlen (execp->ctl.replace_pat);
execp->ctl.lines_per_exec = 0; /* no limit */
execp->ctl.args_per_exec = 0; /* no limit */
execp->replace_vec = xmalloc (sizeof(char*)*execp->num_args);
/* execp->state = xmalloc(sizeof(*(execp->state))); */
bc_init_state (&execp->ctl, &execp->state, execp);
/* Remember the (pre-replacement) arguments for later. */
for (i=0; inum_args; ++i)
{
execp->replace_vec[i] = argv[i+start];
}
}
if (argv[end] == NULL)
*arg_ptr = end;
else
*arg_ptr = end + 1;
return true;
}
/* Get a timestamp and comparison type.
STR is the ASCII representation.
Set *NUM_DAYS to the number of days/minutes/whatever, taken as being
relative to ORIGIN (usually the current moment or midnight).
Thus the sense of the comparison type appears to be reversed.
Set *COMP_TYPE to the kind of comparison that is requested.
Issue OVERFLOWMESSAGE if overflow occurs.
Return true if all okay, false if input error.
Used by -atime, -ctime and -mtime (parsers) to
get the appropriate information for a time predicate processor. */
static bool
get_relative_timestamp (const char *str,
struct time_val *result,
struct timespec origin,
double sec_per_unit,
const char *overflowmessage)
{
double offset, seconds, nanosec;
static const long nanosec_per_sec = 1000000000;
if (get_comp_type (&str, &result->kind))
{
/* Invert the sense of the comparison */
switch (result->kind)
{
case COMP_LT: result->kind = COMP_GT; break;
case COMP_GT: result->kind = COMP_LT; break;
case COMP_EQ:
break; /* inversion leaves it unchanged */
}
/* Convert the ASCII number into floating-point. */
if (xstrtod (str, NULL, &offset, strtod))
{
/* Separate the floating point number the user specified
* (which is a number of days, or minutes, etc) into an
* integral number of seconds (SECONDS) and a fraction (NANOSEC).
*/
nanosec = modf (offset * sec_per_unit, &seconds);
nanosec *= 1.0e9; /* convert from fractional seconds to ns. */
assert (nanosec < nanosec_per_sec);
/* Perform the subtraction, and then check for overflow.
* On systems where signed aritmetic overflow does not
* wrap, this check may be unreliable. The C standard
* does not require this approach to work, but I am aware
* of no platforms where it fails.
*/
result->ts.tv_sec = origin.tv_sec - seconds;
if ((origin.tv_sec < result->ts.tv_sec) != (seconds < 0))
{
/* an overflow has occurred. */
die (EXIT_FAILURE, 0, overflowmessage, str);
}
result->ts.tv_nsec = origin.tv_nsec - nanosec;
if (origin.tv_nsec < nanosec)
{
/* Perform a carry operation */
result->ts.tv_nsec += nanosec_per_sec;
result->ts.tv_sec -= 1;
}
return true;
}
else
{
/* Conversion from ASCII to double failed. */
return false;
}
}
else
{
return false;
}
}
/* Insert a time predicate based on the information in ENTRY.
ARGV is a pointer to the argument array.
ARG_PTR is a pointer to an index into the array, incremented if
all went well.
Return true if input is valid, false if not.
A new predicate node is assigned, along with an argument node
obtained with malloc.
Used by -atime, -ctime, and -mtime parsers. */
static bool
parse_time (const struct parser_table* entry, char *argv[], int *arg_ptr)
{
struct predicate *our_pred;
struct time_val tval;
enum comparison_type comp;
const char *timearg, *orig_timearg;
const char *errmsg = _("arithmetic overflow while converting %s "
"days to a number of seconds");
struct timespec origin;
const int saved_argc = *arg_ptr;
if (!collect_arg (argv, arg_ptr, &timearg))
return false;
orig_timearg = timearg;
/* Decide the origin by previewing the comparison type. */
origin = options.cur_day_start;
if (get_comp_type (&timearg, &comp))
{
/* Remember, we invert the sense of the comparison, so this tests
* against COMP_LT instead of COMP_GT...
*/
if (COMP_LT == comp)
{
uintmax_t expected = origin.tv_sec + (DAYSECS-1);
origin.tv_sec += (DAYSECS-1);
if (expected != (uintmax_t)origin.tv_sec)
{
die (EXIT_FAILURE, 0,
_("arithmetic overflow when trying to calculate the end of today"));
}
}
}
/* We discard the value of comp here, as get_relative_timestamp
* will set tval.kind. For that to work, we have to restore
* timearg so that it points to the +/- prefix, if any. get_comp_type()
* will have advanced timearg, so we restore it.
*/
timearg = orig_timearg;
if (!get_relative_timestamp (timearg, &tval, origin, DAYSECS, errmsg))
{
*arg_ptr = saved_argc; /* don't consume the invalid argument */
return false;
}
our_pred = insert_primary (entry, orig_timearg);
our_pred->args.reftime = tval;
our_pred->est_success_rate = estimate_timestamp_success_rate (tval.ts.tv_sec);
if (options.debug_options & DebugExpressionTree)
{
time_t t;
fprintf (stderr, "inserting %s\n", our_pred->p_name);
fprintf (stderr, " type: %s %s ",
(tval.kind == COMP_GT) ? "gt" :
((tval.kind == COMP_LT) ? "lt" : ((tval.kind == COMP_EQ) ? "eq" : "?")),
(tval.kind == COMP_GT) ? " >" :
((tval.kind == COMP_LT) ? " <" : ((tval.kind == COMP_EQ) ? ">=" : " ?")));
t = our_pred->args.reftime.ts.tv_sec;
fprintf (stderr, "%"PRIuMAX" %s",
(uintmax_t) our_pred->args.reftime.ts.tv_sec,
ctime (&t));
if (tval.kind == COMP_EQ)
{
t = our_pred->args.reftime.ts.tv_sec + DAYSECS;
fprintf (stderr, " < %"PRIuMAX" %s",
(uintmax_t) t, ctime (&t));
}
}
return true;
}
/* Get the comparison type prefix (if any) from a number argument.
The prefix is at *STR.
Set *COMP_TYPE to the kind of comparison that is requested.
Advance *STR beyond any initial comparison prefix.
Return true if all okay, false if input error. */
static bool
get_comp_type (const char **str, enum comparison_type *comp_type)
{
switch (**str)
{
case '+':
*comp_type = COMP_GT;
(*str)++;
break;
case '-':
*comp_type = COMP_LT;
(*str)++;
break;
default:
*comp_type = COMP_EQ;
break;
}
return true;
}
/* Get a number with comparison information.
The sense of the comparison information is 'normal'; that is,
'+' looks for a count > than the number and '-' less than.
STR is the ASCII representation of the number.
Set *NUM to the number.
Set *COMP_TYPE to the kind of comparison that is requested.
Return true if all okay, false if input error. */
static bool
get_num (const char *str,
uintmax_t *num,
enum comparison_type *comp_type)
{
char *pend;
if (str == NULL)
return false;
/* Figure out the comparison type if the caller accepts one. */
if (comp_type)
{
if (!get_comp_type (&str, comp_type))
return false;
}
return xstrtoumax (str, &pend, 10, num, "") == LONGINT_OK;
}
/* Insert a number predicate.
ARGV is a pointer to the argument array.
*ARG_PTR is an index into ARGV, incremented if all went well.
*PRED is the predicate processor to insert.
Return true if input is valid, false if error.
A new predicate node is assigned, along with an argument node
obtained with malloc.
Used by -inum, -uid, -gid and -links parsers. */
static struct predicate *
insert_num (char **argv, int *arg_ptr, const struct parser_table *entry)
{
const char *numstr;
if (collect_arg (argv, arg_ptr, &numstr))
{
uintmax_t num;
enum comparison_type c_type;
if (get_num (numstr, &num, &c_type))
{
struct predicate *our_pred = insert_primary (entry, numstr);
our_pred->args.numinfo.kind = c_type;
our_pred->args.numinfo.l_val = num;
if (options.debug_options & DebugExpressionTree)
{
fprintf (stderr, "inserting %s\n", our_pred->p_name);
fprintf (stderr, " type: %s %s ",
(c_type == COMP_GT) ? "gt" :
((c_type == COMP_LT) ? "lt" : ((c_type == COMP_EQ) ? "eq" : "?")),
(c_type == COMP_GT) ? " >" :
((c_type == COMP_LT) ? " <" : ((c_type == COMP_EQ) ? " =" : " ?")));
fprintf (stderr, "%"PRIuMAX"\n", our_pred->args.numinfo.l_val);
}
return our_pred;
}
else
{
const char *predicate = argv[(*arg_ptr)-2];
die (EXIT_FAILURE, 0,
_("non-numeric argument to %s: %s"),
predicate,
quotearg_n_style (0, options.err_quoting_style, numstr));
/*NOTREACHED*/
return NULL;
}
}
return NULL;
}
static void
open_output_file (const char *path, struct format_val *p)
{
p->segment = NULL;
p->quote_opts = clone_quoting_options (NULL);
if (!strcmp (path, "/dev/stderr"))
{
p->stream = stderr;
p->filename = _("standard error");
}
else if (!strcmp (path, "/dev/stdout"))
{
p->stream = stdout;
p->filename = _("standard output");
}
else
{
p->stream = sharefile_fopen (state.shared_files, path);
p->filename = path;
if (p->stream == NULL)
{
fatal_nontarget_file_error (errno, path);
}
}
p->dest_is_tty = stream_is_tty (p->stream);
}
static void
open_stdout (struct format_val *p)
{
open_output_file ("/dev/stdout", p);
}