diff options
author | Chris PeBenito <pebenito@gentoo.org> | 2008-01-30 14:02:05 +0000 |
---|---|---|
committer | Chris PeBenito <pebenito@gentoo.org> | 2008-01-30 14:02:05 +0000 |
commit | 3256530c13a9979c29178971d99e57550f34de55 (patch) | |
tree | 76694e22447751424645cccd6720eb7d68a0fe94 /sys-apps/findutils/files | |
parent | sparc stable wrt #207718, remove old (diff) | |
download | gentoo-2-3256530c13a9979c29178971d99e57550f34de55.tar.gz gentoo-2-3256530c13a9979c29178971d99e57550f34de55.tar.bz2 gentoo-2-3256530c13a9979c29178971d99e57550f34de55.zip |
sys-apps/findutils: update selinux patch.
(Portage version: 2.1.4)
Diffstat (limited to 'sys-apps/findutils/files')
-rw-r--r-- | sys-apps/findutils/files/findutils-4.3.12-selinux.diff | 9951 |
1 files changed, 9951 insertions, 0 deletions
diff --git a/sys-apps/findutils/files/findutils-4.3.12-selinux.diff b/sys-apps/findutils/files/findutils-4.3.12-selinux.diff new file mode 100644 index 000000000000..fb772671e444 --- /dev/null +++ b/sys-apps/findutils/files/findutils-4.3.12-selinux.diff @@ -0,0 +1,9951 @@ +diff -purN findutils-4.3.12.orig/find/Makefile.am findutils-4.3.12/find/Makefile.am +--- findutils-4.3.12.orig/find/Makefile.am 2007-07-22 08:29:31.000000000 -0400 ++++ findutils-4.3.12/find/Makefile.am 2008-01-30 08:46:05.750843391 -0500 +@@ -2,6 +2,7 @@ AUTOMAKE_OPTIONS = std-options + localedir = $(datadir)/locale + # noinst_PROGRAMS = regexprops + # regexprops_SOURCES = regexprops.c ++DEFS = @DEFS@ -I. -I$(srcdir) -I.. -DWITH_SELINUX + + noinst_LIBRARIES = libfindtools.a + libfindtools_a_SOURCES = finddata.c fstype.c parser.c pred.c tree.c util.c +@@ -26,7 +27,7 @@ endif + + EXTRA_DIST = defs.h $(man_MANS) + INCLUDES = -I../gnulib/lib -I$(top_srcdir)/lib -I$(top_srcdir)/gnulib/lib -I../intl -DLOCALEDIR=\"$(localedir)\" +-LDADD = ./libfindtools.a ../lib/libfind.a ../gnulib/lib/libgnulib.a @INTLLIBS@ @LIB_CLOCK_GETTIME@ @FINDLIBS@ ++LDADD = ./libfindtools.a ../lib/libfind.a ../gnulib/lib/libgnulib.a @INTLLIBS@ @LIB_CLOCK_GETTIME@ @FINDLIBS@ -lselinux + man_MANS = find.1 + SUBDIRS = . testsuite + +diff -purN findutils-4.3.12.orig/find/Makefile.in findutils-4.3.12/find/Makefile.in +--- findutils-4.3.12.orig/find/Makefile.in 2007-12-19 16:27:24.000000000 -0500 ++++ findutils-4.3.12/find/Makefile.in 2008-01-30 08:46:05.750843391 -0500 +@@ -275,7 +275,7 @@ CPP = @CPP@ + CPPFLAGS = @CPPFLAGS@ + CYGPATH_W = @CYGPATH_W@ + DEFAULT_ARG_SIZE = @DEFAULT_ARG_SIZE@ +-DEFS = @DEFS@ ++DEFS = @DEFS@ -I. -I$(srcdir) -I.. -DWITH_SELINUX + DEPDIR = @DEPDIR@ + DIRENT_H = @DIRENT_H@ + ECHO_C = @ECHO_C@ +@@ -601,7 +601,7 @@ libfindtools_a_SOURCES = finddata.c fsty + @WITH_FTS_TRUE@oldfind_SOURCES = find.c + @WITH_FTS_FALSE@ftsfind_SOURCES = ftsfind.c + EXTRA_DIST = defs.h $(man_MANS) +-LDADD = ./libfindtools.a ../lib/libfind.a ../gnulib/lib/libgnulib.a @INTLLIBS@ @LIB_CLOCK_GETTIME@ @FINDLIBS@ ++LDADD = ./libfindtools.a ../lib/libfind.a ../gnulib/lib/libgnulib.a @INTLLIBS@ @LIB_CLOCK_GETTIME@ @FINDLIBS@ -lselinux + man_MANS = find.1 + SUBDIRS = . testsuite + all: all-recursive +diff -purN findutils-4.3.12.orig/find/defs.h findutils-4.3.12/find/defs.h +--- findutils-4.3.12.orig/find/defs.h 2007-11-30 05:19:58.000000000 -0500 ++++ findutils-4.3.12/find/defs.h 2008-01-30 08:46:05.754843619 -0500 +@@ -90,6 +90,9 @@ int get_statinfo PARAMS((const char *pat + #define MODE_RWX (S_IXUSR | S_IXGRP | S_IXOTH | MODE_RW) + #define MODE_ALL (S_ISUID | S_ISGID | S_ISVTX | MODE_RWX) + ++#ifdef WITH_SELINUX ++#include <selinux/selinux.h> ++#endif /*WITH_SELINUX*/ + + struct predicate; + struct options; +@@ -314,6 +317,9 @@ struct predicate + struct samefile_file_id samefileid; /* samefile */ + mode_t type; /* type */ + struct format_val printf_vec; /* printf fprintf fprint ls fls print0 fprint0 print */ ++#ifdef WITH_SELINUX ++ security_context_t scontext; /* scontext */ ++#endif /*WITH_SELINUX*/ + } args; + + /* The next predicate in the user input sequence, +@@ -458,7 +464,9 @@ PREDICATEFUNCTION pred_used; + PREDICATEFUNCTION pred_user; + PREDICATEFUNCTION pred_writable; + PREDICATEFUNCTION pred_xtype; +- ++#ifdef WITH_SELINUX ++PREDICATEFUNCTION pred_scontext; ++#endif /* WITH_SELINUX */ + + + int launch PARAMS((const struct buildcmd_control *ctl, +@@ -605,10 +613,13 @@ struct options + */ + unsigned short optimisation_level; + +- + /* How should we quote filenames in error messages and so forth? + */ + enum quoting_style err_quoting_style; ++ ++#ifdef WITH_SELINUX ++ int (*x_getfilecon)(); ++#endif /* WITH_SELINUX */ + }; + extern struct options options; + +diff -purN findutils-4.3.12.orig/find/find.1 findutils-4.3.12/find/find.1 +--- findutils-4.3.12.orig/find/find.1 2007-12-19 14:53:14.000000000 -0500 ++++ findutils-4.3.12/find/find.1 2008-01-30 08:49:54.983906639 -0500 +@@ -935,6 +935,10 @@ checks the type of the file that + .B \-type + does not check. + ++.IP "\-context \fIscontext\fR" ++.IP "\--context \fIscontext\fR" ++(SELinux only) File has the security context \fIscontext\fR. ++ + .SS ACTIONS + .IP "\-delete\fR" + Delete files; true if removal succeeded. If the removal failed, an +@@ -1340,6 +1344,8 @@ File's type (like in + U=unknown type (shouldn't happen) + .IP %Y + File's type (like %y), plus follow symlinks: L=loop, N=nonexistent ++.IP %Z ++(SELinux only) file's security context. + .PP + A `%' character followed by any other character is discarded, but the + other character is printed (don't rely on this, as further format +diff -purN findutils-4.3.12.orig/find/find.1.orig findutils-4.3.12/find/find.1.orig +--- findutils-4.3.12.orig/find/find.1.orig 1969-12-31 19:00:00.000000000 -0500 ++++ findutils-4.3.12/find/find.1.orig 2007-12-19 14:53:14.000000000 -0500 +@@ -0,0 +1,2027 @@ ++.TH FIND 1 \" -*- nroff -*- ++.SH NAME ++find \- search for files in a directory hierarchy ++.SH SYNOPSIS ++.B find ++[\-H] [\-L] [\-P] [\-D debugopts] [\-Olevel] [path...] [expression] ++.SH DESCRIPTION ++This manual page ++documents the GNU version of ++.BR find . ++GNU ++.B find ++searches the directory tree rooted at each given file name by ++evaluating the given expression from left to right, according to the ++rules of precedence (see section OPERATORS), until the outcome is ++known (the left hand side is false for \fIand\fR operations, true for ++\fIor\fR), at which point ++.B find ++moves on to the next file name. ++.PP ++If you are using ++.B find ++in an environment where security is important (for example if you are ++using it to seach directories that are writable by other users), you ++should read the "Security Considerations" chapter of the findutils ++documentation, which is called \fBFinding Files\fP and comes with ++findutils. That document also includes a lot more detail ++and discussion than this manual page, so you may find it a more useful ++source of information. ++.SH OPTIONS ++The ++.BR \-H , ++.B \-L ++and ++.B \-P ++options control the treatment of symbolic ++links. Command-line arguments following these are taken to be names ++of files or directories to be examined, up to the first argument that ++begins with `\-', or the argument `(' or `!'. That argument and any ++following arguments are taken to be the expression describing what is ++to be searched for. If no paths are given, the current directory is ++used. If no expression is given, the expression ++.B \-print ++is used ++(but you should probably consider using ++.B \-print0 ++instead, anyway). ++.PP ++This manual page talks about `options' within the expression list. ++These options control the behaviour of ++.B find ++but are specified immediately after the last path name. The five ++`real' options ++.BR \-H , ++.BR \-L , ++.BR \-P , ++.B \-D ++and ++.B \-O ++must appear before ++the first path name, if at all. A double dash ++.B \-\- ++can also be used ++to signal that any remaining arguments are not options (though ++ensuring that all start points begin with either `./' or `/' is ++generally safer if you use wildcards in the list of start points). ++.IP \-P ++Never follow symbolic links. This is the default behaviour. When ++.B find ++examines or prints information a file, and the file is a symbolic ++link, the information used shall be taken from the properties of the ++symbolic link itself. ++ ++.IP \-L ++Follow symbolic links. When ++.B find ++examines or prints information about files, the information used shall ++be taken from the properties of the file to which the link points, not ++from the link itself (unless it is a broken symbolic link or ++.B find ++is unable to examine the file to which the link points). Use of this ++option implies ++.BR \-noleaf . ++If you later use the ++.B \-P ++option, ++.B \-noleaf ++will still be in effect. If ++.B \-L ++is in effect and ++.B find ++discovers a symbolic link to a subdirectory during its search, ++the subdirectory pointed to by the symbolic link will be searched. ++.IP ++When the ++.B \-L ++option is in effect, the ++.B \-type ++predicate will always ++match against the type of the file that a symbolic link points to ++rather than the link itself (unless the symbolic link is broken). ++Using ++.B \-L ++causes the ++.B \-lname ++and ++.B \-ilname ++predicates always to return ++false. ++ ++.IP \-H ++Do not follow symbolic links, except while processing the command ++line arguments. When ++.B find ++examines or prints information about files, the information used ++shall be taken from the properties of the symbolic link itself. The ++only exception to this behaviour is when a file specified on the ++command line is a symbolic link, and the link can be resolved. For ++that situation, the information used is taken from whatever the link ++points to (that is, the link is followed). The information about the ++link itself is used as a fallback if the file pointed to by the ++symbolic link cannot be examined. If ++.B \-H ++is in effect and one of the ++paths specified on the command line is a symbolic link to a directory, ++the contents of that directory will be examined (though of course ++\-maxdepth 0 would prevent this). ++.P ++If more than one of ++.BR \-H , ++.B \-L ++and ++.B \-P ++is specified, each overrides the ++others; the last one appearing on the command line takes effect. ++Since it is the default, the ++.B \-P ++option should be considered to be in ++effect unless either ++.B \-H ++or ++.B \-L ++is specified. ++ ++GNU ++.B find ++frequently stats files during the processing of the command line ++itself, before any searching has begun. These options also affect how ++those arguments are processed. Specifically, there are a number of ++tests that compare files listed on the command line against a file we ++are currently considering. In each case, the file specified on the ++command line will have been examined and some of its properties will ++have been saved. If the named file is in fact a symbolic link, and ++the ++.B \-P ++option is in effect (or if neither ++.B \-H ++nor ++.B \-L ++were specified), the information used for the comparison will be taken from ++the properties of the symbolic link. Otherwise, it will be taken from ++the properties of the file the link points to. If ++.B find ++cannot follow the link (for example because it has insufficient ++privileges or the link points to a nonexistent file) the properties of ++the link itself will be used. ++.P ++When the ++.B \-H ++or ++.B \-L options are in effect, any symbolic links listed ++as the argument of ++.B \-newer ++will be dereferenced, and the timestamp ++will be taken from the file to which the symbolic link points. The ++same consideration applies to ++.BR \-newerXY , ++.B \-anewer ++and ++.BR \-cnewer . ++ ++The ++.B \-follow ++option has a similar effect to ++.BR \-L , ++though it takes ++effect at the point where it appears (that is, if ++.B \-L ++is not used but ++.B \-follow ++is, any symbolic links appearing after ++.B \-follow ++on the ++command line will be dereferenced, and those before it will not). ++ ++.IP "\-D debugoptions" ++Print diagnostic information; this can be helpful to diagnose problems ++with why ++.B find ++is not doing what you want. The list of debug options should be comma ++separated. Compatibility of the debug options is not guaranteed ++between releases of findutils. For a complete list of valid debug ++options, see the output of ++.B find \-D ++.BR help . ++Valid debug options include ++.RS ++.IP help ++Explain the debugging options ++.IP tree ++Show the expression tree in its original and optimised form. ++.IP stat ++Print messages as files are examined with the ++.B stat ++and ++.B lstat ++system calls. The ++.B find ++program tries to minimise such calls. ++.IP opt ++Prints diagnostic information relating to the optimisation of the ++expression tree; see the \-O option. ++.IP rates ++Prints a summary indicating how often each predicate succeeded or ++failed. ++.RE ++.IP \-Olevel ++Enables query optimisation. The ++.B find ++program reorders tests to speed up execution while preserving the ++overall effect; that is, predicates with side effects are not ++reordered relative to each other. The optimisations performed at each ++optimisation level are as follows. ++.RS ++.IP 0 ++Equivalent to optimisation level 1. ++.IP 1 ++This is the default optimisation level and corresponds to the ++traditional behaviour. Expressions are reordered so that tests based ++only on the names of files (for example ++.B \-name ++and ++.BR \-regex ) ++are performed first. ++.IP 2 ++Any ++.B \-type ++or ++.B \-xtype ++tests are performed after any tests based only on the names of files, ++but before any tests that require information from the inode. On many ++modern versions of Unix, file types are returned by ++.B readdir() ++and so these predicates are faster to evaluate than predicates which ++need to stat the file first. ++.IP 3 ++At this optimisation level, the full cost-based query optimiser is ++enabled. The order of tests is modified so that cheap (i.e. fast) ++tests are performed first and more expensive ones are performed later, ++if necessary. Within each cost band, predicates are evaluated earlier ++or later according to whether they are likely to succeed or not. For ++.BR \-o , ++predicates which are likely to succeed are evaluated earlier, and for ++.BR \-a , ++predicates which are likely to fail are evaluated earlier. ++.RE ++.IP ++The cost-based optimiser has a fixed idea of how likely any given test ++is to succeed. In some cases the probability takes account of the ++specific nature of the test (for example, ++.B \-type f ++is assumed to be more likely to succeed than ++.BR "\-type c" ). ++The cost-based optimiser is currently being evaluated. If it does ++not actually improve the performance of ++.BR find , ++it will be removed again. Conversely, optimisations that prove to be ++reliable, robust and effective may be enabled at lower optimisation ++levels over time. However, the default behaviour (i.e. optimisation ++level 1) will not be changed in the 4.3.x release series. The ++findutils test suite runs all the tests on ++.B find ++at each optimisation level and ensures that the result is the same. ++.P ++.SH EXPRESSIONS ++The expression is made up of options (which affect overall operation ++rather than the processing of a specific file, and always return ++true), tests (which return a true or false value), and actions (which ++have side effects and return a true or false value), all separated by ++operators. ++.B \-and ++is assumed where the operator is omitted. ++ ++If the expression contains no actions other than ++.BR \-prune , ++.B \-print ++is ++performed on all files for which the expression is true. ++ ++.SS OPTIONS ++.P ++All options always return true. Except for ++.BR \-daystart , ++.B \-follow ++and ++.BR \-regextype , ++the options affect all tests, including tests specified ++before the option. This is because the options are processed when the ++command line is parsed, while the tests don't do anything until files ++are examined. The ++.BR \-daystart , ++.B \-follow ++and ++.B \-regextype ++options are different in this respect, and have an effect only on tests which ++appear later in the command line. Therefore, for clarity, it is best ++to place them at the beginning of the expression. A warning is issued ++if you don't do this. ++ ++.IP \-d ++A synonym for \-depth, for compatibility with FreeBSD, NetBSD, MacOS X and OpenBSD. ++ ++.IP \-daystart ++Measure times (for ++.BR \-amin , ++.BR \-atime , ++.BR \-cmin , ++.BR \-ctime , ++.BR \-mmin , ++and ++.BR \-mtime ) ++from the beginning of today rather than from 24 hours ago. This ++option only affects tests which appear later on the command line. ++ ++.IP \-depth ++Process each directory's contents before the directory itself. The ++\-delete action also implies ++.BR \-depth . ++ ++.IP \-follow ++Deprecated; use the ++.B \-L ++option instead. Dereference symbolic links. ++Implies ++.BR \-noleaf . ++The ++.B \-follow ++option affects only those tests which ++appear after it on the command line. Unless the ++.B \-H ++or ++.B \-L ++option has ++been specified, the position of the ++.B \-follow ++option changes the behaviour of the ++.B \-newer ++predicate; any files listed as the argument ++of ++.B \-newer ++will be dereferenced if they are symbolic links. The same ++consideration applies to ++.BR \-newerXY , ++.B \-anewer ++and ++.BR \-cnewer . ++Similarly, the ++.B \-type ++predicate will always match against the type of the file ++that a symbolic link points to rather than the link itself. Using ++.B \-follow ++causes the ++.B \-lname and ++.B \-ilname ++predicates always to return false. ++ ++.IP "\-help, \-\-help" ++Print a summary of the command-line usage of ++.B find ++and exit. ++ ++.IP \-ignore_readdir_race ++Normally, \fBfind\fR will emit an error message when it fails to stat a file. ++If you give this option and a file is deleted between the time \fBfind\fR ++reads the name of the file from the directory and the time it tries to stat ++the file, no error message will be issued. This also applies to files ++or directories whose names are given on the command line. This option takes ++effect at the time the command line is read, which means that you cannot search ++one part of the filesystem with this option on and part of it with this option ++off (if you need to do that, you will need to issue two \fBfind\fR commands ++instead, one with the option and one without it). ++ ++.IP "\-maxdepth \fIlevels\fR" ++Descend at most \fIlevels\fR (a non-negative integer) levels of ++directories below the command line arguments. ++.B \-maxdepth 0 ++ means only apply the tests and actions to the command line arguments. ++ ++.IP "\-mindepth \fIlevels\fR" ++Do not apply any tests or actions at levels less than \fIlevels\fR (a ++non-negative integer). ++.B \-mindepth 1 ++means process all files except the command line arguments. ++ ++.IP \-mount ++Don't descend directories on other filesystems. An alternate name for ++.BR \-xdev , ++for compatibility with some other versions of ++.BR find . ++ ++.IP \-noignore_readdir_race ++Turns off the effect of ++.BR \-ignore_readdir_race . ++ ++.IP "\-noleaf" ++Do not optimize by assuming that directories contain 2 fewer ++subdirectories than their hard link count. This option is needed when ++searching filesystems that do not follow the Unix directory-link ++convention, such as CD-ROM or MS-DOS filesystems or AFS volume mount ++points. Each directory on a normal Unix filesystem has at least 2 ++hard links: its name and its `.' entry. Additionally, its ++subdirectories (if any) each have a `..' entry linked to that ++directory. When ++.B find ++is examining a directory, after it has statted 2 fewer subdirectories ++than the directory's link count, it knows that the rest of the entries ++in the directory are non-directories (`leaf' files in the directory ++tree). If only the files' names need to be examined, there is no need ++to stat them; this gives a significant increase in search speed. ++ ++.IP "\-regextype \fItype\fR" ++Changes the regular expression syntax understood by ++.B \-regex ++and ++.B \-iregex ++tests which occur later on the command line. Currently-implemented ++types are emacs (this is the default), posix-awk, posix-basic, ++posix-egrep and posix-extended. ++ ++.IP "\-version, \-\-version" ++Print the \fBfind\fR version number and exit. ++ ++.IP "\-warn, \-nowarn" ++Turn warning messages on or off. These warnings apply only to the ++command line usage, not to any conditions that ++.B find ++might encounter when it searches directories. The default behaviour ++corresponds to ++.B \-warn ++if standard input is a tty, and to ++.B \-nowarn ++otherwise. ++ ++.IP \-xdev ++Don't descend directories on other filesystems. ++ ++.SS TESTS ++Some tests, for example ++.B \-newerXY ++and ++.BR -samefile , ++allow comparison between the file currently being examined and some ++reference file specified on the command line. When these tests are ++used, the interpretation of the reference file is determined by the ++options ++.BR \-H , ++.B \-L ++and ++.B \-P ++and any previous ++.BR \-follow , ++but the reference file is only examined once, at the time the command ++line is parsed. If the reference file cannot be examined (for ++example, the ++.BR stat (2) ++system call fails for it), an error message is issued, and ++.B find ++exits with a nonzero status. ++.P ++Numeric arguments can be specified as ++.IP \fI+n\fP ++for greater than ++.IR n , ++.IP \fI\-n\fP ++for less than ++.IR n , ++.IP \fIn\fP ++for exactly ++.IR n . ++.P ++ ++.IP "\-amin \fIn\fR" ++File was last accessed \fIn\fR minutes ago. ++ ++.IP "\-anewer \fIfile\fR" ++File was last accessed more recently than \fIfile\fR was modified. If ++\fIfile\fR is a symbolic link and the ++.B \-H ++option or the ++.B \-L ++option is in effect, the access time of the file it points to is ++always used. ++ ++.IP "\-atime \fIn\fR" ++File was last accessed \fIn\fR*24 hours ago. ++When find figures out how many 24-hour periods ago the file ++was last accessed, any fractional part is ignored, so to match ++.B \-atime ++.BR +1 , ++a file has to have been accessed at least ++.I two ++days ago. ++ ++.IP "\-cmin \fIn\fR" ++File's status was last changed \fIn\fR minutes ago. ++ ++.IP "\-cnewer \fIfile\fR" ++File's status was last changed more recently than \fIfile\fR was ++modified. If \fIfile\fR is a symbolic link and the ++.B \-H ++option or the ++.B \-L ++option is in effect, the status-change time of the file it points ++to is always used. ++ ++.IP "\-ctime \fIn\fR" ++File's status was last changed \fIn\fR*24 hours ago. ++See the comments for ++.B \-atime ++to understand how rounding affects the interpretation of file status ++change times. ++ ++.IP \-empty ++File is empty and is either a regular file or a directory. ++ ++.IP \-executable ++Matches files which are executable and directories which are ++searchable (in a file name resolution sense). This takes into account ++access control lists and other permissions artefacts which the ++.B \-perm ++test ignores. This test makes use of the ++.BR access (2) ++system call, and so can be fooled by NFS servers which do UID ++mapping (or root-squashing), since many systems implement ++.BR access (2) ++in the client's kernel and so cannot make use of the UID mapping ++information held on the server. Because this test is based only on ++the result of the ++.BR access (2) ++system call, there is no guarantee that a file for which this test ++succeeds can actually be executed. ++ ++.IP \-false ++Always false. ++ ++.IP "\-fstype \fItype\fR" ++File is on a filesystem of type \fItype\fR. The valid filesystem ++types vary among different versions of Unix; an incomplete list of ++filesystem types that are accepted on some version of Unix or another ++is: ufs, 4.2, 4.3, nfs, tmp, mfs, S51K, S52K. You can use ++.B \-printf ++with the %F directive to see the types of your filesystems. ++ ++.IP "\-gid \fIn\fR" ++File's numeric group ID is \fIn\fR. ++ ++.IP "\-group \fIgname\fR" ++File belongs to group \fIgname\fR (numeric group ID allowed). ++ ++.IP "\-ilname \fIpattern\fR" ++Like ++.BR \-lname , ++but the match is case insensitive. ++If the ++.B \-L ++option or the ++.B \-follow ++option is in effect, this test returns false unless the symbolic link ++is broken. ++ ++.IP "\-iname \fIpattern\fR" ++Like ++.BR \-name , ++but the match is case insensitive. For example, the ++patterns `fo*' and `F??' match the file names `Foo', `FOO', `foo', ++`fOo', etc. In these patterns, unlike filename expansion by the ++shell, an initial '.' can be matched by `*'. That is, ++.B find \-name *bar ++will match the file `.foobar'. Please note that you should quote ++patterns as a matter of course, otherwise the shell will expand any ++wildcard characters in them. ++ ++.IP "\-inum \fIn\fR" ++File has inode number \fIn\fR. It is normally easier to use the ++.B \-samefile ++test instead. ++ ++.IP "\-ipath \fIpattern\fR" ++Behaves in the same way as ++.BR \-iwholename . ++This option is deprecated, so please do not use it. ++ ++.IP "\-iregex \fIpattern\fR" ++Like ++.BR \-regex , ++but the match is case insensitive. ++ ++.IP "\-iwholename \fIpattern\fR" ++Like ++.BR \-wholename , ++but the match is case insensitive. ++ ++.IP "\-links \fIn\fR" ++File has \fIn\fR links. ++ ++.IP "\-lname \fIpattern\fR" ++File is a symbolic link whose contents match shell pattern ++\fIpattern\fR. The metacharacters do not treat `/' or `.' specially. ++If the ++.B \-L ++option or the ++.B \-follow ++option is in effect, this test returns false unless the symbolic link ++is broken. ++ ++.IP "\-mmin \fIn\fR" ++File's data was last modified \fIn\fR minutes ago. ++ ++.IP "\-mtime \fIn\fR" ++File's data was last modified \fIn\fR*24 hours ago. ++See the comments for ++.B \-atime ++to understand how rounding affects the interpretation of file ++modification times. ++ ++.IP "\-name \fIpattern\fR" ++Base of file name (the path with the leading directories removed) ++matches shell pattern \fIpattern\fR. The metacharacters (`*', `?', ++and `[]') match a `.' at the start of the base name (this is a change ++in findutils-4.2.2; see section STANDARDS CONFORMANCE below). To ignore a ++directory and the files under it, use ++.BR \-prune ; ++see an example in the ++description of ++.BR \-path . ++Braces are not recognised as being ++special, despite the fact that some shells including Bash imbue braces ++with a special meaning in shell patterns. The filename matching is ++performed with the use of the ++.BR fnmatch (3) ++library function. Don't forget to enclose the pattern in quotes ++in order to protect it from expansion by the shell. ++ ++.IP "\-newer \fIfile\fR" ++File was modified more recently than \fIfile\fR. If \fIfile\fR is a ++symbolic link and the ++.B \-H ++option or the ++.B \-L ++option is in effect, the ++modification time of the file it points to is always used. ++ ++.IP "\-newerXY \fIreference\fR" ++Compares the timestamp of the current file with \fIreference\fR. ++The ++.I reference ++argument is normally the name of a file (and one of its timestamps is ++used for the comparison) but it may also be a string describing an ++absolute time. ++.I X ++and ++.I Y ++are placeholders for other letters, and these letters select which ++time belonging to ++how ++.I reference ++is used for the comparison. ++.TS ++ll ++ll ++ll ++ll ++llw(2i). ++a The access time of the file \fIreference\fR ++B The birth time of the file \fIreference\fR ++c The inode status change time of \fIreference\fR ++m The modification time of the file \fIreference\fR ++t \fIreference\fR is interpreted directly as a time ++.TE ++ ++Some combinations are invalid; for example, it is invalid for ++.I X ++to be ++.IR t . ++Some combinations are not implemented on all systems; for example ++.I B ++is not supported on all systems. If an invalid or unsupported ++combination of ++.I XY ++is specified, a fatal error results. Time specifications are ++interpreted as for the argument to the ++.B \-d ++option of GNU ++.BR date . ++If you try to use the birth time of a reference file, and the birth ++time cannot be determined, a fatal error message results. If you ++specify a test which refers to the birth time of files being examined, ++this test will fail for any files where the birth time is unknown. ++ ++.IP \-nogroup ++No group corresponds to file's numeric group ID. ++ ++.IP \-nouser ++No user corresponds to file's numeric user ID. ++ ++.IP "\-path \fIpattern\fR" ++File name matches shell pattern \fIpattern\fR. The metacharacters do ++not treat `/' or `.' specially; so, for example, ++.br ++.in +1i ++find . \-path "./sr*sc" ++.br ++.in -1i ++will print an entry for a directory called `./src/misc' (if one ++exists). To ignore a whole directory tree, use ++.B \-prune ++rather than ++checking every file in the tree. For example, to skip the ++directory `src/emacs' and all files and directories under it, and ++print the names of the other files found, do something like this: ++.br ++.in +1i ++find . \-path ./src/emacs \-prune \-o \-print ++.br ++.in -1i ++Note that the pattern match test applies to the whole file name, ++starting from one of the start points named on the command line. It ++would only make sense to use an absolute path name here if the ++relevant start point is also an absolute path. This means that this ++command will never match anything: ++.br ++.in +1i ++find bar \-path /foo/bar/myfile \-print ++.br ++.in -1i ++The predicate ++.B \-path ++is also supported by HP-UX ++.B find ++and will be in a forthcoming version of the POSIX standard. ++ ++.IP "\-perm \fImode\fR" ++File's permission bits are exactly \fImode\fR (octal or symbolic). ++Since an exact match is required, if you want to use this form for ++symbolic modes, you may have to specify a rather complex mode string. ++For example ++.B \-perm g=w ++will only match files which have mode 0020 ++(that is, ones for which group write permission is the only permission ++set). It is more likely that you will want to use the `/' or `-' ++forms, for example ++.BR "\-perm \-g=w" , ++which matches any file with group write permission. See the ++.B EXAMPLES ++section for some illustrative examples. ++ ++.IP "\-perm \-\fImode\fR" ++All of the permission bits \fImode\fR are set for the file. ++Symbolic modes are accepted in this form, and this is usually the way ++in which would want to use them. You must specify `u', `g' or `o' if ++you use a symbolic mode. See the ++.B EXAMPLES ++section for some illustrative examples. ++ ++.IP "\-perm /\fImode\fR" ++Any of the permission bits \fImode\fR are set for the file. Symbolic ++modes are accepted in this form. You must specify `u', `g' or `o' if ++you use a symbolic mode. See the ++.B EXAMPLES ++section for some illustrative examples. If no permission bits in ++.I mode ++are set, this test currently matches no files. However, it will soon ++be changed to match any file (the idea is to be more consistent with ++the behaviour of ++.B \-perm ++.BR \-000 ). ++ ++.IP "\-perm +\fImode\fR" ++Deprecated, old way of searching for files with any of the permission ++bits in \fImode\fR set. You should use ++.B \-perm \fI/mode\fR ++instead. Trying to use the `+' syntax with symbolic modes will yield ++surprising results. For example, `+u+x' is a valid symbolic mode ++(equivalent to +u,+x, i.e. 0111) and will therefore not be evaluated ++as ++.B \-perm +\fImode\fR ++but instead as the exact mode specifier ++.B \-perm \fImode\fR ++and so it matches files with exact permissions 0111 instead of files with any ++execute bit set. If you found this paragraph confusing, you're not ++alone - just use ++.B \-perm /\fImode\fR. ++This form of the ++.B \-perm ++test is deprecated because the POSIX specification requires the ++interpretation of a leading `+' as being part of a symbolic mode, and ++so we switched to using `/' instead. ++ ++.IP \-readable ++Matches files which are readable. This takes into account access ++control lists and other permissions artefacts which the ++.B \-perm ++test ignores. This test makes use of the ++.BR access (2) ++system call, and so can be fooled by NFS servers which do UID ++mapping (or root-squashing), since many systems implement ++.BR access (2) ++in the client's kernel and so cannot make use of the UID mapping ++information held on the server. ++ ++.IP "\-regex \fIpattern\fR" ++File name matches regular expression \fIpattern\fR. This is a match ++on the whole path, not a search. For example, to match a file named ++`./fubar3', you can use the regular expression `.*bar.' or `.*b.*3', ++but not `f.*r3'. The regular expressions understood by ++.B find ++are by default Emacs Regular Expressions, but this can be ++changed with the ++.B \-regextype ++option. ++ ++.IP "\-samefile \fIname\fR" ++File refers to the same inode as \fIname\fR. When ++.B \-L ++is in effect, this can include symbolic links. ++ ++.IP "\-size \fIn\fR[cwbkMG]" ++File uses \fIn\fP units of space. The following suffixes ++can be used: ++.RS ++.IP `b' ++for 512-byte blocks (this is the default if no suffix is used) ++.IP `c' ++for bytes ++.IP `w' ++for two-byte words ++.IP `k' ++for Kilobytes (units of 1024 bytes) ++.IP `M' ++for Megabytes (units of 1048576 bytes) ++.IP `G' ++for Gigabytes (units of 1073741824 bytes) ++.RE ++.IP ++The size does not count indirect blocks, but it does count blocks in ++sparse files that are not actually allocated. Bear in mind that the ++`%k' and `%b' format specifiers of ++.B \-printf ++handle sparse files ++differently. The `b' suffix always denotes 512-byte blocks and never ++1 Kilobyte blocks, which is different to the behaviour of ++.BR \-ls . ++ ++.IP \-true ++Always true. ++ ++.IP "\-type \fIc\fR" ++File is of type \fIc\fR: ++.RS ++.IP b ++block (buffered) special ++.IP c ++character (unbuffered) special ++.IP d ++directory ++.IP p ++named pipe (FIFO) ++.IP f ++regular file ++.IP l ++symbolic link; this is never true if the ++.B \-L ++option or the ++.B \-follow ++option is in effect, unless the symbolic link is broken. If you want ++to search for symbolic links when ++.B \-L ++is in effect, use ++.BR \-xtype . ++.IP s ++socket ++.IP D ++door (Solaris) ++.RE ++.IP "\-uid \fIn\fR" ++File's numeric user ID is \fIn\fR. ++ ++.IP "\-used \fIn\fR" ++File was last accessed \fIn\fR days after its status was last changed. ++ ++.IP "\-user \fIuname\fR" ++File is owned by user \fIuname\fR (numeric user ID allowed). ++ ++.IP "\-wholename \fIpattern\fR" ++See \-path. This alternative is less portable than ++.BR \-path . ++ ++.IP "\-writable" ++Matches files which are writable. This takes into account access ++control lists and other permissions artefacts which the ++.B \-perm ++test ignores. This test makes use of the ++.BR access (2) ++system call, and so can be fooled by NFS servers which do UID ++mapping (or root-squashing), since many systems implement ++.BR access (2) ++in the client's kernel and so cannot make use of the UID mapping ++information held on the server. ++ ++.IP "\-xtype \fIc\fR" ++The same as ++.B \-type ++unless the file is a symbolic link. For symbolic ++links: if the ++.B \-H ++or ++.B \-P ++option was specified, true if the file is a ++link to a file of type \fIc\fR; if the ++.B \-L ++option has been given, true ++if \fIc\fR is `l'. In other words, for symbolic links, ++.B \-xtype ++checks the type of the file that ++.B \-type ++does not check. ++ ++.SS ACTIONS ++.IP "\-delete\fR" ++Delete files; true if removal succeeded. If the removal failed, an ++error message is issued. ++If ++.B \-delete ++fails, ++.BR find 's ++exit status will be nonzero ++(when it eventually exits). ++Use of ++.B \-delete ++automatically turns on the ++.B \-depth ++option. ++ ++.BR Warnings : ++Don't forget that the find command line is ++evaluated as an expression, so putting ++.B \-delete ++first will make ++.B find ++try to delete everything below the starting points you specified. ++When testing a ++.B find ++command line that you later intend to use with ++.BR \-delete , ++you should explicitly specify ++.B \-depth ++in order to avoid later surprises. Because ++.B \-delete ++implies ++.BR \-depth , ++you cannot usefully use ++.B \-prune ++and ++.B \-delete ++together. ++ ++.IP "\-exec \fIcommand\fR ;" ++Execute \fIcommand\fR; true if 0 status is returned. All following ++arguments to ++.B find ++are taken to be arguments to the command until an argument consisting ++of `;' is encountered. The string `{}' is replaced by the current ++file name being processed everywhere it occurs in the arguments to the ++command, not just in arguments where it is alone, as in some versions ++of ++.BR find . ++Both of these constructions might need to be escaped (with a `\e') or ++quoted to protect them from expansion by the shell. See the ++.B EXAMPLES ++section for examples of the use of the ++.B \-exec ++option. The specified ++command is run once for each matched file. ++The command is executed in the starting directory. There are ++unavoidable security problems surrounding use of the ++.B \-exec ++action; ++you should use the ++.B \-execdir ++option instead. ++ ++.IP "\-exec \fIcommand\fR {} +" ++This variant of the ++.B \-exec ++action runs the specified command on the ++selected files, but the command line is built by appending each ++selected file name at the end; the total number of invocations of the ++command will be much less than the number of matched files. The ++command line is built in much the same way that ++.B xargs ++builds its command lines. Only one instance of `{}' is allowed within ++the command. The command is executed in the starting directory. ++ ++.IP "\-execdir \fIcommand\fR ;" ++.IP "\-execdir \fIcommand\fR {} +" ++Like ++.BR \-exec , ++but the specified command is run from the subdirectory ++containing the matched file, which is not normally the directory in ++which you started ++.BR find . ++This a much more secure method for invoking commands, as it avoids ++race conditions during resolution of the paths to the matched files. ++As with the ++.B \-exec ++action, the `+' form of ++.B \-execdir ++will build a ++command line to process more than one matched file, but any given ++invocation of ++.I command ++will only list files that exist in the same subdirectory. If you use ++this option, you must ensure that your ++.B $PATH ++environment variable does not reference `.'; ++otherwise, an attacker can run any commands they like by leaving an ++appropriately-named file in a directory in which you will run ++.BR \-execdir . ++The same applies to having entries in ++.B $PATH ++which are empty or which are not absolute directory names. ++ ++.IP "\-fls \fIfile\fR" ++True; like ++.B \-ls ++but write to \fIfile\fR like ++.BR \-fprint . ++The output file is always created, even if the predicate is never ++matched. ++See the ++.B UNUSUAL FILENAMES ++section for information about how unusual characters in filenames are handled. ++ ++.IP "\-fprint \fIfile\fR" ++True; print the full file name into file \fIfile\fR. If \fIfile\fR ++does not exist when \fBfind\fR is run, it is created; if it does ++exist, it is truncated. The file names ``/dev/stdout'' and ++``/dev/stderr'' are handled specially; they refer to the standard ++output and standard error output, respectively. ++The output file is always created, even if the predicate is never matched. ++See the ++.B UNUSUAL FILENAMES ++section for information about how unusual characters in filenames are handled. ++ ++.IP "\-fprint0 \fIfile\fR" ++True; like ++.B \-print0 ++but write to \fIfile\fR like ++.BR \-fprint . ++The output file is always created, even if the predicate is never matched. ++See the ++.B UNUSUAL FILENAMES ++section for information about how unusual characters in filenames are handled. ++ ++.IP "\-fprintf \fIfile\fR \fIformat\fR" ++True; like ++.B \-printf ++but write to \fIfile\fR like ++.BR \-fprint . ++The output file is always created, even if the predicate is never matched. ++See the ++.B UNUSUAL FILENAMES ++section for information about how unusual characters in filenames are handled. ++ ++.IP \-ls ++True; list current file in ++.B ls \-dils ++format on standard output. ++The block counts are of 1K blocks, unless the environment variable ++POSIXLY_CORRECT is set, in which case 512-byte blocks are used. ++See the ++.B UNUSUAL FILENAMES ++section for information about how unusual characters in filenames are handled. ++ ++.IP "\-ok \fIcommand\fR ;" ++Like ++.B \-exec ++but ask the user first (on the standard input); if the ++response does not start with `y' or `Y', do not run the command, and ++return false. If the command is run, its standard input is redirected ++from ++.BR /dev/null . ++ ++.IP "\-okdir \fIcommand\fR ;" ++Like ++.B \-execdir ++but ask the user first (on the standard input); if the ++response does not start with `y' or `Y', do not run the command, and ++return false. If the command is run, its standard input is redirected ++from ++.BR /dev/null . ++ ++.IP \-print ++True; print the full file name on the standard output, followed by a ++newline. If you are piping the output of ++.B find ++into another program and there is the faintest possibility that the files ++which you are searching for might contain a newline, then you should ++seriously consider using the ++.B \-print0 ++option instead of ++.BR \-print . ++See the ++.B UNUSUAL FILENAMES ++section for information about how unusual characters in filenames are handled. ++ ++.IP \-print0 ++True; print the full file name on the standard output, followed by a ++null character (instead of the newline character that ++.B \-print ++uses). ++This allows file names that contain newlines or other types of white ++space to be correctly interpreted by programs that process the ++\fBfind\fR output. This option corresponds to the ++.B \-0 ++option of ++.BR xargs . ++ ++.IP "\-printf \fIformat\fR" ++True; print \fIformat\fR on the standard output, interpreting `\e' ++escapes and `%' directives. Field widths and precisions can be ++specified as with the `printf' C function. Please note that many of ++the fields are printed as %s rather than %d, and this may mean that ++flags don't work as you might expect. This also means that the `\-' ++flag does work (it forces fields to be left-aligned). Unlike ++.BR \-print , ++.B \-printf ++does not add a newline at the end of the string. The escapes ++and directives are: ++.RS ++.IP \ea ++Alarm bell. ++.IP \eb ++Backspace. ++.IP \ec ++Stop printing from this format immediately and flush the output. ++.IP \ef ++Form feed. ++.IP \en ++Newline. ++.IP \er ++Carriage return. ++.IP \et ++Horizontal tab. ++.IP \ev ++Vertical tab. ++.IP \e\0 ++ASCII NUL. ++.IP \e\e ++A literal backslash (`\e'). ++.IP \eNNN ++The character whose ASCII code is NNN (octal). ++.PP ++A `\e' character followed by any other character is treated as an ++ordinary character, so they both are printed. ++.IP %% ++A literal percent sign. ++.IP %a ++File's last access time in the format returned by the C `ctime' function. ++.IP %A\fIk\fP ++File's last access time in the format specified by \fIk\fR, which is ++either `@' or a directive for the C `strftime' function. The possible ++values for \fIk\fR are listed below; some of them might not be ++available on all systems, due to differences in `strftime' between ++systems. ++.RS ++.IP @ ++seconds since Jan. 1, 1970, 00:00 GMT, with fractional part. ++.PP ++Time fields: ++.IP H ++hour (00..23) ++.IP I ++hour (01..12) ++.IP k ++hour ( 0..23) ++.IP l ++hour ( 1..12) ++.IP M ++minute (00..59) ++.IP p ++locale's AM or PM ++.IP r ++time, 12-hour (hh:mm:ss [AP]M) ++.IP S ++Second (00.00 .. 61.00). There is a fractional part. ++.IP T ++time, 24-hour (hh:mm:ss) ++.IP + ++Date and time, separated by `+', for example ++`2004\-04\-28+22:22:05.0'. This is a GNU extension. The time is ++given in the current timezone (which may be affected by setting the TZ ++environment variable). The seconds field includes a fractional part. ++.IP X ++locale's time representation (H:M:S) ++.IP Z ++time zone (e.g., EDT), or nothing if no time zone is determinable ++.PP ++Date fields: ++.IP a ++locale's abbreviated weekday name (Sun..Sat) ++.IP A ++locale's full weekday name, variable length (Sunday..Saturday) ++.IP b ++locale's abbreviated month name (Jan..Dec) ++.IP B ++locale's full month name, variable length (January..December) ++.IP c ++locale's date and time (Sat Nov 04 12:02:33 EST 1989). The format is ++the same as for ++.BR ctime (3) ++and so to preserve compatibility with that format, there is no fractional part ++in the seconds field. ++.IP d ++day of month (01..31) ++.IP D ++date (mm/dd/yy) ++.IP h ++same as b ++.IP j ++day of year (001..366) ++.IP m ++month (01..12) ++.IP U ++week number of year with Sunday as first day of week (00..53) ++.IP w ++day of week (0..6) ++.IP W ++week number of year with Monday as first day of week (00..53) ++.IP x ++locale's date representation (mm/dd/yy) ++.IP y ++last two digits of year (00..99) ++.IP Y ++year (1970...) ++.RE ++.IP %b ++The amount of disk space used for this file in 512-byte blocks. Since disk ++space is allocated in multiples of the filesystem block size this is usually ++greater than %s/512, but it can also be smaller if the file is a sparse file. ++.IP %c ++File's last status change time in the format returned by the C `ctime' ++function. ++.IP %C\fIk\fP ++File's last status change time in the format specified by \fIk\fR, ++which is the same as for %A. ++.IP %d ++File's depth in the directory tree; 0 means the file is a command line ++argument. ++.IP %D ++The device number on which the file exists (the st_dev field of struct ++stat), in decimal. ++.IP %f ++File's name with any leading directories removed (only the last element). ++.IP %F ++Type of the filesystem the file is on; this value can be used for ++\-fstype. ++.IP %g ++File's group name, or numeric group ID if the group has no name. ++.IP %G ++File's numeric group ID. ++.IP %h ++Leading directories of file's name (all but the last element). ++If the file name contains no slashes (since it is in the current ++directory) the %h specifier expands to ".". ++.IP %H ++Command line argument under which file was found. ++.IP %i ++File's inode number (in decimal). ++.IP %k ++The amount of disk space used for this file in 1K blocks. Since disk space is ++allocated in multiples of the filesystem block size this is usually greater ++than %s/1024, but it can also be smaller if the file is a sparse file. ++.IP %l ++Object of symbolic link (empty string if file is not a symbolic link). ++.IP %m ++File's permission bits (in octal). This option uses the `traditional' ++numbers which most Unix implementations use, but if your particular ++implementation uses an unusual ordering of octal permissions bits, you ++will see a difference between the actual value of the file's mode and ++the output of %m. Normally you will want to have a leading ++zero on this number, and to do this, you should use the ++.B # ++flag (as in, for example, `%#m'). ++.IP %M ++File's permissions (in symbolic form, as for ++.BR ls ). ++This directive is supported in findutils 4.2.5 and later. ++.IP %n ++Number of hard links to file. ++.IP %p ++File's name. ++.IP %P ++File's name with the name of the command line argument under which ++it was found removed. ++.IP %s ++File's size in bytes. ++.IP %S ++File's sparseness. This is calculated as (BLOCKSIZE*st_blocks / ++st_size). The exact value you will get for an ordinary file of a ++certain length is system-dependent. However, normally sparse files ++will have values less than 1.0, and files which use indirect blocks ++may have a value which is greater than 1.0. The value used for ++BLOCKSIZE is system-dependent, but is usually 512 bytes. If the file ++size is zero, the value printed is undefined. On systems which lack ++support for st_blocks, a file's sparseness is assumed to be 1.0. ++.IP %t ++File's last modification time in the format returned by the C `ctime' ++function. ++.IP %T\fIk\fP ++File's last modification time in the format specified by \fIk\fR, ++which is the same as for %A. ++.IP %u ++File's user name, or numeric user ID if the user has no name. ++.IP %U ++File's numeric user ID. ++.IP %y ++File's type (like in ++.BR "ls \-l" ), ++U=unknown type (shouldn't happen) ++.IP %Y ++File's type (like %y), plus follow symlinks: L=loop, N=nonexistent ++.PP ++A `%' character followed by any other character is discarded, but the ++other character is printed (don't rely on this, as further format ++characters may be introduced). A `%' at the end of the format ++argument causes undefined behaviour since there is no following ++character. In some locales, it may hide your door keys, while in ++others it may remove the final page from the novel you are reading. ++ ++The %m and %d directives support the ++.B # ++, ++.B 0 ++and ++.B + ++flags, but the other directives do not, even if they ++print numbers. Numeric directives that do not support these flags ++include ++.BR G , ++.BR U , ++.BR b , ++.BR D , ++.B k ++and ++.BR n . ++The `\-' format flag is supported and changes the alignment of a field ++from right-justified (which is the default) to left-justified. ++.PP ++See the ++.B UNUSUAL FILENAMES ++section for information about how unusual characters in filenames are handled. ++ ++ ++.RE ++.IP \-prune ++True; if the file is a directory, do not descend into it. If ++.B \-depth ++is given, false; no effect. Because ++.B \-delete ++implies ++.BR \-depth , ++you cannot usefully use ++.B \-prune ++and ++.B \-delete together. ++ ++.IP "\-quit" ++Exit immediately. No child processes will be left running, but no more ++paths specified on the command line will be processed. For example, ++.B find /tmp/foo /tmp/bar \-print \-quit ++will print only ++.BR /tmp/foo . ++Any command lines which have been built up with ++.B \-execdir ... {} + ++will be invoked before ++.B find ++exits. The exit status may or may not be zero, depending on whether ++an error has already occurred. ++ ++.SS UNUSUAL FILENAMES ++Many of the actions of ++.B find ++result in the printing of data which is under the control of other ++users. This includes file names, sizes, modification times and so ++forth. File names are a potential problem since they can contain any ++character except `\e0' and `/'. Unusual characters in file names can ++do unexpected and often undesirable things to your terminal (for ++example, changing the settings of your function keys on some ++terminals). Unusual characters are handled differently by various ++actions, as described below. ++ ++.IP "\-print0, \-fprint0\" ++Always print the exact filename, unchanged, even if the output is ++going to a terminal. ++ ++.IP "\-ls, \-fls" ++Unusual characters are always escaped. White space, backslash, and ++double quote characters are printed using C-style escaping (for ++example `\ef', `\e"'). Other unusual characters are printed using an ++octal escape. Other printable characters (for ++.B \-ls ++and ++.B \-fls ++these are the characters between octal 041 and 0176) are printed as-is. ++ ++.IP "\-printf, \-fprintf" ++If the output is not going to a terminal, it is printed as-is. ++Otherwise, the result depends on which directive is in use. The ++directives %D, %F, %g, %G, %H, %Y, and %y expand to values which are ++not under control of files' owners, and so are printed as-is. The ++directives %a, %b, %c, %d, %i, %k, %m, %M, %n, %s, %t, %u and %U have ++values which are under the control of files' owners but which cannot ++be used to send arbitrary data to the terminal, and so these are ++printed as-is. The directives %f, %h, %l, %p and %P are quoted. This ++quoting is performed in the same way as for GNU ++.BR ls . ++This is not the same quoting mechanism as the one used for ++.B \-ls ++and ++.BR \-fls . ++If you are able to decide what format to use for the output of ++.B find ++then it is normally better to use `\e0' as a terminator ++than to use newline, as file names can contain white space and newline ++characters. ++ ++.IP "\-print, \-fprint" ++Quoting is handled in the same way as for ++.B \-printf ++and ++.BR \-fprintf . ++If you are using ++.B find ++in a script or in a situation where the matched files might have ++arbitrary names, you should consider using ++.B \-print0 ++instead of ++.BR \-print . ++.P ++The ++.B \-ok ++and ++.B \-okdir ++actions print the current filename as-is. This may change in a future release. ++.SS OPERATORS ++.P ++Listed in order of decreasing precedence: ++ ++.IP "( \fIexpr\fR )" ++Force precedence. Since parentheses are special to the shell, you ++will normally need to quote them. Many of the examples in this manual ++page use backslashes for this purpose: `\e(...\e)' instead of `(...)'. ++ ++.IP "! \fIexpr\fR" ++True if \fIexpr\fR is false. This character will also usually need ++protection from interpretation by the shell. ++ ++.IP "\-not \fIexpr\fR" ++Same as ! \fIexpr\fR, but not POSIX compliant. ++ ++.IP "\fIexpr1 expr2\fR" ++Two expressions in a row are taken to be joined with an ++implied "and"; \fIexpr2\fR is not evaluated if \fIexpr1\fR is false. ++ ++.IP "\fIexpr1\fR \-a \fIexpr2\fR" ++Same as \fIexpr1 expr2\fR. ++ ++.IP "\fIexpr1\fR \-and \fIexpr2\fR" ++Same as \fIexpr1 expr2\fR, but not POSIX compliant. ++ ++.IP "\fIexpr1\fR \-o \fIexpr2\fR" ++Or; \fIexpr2\fR is not evaluated if \fIexpr1\fR is true. ++ ++.IP "\fIexpr1\fR \-or \fIexpr2\fR" ++Same as \fIexpr1\fR ++.B \-o ++\fIexpr2\fR, but not POSIX compliant. ++ ++.IP "\fIexpr1\fR , \fIexpr2\fR" ++List; both \fIexpr1\fR and \fIexpr2\fR are always evaluated. The ++value of \fIexpr1\fR is discarded; the value of the list is the value ++of \fIexpr2\fR. The comma operator can be useful for searching for ++several different types of thing, but traversing the filesystem ++hierarchy only once. The ++.B \-fprintf ++action can be used to list the various matched items into several ++different output files. ++ ++ ++.SH "STANDARDS CONFORMANCE" ++For closest compliance to the POSIX standard, you should set the ++POSIXLY_CORRECT environment variable. The following options are ++specified in the POSIX standard (IEEE Std 1003.1, 2003 Edition): ++ ++.IP \fB\-H\fR ++This option is supported. ++ ++.IP \fB\-L\fR ++This option is supported. ++ ++.IP \fB\-name\fR ++This option is supported, but POSIX conformance depends on the ++POSIX conformance of the system's ++.BR fnmatch (3) ++library function. As of findutils-4.2.2, shell metacharacters ++(`*', `?' or `[]' for example) will match a leading `.', because ++IEEE PASC interpretation 126 requires this. This is a change from ++previous versions of findutils. ++ ++.IP \fB\-type\fR ++Supported. POSIX specifies `b', `c', `d', `l', `p', `f' and `s'. ++GNU find also supports `D', representing a Door, where the OS provides these. ++ ++.IP \fB\-ok\fR ++Supported. Interpretation of the response is not locale-dependent ++(see ENVIRONMENT VARIABLES). ++ ++.IP \fB\-newer\fR ++Supported. If the file specified is a symbolic link, it is always ++dereferenced. This is a change from previous behaviour, which used to ++take the relevant time from the symbolic link; see the HISTORY section ++below. ++ ++.IP \fB\-perm\fR ++Supported. If the POSIXLY_CORRECT environment variable is not set, ++some mode arguments (for example +a+x) which are not valid in POSIX ++are supported for backward-compatibility. ++ ++.IP "Other predicates" ++The predicates ++.BR \-atime , ++.BR \-ctime , ++.BR \-depth , ++.BR \-group , ++.BR \-links , ++.BR \-mtime , ++.BR \-nogroup , ++.BR \-nouser , ++.BR \-print , ++.BR \-prune , ++.BR \-size , ++.BR \-user ++and ++.B \-xdev ++are all supported. ++ ++.P ++The POSIX standard specifies parentheses `(', `)', negation `!' and the ++`and' and `or' operators ( ++.BR \-a , ++.BR \-o ). ++.P ++All other options, predicates, expressions and so forth are extensions ++beyond the POSIX standard. Many of these extensions are not unique to ++GNU find, however. ++.P ++The POSIX standard requires that ++.B find ++detects loops: ++.IP ++The ++.B find ++utility shall detect infinite loops; that is, entering a ++previously visited directory that is an ancestor of the last file ++encountered. When it detects an infinite loop, find shall write a ++diagnostic message to standard error and shall either recover its ++position in the hierarchy or terminate. ++.P ++GNU ++.B find ++complies with these requirements. The link count of ++directories which contain entries which are hard links to an ancestor ++will often be lower than they otherwise should be. This can mean that ++GNU find will sometimes optimise away the visiting of a subdirectory ++which is actually a link to an ancestor. Since ++.B find ++does not actually enter such a subdirectory, it is allowed to avoid ++emitting a diagnostic message. Although this behaviour may be ++somewhat confusing, it is unlikely that anybody actually depends on ++this behaviour. If the leaf optimisation has been turned off with ++.BR \-noleaf , ++the directory entry will always be examined and the diagnostic message ++will be issued where it is appropriate. Symbolic links cannot be used ++to create filesystem cycles as such, but if the ++.B \-L ++option or the ++.B \-follow ++option is in use, a diagnostic message is issued when ++.B find ++encounters a loop of symbolic links. As with loops containing hard ++links, the leaf optimisation will often mean that ++.B find ++knows that it doesn't need to call ++.I stat() ++or ++.I chdir() ++on the symbolic link, so this diagnostic is frequently not necessary. ++.P ++The ++.B \-d ++option is supported for compatibility with various BSD systems, ++but you should use the POSIX-compliant option ++.B \-depth ++instead. ++.P ++The POSIXLY_CORRECT environment variable does not affect the behaviour ++of the ++.B \-regex ++or ++.B \-iregex ++tests because those tests aren't specified in the POSIX standard. ++.SH "ENVIRONMENT VARIABLES" ++ ++.IP LANG ++Provides a default value for the internationalization variables that ++are unset or null. ++ ++.IP LC_ALL ++If set to a non-empty string value, override the values of all the ++other internationalization variables. ++ ++.IP LC_COLLATE ++The POSIX standard specifies that this variable affects the pattern ++matching to be used for the ++.B \-name ++option. GNU find uses the ++.BR fnmatch (3) ++library function, and so support for `LC_COLLATE' depends on the ++system library. ++ ++.IP ++POSIX also specifies that the `LC_COLLATE' environment ++variable affects the interpretation of the user's response to the ++query issued by ++.BR \-ok' , ++but this is not the case for GNU find. ++ ++.IP LC_CTYPE ++This variable affects the treatment of character classes used with ++the ++.B \-name ++test, if the system's ++.BR fnmatch (3) ++library function supports this. It has no effect on the behaviour ++of the ++.B \-ok ++expression. ++ ++.IP LC_MESSAGES ++Determines the locale to be used for internationalised messages. ++ ++.IP NLSPATH ++Determines the location of the internationalisation message catalogues. ++ ++.IP PATH ++Affects the directories which are searched to find the executables ++invoked by ++.BR \-exec , ++.BR \-execdir , ++.B \-ok ++and ++.BR \-okdir . ++ ++.IP POSIXLY_CORRECT ++Determines the block size used by ++.B \-ls ++and ++.BR \-fls . ++If ++.B POSIXLY_CORRECT ++is set, blocks are units of 512 bytes. Otherwise ++they are units of 1024 bytes. ++.IP ++Setting this variable also turns off ++warning messages (that is, implies ++.BR \-nowarn ) ++by default, because POSIX requires that apart from ++the output for ++.BR \-ok , ++all messages printed on stderr are diagnositcs and must result in a ++non-zero exit status. ++.IP ++When POSIXLY_CORRECT is not set, ++.B \-perm +++zzz ++is treated just like ++.B \-perm ++/zzz ++if +++zzz is not a valid symbolic mode. When POSIXLY_CORRECT is set, such ++constructs are treated as an error. ++ ++.IP TZ ++Affects the time zone used for some of the time-related format ++directives of ++.B \-printf ++and ++.BR \-fprintf . ++.SH "EXAMPLES" ++.nf ++.B find /tmp \-name core \-type f \-print | xargs /bin/rm \-f ++ ++.fi ++Find files named ++.B core ++in or below the directory ++.B /tmp ++and delete them. Note that this will work incorrectly if there are ++any filenames containing newlines, single or double quotes, or spaces. ++.P ++.B find /tmp \-name core \-type f \-print0 | xargs \-0 /bin/rm \-f ++ ++.fi ++Find files named ++.B core ++in or below the directory ++.B /tmp ++and delete them, processing filenames in such a way that file or ++directory names containing single or double quotes, spaces or newlines ++are correctly handled. The ++.B \-name ++test comes before the ++.B \-type ++test in order to avoid having to call ++.B stat(2) ++on every file. ++ ++.P ++.nf ++.B find . \-type f \-exec file \(aq{}\(aq \e\; ++ ++.fi ++Runs `file' on every file in or below the current directory. Notice ++that the braces are enclosed in single quote marks to protect them ++from interpretation as shell script punctuation. The semicolon is ++similarly protected by the use of a backslash, though single quotes ++could have been used in that case also. ++ ++.P ++.nf ++.B find / \e ++.B \e( \-perm \-4000 \-fprintf /root/suid.txt "%#m %u %p\en" \e) , \e ++.B \e( \-size +100M \-fprintf /root/big.txt "%\-10s %p\en" \e) ++ ++.fi ++Traverse the filesystem just once, listing setuid files and ++directories into ++.B /root/suid.txt ++and large files into ++.BR /root/big.txt . ++ ++.P ++.nf ++.B find $HOME \-mtime 0 ++ ++.fi ++Search for files in your home directory which have been modified in ++the last twenty-four hours. This command works this way because the ++time since each file was last modified is divided by 24 hours and any ++remainder is discarded. That means that to match ++.B \-mtime ++.BR 0 , ++a file will have to have a modification in the past which is less than ++24 hours ago. ++ ++.P ++.nf ++.B find /sbin /usr/sbin -executable \e! -readable \-print ++ ++.fi ++Search for files which are executable but not readable. ++ ++.P ++.nf ++.B find . \-perm 664 ++ ++.fi ++Search for files which have read and write permission for their owner, ++and group, but which other users can read but not write to. Files ++which meet these criteria but have other permissions bits set (for ++example if someone can execute the file) will not be matched. ++ ++.P ++.nf ++.B find . \-perm \-664 ++ ++.fi ++Search for files which have read and write permission for their owner ++and group, and which other users can read, without regard to the ++presence of any extra permission bits (for example the executable ++bit). This will match a file which has mode 0777, for example. ++ ++.P ++.nf ++.B find . \-perm /222 ++ ++.fi ++Search for files which are writable by somebody (their owner, or ++their group, or anybody else). ++ ++.P ++.nf ++.B find . \-perm /220 ++.B find . \-perm /u+w,g+w ++.B find . \-perm /u=w,g=w ++ ++.fi ++All three of these commands do the same thing, but the first one uses ++the octal representation of the file mode, and the other two use the ++symbolic form. These commands all search for files which are ++writable by either their owner or their group. The files don't have ++to be writable by both the owner and group to be matched; either will ++do. ++ ++.P ++.nf ++.B find . \-perm \-220 ++.B find . \-perm \-g+w,u+w ++ ++.fi ++Both these commands do the same thing; search for files which are ++writable by both their owner and their group. ++ ++.P ++.nf ++.B find . \-perm \-444 \-perm /222 ! \-perm /111 ++.B find . \-perm \-a+r \-perm /a+w ! \-perm /a+x ++ ++.fi ++These two commands both search for files that are readable for ++everybody ( ++.B \-perm \-444 ++or ++.BR "\-perm \-a+r" ), ++have at least one write bit ++set ( ++.B \-perm /222 ++or ++.BR "\-perm /a+w" ) ++but are not executable for anybody ( ++.B ! \-perm /111 ++and ++.B ! \-perm /a+x ++respectively). ++ ++.P ++.nf ++.B cd /source-dir ++.B find . \-name .snapshot \-prune \-o \e( \e! \-name "*~" \-print0 \e)| ++.B cpio \-pmd0 /dest-dir ++ ++.fi ++This command copies the contents of ++.B /source-dir ++to ++.BR /dest-dir , ++but omits files and directories named ++.B .snapshot ++(and anything in them). It also omits files or directories whose name ++ends in ++.BR ~ , ++but not their contents. The construct ++.B \-prune \-o \e( ... \-print0 \e) ++is quite common. The idea here is that the expression before ++.B \-prune ++matches things which are to be pruned. However, the ++.B \-prune ++action itself returns true, so the following ++.B \-o ++ensures that the right hand side is evaluated only for those ++directories which didn't get pruned (the contents of the pruned ++directories are not even visited, so their contents are irrelevant). ++The expression on the right hand side of the ++.B \-o ++is in parentheses only for clarity. It emphasises that the ++.B \-print0 ++action takes place only for things that didn't have ++.B \-prune ++applied to them. Because the default `and' condition between tests ++binds more tightly than ++.BR \-o , ++this is the default anyway, but the parentheses help to show ++what is going on. ++ ++.SH EXIT STATUS ++.PP ++.B find ++exits with status 0 if all files are processed successfully, greater ++than 0 if errors occur. This is deliberately a very broad ++description, but if the return value is non-zero, you should not rely ++on the correctness of the results of ++.BR find . ++ ++.SH "SEE ALSO" ++\fBlocate\fP(1), \fBlocatedb\fP(5), \fBupdatedb\fP(1), \fBxargs\fP(1), ++\fBchmod\fP(1), \fBfnmatch\fP(3), \fBregex\fP(7), \fBstat\fP(2), ++\fBlstat\fP(2), \fBls\fP(1), \fBprintf\fP(3), \fBstrftime\fP(3), ++\fBctime\fP(3), \fBFinding Files\fP (on-line in Info, or printed). ++.SH "HISTORY" ++As of findutils-4.2.2, shell metacharacters (`*', `?' or `[]' for ++example) used in filename patterns will match a leading `.', because ++IEEE POSIX interpretation 126 requires this. ++.P ++The syntax ++\.B \-perm +MODE ++was deprecated in findutils-4.2.21, in favour of ++\.B \-perm ++.BR /MODE . ++As of findutils-4.3.3, ++.B \-perm /000 ++now matches all files instead of none. ++.P ++Nanosecond-resolution ++timestamps were implemented in findutils-4.3.3. ++.P ++As of findutils-4.3.11, the ++.B \-delete ++action sets ++.BR find 's ++exit status to a nonzero value when it fails. ++However, ++.B find ++will not exit immediately. Previously, ++.BR find 's ++exit status was unaffected by the failure of ++.BR \-delete . ++.TS ++l l l . ++Feature Added in Also occurs in ++\-newerXY 4.3.3 BSD ++\-D 4.3.1 ++\-O 4.3.1 ++\-readable 4.3.0 ++\-writable 4.3.0 ++\-executable 4.3.0 ++\-regextype 4.2.24 ++\-exec ... + 4.2.12 POSIX ++\-execdir 4.2.12 BSD ++\-okdir 4.2.12 ++\-samefile 4.2.11 ++\-H 4.2.5 POSIX ++\-L 4.2.5 POSIX ++\-P 4.2.5 BSD ++\-delete 4.2.3 ++\-quit 4.2.3 ++\-d 4.2.3 BSD ++\-wholename 4.2.0 ++\-iwholename 4.2.0 ++\-ignore_readdir_race 4.2.0 ++\-fls 4.0 ++\-ilname 3.8 ++\-iname 3.8 ++\-ipath 3.8 ++\-iregex 3.8 ++.TE ++.SH "NON-BUGS" ++.nf ++.B $ find . \-name *.c \-print ++find: paths must precede expression ++Usage: find [\-H] [\-L] [\-P] [\-Olevel] [\-D help|tree|search|stat|rates|opt|exec] [path...] [expression] ++.fi ++.P ++This happens because ++.I *.c ++has been expanded by the shell ++resulting in ++.B find ++actually receiving a command line like this: ++.nf ++ ++.B find . \-name bigram.c code.c frcode.c locate.c \-print ++ ++.fi ++That command is of course not going to work. Instead of doing things ++this way, you should enclose the pattern in quotes or escape the wildcard: ++.nf ++.B $ find . \-name \e*.c \-print ++.fi ++ ++.SH "BUGS" ++.P ++There are security problems inherent in the behaviour that the POSIX ++standard specifies for ++.BR find , ++which therefore cannot be fixed. For example, the ++.B \-exec ++action is ++inherently insecure, and ++.B \-execdir ++should be used instead. ++Please see \fBFinding Files\fP for more information. ++.P ++The environment variable ++.B LC_COLLATE ++has no effect on the ++.B \-ok ++action. ++.P ++The best way to report a bug is to use the form at ++http://savannah.gnu.org/bugs/?group=findutils. ++The reason for this is that you will then be able to track progress in ++fixing the problem. Other comments about \fBfind\fP(1) and about ++the findutils package in general can be sent to the ++.I bug\-findutils ++mailing list. To join the list, send email to ++.IR bug\-findutils\-request@gnu.org . +diff -purN findutils-4.3.12.orig/find/find.c findutils-4.3.12/find/find.c +--- findutils-4.3.12.orig/find/find.c 2007-12-19 16:12:34.000000000 -0500 ++++ findutils-4.3.12/find/find.c 2008-01-30 08:46:05.754843619 -0500 +@@ -1270,7 +1270,7 @@ process_path (char *pathname, char *name + static void + process_dir (char *pathname, char *name, int pathlen, const struct stat *statp, char *parent) + { +- int subdirs_left; /* Number of unexamined subdirs in PATHNAME. */ ++ int subdirs_left=0; /* Number of unexamined subdirs in PATHNAME. */ + boolean subdirs_unreliable; /* if true, cannot use dir link count as subdir limif (if false, it may STILL be unreliable) */ + unsigned int idx; /* Which entry are we on? */ + struct stat stat_buf; +diff -purN findutils-4.3.12.orig/find/find.c.orig findutils-4.3.12/find/find.c.orig +--- findutils-4.3.12.orig/find/find.c.orig 1969-12-31 19:00:00.000000000 -0500 ++++ findutils-4.3.12/find/find.c.orig 2007-12-19 16:12:34.000000000 -0500 +@@ -0,0 +1,1537 @@ ++/* find -- search for files in a directory hierarchy ++ Copyright (C) 1990, 91, 92, 93, 94, 2000, ++ 2003, 2004, 2005, 2007 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/>. ++*/ ++/* GNU find was written by Eric Decker <cire@cisco.com>, ++ with enhancements by David MacKenzie <djm@gnu.org>, ++ Jay Plett <jay@silence.princeton.nj.us>, ++ and Tim Wood <axolotl!tim@toad.com>. ++ The idea for -print0 and xargs -0 came from ++ Dan Bernstein <brnstnd@kramden.acf.nyu.edu>. ++ Improvements have been made by James Youngman <jay@gnu.org>. ++*/ ++ ++ ++#include <config.h> ++#include "defs.h" ++ ++#define USE_SAFE_CHDIR 1 ++#undef STAT_MOUNTPOINTS ++ ++ ++#include <errno.h> ++#include <assert.h> ++ ++#include <sys/stat.h> ++#include <fcntl.h> ++#include <openat.h> ++ ++#include "xalloc.h" ++#include "human.h" ++#include "canonicalize.h" ++#include <modetype.h> ++ ++#include "closein.h" ++#include "savedirinfo.h" ++#include "buildcmd.h" ++#include "dirname.h" ++#include "quote.h" ++#include "quotearg.h" ++#include "xgetcwd.h" ++#include "error.h" ++ ++#ifdef HAVE_LOCALE_H ++#include <locale.h> ++#endif ++ ++#if ENABLE_NLS ++# include <libintl.h> ++# define _(Text) gettext (Text) ++#else ++# define _(Text) Text ++#define textdomain(Domain) ++#define bindtextdomain(Package, Directory) ++#define ngettext(singular,plural,n) ((1==n) ? singular : plural) ++#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 ++ ++#ifdef STAT_MOUNTPOINTS ++static void init_mounted_dev_list(int mandatory); ++#endif ++ ++static void process_top_path PARAMS((char *pathname, mode_t mode)); ++static int process_path PARAMS((char *pathname, char *name, boolean leaf, char *parent, mode_t type)); ++static void process_dir PARAMS((char *pathname, char *name, int pathlen, const struct stat *statp, char *parent)); ++ ++ ++ ++/* Name this program was run with. */ ++char *program_name; ++ ++/* A file descriptor open to the initial working directory. ++ Doing it this way allows us to work when the i.w.d. has ++ unreadable parents. */ ++int starting_desc; ++ ++/* The stat buffer of the initial working directory. */ ++static struct stat starting_stat_buf; ++ ++enum ChdirSymlinkHandling ++ { ++ SymlinkHandleDefault, /* Normally the right choice */ ++ SymlinkFollowOk /* see comment in process_top_path() */ ++ }; ++ ++ ++enum TraversalDirection ++ { ++ TraversingUp, ++ TraversingDown ++ }; ++ ++enum WdSanityCheckFatality ++ { ++ FATAL_IF_SANITY_CHECK_FAILS, ++ RETRY_IF_SANITY_CHECK_FAILS, ++ NON_FATAL_IF_SANITY_CHECK_FAILS ++ }; ++ ++ ++int get_current_dirfd(void) ++{ ++ return AT_FDCWD; ++} ++ ++ ++int ++main (int argc, char **argv) ++{ ++ int i; ++ int end_of_leading_options = 0; /* First arg after any -H/-L etc. */ ++ struct predicate *eval_tree; ++ ++ program_name = argv[0]; ++ state.exit_status = 0; ++ ++ /* Set the option defaults before we do the locale ++ * initialisation as check_nofollow() needs to be executed in the ++ * POSIX locale. ++ */ ++ set_option_defaults(&options); ++ ++#ifdef HAVE_SETLOCALE ++ setlocale (LC_ALL, ""); ++#endif ++ bindtextdomain (PACKAGE, LOCALEDIR); ++ textdomain (PACKAGE); ++ atexit (close_stdin); ++ ++ /* Check for -P, -H or -L options. */ ++ end_of_leading_options = process_leading_options(argc, argv); ++ ++ if (options.debug_options & DebugStat) ++ options.xstat = debug_stat; ++ ++#ifdef DEBUG ++ fprintf (stderr, "cur_day_start = %s", ctime (&options.cur_day_start)); ++#endif /* DEBUG */ ++ ++ /* state.cwd_dir_fd has to be initialised before we call build_expression_tree() ++ * because command-line parsing may lead us to stat some files. ++ */ ++ state.cwd_dir_fd = AT_FDCWD; ++ ++ /* We are now processing the part of the "find" command line ++ * after the -H/-L options (if any). ++ */ ++ eval_tree = build_expression_tree(argc, argv, end_of_leading_options); ++ ++ ++ /* safely_chdir() needs to check that it has ended up in the right place. ++ * To avoid bailing out when something gets automounted, it checks if ++ * the target directory appears to have had a directory mounted on it as ++ * we chdir()ed. The problem with this is that in order to notice that ++ * a file system was mounted, we would need to lstat() all the mount points. ++ * That strategy loses if our machine is a client of a dead NFS server. ++ * ++ * Hence if safely_chdir() and wd_sanity_check() can manage without needing ++ * to know the mounted device list, we do that. ++ */ ++ if (!options.open_nofollow_available) ++ { ++#ifdef STAT_MOUNTPOINTS ++ init_mounted_dev_list(0); ++#endif ++ } ++ ++ ++ starting_desc = open (".", O_RDONLY ++#if defined O_LARGEFILE ++ |O_LARGEFILE ++#endif ++ ); ++ if (0 <= starting_desc && fchdir (starting_desc) != 0) ++ { ++ close (starting_desc); ++ starting_desc = -1; ++ } ++ ++ if (starting_desc < 0) ++ { ++ starting_dir = xgetcwd (); ++ if (! starting_dir) ++ error (1, errno, _("cannot get current directory")); ++ } ++ set_stat_placeholders(&starting_stat_buf); ++ if ((*options.xstat) (".", &starting_stat_buf) != 0) ++ error (1, errno, _("cannot stat current directory")); ++ ++ /* If no paths are given, default to ".". */ ++ for (i = end_of_leading_options; i < argc && !looks_like_expression(argv[i], true); i++) ++ { ++ process_top_path (argv[i], 0); ++ } ++ ++ /* If there were no path arguments, default to ".". */ ++ if (i == end_of_leading_options) ++ { ++ /* ++ * We use a temporary variable here because some actions modify ++ * the path temporarily. Hence if we use a string constant, ++ * we get a coredump. The best example of this is if we say ++ * "find -printf %H" (note, not "find . -printf %H"). ++ */ ++ char defaultpath[2] = "."; ++ process_top_path (defaultpath, 0); ++ } ++ ++ /* If "-exec ... {} +" has been used, there may be some ++ * partially-full command lines which have been built, ++ * but which are not yet complete. Execute those now. ++ */ ++ show_success_rates(eval_tree); ++ cleanup(); ++ return state.exit_status; ++} ++ ++boolean is_fts_enabled(int *ftsoptions) ++{ ++ /* this version of find (i.e. this main()) does not use fts. */ ++ *ftsoptions = 0; ++ return false; ++} ++ ++ ++static char * ++specific_dirname(const char *dir) ++{ ++ char dirbuf[1024]; ++ ++ if (0 == strcmp(".", dir)) ++ { ++ /* OK, what's '.'? */ ++ if (NULL != getcwd(dirbuf, sizeof(dirbuf))) ++ { ++ return strdup(dirbuf); ++ } ++ else ++ { ++ return strdup(dir); ++ } ++ } ++ else ++ { ++ char *result = canonicalize_filename_mode(dir, CAN_EXISTING); ++ if (NULL == result) ++ return strdup(dir); ++ else ++ return result; ++ } ++} ++ ++ ++ ++/* Return non-zero if FS is the name of a file system that is likely to ++ * be automounted ++ */ ++static int ++fs_likely_to_be_automounted(const char *fs) ++{ ++ return ( (0==strcmp(fs, "nfs")) || (0==strcmp(fs, "autofs")) || (0==strcmp(fs, "subfs"))); ++} ++ ++ ++ ++#ifdef STAT_MOUNTPOINTS ++static dev_t *mounted_devices = NULL; ++static size_t num_mounted_devices = 0u; ++ ++ ++static void ++init_mounted_dev_list(int mandatory) ++{ ++ assert (NULL == mounted_devices); ++ assert (0 == num_mounted_devices); ++ mounted_devices = get_mounted_devices(&num_mounted_devices); ++ if (mandatory && (NULL == mounted_devices)) ++ { ++ error(1, 0, "Cannot read list of mounted devices."); ++ } ++} ++ ++static void ++refresh_mounted_dev_list(void) ++{ ++ if (mounted_devices) ++ { ++ free(mounted_devices); ++ mounted_devices = 0; ++ } ++ num_mounted_devices = 0u; ++ init_mounted_dev_list(1); ++} ++ ++ ++/* Search for device DEV in the array LIST, which is of size N. */ ++static int ++dev_present(dev_t dev, const dev_t *list, size_t n) ++{ ++ if (list) ++ { ++ while (n-- > 0u) ++ { ++ if ( (*list++) == dev ) ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++enum MountPointStateChange ++ { ++ MountPointRecentlyMounted, ++ MountPointRecentlyUnmounted, ++ MountPointStateUnchanged ++ }; ++ ++ ++ ++static enum MountPointStateChange ++get_mount_state(dev_t newdev) ++{ ++ int new_is_present, new_was_present; ++ ++ new_was_present = dev_present(newdev, mounted_devices, num_mounted_devices); ++ refresh_mounted_dev_list(); ++ new_is_present = dev_present(newdev, mounted_devices, num_mounted_devices); ++ ++ if (new_was_present == new_is_present) ++ return MountPointStateUnchanged; ++ else if (new_is_present) ++ return MountPointRecentlyMounted; ++ else ++ return MountPointRecentlyUnmounted; ++} ++ ++ ++ ++/* We stat()ed a directory, chdir()ed into it (we know this ++ * since direction is TraversingDown), stat()ed it again, ++ * and noticed that the device numbers are different. Check ++ * if the file system was recently mounted. ++ * ++ * If it was, it looks like chdir()ing into the directory ++ * caused a file system to be mounted. Maybe automount is ++ * running. Anyway, that's probably OK - but it happens ++ * only when we are moving downward. ++ * ++ * We also allow for the possibility that a similar thing ++ * has happened with the unmounting of a file system. This ++ * is much rarer, as it relies on an automounter timeout ++ * occurring at exactly the wrong moment. ++ */ ++static enum WdSanityCheckFatality ++dirchange_is_fatal(const char *specific_what, ++ enum WdSanityCheckFatality isfatal, ++ int silent, ++ struct stat *newinfo) ++{ ++ enum MountPointStateChange transition = get_mount_state(newinfo->st_dev); ++ switch (transition) ++ { ++ case MountPointRecentlyUnmounted: ++ isfatal = NON_FATAL_IF_SANITY_CHECK_FAILS; ++ if (!silent) ++ { ++ error (0, 0, ++ _("Warning: file system %s has recently been unmounted."), ++ safely_quote_err_filename(0, specific_what)); ++ } ++ break; ++ ++ case MountPointRecentlyMounted: ++ isfatal = NON_FATAL_IF_SANITY_CHECK_FAILS; ++ if (!silent) ++ { ++ error (0, 0, ++ _("Warning: file system %s has recently been mounted."), ++ safely_quote_err_filename(0, specific_what)); ++ } ++ break; ++ ++ case MountPointStateUnchanged: ++ /* leave isfatal as it is */ ++ break; ++ } ++ ++ return isfatal; ++} ++ ++ ++#endif ++ ++ ++ ++/* Examine the results of the stat() of a directory from before we ++ * entered or left it, with the results of stat()ing it afterward. If ++ * these are different, the file system tree has been modified while we ++ * were traversing it. That might be an attempt to use a race ++ * condition to persuade find to do something it didn't intend ++ * (e.g. an attempt by an ordinary user to exploit the fact that root ++ * sometimes runs find on the whole file system). However, this can ++ * also happen if automount is running (certainly on Solaris). With ++ * automount, moving into a directory can cause a file system to be ++ * mounted there. ++ * ++ * To cope sensibly with this, we will raise an error if we see the ++ * device number change unless we are chdir()ing into a subdirectory, ++ * and the directory we moved into has been mounted or unmounted "recently". ++ * Here "recently" means since we started "find" or we last re-read ++ * the /etc/mnttab file. ++ * ++ * If the device number does not change but the inode does, that is a ++ * problem. ++ * ++ * If the device number and inode are both the same, we are happy. ++ * ++ * If a file system is (un)mounted as we chdir() into the directory, that ++ * may mean that we're now examining a section of the file system that might ++ * have been excluded from consideration (via -prune or -quit for example). ++ * Hence we print a warning message to indicate that the output of find ++ * might be inconsistent due to the change in the file system. ++ */ ++static boolean ++wd_sanity_check(const char *thing_to_stat, ++ const char *progname, ++ const char *what, ++ dev_t old_dev, ++ ino_t old_ino, ++ struct stat *newinfo, ++ int parent, ++ int line_no, ++ enum TraversalDirection direction, ++ enum WdSanityCheckFatality isfatal, ++ boolean *changed) /* output parameter */ ++{ ++ const char *fstype; ++ char *specific_what = NULL; ++ int silent = 0; ++ const char *current_dir = "."; ++ ++ *changed = false; ++ ++ set_stat_placeholders(newinfo); ++ if ((*options.xstat) (current_dir, newinfo) != 0) ++ fatal_file_error(thing_to_stat); ++ ++ if (old_dev != newinfo->st_dev) ++ { ++ *changed = true; ++ specific_what = specific_dirname(what); ++ fstype = filesystem_type(newinfo, current_dir); ++ silent = fs_likely_to_be_automounted(fstype); ++ ++ /* This condition is rare, so once we are here it is ++ * reasonable to perform an expensive computation to ++ * determine if we should continue or fail. ++ */ ++ if (TraversingDown == direction) ++ { ++#ifdef STAT_MOUNTPOINTS ++ isfatal = dirchange_is_fatal(specific_what,isfatal,silent,newinfo); ++#else ++ isfatal = RETRY_IF_SANITY_CHECK_FAILS; ++#endif ++ } ++ ++ switch (isfatal) ++ { ++ case FATAL_IF_SANITY_CHECK_FAILS: ++ { ++ fstype = filesystem_type(newinfo, current_dir); ++ error (1, 0, ++ _("%1$s%2$s changed during execution of %3$s " ++ "(old device number %4$ld, new device number %5$ld, file system type is %6$s) [ref %7$ld]"), ++ safely_quote_err_filename(0, specific_what), ++ parent ? "/.." : "", ++ safely_quote_err_filename(1, progname), ++ (long) old_dev, ++ (long) newinfo->st_dev, ++ fstype, ++ (long)line_no); ++ /*NOTREACHED*/ ++ return false; ++ } ++ ++ case NON_FATAL_IF_SANITY_CHECK_FAILS: ++ { ++ /* Since the device has changed under us, the inode number ++ * will almost certainly also be different. However, we have ++ * already decided that this is not a problem. Hence we return ++ * without checking the inode number. ++ */ ++ free(specific_what); ++ return true; ++ } ++ ++ case RETRY_IF_SANITY_CHECK_FAILS: ++ return false; ++ } ++ } ++ ++ /* Device number was the same, check if the inode has changed. */ ++ if (old_ino != newinfo->st_ino) ++ { ++ *changed = true; ++ specific_what = specific_dirname(what); ++ fstype = filesystem_type(newinfo, current_dir); ++ ++ error ((isfatal == FATAL_IF_SANITY_CHECK_FAILS) ? 1 : 0, ++ 0, /* no relevant errno value */ ++ _("%1$s%2$s changed during execution of %3$s " ++ "(old inode number %4$ld, new inode number %5$ld, file system type is %5$s) [ref %7$ld]"), ++ safely_quote_err_filename(0, specific_what), ++ parent ? "/.." : "", ++ safely_quote_err_filename(1, progname), ++ (long) old_ino, ++ (long) newinfo->st_ino, ++ fstype, ++ (long)line_no); ++ free(specific_what); ++ return false; ++ } ++ ++ return true; ++} ++ ++enum SafeChdirStatus ++ { ++ SafeChdirOK, ++ SafeChdirFailSymlink, ++ SafeChdirFailNotDir, ++ SafeChdirFailStat, ++ SafeChdirFailWouldBeUnableToReturn, ++ SafeChdirFailChdirFailed, ++ SafeChdirFailNonexistent, ++ SafeChdirFailDestUnreadable ++ }; ++ ++/* Safely perform a change in directory. We do this by calling ++ * lstat() on the subdirectory, using chdir() to move into it, and ++ * then lstat()ing ".". We compare the results of the two stat calls ++ * to see if they are consistent. If not, we sound the alarm. ++ * ++ * If following_links() is true, we do follow symbolic links. ++ */ ++static enum SafeChdirStatus ++safely_chdir_lstat(const char *dest, ++ enum TraversalDirection direction, ++ struct stat *statbuf_dest, ++ enum ChdirSymlinkHandling symlink_follow_option, ++ boolean *did_stat) ++{ ++ struct stat statbuf_arrived; ++ int rv, dotfd=-1; ++ int saved_errno; /* specific_dirname() changes errno. */ ++ boolean rv_set = false; ++ boolean statflag = false; ++ int tries = 0; ++ enum WdSanityCheckFatality isfatal = RETRY_IF_SANITY_CHECK_FAILS; ++ ++ saved_errno = errno = 0; ++ ++ dotfd = open(".", O_RDONLY ++#if defined O_LARGEFILE ++ |O_LARGEFILE ++#endif ++ ); ++ ++ /* We jump back to here if wd_sanity_check() ++ * recoverably triggers an alert. ++ */ ++ retry: ++ ++tries; ++ ++ if (dotfd >= 0) ++ { ++ /* Stat the directory we're going to. */ ++ set_stat_placeholders(statbuf_dest); ++ if (0 == options.xstat(dest, statbuf_dest)) ++ { ++ statflag = true; ++ ++#ifdef S_ISLNK ++ /* symlink_follow_option might be set to SymlinkFollowOk, which ++ * would allow us to chdir() into a symbolic link. This is ++ * only useful for the case where the directory we're ++ * chdir()ing into is the basename of a command line ++ * argument, for example where "foo/bar/baz" is specified on ++ * the command line. When -P is in effect (the default), ++ * baz will not be followed if it is a symlink, but if bar ++ * is a symlink, it _should_ be followed. Hence we need the ++ * ability to override the policy set by following_links(). ++ */ ++ if (!following_links() && S_ISLNK(statbuf_dest->st_mode)) ++ { ++ /* We're not supposed to be following links, but this is ++ * a link. Check symlink_follow_option to see if we should ++ * make a special exception. ++ */ ++ if (symlink_follow_option == SymlinkFollowOk) ++ { ++ /* We need to re-stat() the file so that the ++ * sanity check can pass. ++ */ ++ if (0 != stat(dest, statbuf_dest)) ++ { ++ rv = SafeChdirFailNonexistent; ++ rv_set = true; ++ saved_errno = errno; ++ goto fail; ++ } ++ statflag = true; ++ } ++ else ++ { ++ /* Not following symlinks, so the attempt to ++ * chdir() into a symlink should be prevented. ++ */ ++ rv = SafeChdirFailSymlink; ++ rv_set = true; ++ saved_errno = 0; /* silence the error message */ ++ goto fail; ++ } ++ } ++#endif ++#ifdef S_ISDIR ++ /* Although the immediately following chdir() would detect ++ * the fact that this is not a directory for us, this would ++ * result in an extra system call that fails. Anybody ++ * examining the system-call trace should ideally not be ++ * concerned that something is actually failing. ++ */ ++ if (!S_ISDIR(statbuf_dest->st_mode)) ++ { ++ rv = SafeChdirFailNotDir; ++ rv_set = true; ++ saved_errno = 0; /* silence the error message */ ++ goto fail; ++ } ++#endif ++ ++ if (options.debug_options & DebugSearch) ++ fprintf(stderr, "safely_chdir(): chdir(\"%s\")\n", dest); ++ ++ if (0 == chdir(dest)) ++ { ++ /* check we ended up where we wanted to go */ ++ boolean changed = false; ++ if (!wd_sanity_check(".", program_name, ".", ++ statbuf_dest->st_dev, ++ statbuf_dest->st_ino, ++ &statbuf_arrived, ++ 0, __LINE__, direction, ++ isfatal, ++ &changed)) ++ { ++ /* Only allow one failure. */ ++ if (RETRY_IF_SANITY_CHECK_FAILS == isfatal) ++ { ++ if (0 == fchdir(dotfd)) ++ { ++ isfatal = FATAL_IF_SANITY_CHECK_FAILS; ++ goto retry; ++ } ++ else ++ { ++ /* Failed to return to original directory, ++ * but we know that the current working ++ * directory is not the one that we intend ++ * to be in. Since fchdir() failed, we ++ * can't recover from this and so this error ++ * is fatal. ++ */ ++ error(1, errno, ++ "failed to return to parent directory"); ++ } ++ } ++ else ++ { ++ /* XXX: not sure what to use as an excuse here. */ ++ rv = SafeChdirFailNonexistent; ++ rv_set = true; ++ saved_errno = 0; ++ goto fail; ++ } ++ } ++ ++ close(dotfd); ++ return SafeChdirOK; ++ } ++ else ++ { ++ saved_errno = errno; ++ if (ENOENT == saved_errno) ++ { ++ rv = SafeChdirFailNonexistent; ++ rv_set = true; ++ if (options.ignore_readdir_race) ++ errno = 0; /* don't issue err msg */ ++ } ++ else if (ENOTDIR == saved_errno) ++ { ++ /* This can happen if the we stat a directory, ++ * and then file system activity changes it into ++ * a non-directory. ++ */ ++ saved_errno = 0; /* don't issue err msg */ ++ rv = SafeChdirFailNotDir; ++ rv_set = true; ++ } ++ else ++ { ++ rv = SafeChdirFailChdirFailed; ++ rv_set = true; ++ } ++ goto fail; ++ } ++ } ++ else ++ { ++ saved_errno = errno; ++ rv = SafeChdirFailStat; ++ rv_set = true; ++ ++ if ( (ENOENT == saved_errno) || (0 == state.curdepth)) ++ saved_errno = 0; /* don't issue err msg */ ++ goto fail; ++ } ++ } ++ else ++ { ++ /* We do not have read permissions on "." */ ++ rv = SafeChdirFailWouldBeUnableToReturn; ++ rv_set = true; ++ goto fail; ++ } ++ ++ /* This is the success path, so we clear errno. The caller probably ++ * won't be calling error() anyway. ++ */ ++ saved_errno = 0; ++ ++ /* We use the same exit path for success or failure. ++ * which has occurred is recorded in RV. ++ */ ++ fail: ++ /* We do not call error() as this would result in a duplicate error ++ * message when the caller does the same thing. ++ */ ++ if (saved_errno) ++ errno = saved_errno; ++ ++ if (dotfd >= 0) ++ { ++ close(dotfd); ++ dotfd = -1; ++ } ++ ++ *did_stat = statflag; ++ assert (rv_set); ++ return rv; ++} ++ ++#if defined O_NOFOLLOW ++/* Safely change working directory to the specified subdirectory. If ++ * we are not allowed to follow symbolic links, we use open() with ++ * O_NOFOLLOW, followed by fchdir(). This ensures that we don't ++ * follow symbolic links (of course, we do follow them if the -L ++ * option is in effect). ++ */ ++static enum SafeChdirStatus ++safely_chdir_nofollow(const char *dest, ++ enum TraversalDirection direction, ++ struct stat *statbuf_dest, ++ enum ChdirSymlinkHandling symlink_follow_option, ++ boolean *did_stat) ++{ ++ int extraflags, fd; ++ ++ (void) direction; ++ (void) statbuf_dest; ++ ++ extraflags = 0; ++ *did_stat = false; ++ ++ switch (symlink_follow_option) ++ { ++ case SymlinkFollowOk: ++ extraflags = 0; ++ break; ++ ++ case SymlinkHandleDefault: ++ if (following_links()) ++ extraflags = 0; ++ else ++ extraflags = O_NOFOLLOW; ++ break; ++ } ++ ++ errno = 0; ++ fd = open(dest, O_RDONLY ++#if defined O_LARGEFILE ++ |O_LARGEFILE ++#endif ++ |extraflags); ++ if (fd < 0) ++ { ++ switch (errno) ++ { ++ case ELOOP: ++ return SafeChdirFailSymlink; /* This is why we use O_NOFOLLOW */ ++ case ENOENT: ++ return SafeChdirFailNonexistent; ++ default: ++ return SafeChdirFailDestUnreadable; ++ } ++ } ++ ++ errno = 0; ++ if (0 == fchdir(fd)) ++ { ++ close(fd); ++ return SafeChdirOK; ++ } ++ else ++ { ++ int saved_errno = errno; ++ close(fd); ++ errno = saved_errno; ++ ++ switch (errno) ++ { ++ case ENOTDIR: ++ return SafeChdirFailNotDir; ++ ++ case EACCES: ++ case EBADF: /* Shouldn't happen */ ++ case EINTR: ++ case EIO: ++ default: ++ return SafeChdirFailChdirFailed; ++ } ++ } ++} ++#endif ++ ++static enum SafeChdirStatus ++safely_chdir(const char *dest, ++ enum TraversalDirection direction, ++ struct stat *statbuf_dest, ++ enum ChdirSymlinkHandling symlink_follow_option, ++ boolean *did_stat) ++{ ++ enum SafeChdirStatus result; ++ ++ /* We're about to leave a directory. If there are any -execdir ++ * argument lists which have been built but have not yet been ++ * processed, do them now because they must be done in the same ++ * directory. ++ */ ++ complete_pending_execdirs(get_current_dirfd()); ++ ++#if !defined(O_NOFOLLOW) ++ options.open_nofollow_available = false; ++#endif ++ if (options.open_nofollow_available) ++ { ++ result = safely_chdir_nofollow(dest, direction, statbuf_dest, ++ symlink_follow_option, did_stat); ++ if (SafeChdirFailDestUnreadable != result) ++ { ++ return result; ++ } ++ else ++ { ++ /* Savannah bug #15384: fall through to use safely_chdir_lstat ++ * if the directory is not readable. ++ */ ++ /* Do nothing. */ ++ } ++ } ++ /* Even if O_NOFOLLOW is available, we may need to use the alternative ++ * method, since parent of the start point may be executable but not ++ * readable. ++ */ ++ return safely_chdir_lstat(dest, direction, statbuf_dest, ++ symlink_follow_option, did_stat); ++} ++ ++ ++ ++/* Safely go back to the starting directory. */ ++static void ++chdir_back (void) ++{ ++ struct stat stat_buf; ++ boolean dummy; ++ ++ if (starting_desc < 0) ++ { ++ if (options.debug_options & DebugSearch) ++ fprintf(stderr, "chdir_back(): chdir(\"%s\")\n", starting_dir); ++ ++#ifdef STAT_MOUNTPOINTS ++ /* We will need the mounted device list. Get it now if we don't ++ * already have it. ++ */ ++ if (NULL == mounted_devices) ++ init_mounted_dev_list(1); ++#endif ++ ++ if (chdir (starting_dir) != 0) ++ fatal_file_error(starting_dir); ++ ++ wd_sanity_check(starting_dir, ++ program_name, ++ starting_dir, ++ starting_stat_buf.st_dev, ++ starting_stat_buf.st_ino, ++ &stat_buf, 0, __LINE__, ++ TraversingUp, ++ FATAL_IF_SANITY_CHECK_FAILS, ++ &dummy); ++ } ++ else ++ { ++ if (options.debug_options & DebugSearch) ++ fprintf(stderr, "chdir_back(): chdir(<starting-point>)\n"); ++ ++ if (fchdir (starting_desc) != 0) ++ { ++ fatal_file_error(starting_dir); ++ } ++ } ++} ++ ++/* Move to the parent of a given directory and then call a function, ++ * restoring the cwd. Don't bother changing directory if the ++ * specified directory is a child of "." or is the root directory. ++ */ ++static void ++at_top (char *pathname, ++ mode_t mode, ++ struct stat *pstat, ++ void (*action)(char *pathname, ++ char *basename, ++ int mode, ++ struct stat *pstat)) ++{ ++ int dirchange; ++ char *parent_dir = dir_name (pathname); ++ char *base = last_component (pathname); ++ ++ state.curdepth = 0; ++ state.starting_path_length = strlen (pathname); ++ ++ if (0 == *base ++ || 0 == strcmp(parent_dir, ".")) ++ { ++ dirchange = 0; ++ base = pathname; ++ } ++ else ++ { ++ enum TraversalDirection direction; ++ enum SafeChdirStatus chdir_status; ++ struct stat st; ++ boolean did_stat = false; ++ ++ dirchange = 1; ++ if (0 == strcmp(base, "..")) ++ direction = TraversingUp; ++ else ++ direction = TraversingDown; ++ ++ /* We pass SymlinkFollowOk to safely_chdir(), which allows it to ++ * chdir() into a symbolic link. This is only useful for the ++ * case where the directory we're chdir()ing into is the ++ * basename of a command line argument, for example where ++ * "foo/bar/baz" is specified on the command line. When -P is ++ * in effect (the default), baz will not be followed if it is a ++ * symlink, but if bar is a symlink, it _should_ be followed. ++ * Hence we need the ability to override the policy set by ++ * following_links(). ++ */ ++ chdir_status = safely_chdir(parent_dir, direction, &st, SymlinkFollowOk, &did_stat); ++ if (SafeChdirOK != chdir_status) ++ { ++ const char *what = (SafeChdirFailWouldBeUnableToReturn == chdir_status) ? "." : parent_dir; ++ if (errno) ++ error (0, errno, "%s", ++ safely_quote_err_filename(0, what)); ++ else ++ error (0, 0, _("Failed to safely change directory into %s"), ++ safely_quote_err_filename(0, parent_dir)); ++ ++ /* We can't process this command-line argument. */ ++ state.exit_status = 1; ++ return; ++ } ++ } ++ ++ free (parent_dir); ++ parent_dir = NULL; ++ ++ action(pathname, base, mode, pstat); ++ ++ if (dirchange) ++ { ++ chdir_back(); ++ } ++} ++ ++ ++static void do_process_top_dir(char *pathname, ++ char *base, ++ int mode, ++ struct stat *pstat) ++{ ++ (void) pstat; ++ ++ process_path (pathname, base, false, ".", mode); ++ complete_pending_execdirs(get_current_dirfd()); ++} ++ ++static void do_process_predicate(char *pathname, ++ char *base, ++ int mode, ++ struct stat *pstat) ++{ ++ (void) mode; ++ ++ state.rel_pathname = base; /* cwd_dir_fd was already set by safely_chdir */ ++ apply_predicate (pathname, pstat, get_eval_tree()); ++} ++ ++ ++ ++ ++/* Descend PATHNAME, which is a command-line argument. ++ ++ Actions like -execdir assume that we are in the ++ parent directory of the file we're examining, ++ and on entry to this function our working directory ++ is whatever it was when find was invoked. Therefore ++ If PATHNAME is "." we just leave things as they are. ++ Otherwise, we figure out what the parent directory is, ++ and move to that. ++*/ ++static void ++process_top_path (char *pathname, mode_t mode) ++{ ++ at_top(pathname, mode, NULL, do_process_top_dir); ++} ++ ++ ++/* Info on each directory in the current tree branch, to avoid ++ getting stuck in symbolic link loops. */ ++static struct dir_id *dir_ids = NULL; ++/* Entries allocated in `dir_ids'. */ ++static int dir_alloc = 0; ++/* Index in `dir_ids' of directory currently being searched. ++ This is always the last valid entry. */ ++static int dir_curr = -1; ++/* (Arbitrary) number of entries to grow `dir_ids' by. */ ++#define DIR_ALLOC_STEP 32 ++ ++ ++ ++/* We've detected a file system loop. This is caused by one of ++ * two things: ++ * ++ * 1. Option -L is in effect and we've hit a symbolic link that ++ * points to an ancestor. This is harmless. We won't traverse the ++ * symbolic link. ++ * ++ * 2. We have hit a real cycle in the directory hierarchy. In this ++ * case, we issue a diagnostic message (POSIX requires this) and we ++ * skip that directory entry. ++ */ ++static void ++issue_loop_warning(const char *name, const char *pathname, int level) ++{ ++ struct stat stbuf_link; ++ if (lstat(name, &stbuf_link) != 0) ++ stbuf_link.st_mode = S_IFREG; ++ ++ if (S_ISLNK(stbuf_link.st_mode)) ++ { ++ error(0, 0, ++ _("Symbolic link %s is part of a loop in the directory hierarchy; we have already visited the directory to which it points."), ++ safely_quote_err_filename(0, pathname)); ++ /* XXX: POSIX appears to require that the exit status be non-zero if a ++ * diagnostic is issued. ++ */ ++ } ++ else ++ { ++ int distance = 1 + (dir_curr-level); ++ /* We have found an infinite loop. POSIX requires us to ++ * issue a diagnostic. Usually we won't get to here ++ * because when the leaf optimisation is on, it will cause ++ * the subdirectory to be skipped. If /a/b/c/d is a hard ++ * link to /a/b, then the link count of /a/b/c is 2, ++ * because the ".." entry of /b/b/c/d points to /a, not ++ * to /a/b/c. ++ */ ++ error(0, 0, ++ ngettext( ++ "Filesystem loop detected; %1$s has the same device number and inode as " ++ "a directory which is %2$d level higher in the file system hierarchy", ++ "Filesystem loop detected; %1$s has the same device number and inode as " ++ "a directory which is %2$d levels higher in the file system hierarchy", ++ (long)distance), ++ safely_quote_err_filename(0, pathname), ++ distance); ++ } ++} ++ ++ ++ ++/* Recursively descend path PATHNAME, applying the predicates. ++ LEAF is true if PATHNAME is known to be in a directory that has no ++ more unexamined subdirectories, and therefore it is not a directory. ++ Knowing this allows us to avoid calling stat as long as possible for ++ leaf files. ++ ++ NAME is PATHNAME relative to the current directory. We access NAME ++ but print PATHNAME. ++ ++ PARENT is the path of the parent of NAME, relative to find's ++ starting directory. ++ ++ Return nonzero iff PATHNAME is a directory. */ ++ ++static int ++process_path (char *pathname, char *name, boolean leaf, char *parent, ++ mode_t mode) ++{ ++ struct stat stat_buf; ++ static dev_t root_dev; /* Device ID of current argument pathname. */ ++ int i; ++ struct predicate *eval_tree; ++ ++ eval_tree = get_eval_tree(); ++ /* Assume it is a non-directory initially. */ ++ stat_buf.st_mode = 0; ++ state.rel_pathname = name; ++ state.type = 0; ++ state.have_stat = false; ++ state.have_type = false; ++ ++ if (!digest_mode(mode, pathname, name, &stat_buf, leaf)) ++ return 0; ++ ++ if (!S_ISDIR (state.type)) ++ { ++ if (state.curdepth >= options.mindepth) ++ apply_predicate (pathname, &stat_buf, eval_tree); ++ return 0; ++ } ++ ++ /* From here on, we're working on a directory. */ ++ ++ ++ /* Now we really need to stat the directory, even if we know the ++ * type, because we need information like struct stat.st_rdev. ++ */ ++ if (get_statinfo(pathname, name, &stat_buf) != 0) ++ return 0; ++ ++ state.have_stat = true; ++ mode = state.type = stat_buf.st_mode; /* use full info now that we have it. */ ++ state.stop_at_current_level = ++ options.maxdepth >= 0 ++ && state.curdepth >= options.maxdepth; ++ ++ /* If we've already seen this directory on this branch, ++ don't descend it again. */ ++ for (i = 0; i <= dir_curr; i++) ++ if (stat_buf.st_ino == dir_ids[i].ino && ++ stat_buf.st_dev == dir_ids[i].dev) ++ { ++ state.stop_at_current_level = true; ++ issue_loop_warning(name, pathname, i); ++ } ++ ++ if (dir_alloc <= ++dir_curr) ++ { ++ dir_alloc += DIR_ALLOC_STEP; ++ dir_ids = (struct dir_id *) ++ xrealloc ((char *) dir_ids, dir_alloc * sizeof (struct dir_id)); ++ } ++ dir_ids[dir_curr].ino = stat_buf.st_ino; ++ dir_ids[dir_curr].dev = stat_buf.st_dev; ++ ++ if (options.stay_on_filesystem) ++ { ++ if (state.curdepth == 0) ++ root_dev = stat_buf.st_dev; ++ else if (stat_buf.st_dev != root_dev) ++ state.stop_at_current_level = true; ++ } ++ ++ if (options.do_dir_first && state.curdepth >= options.mindepth) ++ apply_predicate (pathname, &stat_buf, eval_tree); ++ ++ if (options.debug_options & DebugSearch) ++ fprintf(stderr, "pathname = %s, stop_at_current_level = %d\n", ++ pathname, state.stop_at_current_level); ++ ++ if (state.stop_at_current_level == false) ++ { ++ /* Scan directory on disk. */ ++ process_dir (pathname, name, strlen (pathname), &stat_buf, parent); ++ } ++ ++ if (options.do_dir_first == false && state.curdepth >= options.mindepth) ++ { ++ /* The fields in 'state' are now out of date. Correct them. ++ */ ++ if (!digest_mode(mode, pathname, name, &stat_buf, leaf)) ++ return 0; ++ ++ if (0 == dir_curr) ++ { ++ at_top(pathname, mode, &stat_buf, do_process_predicate); ++ } ++ else ++ { ++ do_process_predicate(pathname, name, mode, &stat_buf); ++ } ++ } ++ ++ dir_curr--; ++ ++ return 1; ++} ++ ++ ++/* Scan directory PATHNAME and recurse through process_path for each entry. ++ ++ PATHLEN is the length of PATHNAME. ++ ++ NAME is PATHNAME relative to the current directory. ++ ++ STATP is the results of *options.xstat on it. ++ ++ PARENT is the path of the parent of NAME, relative to find's ++ starting directory. */ ++ ++static void ++process_dir (char *pathname, char *name, int pathlen, const struct stat *statp, char *parent) ++{ ++ int subdirs_left; /* Number of unexamined subdirs in PATHNAME. */ ++ boolean subdirs_unreliable; /* if true, cannot use dir link count as subdir limif (if false, it may STILL be unreliable) */ ++ unsigned int idx; /* Which entry are we on? */ ++ struct stat stat_buf; ++ size_t dircount = 0u; ++ struct savedir_dirinfo *dirinfo; ++#if 0 ++ printf("process_dir: pathname=%s name=%s statp->st_nlink=%d st_ino=%d\n", ++ pathname, ++ name, ++ (int)statp->st_nlink, ++ (int)statp->st_ino); ++#endif ++ if (statp->st_nlink < 2) ++ { ++ subdirs_unreliable = true; ++ subdirs_left = 0; ++ } ++ else ++ { ++ subdirs_unreliable = false; /* not necessarily right */ ++ subdirs_left = statp->st_nlink - 2; /* Account for name and ".". */ ++ } ++ ++ errno = 0; ++ dirinfo = xsavedir(name, 0); ++ ++ ++ if (dirinfo == NULL) ++ { ++ assert (errno != 0); ++ error (0, errno, "%s", safely_quote_err_filename(0, pathname)); ++ state.exit_status = 1; ++ } ++ else ++ { ++ register char *namep; /* Current point in `name_space'. */ ++ char *cur_path; /* Full path of each file to process. */ ++ char *cur_name; /* Base name of each file to process. */ ++ unsigned cur_path_size; /* Bytes allocated for `cur_path'. */ ++ register unsigned file_len; /* Length of each path to process. */ ++ register unsigned pathname_len; /* PATHLEN plus trailing '/'. */ ++ boolean did_stat = false; ++ ++ if (pathname[pathlen - 1] == '/') ++ pathname_len = pathlen + 1; /* For '\0'; already have '/'. */ ++ else ++ pathname_len = pathlen + 2; /* For '/' and '\0'. */ ++ cur_path_size = 0; ++ cur_path = NULL; ++ ++ /* We're about to leave the directory. If there are any ++ * -execdir argument lists which have been built but have not ++ * yet been processed, do them now because they must be done in ++ * the same directory. ++ */ ++ complete_pending_execdirs(get_current_dirfd()); ++ ++ if (strcmp (name, ".")) ++ { ++ enum SafeChdirStatus status = safely_chdir (name, TraversingDown, &stat_buf, SymlinkHandleDefault, &did_stat); ++ switch (status) ++ { ++ case SafeChdirOK: ++ /* If there had been a change but wd_sanity_check() ++ * accepted it, we need to accept that on the ++ * way back up as well, so modify our record ++ * of what we think we should see later. ++ * If there was no change, the assignments are a no-op. ++ * ++ * However, before performing the assignment, we need to ++ * check that we have the stat information. If O_NOFOLLOW ++ * is available, safely_chdir() will not have needed to use ++ * stat(), and so stat_buf will just contain random data. ++ */ ++ if (!did_stat) ++ { ++ /* If there is a link we need to follow it. Hence ++ * the direct call to stat() not through (options.xstat) ++ */ ++ set_stat_placeholders(&stat_buf); ++ if (0 != stat(".", &stat_buf)) ++ break; /* skip the assignment. */ ++ } ++ dir_ids[dir_curr].dev = stat_buf.st_dev; ++ dir_ids[dir_curr].ino = stat_buf.st_ino; ++ ++ break; ++ ++ case SafeChdirFailWouldBeUnableToReturn: ++ error (0, errno, "."); ++ state.exit_status = 1; ++ break; ++ ++ case SafeChdirFailNonexistent: ++ case SafeChdirFailDestUnreadable: ++ case SafeChdirFailStat: ++ case SafeChdirFailNotDir: ++ case SafeChdirFailChdirFailed: ++ error (0, errno, "%s", ++ safely_quote_err_filename(0, pathname)); ++ state.exit_status = 1; ++ return; ++ ++ case SafeChdirFailSymlink: ++ error (0, 0, ++ _("warning: not following the symbolic link %s"), ++ safely_quote_err_filename(0, pathname)); ++ state.exit_status = 1; ++ return; ++ } ++ } ++ ++ for (idx=0; idx < dirinfo->size; ++idx) ++ { ++ /* savedirinfo() may return dirinfo=NULL if extended information ++ * is not available. ++ */ ++ mode_t mode = (dirinfo->entries[idx].flags & SavedirHaveFileType) ? ++ dirinfo->entries[idx].type_info : 0; ++ namep = dirinfo->entries[idx].name; ++ ++ /* Append this directory entry's name to the path being searched. */ ++ file_len = pathname_len + strlen (namep); ++ if (file_len > cur_path_size) ++ { ++ while (file_len > cur_path_size) ++ cur_path_size += 1024; ++ if (cur_path) ++ free (cur_path); ++ cur_path = xmalloc (cur_path_size); ++ strcpy (cur_path, pathname); ++ cur_path[pathname_len - 2] = '/'; ++ } ++ cur_name = cur_path + pathname_len - 1; ++ strcpy (cur_name, namep); ++ ++ state.curdepth++; ++ if (!options.no_leaf_check && !subdirs_unreliable) ++ { ++ if (mode && S_ISDIR(mode) && (subdirs_left == 0)) ++ { ++ /* This is a subdirectory, but the number of directories we ++ * have found now exceeds the number we would expect given ++ * the hard link count on the parent. This is likely to be ++ * a bug in the file system driver (e.g. Linux's ++ * /proc file system) or may just be a fact that the OS ++ * doesn't really handle hard links with Unix semantics. ++ * In the latter case, -noleaf should be used routinely. ++ */ ++ error(0, 0, _("WARNING: Hard link count is wrong for %1$s " ++ "(saw only st_nlink=%2$d but we already saw %3$d subdirectories): " ++ "this may be a bug in your file system driver. " ++ "Automatically turning on find's -noleaf option. " ++ "Earlier results may have failed to include directories " ++ "that should have been searched."), ++ safely_quote_err_filename(0, pathname), ++ statp->st_nlink, ++ dircount); ++ state.exit_status = 1; /* We know the result is wrong, now */ ++ options.no_leaf_check = true; /* Don't make same ++ mistake again */ ++ subdirs_unreliable = 1; ++ subdirs_left = 1; /* band-aid for this iteration. */ ++ } ++ ++ /* Normal case optimization. On normal Unix ++ file systems, a directory that has no subdirectories ++ has two links: its name, and ".". Any additional ++ links are to the ".." entries of its subdirectories. ++ Once we have processed as many subdirectories as ++ there are additional links, we know that the rest of ++ the entries are non-directories -- in other words, ++ leaf files. */ ++ { ++ int count; ++ count = process_path (cur_path, cur_name, ++ subdirs_left == 0, pathname, ++ mode); ++ subdirs_left -= count; ++ dircount += count; ++ } ++ } ++ else ++ { ++ /* There might be weird (e.g., CD-ROM or MS-DOS) file systems ++ mounted, which don't have Unix-like directory link counts. */ ++ process_path (cur_path, cur_name, false, pathname, mode); ++ } ++ ++ state.curdepth--; ++ } ++ ++ ++ /* We're about to leave the directory. If there are any ++ * -execdir argument lists which have been built but have not ++ * yet been processed, do them now because they must be done in ++ * the same directory. ++ */ ++ complete_pending_execdirs(get_current_dirfd()); ++ ++ if (strcmp (name, ".")) ++ { ++ enum SafeChdirStatus status; ++ struct dir_id did; ++ ++ /* We could go back and do the next command-line arg ++ instead, maybe using longjmp. */ ++ char const *dir; ++ boolean deref = following_links() ? true : false; ++ ++ if ( (state.curdepth>0) && !deref) ++ dir = ".."; ++ else ++ { ++ chdir_back (); ++ dir = parent; ++ } ++ ++ did_stat = false; ++ status = safely_chdir (dir, TraversingUp, &stat_buf, SymlinkHandleDefault, &did_stat); ++ switch (status) ++ { ++ case SafeChdirOK: ++ break; ++ ++ case SafeChdirFailWouldBeUnableToReturn: ++ error (1, errno, "."); ++ return; ++ ++ case SafeChdirFailNonexistent: ++ case SafeChdirFailDestUnreadable: ++ case SafeChdirFailStat: ++ case SafeChdirFailSymlink: ++ case SafeChdirFailNotDir: ++ case SafeChdirFailChdirFailed: ++ error (1, errno, "%s", safely_quote_err_filename(0, pathname)); ++ return; ++ } ++ ++ if (dir_curr > 0) ++ { ++ did.dev = dir_ids[dir_curr-1].dev; ++ did.ino = dir_ids[dir_curr-1].ino; ++ } ++ else ++ { ++ did.dev = starting_stat_buf.st_dev; ++ did.ino = starting_stat_buf.st_ino; ++ } ++ } ++ ++ if (cur_path) ++ free (cur_path); ++ free_dirinfo(dirinfo); ++ } ++ ++ if (subdirs_unreliable) ++ { ++ /* Make sure we hasn't used the variable subdirs_left if we knew ++ * we shouldn't do so. ++ */ ++ assert (0 == subdirs_left || options.no_leaf_check); ++ } ++} +diff -purN findutils-4.3.12.orig/find/parser.c findutils-4.3.12/find/parser.c +--- findutils-4.3.12.orig/find/parser.c 2007-12-19 16:12:34.000000000 -0500 ++++ findutils-4.3.12/find/parser.c 2008-01-30 08:46:05.754843619 -0500 +@@ -53,6 +53,13 @@ + #include <unistd.h> + #include <sys/stat.h> + ++#ifdef WITH_SELINUX ++#include <selinux/selinux.h> ++int optionh_getfilecon(const char *name, security_context_t *p); ++int optionl_getfilecon(const char *name, security_context_t *p); ++int optionp_getfilecon(const char *name, security_context_t *p); ++#endif /*WITH_SELINUX*/ ++ + #if ENABLE_NLS + # include <libintl.h> + # define _(Text) gettext (Text) +@@ -156,6 +163,9 @@ static boolean parse_noignore_race PARAM + static boolean parse_warn PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); + static boolean parse_xtype PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); + static boolean parse_quit PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++#ifdef WITH_SELINUX ++static boolean parse_scontext PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++#endif /*WITH_SELINUX*/ + + boolean parse_print PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); + +@@ -341,6 +351,8 @@ static struct parser_table const parse_t + {ARG_TEST, "-help", parse_help, NULL}, /* GNU */ + {ARG_TEST, "version", parse_version, NULL}, /* GNU */ + {ARG_TEST, "-version", parse_version, NULL}, /* GNU */ ++ {ARG_TEST, "context", parse_scontext, pred_scontext}, /* SELinux */ ++ {ARG_TEST, "-context", parse_scontext, pred_scontext}, /* SELinux */ + {0, 0, 0, 0} + }; + +@@ -452,10 +464,16 @@ set_follow_state(enum SymlinkOption opt) + case SYMLINK_ALWAYS_DEREF: /* -L */ + options.xstat = optionl_stat; + options.no_leaf_check = true; ++#ifdef WITH_SELINUX ++ options.x_getfilecon = optionl_getfilecon; ++#endif /* WITH_SELINUX */ + break; + + case SYMLINK_NEVER_DEREF: /* -P (default) */ + options.xstat = optionp_stat; ++#ifdef WITH_SELINUX ++ options.x_getfilecon = optionp_getfilecon; ++#endif /* WITH_SELINUX */ + /* Can't turn no_leaf_check off because the user might have specified + * -noleaf anyway + */ +@@ -464,6 +482,9 @@ set_follow_state(enum SymlinkOption opt) + case SYMLINK_DEREF_ARGSONLY: /* -H */ + options.xstat = optionh_stat; + options.no_leaf_check = true; ++#ifdef WITH_SELINUX ++ options.x_getfilecon = optionh_getfilecon; ++#endif /* WITH_SELINUX */ + } + } + options.symlink_handling = opt; +@@ -667,6 +688,94 @@ collect_arg_stat_info(char **argv, int * + + The predicate structure is updated with the new information. */ + ++#ifdef WITH_SELINUX ++ ++static int ++fallback_getfilecon(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 lgetfilecon(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. ++ */ ++int ++optionh_getfilecon(const char *name, security_context_t *p) ++{ ++ if (0 == state.curdepth) ++ { ++ /* This file is from the command line; deference the link (if it ++ * is a link). ++ */ ++ int rv = getfilecon(name, p); ++ if (0 == rv) ++ return 0; /* success */ ++ else ++ return fallback_getfilecon(name, p, rv); ++ } ++ else ++ { ++ /* Not a file on the command line; do not derefernce the link. ++ */ ++ return lgetfilecon(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. ++ */ ++int ++optionl_getfilecon(const char *name, security_context_t *p) ++{ ++ int rv = getfilecon(name, p); ++ if (0 == rv) ++ return 0; /* normal case. */ ++ else ++ return fallback_getfilecon(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. ++ */ ++int ++optionp_getfilecon(const char *name, security_context_t *p) ++{ ++ return lgetfilecon(name, p); ++} ++#endif /* WITH_SELINUX */ ++ + + static boolean + parse_and (const struct parser_table* entry, char **argv, int *arg_ptr) +@@ -1124,6 +1233,10 @@ tests (N can be +N or -N or N): -amin N + -readable -writable -executable\n\ + -wholename PATTERN -size N[bcwkMG] -true -type [bcdpflsD] -uid N\n\ + -used N -user NAME -xtype [bcdpfls]\n")); ++#ifdef WITH_SELINUX ++ puts (_("\ ++ -context CONTEXT\n")); ++#endif /*WITH_SELINUX*/ + puts (_("\ + actions: -delete -print0 -printf FORMAT -fprintf FILE FORMAT -print \n\ + -fprint0 FILE -fprint FILE -ls -fls FILE -prune -quit\n\ +@@ -2522,6 +2635,29 @@ parse_version (const struct parser_table + exit (0); + } + ++#ifdef WITH_SELINUX ++ ++static boolean ++parse_scontext ( const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ struct predicate *our_pred; ++ ++ if ( (argv == NULL) || (argv[*arg_ptr] == NULL) ) ++ return( false ); ++ ++ our_pred = insert_primary(entry); ++ our_pred->need_stat = false; ++#ifdef DEBUG ++ our_pred->p_name = find_pred_name (pred_scontext); ++#endif /*DEBUG*/ ++ our_pred->args.scontext = argv[*arg_ptr];; ++ ++ (*arg_ptr)++; ++ return( true ); ++} ++ ++#endif /*WITH_SELINUX*/ ++ + static boolean + parse_xdev (const struct parser_table* entry, char **argv, int *arg_ptr) + { +@@ -2773,7 +2909,11 @@ insert_fprintf (struct format_val *vec, + if (*scan2 == '.') + for (scan2++; ISDIGIT (*scan2); scan2++) + /* Do nothing. */ ; ++#ifdef WITH_SELINUX ++ if (strchr ("abcdDfFgGhHiklmMnpPsStuUyYZ", *scan2)) ++#else + if (strchr ("abcdDfFgGhHiklmMnpPsStuUyY", *scan2)) ++#endif + { + segmentp = make_segment (segmentp, format, scan2 - format, + KIND_FORMAT, *scan2, 0, +diff -purN findutils-4.3.12.orig/find/parser.c.orig findutils-4.3.12/find/parser.c.orig +--- findutils-4.3.12.orig/find/parser.c.orig 1969-12-31 19:00:00.000000000 -0500 ++++ findutils-4.3.12/find/parser.c.orig 2007-12-19 16:12:34.000000000 -0500 +@@ -0,0 +1,3502 @@ ++/* 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 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/>. ++*/ ++ ++#include <config.h> ++ ++#include "defs.h" ++#include <ctype.h> ++#include <math.h> ++#include <assert.h> ++#include <pwd.h> ++#include <errno.h> ++#include <grp.h> ++#include <fnmatch.h> ++#include "modechange.h" ++#include "modetype.h" ++#include "xstrtol.h" ++#include "xalloc.h" ++#include "quote.h" ++#include "quotearg.h" ++#include "buildcmd.h" ++#include "nextelem.h" ++#include "stdio-safer.h" ++#include "regextype.h" ++#include "stat-time.h" ++#include "xstrtod.h" ++#include "fts_.h" ++#include "getdate.h" ++#include "error.h" ++#include "findutils-version.h" ++ ++#include <fcntl.h> ++ ++ ++/* The presence of unistd.h is assumed by gnulib these days, so we ++ * might as well assume it too. ++ */ ++/* We need <unistd.h> for isatty(). */ ++#include <unistd.h> ++#include <sys/stat.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 ++ ++#if !defined (isascii) || defined (STDC_HEADERS) ++#ifdef isascii ++#undef isascii ++#endif ++#define isascii(c) 1 ++#endif ++ ++#define ISDIGIT(c) (isascii ((unsigned char)c) && isdigit ((unsigned char)c)) ++#define ISUPPER(c) (isascii ((unsigned char)c) && isupper ((unsigned char)c)) ++ ++#ifndef HAVE_ENDGRENT ++#define endgrent() ++#endif ++#ifndef HAVE_ENDPWENT ++#define endpwent() ++#endif ++ ++static boolean parse_accesscheck PARAMS((const struct parser_table* entry, char **argv, int *arg_ptr)); ++static boolean parse_amin PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_and PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_anewer PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_cmin PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_cnewer PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_comma PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_daystart PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_delete PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_d PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_depth PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_empty PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_exec PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_execdir PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_false PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_fls PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_fprintf PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_follow PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_fprint PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_fprint0 PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_fstype PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_gid PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_group PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_help PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_ilname PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_iname PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_inum PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_ipath PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_iregex PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_iwholename PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_links PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_lname PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_ls PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_maxdepth PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_mindepth PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_mmin PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_name PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_negate PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_newer PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_newerXY PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_noleaf PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_nogroup PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_nouser PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_nowarn PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_ok PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_okdir PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_or PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_path PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_perm PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_print0 PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_printf PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_prune PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_regex PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_regextype PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_samefile PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++#if 0 ++static boolean parse_show_control_chars PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++#endif ++static boolean parse_size PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_time PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_true PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_type PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_uid PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_used PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_user PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_version PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_wholename PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_xdev PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_ignore_race PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_noignore_race PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_warn PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_xtype PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++static boolean parse_quit PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++ ++boolean parse_print PARAMS((const struct parser_table*, char *argv[], int *arg_ptr)); ++ ++ ++static boolean insert_type PARAMS((char **argv, int *arg_ptr, ++ const struct parser_table *entry, ++ PRED_FUNC which_pred)); ++static boolean insert_regex PARAMS((char *argv[], int *arg_ptr, ++ const struct parser_table *entry, ++ int regex_options)); ++static boolean insert_fprintf (struct format_val *vec, ++ const struct parser_table *entry, ++ PRED_FUNC func, ++ const char *format); ++ ++static struct segment **make_segment PARAMS((struct segment **segment, ++ char *format, int len, ++ int kind, char format_char, ++ char aux_format_char, ++ struct predicate *pred)); ++static boolean insert_exec_ok PARAMS((const char *action, ++ const struct parser_table *entry, ++ int dirfd, ++ char *argv[], ++ int *arg_ptr)); ++static boolean get_comp_type PARAMS((const char **str, ++ enum comparison_type *comp_type)); ++static boolean get_relative_timestamp PARAMS((const char *str, ++ struct time_val *tval, ++ time_t origin, ++ double sec_per_unit, ++ const char *overflowmessage)); ++static boolean get_num PARAMS((const char *str, ++ uintmax_t *num, ++ enum comparison_type *comp_type)); ++static struct predicate* insert_num PARAMS((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 boolean stream_is_tty(FILE *fp); ++static boolean parse_noop PARAMS((const struct parser_table* entry, ++ char **argv, int *arg_ptr)); ++ ++#define PASTE(x,y) x##y ++#define STRINGIFY(s) #s ++ ++#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_ACTION_NP(what,suffix) \ ++ { (ARG_ACTION), (what), PASTE(parse_,suffix), NULL } ++ ++#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_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_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_OPTION ("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_OPTION ("warn", warn), /* GNU */ ++ 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; ++ ++ ++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 (1, 0, _("The -delete action atomatically 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.no_leaf_check = true; ++ break; ++ ++ case SYMLINK_NEVER_DEREF: /* -P (default) */ ++ options.xstat = optionp_stat; ++ /* 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.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; ++} ++ ++ ++/* Check that it is legal to fid the given primary in its ++ * position and return it. ++ */ ++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) ++ && options.warnings ) ++ { ++ /* option which follows a non-option */ ++ error (0, 0, ++ _("warning: you have specified the %1$s " ++ "option after a non-option argument %2$s, " ++ "but options are not positional (%3$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 (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) ++{ ++ int num_days = (options.cur_day_start - when) / 86400; ++ return estimate_file_age_success_rate(num_days); ++} ++ ++/* Collect an argument from the argument list, or ++ * return false. ++ */ ++static boolean ++collect_arg(char **argv, int *arg_ptr, const 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 boolean ++collect_arg_stat_info(char **argv, int *arg_ptr, struct stat *p) ++{ ++ const char *filename; ++ if (collect_arg(argv, arg_ptr, &filename)) ++ { ++ if (0 == (options.xstat)(filename, p)) ++ { ++ return true; ++ } ++ else ++ { ++ fatal_file_error(filename); ++ } ++ } ++ else ++ { ++ 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 boolean ++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 (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 boolean ++parse_anewer (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ struct stat stat_newer; ++ ++ set_stat_placeholders(&stat_newer); ++ if (collect_arg_stat_info(argv, arg_ptr, &stat_newer)) ++ { ++ struct predicate *our_pred = insert_primary (entry); ++ 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; ++} ++ ++boolean ++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 (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 boolean ++parse_cnewer (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ struct stat stat_newer; ++ ++ set_stat_placeholders(&stat_newer); ++ if (collect_arg_stat_info(argv, arg_ptr, &stat_newer)) ++ { ++ struct predicate *our_pred = insert_primary (entry); ++ 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 boolean ++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 (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 boolean ++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 += DAYSECS; ++ local = localtime (&options.cur_day_start); ++ options.cur_day_start -= (local ++ ? (local->tm_sec + local->tm_min * 60 ++ + local->tm_hour * 3600) ++ : options.cur_day_start % DAYSECS); ++ options.full_days = true; ++ } ++ return true; ++} ++ ++static boolean ++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 (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 boolean ++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 boolean ++parse_d (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ if (options.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 boolean ++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 (entry); ++ our_pred->est_success_rate = 0.01f; /* assume 1% of files are empty. */ ++ return true; ++} ++ ++static boolean ++parse_exec (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_exec_ok ("-exec", entry, get_start_dirfd(), argv, arg_ptr); ++} ++ ++static boolean ++parse_execdir (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_exec_ok ("-execdir", entry, -1, argv, arg_ptr); ++} ++ ++static boolean ++parse_false (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ struct predicate *our_pred; ++ ++ (void) argv; ++ (void) arg_ptr; ++ ++ our_pred = insert_primary (entry); ++ 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 boolean ++insert_fls (const struct parser_table* entry, const char *filename) ++{ ++ struct predicate *our_pred = insert_primary (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 boolean ++parse_fls (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ const char *filename; ++ return collect_arg(argv, arg_ptr, &filename) ++ && insert_fls(entry, filename); ++} ++ ++static boolean ++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 boolean ++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); ++ 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 boolean ++insert_fprint(const struct parser_table* entry, const char *filename) ++{ ++ struct predicate *our_pred = insert_primary (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->need_stat = our_pred->need_type = false; ++ our_pred->est_success_rate = 1.0f; ++ return true; ++} ++ ++ ++static boolean ++parse_fprint0 (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ const char *filename; ++ if (collect_arg(argv, arg_ptr, &filename)) ++ return insert_fprint(entry, filename); ++ else ++ return false; ++} ++ ++static float estimate_fstype_success_rate(const char *fsname) ++{ ++ struct stat dir_stat; ++ const char *dir = "/"; ++ if (0 == stat(dir, &dir_stat)) ++ { ++ const char *fstype = filesystem_type(&dir_stat, 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 boolean ++parse_fstype (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ const char *typename; ++ if (collect_arg(argv, arg_ptr, &typename)) ++ { ++ struct predicate *our_pred = insert_primary (entry); ++ 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 ++ { ++ return false; ++ } ++} ++ ++static boolean ++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 ++ { ++ return false; ++ } ++} ++ ++ ++static int ++safe_atoi (const char *s) ++{ ++ long lval; ++ char *end; ++ ++ errno = 0; ++ lval = strtol(s, &end, 10); ++ if ( (LONG_MAX == lval) || (LONG_MIN == lval) ) ++ { ++ /* max/min possible value, or an error. */ ++ if (errno == ERANGE) ++ { ++ /* too big, or too small. */ ++ error(1, errno, "%s", s); ++ } ++ else ++ { ++ /* not a valid number */ ++ error(1, errno, "%s", s); ++ } ++ /* Otherwise, we do a range chack against INT_MAX and INT_MIN ++ * below. ++ */ ++ } ++ ++ if (lval > INT_MAX || lval < INT_MIN) ++ { ++ /* The number was in range for long, but not int. */ ++ errno = ERANGE; ++ error(1, errno, "%s", s); ++ } ++ else if (*end) ++ { ++ error(1, errno, "Unexpected suffix %s on %s", ++ quotearg_n_style(0, options.err_quoting_style, end), ++ quotearg_n_style(1, options.err_quoting_style, s)); ++ } ++ else if (end == s) ++ { ++ error(1, errno, "Expected an integer: %s", ++ quotearg_n_style(0, options.err_quoting_style, s)); ++ } ++ return (int)lval; ++} ++ ++ ++static boolean ++parse_group (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ const char *groupname; ++ ++ 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); ++ } ++ else ++ { ++ /* XXX: no test in test suite for this */ ++ error(1, 0, _("%1$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 %2$s"), ++ quotearg_n_style(0, options.err_quoting_style, groupname), ++ quotearg_n_style(1, options.err_quoting_style, groupname+gid_len)); ++ return false; ++ } ++ } ++ else ++ { ++ if (*groupname) ++ { ++ /* XXX: no test in test suite for this */ ++ error(1, 0, _("%s is not the name of an existing group"), ++ quotearg_n_style(0, options.err_quoting_style, groupname)); ++ } ++ else ++ { ++ error(1, 0, _("argument to -group is empty, but should be a group name")); ++ } ++ return false; ++ } ++ } ++ our_pred = insert_primary (entry); ++ 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 boolean ++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]\n")); ++ puts (_("\ ++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 (0); ++} ++ ++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 boolean ++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); ++ 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 boolean ++fnmatch_sanitycheck(void) ++{ ++ static boolean checked = false; ++ if (!checked) ++ { ++ if (0 != fnmatch("foo", "foo", 0) ++ || 0 == fnmatch("Foo", "foo", 0) ++ || 0 != fnmatch("Foo", "foo", FNM_CASEFOLD)) ++ { ++ error (1, 0, _("sanity check of the fnmatch() library function failed.")); ++ return false; ++ } ++ checked = true; ++ } ++ return checked; ++} ++ ++ ++static boolean ++check_name_arg(const char *pred, const char *arg) ++{ ++ if (options.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 boolean ++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); ++ 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 boolean ++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; ++ return true; ++ } ++ else ++ { ++ return false; ++ } ++} ++ ++/* -ipath is 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" ++ */ ++static boolean ++parse_ipath (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_withpred (entry, pred_ipath); ++ 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 boolean ++parse_iwholename (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return parse_ipath (entry, argv, arg_ptr); ++} ++ ++static boolean ++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 boolean ++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 ++ { ++ return false; ++ } ++} ++ ++static boolean ++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); ++ our_pred->args.str = name; ++ our_pred->est_success_rate = 0.1 * estimate_pattern_match_rate(name, 0); ++ return true; ++ } ++ return false; ++} ++ ++static boolean ++parse_ls (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ (void) &argv; ++ (void) &arg_ptr; ++ return insert_fls(entry, NULL); ++} ++ ++static boolean ++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); ++ if (*limitptr >= 0) ++ { ++ return parse_noop(entry, argv, arg_ptr); ++ } ++ } ++ error(1, 0, _("Expected a positive decimal integer argument to %1$s, but got %2$s"), ++ predicate, ++ quotearg_n_style(0, options.err_quoting_style, depthstr)); ++ return false; ++ } ++ /* missing argument */ ++ return false; ++} ++ ++ ++static boolean ++parse_maxdepth (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_depthspec(entry, argv, arg_ptr, &options.maxdepth); ++} ++ ++static boolean ++parse_mindepth (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_depthspec(entry, argv, arg_ptr, &options.mindepth); ++} ++ ++ ++static boolean ++do_parse_xmin (const struct parser_table* entry, ++ char **argv, ++ int *arg_ptr, ++ enum xval xv) ++{ ++ const char *minutes; ++ ++ if (collect_arg(argv, arg_ptr, &minutes)) ++ { ++ struct time_val tval; ++ tval.xval = xv; ++ if (get_relative_timestamp(minutes, &tval, ++ options.cur_day_start + DAYSECS, 60, ++ "arithmetic overflow while converting %s " ++ "minutes to a number of seconds")) ++ { ++ struct predicate *our_pred = insert_primary (entry); ++ our_pred->args.reftime = tval; ++ our_pred->est_success_rate = estimate_timestamp_success_rate(tval.ts.tv_sec); ++ return true; ++ } ++ } ++ return false; ++} ++static boolean ++parse_amin (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return do_parse_xmin(entry, argv, arg_ptr, XVAL_ATIME); ++} ++ ++static boolean ++parse_cmin (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return do_parse_xmin(entry, argv, arg_ptr, XVAL_CTIME); ++} ++ ++ ++static boolean ++parse_mmin (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return do_parse_xmin(entry, argv, arg_ptr, XVAL_MTIME); ++} ++ ++static boolean ++parse_name (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ const char *name; ++ if (collect_arg(argv, arg_ptr, &name)) ++ { ++ fnmatch_sanitycheck(); ++ if (check_name_arg("-name", name)) ++ { ++ struct predicate *our_pred = insert_primary (entry); ++ 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 boolean ++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); ++ 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 boolean ++parse_newer (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ struct predicate *our_pred; ++ struct stat stat_newer; ++ ++ set_stat_placeholders(&stat_newer); ++ if (collect_arg_stat_info(argv, arg_ptr, &stat_newer)) ++ { ++ our_pred = insert_primary (entry); ++ 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 boolean ++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) ++ if ('B' == x || 'B' == y) ++ { ++ error(0, 0, ++ _("This system does not provide a way to find the birth time of a file.")); ++ return 0; ++ } ++#endif ++ ++ /* -newertY (for any Y) is invalid. */ ++ if (x == 't' ++ || 0 == strchr(validchars, x) ++ || 0 == 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) ++ */ ++ (*arg_ptr)++; ++ ++ our_pred = insert_primary (entry); ++ ++ ++ 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 (!get_date(&our_pred->args.reftime.ts, ++ argv[*arg_ptr], ++ &options.start_time)) ++ { ++ error(1, 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_file_error(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(1, 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 boolean ++parse_noleaf (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ options.no_leaf_check = true; ++ return parse_noop(entry, argv, arg_ptr); ++} ++ ++#ifdef CACHE_IDS ++/* Arbitrary amount by which to increase size ++ of `uid_unused' and `gid_unused'. */ ++#define ALLOC_STEP 2048 ++ ++/* Boolean: if uid_unused[n] is nonzero, then UID n has no passwd entry. */ ++char *uid_unused = NULL; ++ ++/* Number of elements in `uid_unused'. */ ++unsigned uid_allocated; ++ ++/* Similar for GIDs and group entries. */ ++char *gid_unused = NULL; ++unsigned gid_allocated; ++#endif ++ ++static boolean ++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); ++ our_pred->est_success_rate = 1e-4; ++#ifdef CACHE_IDS ++ if (gid_unused == NULL) ++ { ++ struct group *gr; ++ ++ gid_allocated = ALLOC_STEP; ++ gid_unused = xmalloc (gid_allocated); ++ memset (gid_unused, 1, gid_allocated); ++ setgrent (); ++ while ((gr = getgrent ()) != NULL) ++ { ++ if ((unsigned) gr->gr_gid >= gid_allocated) ++ { ++ unsigned new_allocated = (unsigned) gr->gr_gid + ALLOC_STEP; ++ gid_unused = xrealloc (gid_unused, new_allocated); ++ memset (gid_unused + gid_allocated, 1, ++ new_allocated - gid_allocated); ++ gid_allocated = new_allocated; ++ } ++ gid_unused[(unsigned) gr->gr_gid] = 0; ++ } ++ endgrent (); ++ } ++#endif ++ return true; ++} ++ ++static boolean ++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 (entry); ++ our_pred->est_success_rate = 1e-3; ++#ifdef CACHE_IDS ++ if (uid_unused == NULL) ++ { ++ struct passwd *pw; ++ ++ uid_allocated = ALLOC_STEP; ++ uid_unused = xmalloc (uid_allocated); ++ memset (uid_unused, 1, uid_allocated); ++ setpwent (); ++ while ((pw = getpwent ()) != NULL) ++ { ++ if ((unsigned) pw->pw_uid >= uid_allocated) ++ { ++ unsigned new_allocated = (unsigned) pw->pw_uid + ALLOC_STEP; ++ uid_unused = xrealloc (uid_unused, new_allocated); ++ memset (uid_unused + uid_allocated, 1, ++ new_allocated - uid_allocated); ++ uid_allocated = new_allocated; ++ } ++ uid_unused[(unsigned) pw->pw_uid] = 0; ++ } ++ endpwent (); ++ } ++#endif ++ return true; ++} ++ ++static boolean ++parse_nowarn (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ options.warnings = false; ++ return parse_noop(entry, argv, arg_ptr); ++} ++ ++static boolean ++parse_ok (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_exec_ok ("-ok", entry, get_start_dirfd(), argv, arg_ptr); ++} ++ ++static boolean ++parse_okdir (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_exec_ok ("-okdir", entry, -1, argv, arg_ptr); ++} ++ ++boolean ++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); ++ 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 boolean ++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 (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; ++} ++ ++/* 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 boolean ++parse_path (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_withpred (entry, pred_path); ++ 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 boolean ++parse_wholename (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return parse_path (entry, argv, arg_ptr); ++} ++ ++static void ++non_posix_mode(const char *mode) ++{ ++ if (options.posixly_correct) ++ { ++ error (1, 0, _("Mode %s is not valid when POSIXLY_CORRECT is on."), ++ quotearg_n_style(0, options.err_quoting_style, mode)); ++ } ++} ++ ++ ++static boolean ++parse_perm (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ mode_t perm_val[2]; ++ float rate; ++ int mode_start = 0; ++ boolean havekind = false; ++ enum permissions_type kind = PERM_EXACT; ++ struct mode_change *change = NULL; ++ 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; ++ havekind = true; ++ rate = 0.2; ++ break; ++ ++ case '+': ++ change = mode_compile (perm_expr); ++ if (NULL == change) ++ { ++ /* Most likely the caller is an old script that is still ++ * using the obsolete GNU syntax '-perm +MODE'. This old ++ * syntax was withdrawn in favor of '-perm /MODE' because ++ * it is incompatible with POSIX in some cases, but we ++ * still support uses of it that are not incompatible with ++ * POSIX. ++ * ++ * Example: POSIXLY_CORRECT=y find -perm +a+x ++ */ ++ non_posix_mode(perm_expr); ++ ++ /* support the previous behaviour. */ ++ mode_start = 1; ++ kind = PERM_ANY; ++ rate = 0.3; ++ } ++ else ++ { ++ /* This is a POSIX-compatible usage */ ++ mode_start = 0; ++ kind = PERM_EXACT; ++ rate = 0.1; ++ } ++ havekind = true; ++ break; ++ ++ case '/': /* GNU extension */ ++ non_posix_mode(perm_expr); ++ mode_start = 1; ++ kind = PERM_ANY; ++ havekind = true; ++ 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; ++ havekind = true; ++ rate = 0.01; ++ break; ++ } ++ ++ if (NULL == change) ++ { ++ change = mode_compile (perm_expr + mode_start); ++ if (NULL == change) ++ error (1, 0, _("invalid mode %s" ++ /* TRANSLATORS: the argument is a ++ * file permission string like 'u=rw,go=' ++ */), ++ 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; ++ havekind = true; ++ ++ /* 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); ++ our_pred->est_success_rate = rate; ++ if (havekind) ++ { ++ our_pred->args.perm.kind = kind; ++ } ++ else ++ { ++ ++ switch (perm_expr[0]) ++ { ++ case '-': ++ our_pred->args.perm.kind = PERM_AT_LEAST; ++ break; ++ case '+': ++ our_pred->args.perm.kind = PERM_ANY; ++ break; ++ default: ++ our_pred->args.perm.kind = PERM_EXACT; ++ break; ++ } ++ } ++ memcpy (our_pred->args.perm.val, perm_val, sizeof perm_val); ++ return true; ++} ++ ++boolean ++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 (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 boolean ++parse_print0 (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_fprint(entry, NULL); ++} ++ ++static boolean ++parse_printf (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ const char *format; ++ if (collect_arg(argv, arg_ptr, &format)) ++ { ++ struct format_val fmt; ++ open_stdout(&fmt); ++ return insert_fprintf (&fmt, entry, pred_fprintf, format); ++ } ++ return false; ++} ++ ++static boolean ++parse_fprintf (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ const char *format, *filename; ++ if (collect_arg(argv, arg_ptr, &filename)) ++ { ++ if (collect_arg(argv, arg_ptr, &format)) ++ { ++ struct format_val fmt; ++ open_output_file (filename, &fmt); ++ return insert_fprintf (&fmt, entry, pred_fprintf, format); ++ } ++ } ++ return false; ++} ++ ++static boolean ++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 (entry); ++ 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 boolean ++parse_quit (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ struct predicate *our_pred = insert_primary (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 boolean ++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 boolean ++parse_regex (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_regex (argv, arg_ptr, entry, options.regex_options); ++} ++ ++static boolean ++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); ++ 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 (1, 0, "%s", error_message); ++ our_pred->est_success_rate = estimate_pattern_match_rate(rx, 1); ++ return true; ++ } ++ return false; ++} ++ ++static boolean ++parse_size (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ struct predicate *our_pred; ++ 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; ++ ++ len = strlen (argv[*arg_ptr]); ++ if (len == 0) ++ error (1, 0, _("invalid null argument to -size")); ++ ++ suffix = argv[*arg_ptr][len - 1]; ++ switch (suffix) ++ { ++ case 'b': ++ blksize = 512; ++ argv[*arg_ptr][len - 1] = '\0'; ++ break; ++ ++ case 'c': ++ blksize = 1; ++ argv[*arg_ptr][len - 1] = '\0'; ++ break; ++ ++ case 'k': ++ blksize = 1024; ++ argv[*arg_ptr][len - 1] = '\0'; ++ break; ++ ++ case 'M': /* Megabytes */ ++ blksize = 1024*1024; ++ argv[*arg_ptr][len - 1] = '\0'; ++ break; ++ ++ case 'G': /* Gigabytes */ ++ blksize = 1024*1024*1024; ++ argv[*arg_ptr][len - 1] = '\0'; ++ break; ++ ++ case 'w': ++ blksize = 2; ++ argv[*arg_ptr][len - 1] = '\0'; ++ break; ++ ++ case '0': ++ case '1': ++ case '2': ++ case '3': ++ case '4': ++ case '5': ++ case '6': ++ case '7': ++ case '8': ++ case '9': ++ break; ++ ++ default: ++ error (1, 0, _("invalid -size type `%c'"), argv[*arg_ptr][len - 1]); ++ } ++ /* TODO: accept fractional megabytes etc. ? */ ++ if (!get_num (argv[*arg_ptr], &num, &c_type)) ++ { ++ error(1, 0, ++ _("Invalid argument `%s%c' to -size"), ++ argv[*arg_ptr], (int)suffix); ++ return false; ++ } ++ our_pred = insert_primary (entry); ++ 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 boolean ++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; ++ ++ set_stat_placeholders(&st); ++ if (!collect_arg_stat_info(argv, arg_ptr, &st)) ++ 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; /* means, uninitialised */ ++ 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 out call to stat and ++ * the call to open. ++ */ ++ fd = open(argv[*arg_ptr], 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_file_error(argv[*arg_ptr]); ++ } ++ 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) (argv[*arg_ptr], &st)) ++ fatal_file_error(argv[*arg_ptr]); ++ ++ 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); ++ 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; ++ 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 boolean ++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 (1, 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 (1, errno, "%s", errmsg); ++ return false; ++ } ++ (*arg_ptr)++; /* consume the argument. */ ++ return true; ++ } ++} ++#endif ++ ++ ++static boolean ++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 (entry); ++ our_pred->need_stat = our_pred->need_type = false; ++ our_pred->est_success_rate = 1.0f; ++ return true; ++} ++ ++static boolean ++parse_noop (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ (void) entry; ++ return parse_true(get_noop(), argv, arg_ptr); ++} ++ ++static boolean ++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 (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 boolean ++parse_type (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_type (argv, arg_ptr, entry, pred_type); ++} ++ ++static boolean ++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 ++ { ++ return false; ++ } ++} ++ ++static boolean ++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. */ ++ if (get_relative_timestamp(offset_str, &tval, 0, DAYSECS, errmsg)) ++ { ++ our_pred = insert_primary (entry); ++ our_pred->args.reftime = tval; ++ our_pred->est_success_rate = estimate_file_age_success_rate(tval.ts.tv_sec / DAYSECS); ++ return true; ++ } ++ else ++ { ++ error(1, 0, _("Invalid argument %s to -used"), offset_str); ++ return false; ++ } ++ } ++ else ++ { ++ return false; /* missing argument */ ++ } ++} ++ ++static boolean ++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 ++ { ++ int uid_len = strspn (username, "0123456789"); ++ if (uid_len && (username[uid_len]==0)) ++ uid = safe_atoi (username); ++ else ++ return false; ++ } ++ our_pred = insert_primary (entry); ++ our_pred->args.uid = uid; ++ our_pred->est_success_rate = (our_pred->args.uid < 100) ? 0.99 : 0.2; ++ return true; ++ } ++ return false; ++} ++ ++static boolean ++parse_version (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ int features = 0; ++ int flags; ++ ++ (void) argv; ++ (void) arg_ptr; ++ (void) entry; ++ ++ display_findutils_version("find"); ++ printf (_("Features enabled: ")); ++ ++#if CACHE_IDS ++ printf("CACHE_IDS "); ++ ++features; ++#endif ++#if DEBUG ++ printf("DEBUG "); ++ ++features; ++#endif ++#if DEBUG_STAT ++ printf("DEBUG_STAT "); ++ ++features; ++#endif ++#if defined USE_STRUCT_DIRENT_D_TYPE && defined HAVE_STRUCT_DIRENT_D_TYPE ++ printf("D_TYPE "); ++ ++features; ++#endif ++#if defined O_NOFOLLOW ++ printf("O_NOFOLLOW(%s) ", ++ (options.open_nofollow_available ? "enabled" : "disabled")); ++ ++features; ++#endif ++#if defined LEAF_OPTIMISATION ++ printf("LEAF_OPTIMISATION "); ++ ++features; ++#endif ++ ++ flags = 0; ++ if (is_fts_enabled(&flags)) ++ { ++ int nflags = 0; ++ printf("FTS("); ++ ++features; ++ ++ if (flags & FTS_CWDFD) ++ { ++ if (nflags) ++ { ++ printf(","); ++ } ++ printf("FTS_CWDFD"); ++ ++nflags; ++ } ++ printf(") "); ++ } ++ ++ printf("CBO(level=%d) ", (int)(options.optimisation_level)); ++ ++features; ++ ++ if (0 == features) ++ { ++ /* For the moment, leave this as English in case someone wants ++ to parse these strings. */ ++ printf("none"); ++ } ++ printf("\n"); ++ ++ exit (0); ++} ++ ++static boolean ++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 boolean ++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 boolean ++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 boolean ++parse_warn (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ options.warnings = true; ++ return parse_noop(entry, argv, arg_ptr); ++} ++ ++static boolean ++parse_xtype (const struct parser_table* entry, char **argv, int *arg_ptr) ++{ ++ return insert_type (argv, arg_ptr, entry, pred_xtype); ++} ++ ++static boolean ++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(1, 0, _("Arguments to -type should contain only one letter")); ++ 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; ++#ifdef S_IFLNK ++ case 'l': /* symbolic link */ ++ type_cell = S_IFLNK; ++ rate = 0.1f; ++ break; ++#endif ++#ifdef S_IFIFO ++ case 'p': /* pipe */ ++ type_cell = S_IFIFO; ++ rate = 0.01f; ++ break; ++#endif ++#ifdef S_IFSOCK ++ case 's': /* socket */ ++ type_cell = S_IFSOCK; ++ rate = 0.01f; ++ break; ++#endif ++#ifdef S_IFDOOR ++ case 'D': /* Solaris door */ ++ type_cell = S_IFDOOR; ++ rate = 0.01f; ++ break; ++#endif ++ default: /* None of the above ... nuke 'em. */ ++ error(1, 0, _("Unknown argument to -type: %c"), (*typeletter)); ++ return false; ++ } ++ our_pred = insert_primary_withpred (entry, which_pred); ++ 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 boolean ++stream_is_tty(FILE *fp) ++{ ++ int fd = fileno(fp); ++ if (-1 == fd) ++ { ++ return false; /* not a valid stream */ ++ } ++ else ++ { ++ return isatty(fd) ? true : false; ++ } ++ ++} ++ ++ ++ ++ ++/* XXX: do we need to pass FUNC to this function? */ ++static boolean ++insert_fprintf (struct format_val *vec, ++ const struct parser_table *entry, PRED_FUNC func, ++ const char *format_const) ++{ ++ char *format = (char*)format_const; /* XXX: casting away constness */ ++ register char *scan; /* Current address in scanning `format'. */ ++ register char *scan2; /* Address inside of element being scanned. */ ++ struct segment **segmentp; /* Address of current segment. */ ++ struct predicate *our_pred; ++ ++ our_pred = insert_primary_withpred (entry, func); ++ our_pred->side_effects = our_pred->no_default_print = true; ++ our_pred->args.printf_vec = *vec; ++ our_pred->need_type = false; ++ our_pred->need_stat = false; ++ our_pred->p_cost = NeedsNothing; ++ ++ segmentp = &our_pred->args.printf_vec.segment; ++ *segmentp = NULL; ++ ++ for (scan = format; *scan; scan++) ++ { ++ if (*scan == '\\') ++ { ++ scan2 = scan + 1; ++ if (*scan2 >= '0' && *scan2 <= '7') ++ { ++ register int n, i; ++ ++ for (i = n = 0; i < 3 && (*scan2 >= '0' && *scan2 <= '7'); ++ i++, scan2++) ++ n = 8 * n + *scan2 - '0'; ++ scan2--; ++ *scan = n; ++ } ++ else ++ { ++ switch (*scan2) ++ { ++ case 'a': ++ *scan = 7; ++ break; ++ case 'b': ++ *scan = '\b'; ++ break; ++ case 'c': ++ make_segment (segmentp, format, scan - format, ++ KIND_STOP, 0, 0, ++ our_pred); ++ if (our_pred->need_stat && (our_pred->p_cost < NeedsStatInfo)) ++ our_pred->p_cost = NeedsStatInfo; ++ return true; ++ case 'f': ++ *scan = '\f'; ++ break; ++ case 'n': ++ *scan = '\n'; ++ break; ++ case 'r': ++ *scan = '\r'; ++ break; ++ case 't': ++ *scan = '\t'; ++ break; ++ case 'v': ++ *scan = '\v'; ++ break; ++ case '\\': ++ /* *scan = '\\'; * it already is */ ++ break; ++ default: ++ error (0, 0, ++ _("warning: unrecognized escape `\\%c'"), *scan2); ++ scan++; ++ continue; ++ } ++ } ++ segmentp = make_segment (segmentp, format, scan - format + 1, ++ KIND_PLAIN, 0, 0, ++ our_pred); ++ format = scan2 + 1; /* Move past the escape. */ ++ scan = scan2; /* Incremented immediately by `for'. */ ++ } ++ else if (*scan == '%') ++ { ++ if (scan[1] == 0) ++ { ++ /* Trailing %. We don't like those. */ ++ error (1, 0, _("error: %s at end of format string"), scan); ++ } ++ else if (scan[1] == '%') ++ { ++ segmentp = make_segment (segmentp, format, scan - format + 1, ++ KIND_PLAIN, 0, 0, ++ our_pred); ++ scan++; ++ format = scan + 1; ++ continue; ++ } ++ /* Scan past flags, width and precision, to verify kind. */ ++ for (scan2 = scan; *++scan2 && strchr ("-+ #", *scan2);) ++ /* Do nothing. */ ; ++ while (ISDIGIT (*scan2)) ++ scan2++; ++ if (*scan2 == '.') ++ for (scan2++; ISDIGIT (*scan2); scan2++) ++ /* Do nothing. */ ; ++ if (strchr ("abcdDfFgGhHiklmMnpPsStuUyY", *scan2)) ++ { ++ segmentp = make_segment (segmentp, format, scan2 - format, ++ KIND_FORMAT, *scan2, 0, ++ our_pred); ++ scan = scan2; ++ format = scan + 1; ++ } ++ else if (strchr ("ABCT", *scan2) && scan2[1]) ++ { ++ segmentp = make_segment (segmentp, format, scan2 - format, ++ KIND_FORMAT, scan2[0], scan2[1], ++ our_pred); ++ scan = scan2 + 1; ++ format = scan + 1; ++ continue; ++ } ++ else ++ { ++ /* An unrecognized % escape. Print the char after the %. */ ++ error (0, 0, _("warning: unrecognized format directive `%%%c'"), ++ *scan2); ++ segmentp = make_segment (segmentp, format, scan - format, ++ KIND_PLAIN, 0, 0, ++ our_pred); ++ format = scan + 1; ++ continue; ++ } ++ } ++ } ++ ++ if (scan > format) ++ make_segment (segmentp, format, scan - format, KIND_PLAIN, 0, 0, ++ our_pred); ++ return true; ++} ++ ++/* Create a new fprintf segment in *SEGMENT, with type KIND, ++ from the text in FORMAT, which has length LEN. ++ Return the address of the `next' pointer of the new segment. */ ++ ++static struct segment ** ++make_segment (struct segment **segment, ++ char *format, ++ int len, ++ int kind, ++ char format_char, ++ char aux_format_char, ++ struct predicate *pred) ++{ ++ enum EvaluationCost mycost = NeedsNothing; ++ char *fmt; ++ ++ *segment = xmalloc (sizeof (struct segment)); ++ ++ (*segment)->segkind = kind; ++ (*segment)->format_char[0] = format_char; ++ (*segment)->format_char[1] = aux_format_char; ++ (*segment)->next = NULL; ++ (*segment)->text_len = len; ++ ++ fmt = (*segment)->text = xmalloc (len + sizeof "d"); ++ strncpy (fmt, format, len); ++ fmt += len; ++ ++ switch (kind) ++ { ++ case KIND_PLAIN: /* Plain text string, no % conversion. */ ++ case KIND_STOP: /* Terminate argument, no newline. */ ++ assert (0 == format_char); ++ assert (0 == aux_format_char); ++ *fmt = '\0'; ++ if (mycost > pred->p_cost) ++ pred->p_cost = NeedsNothing; ++ return &(*segment)->next; ++ break; ++ } ++ ++ assert (kind == KIND_FORMAT); ++ switch (format_char) ++ { ++ case 'l': /* object of symlink */ ++ pred->need_stat = true; ++ mycost = NeedsLinkName; ++ *fmt++ = 's'; ++ break; ++ ++ case 'y': /* file type */ ++ pred->need_type = true; ++ mycost = NeedsType; ++ *fmt++ = 's'; ++ break; ++ ++ case 'a': /* atime in `ctime' format */ ++ case 'A': /* atime in user-specified strftime format */ ++ case 'B': /* birth time in user-specified strftime format */ ++ case 'c': /* ctime in `ctime' format */ ++ case 'C': /* ctime in user-specified strftime format */ ++ case 'F': /* file system type */ ++ case 'g': /* group name */ ++ case 'i': /* inode number */ ++ case 'M': /* mode in `ls -l' format (eg., "drwxr-xr-x") */ ++ case 's': /* size in bytes */ ++ case 't': /* mtime in `ctime' format */ ++ case 'T': /* mtime in user-specified strftime format */ ++ case 'u': /* user name */ ++ pred->need_stat = true; ++ mycost = NeedsStatInfo; ++ *fmt++ = 's'; ++ break; ++ ++ case 'S': /* sparseness */ ++ pred->need_stat = true; ++ mycost = NeedsStatInfo; ++ *fmt++ = 'g'; ++ break; ++ ++ case 'Y': /* symlink pointed file type */ ++ pred->need_stat = true; ++ mycost = NeedsType; /* true for amortised effect */ ++ *fmt++ = 's'; ++ break; ++ ++ case 'f': /* basename of path */ ++ case 'h': /* leading directories part of path */ ++ case 'p': /* pathname */ ++ case 'P': /* pathname with ARGV element stripped */ ++ *fmt++ = 's'; ++ break; ++ ++ case 'H': /* ARGV element file was found under */ ++ *fmt++ = 's'; ++ break; ++ ++ /* Numeric items that one might expect to honour ++ * #, 0, + flags but which do not. ++ */ ++ case 'G': /* GID number */ ++ case 'U': /* UID number */ ++ case 'b': /* size in 512-byte blocks (NOT birthtime in ctime fmt)*/ ++ case 'D': /* Filesystem device on which the file exits */ ++ case 'k': /* size in 1K blocks */ ++ case 'n': /* number of links */ ++ pred->need_stat = true; ++ mycost = NeedsStatInfo; ++ *fmt++ = 's'; ++ break; ++ ++ /* Numeric items that DO honour #, 0, + flags. ++ */ ++ case 'd': /* depth in search tree (0 = ARGV element) */ ++ *fmt++ = 'd'; ++ break; ++ ++ case 'm': /* mode as octal number (perms only) */ ++ *fmt++ = 'o'; ++ pred->need_stat = true; ++ mycost = NeedsStatInfo; ++ break; ++ ++ case '{': ++ case '[': ++ case '(': ++ error (1, 0, ++ _("error: the format directive `%%%c' is reserved for future use"), ++ (int)kind); ++ /*NOTREACHED*/ ++ break; ++ } ++ *fmt = '\0'; ++ ++ if (mycost > pred->p_cost) ++ pred->p_cost = mycost; ++ return &(*segment)->next; ++} ++ ++static void ++check_path_safety(const char *action, char **argv) ++{ ++ char *s; ++ const char *path = getenv("PATH"); ++ 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; ++ } ++ ++ (void)argv; ++ ++ s = next_element(path, 1); ++ while ((s = next_element ((char *) NULL, 1)) != NULL) ++ { ++ if (0 == strcmp(s, ".")) ++ { ++ error(1, 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 \".\" or leading or trailing " ++ "colons)"), ++ action); ++ } ++ else if ('/' != s[0]) ++ { ++ /* Relative paths are also dangerous in $PATH. */ ++ error(1, 0, _("The relative path %1$s is included in the PATH " ++ "environment variable, which is insecure in " ++ "combination with the %2$s action of find. " ++ "Please remove that entry from $PATH"), ++ safely_quote_err_filename(0, s), ++ action); ++ } ++ } ++} ++ ++ ++/* handles both exec and ok predicate */ ++static boolean ++new_insert_exec_ok (const char *action, ++ const struct parser_table *entry, ++ int dirfd, ++ 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 '{}'. */ ++ boolean allow_plus; /* True if + is a valid terminator */ ++ int brace_count; /* Number of instances of {}. */ ++ 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); ++ 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; ++ ++ 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)) ++ { ++ options.ignore_readdir_race = false; ++ check_path_safety(action, argv); ++ execp->use_current_dir = true; ++ } ++ else ++ { ++ execp->use_current_dir = false; ++ } ++ ++ 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; ++ (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_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(1, 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 && brace_count > 1) ++ { ++ ++ const char *suffix; ++ if (func == pred_execdir) ++ suffix = "dir"; ++ else ++ suffix = ""; ++ ++ error(1, 0, ++ _("Only one instance of {} is supported with -exec%s ... +"), ++ suffix); ++ } ++ ++ /* 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(1, 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; ++} ++ ++ ++ ++static boolean ++insert_exec_ok (const char *action, ++ const struct parser_table *entry, ++ int dirfd, ++ char **argv, ++ int *arg_ptr) ++{ ++ return new_insert_exec_ok(action, entry, dirfd, argv, arg_ptr); ++} ++ ++ ++ ++/* 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 boolean ++get_relative_timestamp (const char *str, ++ struct time_val *result, ++ time_t origin, ++ double sec_per_unit, ++ const char *overflowmessage) ++{ ++ uintmax_t checkval; ++ double offset, seconds, f; ++ ++ 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; ++ default: break; ++ } ++ ++ /* 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 (F). ++ */ ++ f = modf(offset * sec_per_unit, &seconds); ++ ++ result->ts.tv_sec = origin - seconds; ++ result->ts.tv_nsec = fabs(f * 1e9); ++ ++ /* Check for overflow. */ ++ checkval = (uintmax_t)origin - seconds; ++ if (checkval != result->ts.tv_sec) ++ { ++ /* an overflow has occurred. */ ++ error (1, 0, overflowmessage, str); ++ } ++ 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 boolean ++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"; ++ time_t origin; ++ ++ 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 + (DAYSECS-1); ++ origin += (DAYSECS-1); ++ if (origin != expected) ++ { ++ error(1, 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)) ++ return false; ++ ++ our_pred = insert_primary (entry); ++ 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 boolean ++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 boolean ++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); ++ 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 = fopen_safer (path, "w"); ++ p->filename = path; ++ ++ if (p->stream == NULL) ++ { ++ fatal_file_error(path); ++ } ++ } ++ ++ p->dest_is_tty = stream_is_tty(p->stream); ++} ++ ++static void ++open_stdout (struct format_val *p) ++{ ++ open_output_file("/dev/stdout", p); ++} +diff -purN findutils-4.3.12.orig/find/pred.c findutils-4.3.12/find/pred.c +--- findutils-4.3.12.orig/find/pred.c 2007-12-19 16:12:34.000000000 -0500 ++++ findutils-4.3.12/find/pred.c 2008-01-30 08:46:05.758843847 -0500 +@@ -47,6 +47,14 @@ + #include "error.h" + #include "verify.h" + ++#ifdef WITH_SELINUX ++#include <selinux/selinux.h> ++#endif /*WITH_SELINUX*/ ++ ++#ifndef FNM_CASEFOLD ++#define FNM_CASEFOLD (1<<4) ++#endif /*FNM_CASEFOLD*/ ++ + #if ENABLE_NLS + # include <libintl.h> + # define _(Text) gettext (Text) +@@ -229,6 +237,9 @@ struct pred_assoc pred_table[] = + {pred_user, "user "}, + {pred_writable, "writable "}, + {pred_xtype, "xtype "}, ++#ifdef WITH_SELINUX ++ {pred_scontext, "context"}, ++#endif /*WITH_SELINUX*/ + {0, "none "} + }; + #endif +@@ -1045,6 +1056,26 @@ do_fprintf(struct format_val *dest, + mode_to_filetype(stat_buf->st_mode & S_IFMT)); + } + break; ++#ifdef WITH_SELINUX ++ case 'Z': /* SELinux security context */ ++ { ++ security_context_t scontext; ++ int rv; ++ rv = (*options.x_getfilecon)(state.rel_pathname, &scontext); ++ ++ if ( rv < 0 ) { ++ fprintf(stderr, "getfileconf(%s): %s", ++ pathname, strerror(errno)); ++ fflush(stderr); ++ } ++ else { ++ segment->text[segment->text_len] = 's'; ++ checked_fprintf (dest, segment->text, scontext); ++ freecon(scontext); ++ } ++ } ++ break ; ++#endif /* WITH_SELINUX */ + } + /* end of KIND_FORMAT case */ + break; +@@ -1838,6 +1869,31 @@ pred_xtype (const char *pathname, struct + */ + return (pred_type (pathname, &sbuf, pred_ptr)); + } ++ ++ ++#ifdef WITH_SELINUX ++ ++boolean ++pred_scontext (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ int rv; ++ security_context_t scontext; ++ ++ rv = (* options.x_getfilecon)(state.rel_pathname, &scontext); ++ ++ if ( rv < 0 ) { ++ (void) fprintf(stderr, "getfilecon(%s): %s\n", pathname, strerror(errno)); ++ (void) fflush(stderr); ++ return ( false ); ++ } ++ ++ rv = (fnmatch(pred_ptr->args.scontext, scontext,0)==0); ++ freecon(scontext); ++ return rv; ++} ++ ++#endif /*WITH_SELINUX*/ ++ + + /* 1) fork to get a child; parent remembers the child pid + 2) child execs the command requested +diff -purN findutils-4.3.12.orig/find/pred.c.orig findutils-4.3.12/find/pred.c.orig +--- findutils-4.3.12.orig/find/pred.c.orig 1969-12-31 19:00:00.000000000 -0500 ++++ findutils-4.3.12/find/pred.c.orig 2007-12-19 16:12:34.000000000 -0500 +@@ -0,0 +1,2405 @@ ++/* pred.c -- execute the expression tree. ++ Copyright (C) 1990, 1991, 1992, 1993, 1994, 2000, 2003, ++ 2004, 2005, 2006, 2007 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/>. ++*/ ++ ++#include <config.h> ++#include "defs.h" ++ ++#include <fnmatch.h> ++#include <signal.h> ++#include <math.h> ++#include <pwd.h> ++#include <grp.h> ++#include <sys/types.h> ++#include <sys/stat.h> ++#include <errno.h> ++#include <assert.h> ++#include <stdarg.h> ++#include <fcntl.h> ++#include <locale.h> ++#include <openat.h> ++#include "xalloc.h" ++#include "dirname.h" ++#include "human.h" ++#include "modetype.h" ++#include "filemode.h" ++#include "wait.h" ++#include "printquoted.h" ++#include "buildcmd.h" ++#include "yesno.h" ++#include "listfile.h" ++#include "stat-time.h" ++#include "dircallback.h" ++#include "error.h" ++#include "verify.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 ++ ++#if !defined(SIGCHLD) && defined(SIGCLD) ++#define SIGCHLD SIGCLD ++#endif ++ ++ ++ ++#if HAVE_DIRENT_H ++# include <dirent.h> ++# define NAMLEN(dirent) strlen((dirent)->d_name) ++#else ++# define dirent direct ++# define NAMLEN(dirent) (dirent)->d_namlen ++# if HAVE_SYS_NDIR_H ++# include <sys/ndir.h> ++# endif ++# if HAVE_SYS_DIR_H ++# include <sys/dir.h> ++# endif ++# if HAVE_NDIR_H ++# include <ndir.h> ++# endif ++#endif ++ ++#ifdef CLOSEDIR_VOID ++/* Fake a return value. */ ++#define CLOSEDIR(d) (closedir (d), 0) ++#else ++#define CLOSEDIR(d) closedir (d) ++#endif ++ ++ ++ ++ ++/* Get or fake the disk device blocksize. ++ Usually defined by sys/param.h (if at all). */ ++#ifndef DEV_BSIZE ++# ifdef BSIZE ++# define DEV_BSIZE BSIZE ++# else /* !BSIZE */ ++# define DEV_BSIZE 4096 ++# endif /* !BSIZE */ ++#endif /* !DEV_BSIZE */ ++ ++/* Extract or fake data from a `struct stat'. ++ ST_BLKSIZE: Preferred I/O blocksize for the file, in bytes. ++ ST_NBLOCKS: Number of blocks in the file, including indirect blocks. ++ ST_NBLOCKSIZE: Size of blocks used when calculating ST_NBLOCKS. */ ++#ifndef HAVE_STRUCT_STAT_ST_BLOCKS ++# define ST_BLKSIZE(statbuf) DEV_BSIZE ++# if defined _POSIX_SOURCE || !defined BSIZE /* fileblocks.c uses BSIZE. */ ++# define ST_NBLOCKS(statbuf) \ ++ (S_ISREG ((statbuf).st_mode) \ ++ || S_ISDIR ((statbuf).st_mode) \ ++ ? (statbuf).st_size / ST_NBLOCKSIZE + ((statbuf).st_size % ST_NBLOCKSIZE != 0) : 0) ++# else /* !_POSIX_SOURCE && BSIZE */ ++# define ST_NBLOCKS(statbuf) \ ++ (S_ISREG ((statbuf).st_mode) \ ++ || S_ISDIR ((statbuf).st_mode) \ ++ ? st_blocks ((statbuf).st_size) : 0) ++# endif /* !_POSIX_SOURCE && BSIZE */ ++#else /* HAVE_STRUCT_STAT_ST_BLOCKS */ ++/* Some systems, like Sequents, return st_blksize of 0 on pipes. */ ++# define ST_BLKSIZE(statbuf) ((statbuf).st_blksize > 0 \ ++ ? (statbuf).st_blksize : DEV_BSIZE) ++# if defined hpux || defined __hpux__ || defined __hpux ++/* HP-UX counts st_blocks in 1024-byte units. ++ This loses when mixing HP-UX and BSD file systems with NFS. */ ++# define ST_NBLOCKSIZE 1024 ++# else /* !hpux */ ++# if defined _AIX && defined _I386 ++/* AIX PS/2 counts st_blocks in 4K units. */ ++# define ST_NBLOCKSIZE (4 * 1024) ++# else /* not AIX PS/2 */ ++# if defined _CRAY ++# define ST_NBLOCKS(statbuf) \ ++ (S_ISREG ((statbuf).st_mode) \ ++ || S_ISDIR ((statbuf).st_mode) \ ++ ? (statbuf).st_blocks * ST_BLKSIZE(statbuf)/ST_NBLOCKSIZE : 0) ++# endif /* _CRAY */ ++# endif /* not AIX PS/2 */ ++# endif /* !hpux */ ++#endif /* HAVE_STRUCT_STAT_ST_BLOCKS */ ++ ++#ifndef ST_NBLOCKS ++# define ST_NBLOCKS(statbuf) \ ++ (S_ISREG ((statbuf).st_mode) \ ++ || S_ISDIR ((statbuf).st_mode) \ ++ ? (statbuf).st_blocks : 0) ++#endif ++ ++#ifndef ST_NBLOCKSIZE ++# define ST_NBLOCKSIZE 512 ++#endif ++ ++ ++#undef MAX ++#define MAX(a, b) ((a) > (b) ? (a) : (b)) ++ ++static boolean match_lname PARAMS((const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr, boolean ignore_case)); ++ ++static char *format_date PARAMS((struct timespec ts, int kind)); ++static char *ctime_format PARAMS((struct timespec ts)); ++ ++#ifdef DEBUG ++struct pred_assoc ++{ ++ PRED_FUNC pred_func; ++ char *pred_name; ++}; ++ ++struct pred_assoc pred_table[] = ++{ ++ {pred_amin, "amin "}, ++ {pred_and, "and "}, ++ {pred_anewer, "anewer "}, ++ {pred_atime, "atime "}, ++ {pred_closeparen, ") "}, ++ {pred_cmin, "cmin "}, ++ {pred_cnewer, "cnewer "}, ++ {pred_comma, ", "}, ++ {pred_ctime, "ctime "}, ++ {pred_delete, "delete "}, ++ {pred_empty, "empty "}, ++ {pred_exec, "exec "}, ++ {pred_execdir, "execdir "}, ++ {pred_executable, "executable "}, ++ {pred_false, "false "}, ++ {pred_fprint, "fprint "}, ++ {pred_fprint0, "fprint0 "}, ++ {pred_fprintf, "fprintf "}, ++ {pred_fstype, "fstype "}, ++ {pred_gid, "gid "}, ++ {pred_group, "group "}, ++ {pred_ilname, "ilname "}, ++ {pred_iname, "iname "}, ++ {pred_inum, "inum "}, ++ {pred_ipath, "ipath "}, ++ {pred_links, "links "}, ++ {pred_lname, "lname "}, ++ {pred_ls, "ls "}, ++ {pred_mmin, "mmin "}, ++ {pred_mtime, "mtime "}, ++ {pred_name, "name "}, ++ {pred_negate, "not "}, ++ {pred_newer, "newer "}, ++ {pred_newerXY, "newerXY "}, ++ {pred_nogroup, "nogroup "}, ++ {pred_nouser, "nouser "}, ++ {pred_ok, "ok "}, ++ {pred_okdir, "okdir "}, ++ {pred_openparen, "( "}, ++ {pred_or, "or "}, ++ {pred_path, "path "}, ++ {pred_perm, "perm "}, ++ {pred_print, "print "}, ++ {pred_print0, "print0 "}, ++ {pred_prune, "prune "}, ++ {pred_quit, "quit "}, ++ {pred_readable, "readable "}, ++ {pred_regex, "regex "}, ++ {pred_samefile,"samefile "}, ++ {pred_size, "size "}, ++ {pred_true, "true "}, ++ {pred_type, "type "}, ++ {pred_uid, "uid "}, ++ {pred_used, "used "}, ++ {pred_user, "user "}, ++ {pred_writable, "writable "}, ++ {pred_xtype, "xtype "}, ++ {0, "none "} ++}; ++#endif ++ ++/* Returns ts1 - ts2 */ ++static double ts_difference(struct timespec ts1, ++ struct timespec ts2) ++{ ++ double d = difftime(ts1.tv_sec, ts2.tv_sec) ++ + (1.0e-9 * (ts1.tv_nsec - ts2.tv_nsec)); ++ return d; ++} ++ ++ ++static int ++compare_ts(struct timespec ts1, ++ struct timespec ts2) ++{ ++ if ((ts1.tv_sec == ts2.tv_sec) && ++ (ts1.tv_nsec == ts2.tv_nsec)) ++ { ++ return 0; ++ } ++ else ++ { ++ double diff = ts_difference(ts1, ts2); ++ return diff < 0.0 ? -1 : +1; ++ } ++} ++ ++/* Predicate processing routines. ++ ++ PATHNAME is the full pathname of the file being checked. ++ *STAT_BUF contains information about PATHNAME. ++ *PRED_PTR contains information for applying the predicate. ++ ++ Return true if the file passes this predicate, false if not. */ ++ ++ ++/* pred_timewindow ++ * ++ * Returns true if THE_TIME is ++ * COMP_GT: after the specified time ++ * COMP_LT: before the specified time ++ * COMP_EQ: less than WINDOW seconds after the specified time. ++ */ ++static boolean ++pred_timewindow(struct timespec ts, struct predicate const *pred_ptr, int window) ++{ ++ switch (pred_ptr->args.reftime.kind) ++ { ++ case COMP_GT: ++ return compare_ts(ts, pred_ptr->args.reftime.ts) > 0; ++ ++ case COMP_LT: ++ return compare_ts(ts, pred_ptr->args.reftime.ts) < 0; ++ ++ case COMP_EQ: ++ { ++ double delta = ts_difference(ts, pred_ptr->args.reftime.ts); ++ return (delta >= 0.0 && delta < window); ++ } ++ } ++ assert (0); ++ abort (); ++} ++ ++ ++boolean ++pred_amin (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) &pathname; ++ return pred_timewindow(get_stat_atime(stat_buf), pred_ptr, 60); ++} ++ ++boolean ++pred_and (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ if (pred_ptr->pred_left == NULL ++ || apply_predicate(pathname, stat_buf, pred_ptr->pred_left)) ++ { ++ return apply_predicate(pathname, stat_buf, pred_ptr->pred_right); ++ } ++ else ++ return false; ++} ++ ++boolean ++pred_anewer (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) &pathname; ++ assert (COMP_GT == pred_ptr->args.reftime.kind); ++ return compare_ts(get_stat_atime(stat_buf), pred_ptr->args.reftime.ts) > 0; ++} ++ ++boolean ++pred_atime (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) &pathname; ++ return pred_timewindow(get_stat_atime(stat_buf), pred_ptr, DAYSECS); ++} ++ ++boolean ++pred_closeparen (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) &pathname; ++ (void) &stat_buf; ++ (void) &pred_ptr; ++ ++ return true; ++} ++ ++boolean ++pred_cmin (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ return pred_timewindow(get_stat_ctime(stat_buf), pred_ptr, 60); ++} ++ ++boolean ++pred_cnewer (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ ++ assert (COMP_GT == pred_ptr->args.reftime.kind); ++ return compare_ts(get_stat_ctime(stat_buf), pred_ptr->args.reftime.ts) > 0; ++} ++ ++boolean ++pred_comma (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ if (pred_ptr->pred_left != NULL) ++ { ++ apply_predicate(pathname, stat_buf,pred_ptr->pred_left); ++ } ++ return apply_predicate(pathname, stat_buf, pred_ptr->pred_right); ++} ++ ++boolean ++pred_ctime (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) &pathname; ++ return pred_timewindow(get_stat_ctime(stat_buf), pred_ptr, DAYSECS); ++} ++ ++static boolean ++perform_delete(int flags) ++{ ++ return 0 == unlinkat(state.cwd_dir_fd, state.rel_pathname, flags); ++} ++ ++ ++boolean ++pred_delete (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pred_ptr; ++ (void) stat_buf; ++ if (strcmp (state.rel_pathname, ".")) ++ { ++ int flags=0; ++ if (state.have_stat && S_ISDIR(stat_buf->st_mode)) ++ flags |= AT_REMOVEDIR; ++ if (perform_delete(flags)) ++ { ++ return true; ++ } ++ else ++ { ++ if (EISDIR == errno) ++ { ++ if ((flags & AT_REMOVEDIR) == 0) ++ { ++ /* unlink() operation failed because we should have done rmdir(). */ ++ flags |= AT_REMOVEDIR; ++ if (perform_delete(flags)) ++ return true; ++ } ++ } ++ } ++ error (0, errno, _("cannot delete %s" ++ /* TRANSLATORS: the argument is either a ++ * file or a directory, but we do not know which. ++ * Mail bug-findutils@gnu.org if you cannot correctly ++ * translate the string without knowing. ++ */), ++ safely_quote_err_filename(0, pathname)); ++ /* Previously I had believed that having the -delete action ++ * return false provided the user with control over whether an ++ * error message is issued. While this is true, the policy of ++ * not affecting the exit status is contrary to the POSIX ++ * requirement that diagnostic messages are accompanied by a ++ * nonzero exit status. While -delete is not a POSIX option and ++ * we can therefore opt not to follow POSIX in this case, that ++ * seems somewhat arbitrary and confusing. So, as of ++ * findutils-4.3.11, we also set the exit status in this case. ++ */ ++ state.exit_status = 1; ++ return false; ++ } ++ else ++ { ++ /* nothing to do. */ ++ return true; ++ } ++} ++ ++boolean ++pred_empty (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) pred_ptr; ++ ++ if (S_ISDIR (stat_buf->st_mode)) ++ { ++ int fd; ++ DIR *d; ++ struct dirent *dp; ++ boolean empty = true; ++ ++ errno = 0; ++ if ((fd = openat(state.cwd_dir_fd, state.rel_pathname, O_RDONLY ++#if defined O_LARGEFILE ++ |O_LARGEFILE ++#endif ++ )) < 0) ++ { ++ error (0, errno, "%s", safely_quote_err_filename(0, pathname)); ++ state.exit_status = 1; ++ return false; ++ } ++ d = fdopendir (fd); ++ if (d == NULL) ++ { ++ error (0, errno, "%s", safely_quote_err_filename(0, pathname)); ++ state.exit_status = 1; ++ return false; ++ } ++ for (dp = readdir (d); dp; dp = readdir (d)) ++ { ++ if (dp->d_name[0] != '.' ++ || (dp->d_name[1] != '\0' ++ && (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) ++ { ++ empty = false; ++ break; ++ } ++ } ++ if (CLOSEDIR (d)) ++ { ++ error (0, errno, "%s", safely_quote_err_filename(0, pathname)); ++ state.exit_status = 1; ++ return false; ++ } ++ return (empty); ++ } ++ else if (S_ISREG (stat_buf->st_mode)) ++ return (stat_buf->st_size == 0); ++ else ++ return (false); ++} ++ ++static boolean ++new_impl_pred_exec (int dirfd, const char *pathname, ++ struct stat *stat_buf, ++ struct predicate *pred_ptr, ++ const char *prefix, size_t pfxlen) ++{ ++ struct exec_val *execp = &pred_ptr->args.exec_vec; ++ size_t len = strlen(pathname); ++ ++ (void) stat_buf; ++ execp->dirfd = dirfd; ++ if (execp->multiple) ++ { ++ /* Push the argument onto the current list. ++ * The command may or may not be run at this point, ++ * depending on the command line length limits. ++ */ ++ bc_push_arg(&execp->ctl, ++ &execp->state, ++ pathname, len+1, ++ prefix, pfxlen, ++ 0); ++ ++ /* remember that there are pending execdirs. */ ++ state.execdirs_outstanding = true; ++ ++ /* POSIX: If the primary expression is punctuated by a plus ++ * sign, the primary shall always evaluate as true ++ */ ++ return true; ++ } ++ else ++ { ++ int i; ++ ++ for (i=0; i<execp->num_args; ++i) ++ { ++ bc_do_insert(&execp->ctl, ++ &execp->state, ++ execp->replace_vec[i], ++ strlen(execp->replace_vec[i]), ++ prefix, pfxlen, ++ pathname, len, ++ 0); ++ } ++ ++ /* Actually invoke the command. */ ++ return execp->ctl.exec_callback(&execp->ctl, ++ &execp->state); ++ } ++} ++ ++ ++boolean ++pred_exec (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ return new_impl_pred_exec(get_start_dirfd(), ++ pathname, stat_buf, pred_ptr, NULL, 0); ++} ++ ++boolean ++pred_execdir (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ const char *prefix = (state.rel_pathname[0] == '/') ? NULL : "./"; ++ (void) &pathname; ++ return new_impl_pred_exec (get_current_dirfd(), ++ state.rel_pathname, stat_buf, pred_ptr, ++ prefix, (prefix ? 2 : 0)); ++} ++ ++boolean ++pred_false (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) &pathname; ++ (void) &stat_buf; ++ (void) &pred_ptr; ++ ++ ++ return (false); ++} ++ ++boolean ++pred_fls (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ FILE * stream = pred_ptr->args.printf_vec.stream; ++ list_file (pathname, state.cwd_dir_fd, state.rel_pathname, stat_buf, ++ options.start_time.tv_sec, ++ options.output_block_size, ++ pred_ptr->literal_control_chars, stream); ++ return true; ++} ++ ++boolean ++pred_fprint (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) &pathname; ++ (void) &stat_buf; ++ ++ print_quoted(pred_ptr->args.printf_vec.stream, ++ pred_ptr->args.printf_vec.quote_opts, ++ pred_ptr->args.printf_vec.dest_is_tty, ++ "%s\n", ++ pathname); ++ return true; ++} ++ ++boolean ++pred_fprint0 (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ FILE * fp = pred_ptr->args.printf_vec.stream; ++ ++ (void) &stat_buf; ++ ++ fputs (pathname, fp); ++ putc (0, fp); ++ return true; ++} ++ ++ ++ ++static char* ++mode_to_filetype(mode_t m) ++{ ++#define HANDLE_TYPE(t,letter) if (m==t) { return letter; } ++#ifdef S_IFREG ++ HANDLE_TYPE(S_IFREG, "f"); /* regular file */ ++#endif ++#ifdef S_IFDIR ++ HANDLE_TYPE(S_IFDIR, "d"); /* directory */ ++#endif ++#ifdef S_IFLNK ++ HANDLE_TYPE(S_IFLNK, "l"); /* symbolic link */ ++#endif ++#ifdef S_IFSOCK ++ HANDLE_TYPE(S_IFSOCK, "s"); /* Unix domain socket */ ++#endif ++#ifdef S_IFBLK ++ HANDLE_TYPE(S_IFBLK, "b"); /* block device */ ++#endif ++#ifdef S_IFCHR ++ HANDLE_TYPE(S_IFCHR, "c"); /* character device */ ++#endif ++#ifdef S_IFIFO ++ HANDLE_TYPE(S_IFIFO, "p"); /* FIFO */ ++#endif ++#ifdef S_IFDOOR ++ HANDLE_TYPE(S_IFDOOR, "D"); /* Door (e.g. on Solaris) */ ++#endif ++ return "U"; /* Unknown */ ++} ++ ++static double ++file_sparseness(const struct stat *p) ++{ ++#if defined HAVE_STRUCT_STAT_ST_BLOCKS ++ if (0 == p->st_size) ++ { ++ if (0 == p->st_blocks) ++ return 1.0; ++ else ++ return p->st_blocks < 0 ? -HUGE_VAL : HUGE_VAL; ++ } ++ else ++ { ++ double blklen = file_blocksize(p) * (double)p->st_blocks; ++ return blklen / p->st_size; ++ } ++#else ++ return 1.0; ++#endif ++} ++ ++ ++ ++static void ++checked_fprintf(struct format_val *dest, const char *fmt, ...) ++{ ++ int rv; ++ va_list ap; ++ ++ va_start(ap, fmt); ++ rv = vfprintf(dest->stream, fmt, ap); ++ if (rv < 0) ++ nonfatal_file_error(dest->filename); ++} ++ ++ ++static void ++checked_print_quoted (struct format_val *dest, ++ const char *format, const char *s) ++{ ++ int rv = print_quoted(dest->stream, dest->quote_opts, dest->dest_is_tty, ++ format, s); ++ if (rv < 0) ++ nonfatal_file_error(dest->filename); ++} ++ ++ ++static void ++checked_fwrite(void *p, size_t siz, size_t nmemb, struct format_val *dest) ++{ ++ int items_written = fwrite(p, siz, nmemb, dest->stream); ++ if (items_written < nmemb) ++ nonfatal_file_error(dest->filename); ++} ++ ++static void ++checked_fflush(struct format_val *dest) ++{ ++ if (0 != fflush(dest->stream)) ++ { ++ nonfatal_file_error(dest->filename); ++ } ++} ++ ++static void ++do_fprintf(struct format_val *dest, ++ struct segment *segment, ++ const char *pathname, ++ const struct stat *stat_buf) ++{ ++ char hbuf[LONGEST_HUMAN_READABLE + 1]; ++ const char *cp; ++ ++ switch (segment->segkind) ++ { ++ case KIND_PLAIN: /* Plain text string (no % conversion). */ ++ /* trusted */ ++ checked_fwrite(segment->text, 1, segment->text_len, dest); ++ break; ++ ++ case KIND_STOP: /* Terminate argument and flush output. */ ++ /* trusted */ ++ checked_fwrite(segment->text, 1, segment->text_len, dest); ++ checked_fflush(dest); ++ break; ++ ++ case KIND_FORMAT: ++ switch (segment->format_char[0]) ++ { ++ case 'a': /* atime in `ctime' format. */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, ctime_format (get_stat_atime(stat_buf))); ++ break; ++ case 'b': /* size in 512-byte blocks */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, ++ human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), ++ hbuf, human_ceiling, ++ ST_NBLOCKSIZE, 512)); ++ break; ++ case 'c': /* ctime in `ctime' format */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, ctime_format (get_stat_ctime(stat_buf))); ++ break; ++ case 'd': /* depth in search tree */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, state.curdepth); ++ break; ++ case 'D': /* Device on which file exists (stat.st_dev) */ ++ /* trusted */ ++ checked_fprintf (dest, segment->text, ++ human_readable ((uintmax_t) stat_buf->st_dev, hbuf, ++ human_ceiling, 1, 1)); ++ break; ++ case 'f': /* base name of path */ ++ /* sanitised */ ++ { ++ char *base = base_name (pathname); ++ checked_print_quoted (dest, segment->text, base); ++ free (base); ++ } ++ break; ++ case 'F': /* file system type */ ++ /* trusted */ ++ checked_print_quoted (dest, segment->text, filesystem_type (stat_buf, pathname)); ++ break; ++ case 'g': /* group name */ ++ /* trusted */ ++ /* (well, the actual group is selected by the user but ++ * its name was selected by the system administrator) ++ */ ++ { ++ struct group *g; ++ ++ g = getgrgid (stat_buf->st_gid); ++ if (g) ++ { ++ segment->text[segment->text_len] = 's'; ++ checked_fprintf (dest, segment->text, g->gr_name); ++ break; ++ } ++ else ++ { ++ /* Do nothing. */ ++ /*FALLTHROUGH*/ ++ } ++ } ++ /*FALLTHROUGH*/ /*...sometimes, so 'G' case.*/ ++ ++ case 'G': /* GID number */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, ++ human_readable ((uintmax_t) stat_buf->st_gid, hbuf, ++ human_ceiling, 1, 1)); ++ break; ++ case 'h': /* leading directories part of path */ ++ /* sanitised */ ++ { ++ cp = strrchr (pathname, '/'); ++ if (cp == NULL) /* No leading directories. */ ++ { ++ /* If there is no slash in the pathname, we still ++ * print the string because it contains characters ++ * other than just '%s'. The %h expands to ".". ++ */ ++ checked_print_quoted (dest, segment->text, "."); ++ } ++ else ++ { ++ char *s = strdup(pathname); ++ s[cp - pathname] = 0; ++ checked_print_quoted (dest, segment->text, s); ++ free(s); ++ } ++ } ++ break; ++ ++ case 'H': /* ARGV element file was found under */ ++ /* trusted */ ++ { ++ char *s = xmalloc(state.starting_path_length+1); ++ memcpy(s, pathname, state.starting_path_length); ++ s[state.starting_path_length] = 0; ++ checked_fprintf (dest, segment->text, s); ++ free(s); ++ } ++ break; ++ ++ case 'i': /* inode number */ ++ /* UNTRUSTED, but not exploitable I think */ ++ checked_fprintf (dest, segment->text, ++ human_readable ((uintmax_t) stat_buf->st_ino, hbuf, ++ human_ceiling, ++ 1, 1)); ++ break; ++ case 'k': /* size in 1K blocks */ ++ /* UNTRUSTED, but not exploitable I think */ ++ checked_fprintf (dest, segment->text, ++ human_readable ((uintmax_t) ST_NBLOCKS (*stat_buf), ++ hbuf, human_ceiling, ++ ST_NBLOCKSIZE, 1024)); ++ break; ++ case 'l': /* object of symlink */ ++ /* sanitised */ ++#ifdef S_ISLNK ++ { ++ char *linkname = 0; ++ ++ if (S_ISLNK (stat_buf->st_mode)) ++ { ++ linkname = get_link_name_at (pathname, state.cwd_dir_fd, state.rel_pathname); ++ if (linkname == 0) ++ state.exit_status = 1; ++ } ++ if (linkname) ++ { ++ checked_print_quoted (dest, segment->text, linkname); ++ free (linkname); ++ } ++ else ++ { ++ /* We still need to honour the field width etc., so this is ++ * not a no-op. ++ */ ++ checked_print_quoted (dest, segment->text, ""); ++ } ++ } ++#endif /* S_ISLNK */ ++ break; ++ ++ case 'M': /* mode as 10 chars (eg., "-rwxr-x--x" */ ++ /* UNTRUSTED, probably unexploitable */ ++ { ++ char modestring[16] ; ++ filemodestring (stat_buf, modestring); ++ modestring[10] = '\0'; ++ checked_fprintf (dest, segment->text, modestring); ++ } ++ break; ++ ++ case 'm': /* mode as octal number (perms only) */ ++ /* UNTRUSTED, probably unexploitable */ ++ { ++ /* Output the mode portably using the traditional numbers, ++ even if the host unwisely uses some other numbering ++ scheme. But help the compiler in the common case where ++ the host uses the traditional numbering scheme. */ ++ mode_t m = stat_buf->st_mode; ++ boolean traditional_numbering_scheme = ++ (S_ISUID == 04000 && S_ISGID == 02000 && S_ISVTX == 01000 ++ && S_IRUSR == 00400 && S_IWUSR == 00200 && S_IXUSR == 00100 ++ && S_IRGRP == 00040 && S_IWGRP == 00020 && S_IXGRP == 00010 ++ && S_IROTH == 00004 && S_IWOTH == 00002 && S_IXOTH == 00001); ++ checked_fprintf (dest, segment->text, ++ (traditional_numbering_scheme ++ ? m & MODE_ALL ++ : ((m & S_ISUID ? 04000 : 0) ++ | (m & S_ISGID ? 02000 : 0) ++ | (m & S_ISVTX ? 01000 : 0) ++ | (m & S_IRUSR ? 00400 : 0) ++ | (m & S_IWUSR ? 00200 : 0) ++ | (m & S_IXUSR ? 00100 : 0) ++ | (m & S_IRGRP ? 00040 : 0) ++ | (m & S_IWGRP ? 00020 : 0) ++ | (m & S_IXGRP ? 00010 : 0) ++ | (m & S_IROTH ? 00004 : 0) ++ | (m & S_IWOTH ? 00002 : 0) ++ | (m & S_IXOTH ? 00001 : 0)))); ++ } ++ break; ++ ++ case 'n': /* number of links */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, ++ human_readable ((uintmax_t) stat_buf->st_nlink, ++ hbuf, ++ human_ceiling, ++ 1, 1)); ++ break; ++ ++ case 'p': /* pathname */ ++ /* sanitised */ ++ checked_print_quoted (dest, segment->text, pathname); ++ break; ++ ++ case 'P': /* pathname with ARGV element stripped */ ++ /* sanitised */ ++ if (state.curdepth > 0) ++ { ++ cp = pathname + state.starting_path_length; ++ if (*cp == '/') ++ /* Move past the slash between the ARGV element ++ and the rest of the pathname. But if the ARGV element ++ ends in a slash, we didn't add another, so we've ++ already skipped past it. */ ++ cp++; ++ } ++ else ++ { ++ cp = ""; ++ } ++ checked_print_quoted (dest, segment->text, cp); ++ break; ++ ++ case 's': /* size in bytes */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, ++ human_readable ((uintmax_t) stat_buf->st_size, ++ hbuf, human_ceiling, 1, 1)); ++ break; ++ ++ case 'S': /* sparseness */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, file_sparseness(stat_buf));; ++ break; ++ ++ case 't': /* mtime in `ctime' format */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, ++ ctime_format (get_stat_mtime(stat_buf))); ++ break; ++ ++ case 'u': /* user name */ ++ /* trusted */ ++ /* (well, the actual user is selected by the user on systems ++ * where chown is not restricted, but the user name was ++ * selected by the system administrator) ++ */ ++ { ++ struct passwd *p; ++ ++ p = getpwuid (stat_buf->st_uid); ++ if (p) ++ { ++ segment->text[segment->text_len] = 's'; ++ checked_fprintf (dest, segment->text, p->pw_name); ++ break; ++ } ++ /* else fallthru */ ++ } ++ /* FALLTHROUGH*/ /* .. to case U */ ++ ++ case 'U': /* UID number */ ++ /* UNTRUSTED, probably unexploitable */ ++ checked_fprintf (dest, segment->text, ++ human_readable ((uintmax_t) stat_buf->st_uid, hbuf, ++ human_ceiling, 1, 1)); ++ break; ++ ++ /* %Y: type of file system entry like `ls -l`: ++ * (d,-,l,s,p,b,c,n) n=nonexistent(symlink) ++ */ ++ case 'Y': /* in case of symlink */ ++ /* trusted */ ++ { ++#ifdef S_ISLNK ++ if (S_ISLNK (stat_buf->st_mode)) ++ { ++ struct stat sbuf; ++ /* If we would normally follow links, do not do so. ++ * If we would normally not follow links, do so. ++ */ ++ if ((following_links() ? lstat : stat) ++ (state.rel_pathname, &sbuf) != 0) ++ { ++ if ( errno == ENOENT ) ++ { ++ checked_fprintf (dest, segment->text, "N"); ++ break; ++ } ++ else if ( errno == ELOOP ) ++ { ++ checked_fprintf (dest, segment->text, "L"); ++ break; ++ } ++ else ++ { ++ checked_fprintf (dest, segment->text, "?"); ++ error (0, errno, "%s", ++ safely_quote_err_filename(0, pathname)); ++ /* exit_status = 1; ++ return ; */ ++ break; ++ } ++ } ++ checked_fprintf (dest, segment->text, ++ mode_to_filetype(sbuf.st_mode & S_IFMT)); ++ } ++#endif /* S_ISLNK */ ++ else ++ { ++ checked_fprintf (dest, segment->text, ++ mode_to_filetype(stat_buf->st_mode & S_IFMT)); ++ } ++ } ++ break; ++ ++ case 'y': ++ /* trusted */ ++ { ++ checked_fprintf (dest, segment->text, ++ mode_to_filetype(stat_buf->st_mode & S_IFMT)); ++ } ++ break; ++ } ++ /* end of KIND_FORMAT case */ ++ break; ++ } ++} ++ ++boolean ++pred_fprintf (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ struct format_val *dest = &pred_ptr->args.printf_vec; ++ struct segment *segment; ++ ++ for (segment = dest->segment; segment; segment = segment->next) ++ { ++ if ( (KIND_FORMAT == segment->segkind) && segment->format_char[1]) /* Component of date. */ ++ { ++ struct timespec ts; ++ int valid = 0; ++ ++ switch (segment->format_char[0]) ++ { ++ case 'A': ++ ts = get_stat_atime(stat_buf); ++ valid = 1; ++ break; ++ case 'B': ++ ts = get_stat_birthtime(stat_buf); ++ if ('@' == segment->format_char[1]) ++ valid = 1; ++ else ++ valid = (ts.tv_nsec >= 0); ++ break; ++ case 'C': ++ ts = get_stat_ctime(stat_buf); ++ valid = 1; ++ break; ++ case 'T': ++ ts = get_stat_mtime(stat_buf); ++ valid = 1; ++ break; ++ default: ++ assert (0); ++ abort (); ++ } ++ /* We trust the output of format_date not to contain ++ * nasty characters, though the value of the date ++ * is itself untrusted data. ++ */ ++ if (valid) ++ { ++ /* trusted */ ++ checked_fprintf (dest, segment->text, ++ format_date (ts, segment->format_char[1])); ++ } ++ else ++ { ++ /* The specified timestamp is not available, output ++ * nothing for the timestamp, but use the rest (so that ++ * for example find foo -printf '[%Bs] %p\n' can print ++ * "[] foo"). ++ */ ++ /* trusted */ ++ checked_fprintf (dest, segment->text, ""); ++ } ++ } ++ else ++ { ++ /* Print a segment which is not a date. */ ++ do_fprintf(dest, segment, pathname, stat_buf); ++ } ++ } ++ return true; ++} ++ ++boolean ++pred_fstype (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ ++ if (strcmp (filesystem_type (stat_buf, pathname), pred_ptr->args.str) == 0) ++ return true; ++ else ++ return false; ++} ++ ++boolean ++pred_gid (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ ++ switch (pred_ptr->args.numinfo.kind) ++ { ++ case COMP_GT: ++ if (stat_buf->st_gid > pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ case COMP_LT: ++ if (stat_buf->st_gid < pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ case COMP_EQ: ++ if (stat_buf->st_gid == pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ } ++ return (false); ++} ++ ++boolean ++pred_group (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ ++ if (pred_ptr->args.gid == stat_buf->st_gid) ++ return (true); ++ else ++ return (false); ++} ++ ++boolean ++pred_ilname (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ return match_lname (pathname, stat_buf, pred_ptr, true); ++} ++ ++/* Common code between -name, -iname. PATHNAME is being visited, STR ++ is name to compare basename against, and FLAGS are passed to ++ fnmatch. Recall that 'find / -name /' is one of the few times where a '/' ++ in the -name must actually find something. */ ++static boolean ++pred_name_common (const char *pathname, const char *str, int flags) ++{ ++ char *p; ++ boolean b; ++ /* We used to use last_component() here, but that would not allow us to modify the ++ * input string, which is const. We could optimise by duplicating the string only ++ * if we need to modify it, and I'll do that if there is a measurable ++ * performance difference on a machine built after 1990... ++ */ ++ char *base = base_name (pathname); ++ /* remove trailing slashes, but leave "/" or "//foo" unchanged. */ ++ strip_trailing_slashes(base); ++ ++ /* FNM_PERIOD is not used here because POSIX requires that it not be. ++ * See http://standards.ieee.org/reading/ieee/interp/1003-2-92_int/pasc-1003.2-126.html ++ */ ++ b = fnmatch (str, base, flags) == 0; ++ free (base); ++ return b; ++} ++ ++boolean ++pred_iname (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) stat_buf; ++ return pred_name_common (pathname, pred_ptr->args.str, FNM_CASEFOLD); ++} ++ ++boolean ++pred_inum (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ ++ switch (pred_ptr->args.numinfo.kind) ++ { ++ case COMP_GT: ++ if (stat_buf->st_ino > pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ case COMP_LT: ++ if (stat_buf->st_ino < pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ case COMP_EQ: ++ if (stat_buf->st_ino == pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ } ++ return (false); ++} ++ ++boolean ++pred_ipath (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) stat_buf; ++ ++ if (fnmatch (pred_ptr->args.str, pathname, FNM_CASEFOLD) == 0) ++ return (true); ++ return (false); ++} ++ ++boolean ++pred_links (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ ++ switch (pred_ptr->args.numinfo.kind) ++ { ++ case COMP_GT: ++ if (stat_buf->st_nlink > pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ case COMP_LT: ++ if (stat_buf->st_nlink < pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ case COMP_EQ: ++ if (stat_buf->st_nlink == pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ } ++ return (false); ++} ++ ++boolean ++pred_lname (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ return match_lname (pathname, stat_buf, pred_ptr, false); ++} ++ ++static boolean ++match_lname (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr, boolean ignore_case) ++{ ++ boolean ret = false; ++#ifdef S_ISLNK ++ if (S_ISLNK (stat_buf->st_mode)) ++ { ++ char *linkname = get_link_name_at (pathname, state.cwd_dir_fd, state.rel_pathname); ++ if (linkname) ++ { ++ if (fnmatch (pred_ptr->args.str, linkname, ++ ignore_case ? FNM_CASEFOLD : 0) == 0) ++ ret = true; ++ free (linkname); ++ } ++ } ++#endif /* S_ISLNK */ ++ return ret; ++} ++ ++boolean ++pred_ls (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ return pred_fls(pathname, stat_buf, pred_ptr); ++} ++ ++boolean ++pred_mmin (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) &pathname; ++ return pred_timewindow(get_stat_mtime(stat_buf), pred_ptr, 60); ++} ++ ++boolean ++pred_mtime (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ return pred_timewindow(get_stat_mtime(stat_buf), pred_ptr, DAYSECS); ++} ++ ++boolean ++pred_name (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) stat_buf; ++ return pred_name_common (pathname, pred_ptr->args.str, 0); ++} ++ ++boolean ++pred_negate (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ return !apply_predicate(pathname, stat_buf, pred_ptr->pred_right); ++} ++ ++boolean ++pred_newer (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ ++ assert (COMP_GT == pred_ptr->args.reftime.kind); ++ return compare_ts(get_stat_mtime(stat_buf), pred_ptr->args.reftime.ts) > 0; ++} ++ ++boolean ++pred_newerXY (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ struct timespec ts; ++ boolean collected = false; ++ ++ assert (COMP_GT == pred_ptr->args.reftime.kind); ++ ++ switch (pred_ptr->args.reftime.xval) ++ { ++ case XVAL_TIME: ++ assert (pred_ptr->args.reftime.xval != XVAL_TIME); ++ return false; ++ ++ case XVAL_ATIME: ++ ts = get_stat_atime(stat_buf); ++ collected = true; ++ break; ++ ++ case XVAL_BIRTHTIME: ++ ts = get_stat_birthtime(stat_buf); ++ collected = true; ++ if (ts.tv_nsec < 0); ++ { ++ /* XXX: Cannot determine birth time. Warn once. */ ++ error(0, 0, _("Warning: cannot determine birth time of file %s"), ++ safely_quote_err_filename(0, pathname)); ++ return false; ++ } ++ break; ++ ++ case XVAL_CTIME: ++ ts = get_stat_ctime(stat_buf); ++ collected = true; ++ break; ++ ++ case XVAL_MTIME: ++ ts = get_stat_mtime(stat_buf); ++ collected = true; ++ break; ++ } ++ ++ assert (collected); ++ return compare_ts(ts, pred_ptr->args.reftime.ts) > 0; ++} ++ ++boolean ++pred_nogroup (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) pred_ptr; ++ ++#ifdef CACHE_IDS ++ extern char *gid_unused; ++ ++ return gid_unused[(unsigned) stat_buf->st_gid]; ++#else ++ return getgrgid (stat_buf->st_gid) == NULL; ++#endif ++} ++ ++boolean ++pred_nouser (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++#ifdef CACHE_IDS ++ extern char *uid_unused; ++#endif ++ ++ (void) pathname; ++ (void) pred_ptr; ++ ++#ifdef CACHE_IDS ++ return uid_unused[(unsigned) stat_buf->st_uid]; ++#else ++ return getpwuid (stat_buf->st_uid) == NULL; ++#endif ++} ++ ++ ++static boolean ++is_ok(const char *program, const char *arg) ++{ ++ fflush (stdout); ++ /* The draft open standard requires that, in the POSIX locale, ++ the last non-blank character of this prompt be '?'. ++ The exact format is not specified. ++ This standard does not have requirements for locales other than POSIX ++ */ ++ /* XXX: printing UNTRUSTED data here. */ ++ fprintf (stderr, _("< %s ... %s > ? " ++ /* TRANSLATORS: we would like, if possible, the final non-blank ++ * character of this string to be '?', but it is not ++ * wholly essential. */ ++ ), program, arg); ++ fflush (stderr); ++ return yesno(); ++} ++ ++boolean ++pred_ok (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ if (is_ok(pred_ptr->args.exec_vec.replace_vec[0], pathname)) ++ return new_impl_pred_exec (get_start_dirfd(), ++ pathname, stat_buf, pred_ptr, NULL, 0); ++ else ++ return false; ++} ++ ++boolean ++pred_okdir (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ const char *prefix = (state.rel_pathname[0] == '/') ? NULL : "./"; ++ if (is_ok(pred_ptr->args.exec_vec.replace_vec[0], pathname)) ++ return new_impl_pred_exec (get_current_dirfd(), ++ state.rel_pathname, stat_buf, pred_ptr, ++ prefix, (prefix ? 2 : 0)); ++ else ++ return false; ++} ++ ++boolean ++pred_openparen (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) stat_buf; ++ (void) pred_ptr; ++ return true; ++} ++ ++boolean ++pred_or (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ if (pred_ptr->pred_left == NULL ++ || !apply_predicate(pathname, stat_buf, pred_ptr->pred_left)) ++ { ++ return apply_predicate(pathname, stat_buf, pred_ptr->pred_right); ++ } ++ else ++ return true; ++} ++ ++boolean ++pred_path (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) stat_buf; ++ if (fnmatch (pred_ptr->args.str, pathname, 0) == 0) ++ return (true); ++ return (false); ++} ++ ++boolean ++pred_perm (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ mode_t mode = stat_buf->st_mode; ++ mode_t perm_val = pred_ptr->args.perm.val[S_ISDIR (mode) != 0]; ++ (void) pathname; ++ switch (pred_ptr->args.perm.kind) ++ { ++ case PERM_AT_LEAST: ++ return (mode & perm_val) == perm_val; ++ break; ++ ++ case PERM_ANY: ++ /* True if any of the bits set in the mask are also set in the file's mode. ++ * ++ * ++ * Otherwise, if onum is prefixed by a hyphen, the primary shall ++ * evaluate as true if at least all of the bits specified in ++ * onum that are also set in the octal mask 07777 are set. ++ * ++ * Eric Blake's interpretation is that the mode argument is zero, ++ ++ */ ++ if (0 == perm_val) ++ return true; /* Savannah bug 14748; we used to return false */ ++ else ++ return (mode & perm_val) != 0; ++ break; ++ ++ case PERM_EXACT: ++ return (mode & MODE_ALL) == perm_val; ++ break; ++ ++ default: ++ abort (); ++ break; ++ } ++} ++ ++ ++struct access_check_args ++{ ++ const char *filename; ++ int access_type; ++ int cb_errno; ++}; ++ ++ ++static int ++access_callback(void *context) ++{ ++ int rv; ++ struct access_check_args *args = context; ++ if ((rv = access(args->filename, args->access_type)) < 0) ++ args->cb_errno = errno; ++ return rv; ++} ++ ++static int ++can_access(int access_type) ++{ ++ struct access_check_args args; ++ args.filename = state.rel_pathname; ++ args.access_type = access_type; ++ args.cb_errno = 0; ++ return 0 == run_in_dir(state.cwd_dir_fd, access_callback, &args); ++} ++ ++ ++boolean ++pred_executable (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) stat_buf; ++ (void) pred_ptr; ++ ++ return can_access(X_OK); ++} ++ ++boolean ++pred_readable (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) stat_buf; ++ (void) pred_ptr; ++ ++ return can_access(R_OK); ++} ++ ++boolean ++pred_writable (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) stat_buf; ++ (void) pred_ptr; ++ ++ return can_access(W_OK); ++} ++ ++boolean ++pred_print (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) stat_buf; ++ (void) pred_ptr; ++ ++ print_quoted(pred_ptr->args.printf_vec.stream, ++ pred_ptr->args.printf_vec.quote_opts, ++ pred_ptr->args.printf_vec.dest_is_tty, ++ "%s\n", pathname); ++ return true; ++} ++ ++boolean ++pred_print0 (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ return pred_fprint0(pathname, stat_buf, pred_ptr); ++} ++ ++boolean ++pred_prune (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) pred_ptr; ++ ++ if (options.do_dir_first == true && /* no effect with -depth */ ++ stat_buf != NULL && ++ S_ISDIR(stat_buf->st_mode)) ++ state.stop_at_current_level = true; ++ ++ /* findutils used to return options.do_dir_first here, so that -prune ++ * returns true only if -depth is not in effect. But POSIX requires ++ * that -prune always evaluate as true. ++ */ ++ return true; ++} ++ ++boolean ++pred_quit (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) stat_buf; ++ (void) pred_ptr; ++ ++ /* Run any cleanups. This includes executing any command lines ++ * we have partly built but not executed. ++ */ ++ cleanup(); ++ ++ /* Since -exec and friends don't leave child processes running in the ++ * background, there is no need to wait for them here. ++ */ ++ exit(state.exit_status); /* 0 for success, etc. */ ++} ++ ++boolean ++pred_regex (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ int len = strlen (pathname); ++(void) stat_buf; ++ if (re_match (pred_ptr->args.regex, pathname, len, 0, ++ (struct re_registers *) NULL) == len) ++ return (true); ++ return (false); ++} ++ ++boolean ++pred_size (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ uintmax_t f_val; ++ ++ (void) pathname; ++ f_val = ((stat_buf->st_size / pred_ptr->args.size.blocksize) ++ + (stat_buf->st_size % pred_ptr->args.size.blocksize != 0)); ++ switch (pred_ptr->args.size.kind) ++ { ++ case COMP_GT: ++ if (f_val > pred_ptr->args.size.size) ++ return (true); ++ break; ++ case COMP_LT: ++ if (f_val < pred_ptr->args.size.size) ++ return (true); ++ break; ++ case COMP_EQ: ++ if (f_val == pred_ptr->args.size.size) ++ return (true); ++ break; ++ } ++ return (false); ++} ++ ++boolean ++pred_samefile (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ /* Potential optimisation: because of the loop protection, we always ++ * know the device of the current directory, hence the device number ++ * of the file we're currently considering. If -L is not in effect, ++ * and the device number of the file we're looking for is not the ++ * same as the device number of the current directory, this ++ * predicate cannot return true. Hence there would be no need to ++ * stat the file we're looking at. ++ */ ++ (void) pathname; ++ ++ /* We will often still have an fd open on the file under consideration, ++ * but that's just to ensure inode number stability by maintaining ++ * a reference to it; we don't need the file for anything else. ++ */ ++ return stat_buf->st_ino == pred_ptr->args.samefileid.ino ++ && stat_buf->st_dev == pred_ptr->args.samefileid.dev; ++} ++ ++boolean ++pred_true (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ (void) stat_buf; ++ (void) pred_ptr; ++ return true; ++} ++ ++boolean ++pred_type (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ mode_t mode; ++ mode_t type = pred_ptr->args.type; ++ ++ assert (state.have_type); ++ ++ if (0 == state.type) ++ { ++ /* This can sometimes happen with broken NFS servers. ++ * See Savannah bug #16378. ++ */ ++ return false; ++ } ++ ++ (void) pathname; ++ ++ if (state.have_stat) ++ mode = stat_buf->st_mode; ++ else ++ mode = state.type; ++ ++#ifndef S_IFMT ++ /* POSIX system; check `mode' the slow way. */ ++ if ((S_ISBLK (mode) && type == S_IFBLK) ++ || (S_ISCHR (mode) && type == S_IFCHR) ++ || (S_ISDIR (mode) && type == S_IFDIR) ++ || (S_ISREG (mode) && type == S_IFREG) ++#ifdef S_IFLNK ++ || (S_ISLNK (mode) && type == S_IFLNK) ++#endif ++#ifdef S_IFIFO ++ || (S_ISFIFO (mode) && type == S_IFIFO) ++#endif ++#ifdef S_IFSOCK ++ || (S_ISSOCK (mode) && type == S_IFSOCK) ++#endif ++#ifdef S_IFDOOR ++ || (S_ISDOOR (mode) && type == S_IFDOOR) ++#endif ++ ) ++#else /* S_IFMT */ ++ /* Unix system; check `mode' the fast way. */ ++ if ((mode & S_IFMT) == type) ++#endif /* S_IFMT */ ++ return (true); ++ else ++ return (false); ++} ++ ++boolean ++pred_uid (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ switch (pred_ptr->args.numinfo.kind) ++ { ++ case COMP_GT: ++ if (stat_buf->st_uid > pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ case COMP_LT: ++ if (stat_buf->st_uid < pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ case COMP_EQ: ++ if (stat_buf->st_uid == pred_ptr->args.numinfo.l_val) ++ return (true); ++ break; ++ } ++ return (false); ++} ++ ++boolean ++pred_used (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ struct timespec delta, at, ct; ++ ++ (void) pathname; ++ ++ /* TODO: this needs to be retested carefully (manually, if necessary) */ ++ at = get_stat_atime(stat_buf); ++ ct = get_stat_ctime(stat_buf); ++ delta.tv_sec = at.tv_sec - ct.tv_sec; ++ delta.tv_nsec = at.tv_nsec - ct.tv_nsec; ++ if (delta.tv_nsec < 0) ++ { ++ delta.tv_nsec += 1000000000; ++ delta.tv_sec -= 1; ++ } ++ return pred_timewindow(delta, pred_ptr, DAYSECS); ++} ++ ++boolean ++pred_user (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ (void) pathname; ++ if (pred_ptr->args.uid == stat_buf->st_uid) ++ return (true); ++ else ++ return (false); ++} ++ ++boolean ++pred_xtype (const char *pathname, struct stat *stat_buf, struct predicate *pred_ptr) ++{ ++ struct stat sbuf; /* local copy, not stat_buf because we're using a different stat method */ ++ int (*ystat) (const char*, struct stat *p); ++ ++ /* If we would normally stat the link itself, stat the target instead. ++ * If we would normally follow the link, stat the link itself instead. ++ */ ++ if (following_links()) ++ ystat = optionp_stat; ++ else ++ ystat = optionl_stat; ++ ++ set_stat_placeholders(&sbuf); ++ if ((*ystat) (state.rel_pathname, &sbuf) != 0) ++ { ++ if (following_links() && errno == ENOENT) ++ { ++ /* If we failed to follow the symlink, ++ * fall back on looking at the symlink itself. ++ */ ++ /* Mimic behavior of ls -lL. */ ++ return (pred_type (pathname, stat_buf, pred_ptr)); ++ } ++ else ++ { ++ error (0, errno, "%s", safely_quote_err_filename(0, pathname)); ++ state.exit_status = 1; ++ } ++ return false; ++ } ++ /* Now that we have our stat() information, query it in the same ++ * way that -type does. ++ */ ++ return (pred_type (pathname, &sbuf, pred_ptr)); ++} ++ ++/* 1) fork to get a child; parent remembers the child pid ++ 2) child execs the command requested ++ 3) parent waits for child; checks for proper pid of child ++ ++ Possible returns: ++ ++ ret errno status(h) status(l) ++ ++ pid x signal# 0177 stopped ++ pid x exit arg 0 term by _exit ++ pid x 0 signal # term by signal ++ -1 EINTR parent got signal ++ -1 other some other kind of error ++ ++ Return true only if the pid matches, status(l) is ++ zero, and the exit arg (status high) is 0. ++ Otherwise return false, possibly printing an error message. */ ++ ++ ++static boolean ++prep_child_for_exec (boolean close_stdin, int dirfd) ++{ ++ boolean ok = true; ++ if (close_stdin) ++ { ++ const char inputfile[] = "/dev/null"; ++ ++ if (close(0) < 0) ++ { ++ error(0, errno, _("Cannot close standard input")); ++ ok = false; ++ } ++ else ++ { ++ if (open(inputfile, O_RDONLY ++#if defined O_LARGEFILE ++ |O_LARGEFILE ++#endif ++ ) < 0) ++ { ++ /* This is not entirely fatal, since ++ * executing the child with a closed ++ * stdin is almost as good as executing it ++ * with its stdin attached to /dev/null. ++ */ ++ error (0, errno, "%s", safely_quote_err_filename(0, inputfile)); ++ /* do not set ok=false, it is OK to continue anyway. */ ++ } ++ } ++ } ++ ++ /* Even if DebugSearch is set, don't announce our change of ++ * directory, since we're not going to emit a subsequent ++ * announcement of a call to stat() anyway, as we're about to exec ++ * something. ++ */ ++ if (dirfd != AT_FDCWD) ++ { ++ assert (dirfd >= 0); ++ if (0 != fchdir(dirfd)) ++ { ++ /* If we cannot execute our command in the correct directory, ++ * we should not execute it at all. ++ */ ++ error(0, errno, _("Failed to change directory")); ++ ok = false; ++ } ++ } ++ return ok; ++} ++ ++ ++ ++int ++launch (const struct buildcmd_control *ctl, ++ struct buildcmd_state *buildstate) ++{ ++ int wait_status; ++ pid_t child_pid; ++ static int first_time = 1; ++ const struct exec_val *execp = buildstate->usercontext; ++ ++ if (!execp->use_current_dir) ++ { ++ assert (starting_desc >= 0); ++ assert (execp->dirfd == starting_desc); ++ } ++ ++ ++ /* Null terminate the arg list. */ ++ bc_push_arg (ctl, buildstate, (char *) NULL, 0, NULL, 0, false); ++ ++ /* Make sure output of command doesn't get mixed with find output. */ ++ fflush (stdout); ++ fflush (stderr); ++ ++ /* Make sure to listen for the kids. */ ++ if (first_time) ++ { ++ first_time = 0; ++ signal (SIGCHLD, SIG_DFL); ++ } ++ ++ child_pid = fork (); ++ if (child_pid == -1) ++ error (1, errno, _("cannot fork")); ++ if (child_pid == 0) ++ { ++ /* We are the child. */ ++ assert (starting_desc >= 0); ++ if (!prep_child_for_exec(execp->close_stdin, execp->dirfd)) ++ { ++ _exit(1); ++ } ++ ++ execvp (buildstate->cmd_argv[0], buildstate->cmd_argv); ++ error (0, errno, "%s", ++ safely_quote_err_filename(0, buildstate->cmd_argv[0])); ++ _exit (1); ++ } ++ ++ ++ /* In parent; set up for next time. */ ++ bc_clear_args(ctl, buildstate); ++ ++ ++ while (waitpid (child_pid, &wait_status, 0) == (pid_t) -1) ++ { ++ if (errno != EINTR) ++ { ++ error (0, errno, _("error waiting for %s"), ++ safely_quote_err_filename(0, buildstate->cmd_argv[0])); ++ state.exit_status = 1; ++ return 0; /* FAIL */ ++ } ++ } ++ ++ if (WIFSIGNALED (wait_status)) ++ { ++ error (0, 0, _("%1$s terminated by signal %2$d"), ++ quotearg_n_style(0, options.err_quoting_style, ++ buildstate->cmd_argv[0]), ++ WTERMSIG (wait_status)); ++ ++ if (execp->multiple) ++ { ++ /* -exec \; just returns false if the invoked command fails. ++ * -exec {} + returns true if the invoked command fails, but ++ * sets the program exit status. ++ */ ++ state.exit_status = 1; ++ } ++ ++ return 1; /* OK */ ++ } ++ ++ if (0 == WEXITSTATUS (wait_status)) ++ { ++ return 1; /* OK */ ++ } ++ else ++ { ++ if (execp->multiple) ++ { ++ /* -exec \; just returns false if the invoked command fails. ++ * -exec {} + returns true if the invoked command fails, but ++ * sets the program exit status. ++ */ ++ state.exit_status = 1; ++ } ++ return 0; /* FAIL */ ++ } ++ ++} ++ ++ ++/* Return a static string formatting the time WHEN according to the ++ * strftime format character KIND. ++ * ++ * This function contains a number of assertions. These look like ++ * runtime checks of the results of computations, which would be a ++ * problem since external events should not be tested for with ++ * "assert" (instead you should use "if"). However, they are not ++ * really runtime checks. The assertions actually exist to verify ++ * that the various buffers are correctly sized. ++ */ ++static char * ++format_date (struct timespec ts, int kind) ++{ ++ /* In theory, we use an extra 10 characters for 9 digits of ++ * nanoseconds and 1 for the decimal point. However, the real ++ * world is more complex than that. ++ * ++ * For example, some systems return junk in the tv_nsec part of ++ * st_birthtime. An example of this is the NetBSD-4.0-RELENG kernel ++ * (at Sat Mar 24 18:46:46 2007) running a NetBSD-3.1-RELEASE ++ * runtime and examining files on an msdos filesytem. So for that ++ * reason we set NS_BUF_LEN to 32, which is simply "long enough" as ++ * opposed to "exactly the right size". Note that the behaviour of ++ * NetBSD appears to be a result of the use of uninitialised data, ++ * as it's not 100% reproducible (more like 25%). ++ */ ++ enum { ++ NS_BUF_LEN = 32, ++ DATE_LEN_PERCENT_APLUS=21 /* length of result of %A+ (it's longer than %c)*/ ++ }; ++ static char buf[128u+10u + MAX(DATE_LEN_PERCENT_APLUS, ++ MAX (LONGEST_HUMAN_READABLE + 2, NS_BUF_LEN+64+200))]; ++ char ns_buf[NS_BUF_LEN]; /* -.9999999990 (- sign can happen!)*/ ++ int charsprinted, need_ns_suffix; ++ struct tm *tm; ++ char fmt[6]; ++ ++ /* human_readable() assumes we pass a buffer which is at least as ++ * long as LONGEST_HUMAN_READABLE. We use an assertion here to ++ * ensure that no nasty unsigned overflow happend in our calculation ++ * of the size of buf. Do the assertion here rather than in the ++ * code for %@ so that we find the problem quickly if it exists. If ++ * you want to submit a patch to move this into the if statement, go ++ * ahead, I'll apply it. But include performance timings ++ * demonstrating that the performance difference is actually ++ * measurable. ++ */ ++ verify (sizeof(buf) >= LONGEST_HUMAN_READABLE); ++ ++ charsprinted = 0; ++ need_ns_suffix = 0; ++ ++ /* Format the main part of the time. */ ++ if (kind == '+') ++ { ++ strcpy (fmt, "%F+%T"); ++ need_ns_suffix = 1; ++ } ++ else ++ { ++ fmt[0] = '%'; ++ fmt[1] = kind; ++ fmt[2] = '\0'; ++ ++ /* %a, %c, and %t are handled in ctime_format() */ ++ switch (kind) ++ { ++ case 'S': ++ case 'T': ++ case 'X': ++ case '@': ++ need_ns_suffix = 1; ++ break; ++ default: ++ need_ns_suffix = 0; ++ break; ++ } ++ } ++ ++ if (need_ns_suffix) ++ { ++ /* Format the nanoseconds part. Leave a trailing zero to ++ * discourage people from writing scripts which extract the ++ * fractional part of the timestamp by using column offsets. ++ * The reason for discouraging this is that in the future, the ++ * granularity may not be nanoseconds. ++ */ ++ ns_buf[0] = 0; ++ charsprinted = snprintf(ns_buf, NS_BUF_LEN, ".%09ld0", (long int)ts.tv_nsec); ++ assert (charsprinted < NS_BUF_LEN); ++ } ++ ++ if (kind != '@' ++ && (tm = localtime (&ts.tv_sec)) ++ && strftime (buf, sizeof buf, fmt, tm)) ++ { ++ /* For %AS, %CS, %TS, add the fractional part of the seconds ++ * information. ++ */ ++ if (need_ns_suffix) ++ { ++ assert ((sizeof buf - strlen(buf)) > strlen(ns_buf)); ++ strcat(buf, ns_buf); ++ } ++ return buf; ++ } ++ else ++ { ++ uintmax_t w = ts.tv_sec; ++ size_t used, len, remaining; ++ ++ /* XXX: note that we are negating an unsigned type which is the ++ * widest possible unsigned type. ++ */ ++ char *p = human_readable (ts.tv_sec < 0 ? -w : w, buf + 1, ++ human_ceiling, 1, 1); ++ assert (p > buf); ++ assert (p < (buf + (sizeof buf))); ++ if (ts.tv_sec < 0) ++ *--p = '-'; /* XXX: Ugh, relying on internal details of human_readable(). */ ++ ++ /* Add the nanoseconds part. Because we cannot enforce a ++ * particlar implementation of human_readable, we cannot assume ++ * any particular value for (p-buf). So we need to be careful ++ * that there is enough space remaining in the buffer. ++ */ ++ if (need_ns_suffix) ++ { ++ len = strlen(p); ++ used = (p-buf) + len; /* Offset into buf of current end */ ++ assert (sizeof buf > used); /* Ensure we can perform subtraction safely. */ ++ remaining = sizeof buf - used - 1u; /* allow space for NUL */ ++ ++ if (strlen(ns_buf) >= remaining) ++ { ++ error(0, 0, ++ "charsprinted=%ld but remaining=%lu: ns_buf=%s", ++ (long)charsprinted, (unsigned long)remaining, ns_buf); ++ } ++ assert (strlen(ns_buf) < remaining); ++ strcat(p, ns_buf); ++ } ++ return p; ++ } ++} ++ ++static const char *weekdays[] = ++ { ++ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ++ }; ++static char * months[] = ++ { ++ "Jan", "Feb", "Mar", "Apr", "May", "Jun", ++ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ++ }; ++ ++ ++static char * ++ctime_format (struct timespec ts) ++{ ++ const struct tm * ptm; ++#define TIME_BUF_LEN 1024u ++ static char resultbuf[TIME_BUF_LEN]; ++ int nout; ++ ++ ptm = localtime(&ts.tv_sec); ++ if (ptm) ++ { ++ assert (ptm->tm_wday >= 0); ++ assert (ptm->tm_wday < 7); ++ assert (ptm->tm_mon >= 0); ++ assert (ptm->tm_mon < 12); ++ assert (ptm->tm_hour >= 0); ++ assert (ptm->tm_hour < 24); ++ assert (ptm->tm_min < 60); ++ assert (ptm->tm_sec <= 61); /* allows 2 leap seconds. */ ++ ++ /* wkday mon mday hh:mm:ss.nnnnnnnnn yyyy */ ++ nout = snprintf(resultbuf, TIME_BUF_LEN, ++ "%3s %3s %2d %02d:%02d:%02d.%010ld %04d", ++ weekdays[ptm->tm_wday], ++ months[ptm->tm_mon], ++ ptm->tm_mday, ++ ptm->tm_hour, ++ ptm->tm_min, ++ ptm->tm_sec, ++ (long int)ts.tv_nsec, ++ 1900 + ptm->tm_year); ++ ++ assert (nout < TIME_BUF_LEN); ++ return resultbuf; ++ } ++ else ++ { ++ /* The time cannot be represented as a struct tm. ++ Output it as an integer. */ ++ return format_date (ts, '@'); ++ } ++} ++ ++/* Copy STR into BUF and trim blanks from the end of BUF. ++ Return BUF. */ ++ ++static char * ++blank_rtrim (str, buf) ++ char *str; ++ char *buf; ++{ ++ int i; ++ ++ if (str == NULL) ++ return (NULL); ++ strcpy (buf, str); ++ i = strlen (buf) - 1; ++ while ((i >= 0) && ((buf[i] == ' ') || buf[i] == '\t')) ++ i--; ++ buf[++i] = '\0'; ++ return (buf); ++} ++ ++/* Print out the predicate list starting at NODE. */ ++void ++print_list (FILE *fp, struct predicate *node) ++{ ++ struct predicate *cur; ++ char name[256]; ++ ++ cur = node; ++ while (cur != NULL) ++ { ++ fprintf (fp, "[%s] ", blank_rtrim (cur->p_name, name)); ++ cur = cur->pred_next; ++ } ++ fprintf (fp, "\n"); ++} ++ ++/* Print out the predicate list starting at NODE. */ ++static void ++print_parenthesised(FILE *fp, struct predicate *node) ++{ ++ int parens = 0; ++ ++ if (node) ++ { ++ if ((pred_is(node, pred_or) || pred_is(node, pred_and)) ++ && node->pred_left == NULL) ++ { ++ /* We print "<nothing> or X" as just "X" ++ * We print "<nothing> and X" as just "X" ++ */ ++ print_parenthesised(fp, node->pred_right); ++ } ++ else ++ { ++ if (node->pred_left || node->pred_right) ++ parens = 1; ++ ++ if (parens) ++ fprintf(fp, "%s", " ( "); ++ print_optlist(fp, node); ++ if (parens) ++ fprintf(fp, "%s", " ) "); ++ } ++ } ++} ++ ++void ++print_optlist (FILE *fp, const struct predicate *p) ++{ ++ if (p) ++ { ++ print_parenthesised(fp, p->pred_left); ++ fprintf (fp, ++ "%s%s", ++ p->need_stat ? "[call stat] " : "", ++ p->need_type ? "[need type] " : ""); ++ print_predicate(fp, p); ++ fprintf(fp, " [%g] ", p->est_success_rate); ++ if (options.debug_options & DebugSuccessRates) ++ { ++ fprintf(fp, "[%ld/%ld", p->perf.successes, p->perf.visits); ++ if (p->perf.visits) ++ { ++ double real_rate = (double)p->perf.successes / (double)p->perf.visits; ++ fprintf(fp, "=%g] ", real_rate); ++ } ++ else ++ { ++ fprintf(fp, "=_] "); ++ } ++ } ++ print_parenthesised(fp, p->pred_right); ++ } ++} ++ ++void show_success_rates(const struct predicate *p) ++{ ++ if (options.debug_options & DebugSuccessRates) ++ { ++ fprintf(stderr, "Predicate success rates after completion:\n"); ++ print_optlist(stderr, p); ++ fprintf(stderr, "\n"); ++ } ++} ++ ++ ++ ++ ++#ifdef _NDEBUG ++/* If _NDEBUG is defined, the assertions will do nothing. Hence ++ * there is no point in having a function body for pred_sanity_check() ++ * if that preprocessor macro is defined. ++ */ ++void ++pred_sanity_check(const struct predicate *predicates) ++{ ++ /* Do nothing, since assert is a no-op with _NDEBUG set */ ++ return; ++} ++#else ++void ++pred_sanity_check(const struct predicate *predicates) ++{ ++ const struct predicate *p; ++ ++ for (p=predicates; p != NULL; p=p->pred_next) ++ { ++ /* All predicates must do something. */ ++ assert (p->pred_func != NULL); ++ ++ /* All predicates must have a parser table entry. */ ++ assert (p->parser_entry != NULL); ++ ++ /* If the parser table tells us that just one predicate function is ++ * possible, verify that that is still the one that is in effect. ++ * If the parser has NULL for the predicate function, that means that ++ * the parse_xxx function fills it in, so we can't check it. ++ */ ++ if (p->parser_entry->pred_func) ++ { ++ assert (p->parser_entry->pred_func == p->pred_func); ++ } ++ ++ switch (p->parser_entry->type) ++ { ++ /* Options all take effect during parsing, so there should ++ * be no predicate entries corresponding to them. Hence we ++ * should not see any ARG_OPTION or ARG_POSITIONAL_OPTION ++ * items. ++ * ++ * This is a silly way of coding this test, but it prevents ++ * a compiler warning (i.e. otherwise it would think that ++ * there would be case statements missing). ++ */ ++ case ARG_OPTION: ++ case ARG_POSITIONAL_OPTION: ++ assert (p->parser_entry->type != ARG_OPTION); ++ assert (p->parser_entry->type != ARG_POSITIONAL_OPTION); ++ break; ++ ++ case ARG_ACTION: ++ assert(p->side_effects); /* actions have side effects. */ ++ if (!pred_is(p, pred_prune) && !pred_is(p, pred_quit)) ++ { ++ /* actions other than -prune and -quit should ++ * inhibit the default -print ++ */ ++ assert (p->no_default_print); ++ } ++ break; ++ ++ /* We happen to know that the only user of ARG_SPECIAL_PARSE ++ * is a test, so handle it like ARG_TEST. ++ */ ++ case ARG_SPECIAL_PARSE: ++ case ARG_TEST: ++ case ARG_PUNCTUATION: ++ case ARG_NOOP: ++ /* Punctuation and tests should have no side ++ * effects and not inhibit default print. ++ */ ++ assert (!p->no_default_print); ++ assert (!p->side_effects); ++ break; ++ } ++ } ++} ++#endif +diff -purN findutils-4.3.12.orig/find/tree.c findutils-4.3.12/find/tree.c +--- findutils-4.3.12.orig/find/tree.c 2007-12-19 16:12:34.000000000 -0500 ++++ findutils-4.3.12/find/tree.c 2008-01-30 08:46:05.758843847 -0500 +@@ -1195,6 +1195,10 @@ build_expression_tree(int argc, char *ar + const struct parser_table *entry_close, *entry_print, *entry_open; + int i, oldi; + ++#ifdef WITH_SELINUX ++ int is_selinux_enabled_flag = is_selinux_enabled()>0; ++#endif /* WITH_SELINUX */ ++ + predicates = NULL; + + /* Find where in ARGV the predicates begin by skipping the list of +@@ -1230,6 +1234,16 @@ build_expression_tree(int argc, char *ar + } + + predicate_name = argv[i]; ++ ++#ifdef WITH_SELINUX ++ if (! is_selinux_enabled_flag) { ++ if ((strncmp(predicate_name,"-context",strlen("-context"))==0) || ++ (strncmp(predicate_name,"--context",strlen("--context"))==0)) { ++ error (1, 0,_("Error: invalid predicate %s: the kernel is not selinux-enabled.\n"),predicate_name); ++ } ++ } ++#endif ++ + parse_entry = find_parser (predicate_name); + if (parse_entry == NULL) + { +@@ -1434,6 +1448,9 @@ get_new_pred (const struct parser_table + last_pred->need_stat = true; + last_pred->need_type = true; + last_pred->args.str = NULL; ++#ifdef WITH_SELINUX ++ last_pred->args.scontext = NULL; ++#endif + last_pred->pred_next = NULL; + last_pred->pred_left = NULL; + last_pred->pred_right = NULL; |