summaryrefslogtreecommitdiff
path: root/find/parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'find/parser.c')
-rw-r--r--find/parser.c3424
1 files changed, 3424 insertions, 0 deletions
diff --git a/find/parser.c b/find/parser.c
new file mode 100644
index 0000000..b57cdda
--- /dev/null
+++ b/find/parser.c
@@ -0,0 +1,3424 @@
+/* parser.c -- convert the command line args into an expression tree.
+ Copyright (C) 1990, 1991, 1992, 1993, 1994, 2000, 2001, 2003, 2004,
+ 2005, 2006, 2007, 2008, 2009, 2010, 2011 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 <http://www.gnu.org/licenses/>.
+*/
+
+/* config.h must always come first. */
+#include <config.h>
+
+/* system headers. */
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <math.h>
+#include <pwd.h>
+#include <regex.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/* gnulib headers. */
+#include "error.h"
+#include "fnmatch.h"
+#include "fts_.h"
+#include "gettext.h"
+#include "modechange.h"
+#include "mountlist.h"
+#include "parse-datetime.h"
+#include "print.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"
+
+/* find headers. */
+#include "buildcmd.h"
+#include "defs.h"
+#include "fdleak.h"
+#include "findutils-version.h"
+
+
+
+
+#if ENABLE_NLS
+# include <libintl.h>
+# define _(Text) gettext (Text)
+#else
+# define _(Text) Text
+#endif
+#ifdef gettext_noop
+# define N_(String) gettext_noop (String)
+#else
+/* See locate.c for explanation as to why not use (String) */
+# define N_(String) String
+#endif
+
+#ifndef HAVE_ENDGRENT
+#define endgrent ()
+#endif
+#ifndef HAVE_ENDPWENT
+#define endpwent ()
+#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_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_help (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_version (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 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_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), /* GNU, HP-UX, RMS prefers wholename, but anyway soon 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 anyway -path will soon be in POSIX */
+ {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 */
+ {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, 0, 0, 0}
+};
+
+
+static const char *first_nonoption_arg = NULL;
+static const struct parser_table *noop = NULL;
+
+static int
+fallback_getfilecon (int fd, const char *name, security_context_t *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:
+#ifdef DEBUG_STAT
+ fprintf (stderr, "fallback_getfilecon(): getfilecon(%s) failed; falling "
+ "back on lgetfilecon()\n", name);
+#endif
+ 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, security_context_t *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, security_context_t *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, security_context_t *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 fnd 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. */
+ error (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 != 0; 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)
+{
+ 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;
+ }
+ else
+ {
+ 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;
+}
+
+
+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 fid 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)
+ {
+ /* 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 %s "
+ "option after a non-option argument %s, "
+ "but options are not positional (%s affects "
+ "tests specified before it as well as those "
+ "specified after it). Please specify options "
+ "before other arguments.\n"),
+ 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 != 0; 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.1)
+ {
+ /* Assume 1% of files have timestamps in the future */
+ return 0.01f;
+ }
+ else if (num_days < 1)
+ {
+ /* Assume 30% of files have timestamps today */
+ return 0.3f;
+ }
+ else if (num_days > 100)
+ {
+ /* 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))
+ {
+ return true;
+ }
+ else
+ {
+ fatal_target_file_error (errno, filename);
+ }
+ }
+ 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
+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
+is_used_fs_type(const char *name)
+{
+ if (0 == strcmp("afs", name))
+ {
+ /* I guess AFS may not appear in /etc/mtab (or equivalent) but still be in use,
+ so assume we always need to check for AFS. */
+ return true;
+ }
+ else
+ {
+ const struct mount_entry *entries = read_file_system_list(false);
+ if (entries)
+ {
+ const struct mount_entry *entry;
+ for (entry = entries; entry; entry = entry->me_next)
+ {
+ if (0 == strcmp(name, entry->me_type))
+ return true;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+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;
+ }
+ else
+ {
+ --*arg_ptr; /* don't consume the invalid argument. */
+ 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 */
+ error (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 */
+ error (EXIT_FAILURE, 0,
+ _("%s is not the name of an existing group"),
+ quotearg_n_style (0, options.err_quoting_style, groupname));
+ }
+ else
+ {
+ error (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 (stdout, 0, NULL);
+ puts (_("\n\
+default path is the current directory; default expression is -print\n\
+expression may consist of: operators, options, tests, and actions:\n"));
+ puts (_("\
+operators (decreasing precedence; -and is implicit where no others are given):\n\
+ ( EXPR ) ! EXPR -not EXPR EXPR1 -a EXPR2 EXPR1 -and EXPR2\n\
+ EXPR1 -o EXPR2 EXPR1 -or EXPR2 EXPR1 , EXPR2\n"));
+ puts (_("\
+positional options (always true): -daystart -follow -regextype\n\n\
+normal options (always true, specified before other expressions):\n\
+ -depth --help -maxdepth LEVELS -mindepth LEVELS -mount -noleaf\n\
+ --version -xdev -ignore_readdir_race -noignore_readdir_race\n"));
+ puts (_("\
+tests (N can be +N or -N or N): -amin N -anewer FILE -atime N -cmin N\n\
+ -cnewer FILE -ctime N -empty -false -fstype TYPE -gid N -group NAME\n\
+ -ilname PATTERN -iname PATTERN -inum N -iwholename PATTERN -iregex PATTERN\n\
+ -links N -lname PATTERN -mmin N -mtime N -name PATTERN -newer FILE"));
+ puts (_("\
+ -nouser -nogroup -path PATTERN -perm [-/]MODE -regex PATTERN\n\
+ -readable -writable -executable\n\
+ -wholename PATTERN -size N[bcwkMG] -true -type [bcdpflsD] -uid N\n\
+ -used N -user NAME -xtype [bcdpfls]"));
+ puts (_("\
+ -context CONTEXT\n"));
+ puts (_("\n\
+actions: -delete -print0 -printf FORMAT -fprintf FILE FORMAT -print \n\
+ -fprint0 FILE -fprint FILE -ls -fls FILE -prune -quit\n\
+ -exec COMMAND ; -exec COMMAND {} + -ok COMMAND ;\n\
+ -execdir COMMAND ; -execdir COMMAND {} + -okdir COMMAND ;\n\
+"));
+ puts (_("Report (and track progress on fixing) bugs via the findutils bug-reporting\n\
+page at http://savannah.gnu.org/ or, if you have no web access, by sending\n\
+email to <bug-findutils@gnu.org>."));
+ exit (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.1 * 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))
+ {
+ error (EXIT_FAILURE, 0,
+ _("sanity check of the fnmatch() library function failed."));
+ return false;
+ }
+ checked = true;
+ }
+ return checked;
+}
+
+
+static bool
+check_name_arg (const char *pred, const char *arg)
+{
+ if (should_issue_warnings () && strchr (arg, '/'))
+ {
+ error (0, 0,_("warning: Unix filenames usually don't contain slashes "
+ "(though pathnames do). That means that '%s %s' will "
+ "probably evaluate to false all the time on this system. "
+ "You might find the '-wholename' test more useful, or "
+ "perhaps '-samefile'. Alternatively, if you are using "
+ "GNU grep, you could "
+ "use 'find ... -print0 | grep -FzZ %s'."),
+ pred,
+ safely_quote_err_filename (0, arg),
+ safely_quote_err_filename (1, arg));
+ }
+ return true; /* allow it anyway */
+}
+
+
+
+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))
+ {
+ if (check_name_arg ("-iname", name))
+ {
+ struct predicate *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;
+ }
+ else
+ {
+ --*arg_ptr; /* don't consume the invalid argument. */
+ 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;
+ }
+ else
+ {
+ --*arg_ptr; /* don't consume the invalid argument. */
+ 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.1 * 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);
+ }
+ }
+ error (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;
+ const int saved_argc = *arg_ptr;
+
+ if (collect_arg (argv, arg_ptr, &name))
+ {
+ fnmatch_sanitycheck ();
+ if (check_name_arg ("-name", name))
+ {
+ struct predicate *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;
+ }
+ else
+ {
+ *arg_ptr = saved_argc; /* don't consume the invalid argument. */
+ }
+ }
+ 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)
+ {
+ error (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))
+ {
+ error (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. */
+ error (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
+ * (a) HPUX find supports this predicate also and
+ * (b) it will soon be in POSIX anyway.
+ */
+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'))
+ error (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)
+ error (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)
+ error (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': /* Megabytes */
+ blksize = 1024*1024;
+ arg[len - 1] = '\0';
+ break;
+
+ case 'G': /* Gigabytes */
+ 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:
+ error (EXIT_FAILURE, 0,
+ _("invalid -size type `%c'"), argv[*arg_ptr][len - 1]);
+ }
+ /* TODO: accept fractional megabytes etc. ? */
+ if (!get_num (arg, &num, &c_type))
+ {
+ char tail[2];
+ tail[0] = suffix;
+ tail[1] = 0;
+
+ error (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))
+ {
+ error (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
+ {
+ error (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;
+ }
+ else
+ {
+ --*arg_ptr; /* don't consume the invalid argument. */
+ 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
+ {
+ error (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])
+ {
+ error (EXIT_FAILURE, 0,
+ _("%s is not the name of a known user"),
+ quotearg_n_style (0, options.err_quoting_style,
+ username));
+ }
+ else
+ {
+ error (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 DEBUG
+ printf ("DEBUG ");
+ has_features = true;
+#endif
+#if DEBUG_STAT
+ printf ("DEBUG_STAT ");
+ 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))
+ {
+ int nflags = 0;
+ printf ("FTS(");
+ has_features = true;
+
+ if (flags & FTS_CWDFD)
+ {
+ if (nflags)
+ {
+ printf (",");
+ }
+ printf ("FTS_CWDFD");
+ has_features = true;
+ }
+ 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)
+ {
+ error (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)
+{
+ mode_t type_cell;
+ struct predicate *our_pred;
+ float rate = 0.5;
+ const char *typeletter;
+
+ if (collect_arg (argv, arg_ptr, &typeletter))
+ {
+ if (strlen (typeletter) != 1u)
+ {
+ error (EXIT_FAILURE, 0,
+ _("Arguments to -type should contain only one letter"));
+ /*NOTREACHED*/
+ return false;
+ }
+
+ switch (typeletter[0])
+ {
+ case 'b': /* block special */
+ type_cell = S_IFBLK;
+ rate = 0.01f;
+ break;
+ case 'c': /* character special */
+ type_cell = S_IFCHR;
+ rate = 0.01f;
+ break;
+ case 'd': /* directory */
+ type_cell = S_IFDIR;
+ rate = 0.4f;
+ break;
+ case 'f': /* regular file */
+ type_cell = S_IFREG;
+ rate = 0.95f;
+ break;
+ case 'l': /* symbolic link */
+#ifdef S_IFLNK
+ type_cell = S_IFLNK;
+ rate = 0.1f;
+#else
+ error (EXIT_FAILURE, 0,
+ _("-type %c is not supported because symbolic links "
+ "are not supported on the platform find was compiled on."),
+ (*typeletter));
+#endif
+ break;
+ case 'p': /* pipe */
+#ifdef S_IFIFO
+ type_cell = S_IFIFO;
+ rate = 0.01f;
+#else
+ error (EXIT_FAILURE, 0,
+ _("-type %c is not supported because FIFOs "
+ "are not supported on the platform find was compiled on."),
+ (*typeletter));
+#endif
+ break;
+ case 's': /* socket */
+#ifdef S_IFSOCK
+ type_cell = S_IFSOCK;
+ rate = 0.01f;
+#else
+ error (EXIT_FAILURE, 0,
+ _("-type %c is not supported because named sockets "
+ "are not supported on the platform find was compiled on."),
+ (*typeletter));
+#endif
+ break;
+ case 'D': /* Solaris door */
+#ifdef S_IFDOOR
+ type_cell = S_IFDOOR;
+ rate = 0.01f;
+#else
+ error (EXIT_FAILURE, 0,
+ _("-type %c is not supported because Solaris doors "
+ "are not supported on the platform find was compiled on."),
+ (*typeletter));
+#endif
+ break;
+ default: /* None of the above ... nuke 'em. */
+ error (EXIT_FAILURE, 0,
+ _("Unknown argument to -type: %c"), (*typeletter));
+ /*NOTREACHED*/
+ return false;
+ }
+ our_pred = insert_primary_withpred (entry, which_pred, typeletter);
+ our_pred->est_success_rate = rate;
+
+ /* 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;
+ }
+ our_pred->args.type = type_cell;
+ return true;
+ }
+ return false;
+}
+
+
+/* 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 . */
+ error (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);
+ error (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;
+ /* If find reads stdin (i.e. for -ok and similar), close stdin
+ * in the child to prevent some script from consiming 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.
+ */
+ error (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)
+ {
+ error (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. */
+ error (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:
+ error (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; i<end-1; ++i)
+ {
+ bc_push_arg (&execp->ctl, &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; i<execp->num_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. */
+ error (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 (origin.tv_sec != expected)
+ {
+ error (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, "%ju %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, " < %ju %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 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, "%ju\n", our_pred->args.numinfo.l_val);
+ }
+ return our_pred;
+ }
+ }
+ 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);
+}