Hi Morgan, thanks for the update! (in-line patch would be better) For maintainers, since Morgan is Red Hat employee no copyright paperwork is needed.
On Tuesday, July 17, 2018 2:05:15 AM CEST Morgan Weetman wrote: > diff --git a/configure.ac b/configure.ac > index 2acf54c7..ab286b6c 100644 You should write a GNU formatted commit message here, like: topic: short comment Long description. * file_changed (method_changed_or_added): Description. > --- a/configure.ac > +++ b/configure.ac > @@ -55,6 +55,26 @@ AC_SUBST(AUXDIR,$ac_aux_dir) > dnl check for --with-fts > FIND_WITH_FTS > > +dnl Disable extended attribute support > +AC_ARG_WITH([extended-attributes], > + AS_HELP_STRING([--without-extended-attributes], [Disable extended > attributes support])) > + > +AS_IF([test "x$with_extended_attributes" != "xno"], [ > + dnl Search for libattr > + AC_SEARCH_LIBS([listxattr], [attr], [AC_DEFINE([HAVE_XATTR], [], > + [libattr is present])], []) > +]) >From what I remember, Darwin defines this method, but not llistxattr which is also used by the code below. I'd say you should check for both methods. More discussion in GNU tar list: https://www.mail-archive.com/bug-tar@gnu.org/msg04586.html > +dnl Disable capabilities > +AC_ARG_WITH([capabilities], > + AS_HELP_STRING([--without-capabilities], [Disable capabilities support])) > + > +AS_IF([test "x$with_capabilities" != "xno"], [ > + dnl Search for libcap > + AC_SEARCH_LIBS([cap_get_file], [cap], [AC_DEFINE([HAVE_CAPABILITIES], [], > + [libcap is present])], []) > +]) > + > AC_ARG_ENABLE(leaf-optimisation, > AS_HELP_STRING(--enable-leaf-optimisation,Enable an optimisation which > saves lstat calls to identify subdirectories on filesystems having > traditional Unix semantics), > [ac_cv_leaf_optimisation=$enableval],[ac_cv_leaf_optimisation=yes]) > diff --git a/find/defs.h b/find/defs.h > index 63f3b2a3..bde58c5d 100644 > --- a/find/defs.h > +++ b/find/defs.h > @@ -417,6 +417,7 @@ PREDICATEFUNCTION pred_amin; > PREDICATEFUNCTION pred_and; > PREDICATEFUNCTION pred_anewer; > PREDICATEFUNCTION pred_atime; > +PREDICATEFUNCTION pred_cap; > PREDICATEFUNCTION pred_closeparen; > PREDICATEFUNCTION pred_cmin; > PREDICATEFUNCTION pred_cnewer; > @@ -435,10 +436,12 @@ PREDICATEFUNCTION pred_fprintf; > PREDICATEFUNCTION pred_fstype; > PREDICATEFUNCTION pred_gid; > PREDICATEFUNCTION pred_group; > +PREDICATEFUNCTION pred_icap; > PREDICATEFUNCTION pred_ilname; > PREDICATEFUNCTION pred_iname; > PREDICATEFUNCTION pred_inum; > PREDICATEFUNCTION pred_ipath; > +PREDICATEFUNCTION pred_ixattr; > PREDICATEFUNCTION pred_links; > PREDICATEFUNCTION pred_lname; > PREDICATEFUNCTION pred_ls; > @@ -469,6 +472,7 @@ PREDICATEFUNCTION pred_uid; > PREDICATEFUNCTION pred_used; > PREDICATEFUNCTION pred_user; > PREDICATEFUNCTION pred_writable; > +PREDICATEFUNCTION pred_xattr; > PREDICATEFUNCTION pred_xtype; > PREDICATEFUNCTION pred_context; > > diff --git a/find/find.1 b/find/find.1 > index 0d44c37c..2e48983b 100644 > --- a/find/find.1 > +++ b/find/find.1 > @@ -609,6 +609,13 @@ a file has to have been accessed at least > .I two > days ago. > > +.IP "\-cap \fIpattern\fR" > +Match file capabilities against the regular expression > +\fIpattern\fR. For example to search for files that have > +CAP_SETUID you could use '.*setuid.*'. This option > +also takes advantage of > +.B \-regextype > + > .IP "\-cmin \fIn\fR" > File's status was last changed \fIn\fR minutes ago. > > @@ -671,6 +678,11 @@ File's numeric group ID is > .IP "\-group \fIgname\fR" > File belongs to group \fIgname\fR (numeric group ID allowed). > > +.IP "\-icap \fIpattern\fR" > +Like > +.BR \-cap , > +but the match is case insensitive. > + > .IP "\-ilname \fIpattern\fR" > Like > .BR \-lname , > @@ -712,6 +724,11 @@ but the match is case insensitive. > See \-ipath. This alternative is less portable than > .BR \-ipath . > > +.IP "\-ixattr \fIpattern\fR" > +Like > +.BR \-xattr , > +but the match is case insensitive. > + > .IP "\-links \fIn\fR" > File has \fIn\fR hard links. > > @@ -1036,6 +1053,13 @@ mapping (or root-squashing), since many systems > implement > in the client's kernel and so cannot make use of the UID mapping > information held on the server. > > +.IP "\-xattr \fIpattern\fR" > +Match extended attributes against the regular expression > +\fIpattern\fR. For example to search for files that have > +capabilities set you could use '.*capa.*'. This option > +also takes advantage of > +.B \-regextype > + > .IP "\-xtype \fIc\fR" > The same as > .B \-type > diff --git a/find/parser.c b/find/parser.c > index d6621506..b4d6e594 100644 > --- a/find/parser.c > +++ b/find/parser.c > @@ -79,6 +79,9 @@ static bool parse_accesscheck (const struct > parser_table*, char *argv[], int * > static bool parse_amin (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_and (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_anewer (const struct parser_table*, char *argv[], > int *arg_ptr); > +#ifdef HAVE_CAPABILITIES I don't think the #ifdefs are needed here, see below.. > +static bool parse_cap (const struct parser_table*, char *argv[], > int *arg_ptr); > +#endif > static bool parse_cmin (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_cnewer (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_comma (const struct parser_table*, char *argv[], > int *arg_ptr); > @@ -98,12 +101,18 @@ static bool parse_fprint0 (const struct > parser_table*, char *argv[], int * > static bool parse_fstype (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_gid (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_group (const struct parser_table*, char *argv[], > int *arg_ptr); > +#ifdef HAVE_CAPABILITIES > +static bool parse_icap (const struct parser_table*, char *argv[], > int *arg_ptr); > +#endif > static bool parse_ilname (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_iname (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_inum (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_ipath (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_iregex (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_iwholename (const struct parser_table*, char *argv[], > int *arg_ptr); > +#ifdef HAVE_XATTR > +static bool parse_ixattr (const struct parser_table*, char *argv[], > int *arg_ptr); > +#endif > static bool parse_links (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_lname (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_ls (const struct parser_table*, char *argv[], > int *arg_ptr); > @@ -141,6 +150,9 @@ static bool parse_xdev (const struct > parser_table*, char *argv[], int * > static bool parse_ignore_race (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_noignore_race (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_warn (const struct parser_table*, char *argv[], > int *arg_ptr); > +#ifdef HAVE_XATTR > +static bool parse_xattr (const struct parser_table*, char *argv[], > int *arg_ptr); > +#endif > static bool parse_xtype (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_quit (const struct parser_table*, char *argv[], > int *arg_ptr); > static bool parse_context (const struct parser_table*, char *argv[], > int *arg_ptr); > @@ -154,6 +166,11 @@ static bool parse_version (const struct > parser_table*, char *argv[], int * > _GL_ATTRIBUTE_NORETURN; > > > +#ifdef HAVE_CAPABILITIES > +static bool insert_cap (char *argv[], int *arg_ptr, > + const struct parser_table *entry, > + int regex_options); > +#endif > static bool insert_type (char **argv, int *arg_ptr, > const struct parser_table *entry, > PRED_FUNC which_pred); > @@ -164,6 +181,11 @@ static bool insert_exec_ok (const char *action, > const struct parser_table *entry, > char *argv[], > int *arg_ptr); > +#ifdef HAVE_XATTR > +static bool insert_xattr (char *argv[], int *arg_ptr, > + const struct parser_table *entry, > + int regex_options); > +#endif > static bool get_comp_type (const char **str, > enum comparison_type *comp_type); > static bool get_relative_timestamp (const char *str, > @@ -229,6 +251,9 @@ static struct parser_table const parse_table[] = > PARSE_PUNCTUATION("and", and), /* GNU */ > PARSE_TEST ("anewer", anewer), /* GNU */ > {ARG_TEST, "atime", parse_time, pred_atime}, /* > POSIX */ > + #ifdef HAVE_CAPABILITIES > + PARSE_TEST ("cap", cap), /* GNU */ > +#endif > PARSE_TEST ("cmin", cmin), /* GNU */ > PARSE_TEST ("cnewer", cnewer), /* GNU */ > {ARG_TEST, "ctime", parse_time, pred_ctime}, /* > POSIX */ > @@ -249,6 +274,9 @@ static struct parser_table const parse_table[] = > PARSE_TEST ("fstype", fstype), /* GNU, Unix */ > PARSE_TEST ("gid", gid), /* GNU */ > PARSE_TEST ("group", group), /* POSIX */ > +#ifdef HAVE_CAPABILITIES > + PARSE_TEST_NP ("icap", icap), /* GNU */ > +#endif > PARSE_OPTION ("ignore_readdir_race", ignore_race), /* GNU */ > PARSE_TEST ("ilname", ilname), /* GNU */ > PARSE_TEST ("iname", iname), /* GNU */ > @@ -256,6 +284,9 @@ static struct parser_table const parse_table[] = > PARSE_TEST ("ipath", ipath), /* GNU, deprecated in > favour of iwholename */ > PARSE_TEST_NP ("iregex", iregex), /* GNU */ > PARSE_TEST_NP ("iwholename", iwholename), /* GNU */ > +#ifdef HAVE_XATTR > + PARSE_TEST_NP ("ixattr", ixattr), /* GNU */ > +#endif > PARSE_TEST ("links", links), /* POSIX */ > PARSE_TEST ("lname", lname), /* GNU */ > PARSE_ACTION ("ls", ls), /* GNU, Unix */ > @@ -301,6 +332,9 @@ static struct parser_table const parse_table[] = > PARSE_TEST ("user", user), /* POSIX */ > PARSE_TEST_NP ("wholename", wholename), /* GNU, replaced > -path, but now -path is standardized since POSIX 2008 */ > {ARG_TEST, "writable", parse_accesscheck, > pred_writable}, /* GNU, 4.3.0+ */ > +#ifdef HAVE_XATTR > + PARSE_TEST ("xattr", xattr), /* GNU */ > +#endif > PARSE_OPTION ("xdev", xdev), /* POSIX */ > PARSE_TEST ("xtype", xtype), /* GNU */ > #ifdef UNIMPLEMENTED_UNIX > @@ -797,6 +831,14 @@ parse_anewer (const struct parser_table* entry, char > **argv, int *arg_ptr) > return false; > } > > +#ifdef HAVE_CAPABILITIES > +static bool > +parse_cap (const struct parser_table* entry, char **argv, int *arg_ptr) > +{ > + return insert_cap (argv, arg_ptr, entry, options.regex_options); > +} > +#endif > + > bool > parse_closeparen (const struct parser_table* entry, char **argv, int > *arg_ptr) > { > @@ -1212,6 +1254,14 @@ estimate_pattern_match_rate (const char *pattern, int > is_regex) > } > } > > +#ifdef HAVE_CAPABILITIES > +static bool > +parse_icap (const struct parser_table* entry, char **argv, int *arg_ptr) > +{ > + return insert_cap (argv, arg_ptr, entry, RE_ICASE|options.regex_options); > +} > +#endif > + > static bool > parse_ilname (const struct parser_table* entry, char **argv, int *arg_ptr) > { > @@ -1324,6 +1374,14 @@ parse_iregex (const struct parser_table* entry, char > **argv, int *arg_ptr) > return insert_regex (argv, arg_ptr, entry, RE_ICASE|options.regex_options); > } > > +#ifdef HAVE_XATTR > +static bool > +parse_ixattr (const struct parser_table* entry, char **argv, int *arg_ptr) > +{ > + return insert_xattr (argv, arg_ptr, entry, RE_ICASE|options.regex_options); > +} > +#endif > + > static bool > parse_links (const struct parser_table* entry, char **argv, int *arg_ptr) > { > @@ -2627,6 +2685,46 @@ parse_warn (const struct parser_table* entry, char > **argv, int *arg_ptr) > return parse_noop (entry, argv, arg_ptr); > } > > +#ifdef HAVE_XATTR > +static bool > +parse_xattr (const struct parser_table* entry, char **argv, int *arg_ptr) > +{ > + return insert_xattr (argv, arg_ptr, entry, options.regex_options); > +} > + > +static bool > +insert_xattr (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_xattr, rx); > + our_pred->need_stat = our_pred->need_type = false; > + re = xmalloc (sizeof (struct re_pattern_buffer)); > + our_pred->args.regex = re; > + re->allocated = 100; > + re->buffer = xmalloc (re->allocated); > + re->fastmap = NULL; > + > + re_set_syntax (regex_options); > + re->syntax = regex_options; > + re->translate = NULL; > + > + error_message = re_compile_pattern (rx, strlen (rx), re); > + if (error_message) > + error (EXIT_FAILURE, 0, "%s", error_message); > + our_pred->est_success_rate = estimate_pattern_match_rate (rx, 1); > + return true; > + } > + return false; > +} > +#endif > + > static bool > parse_xtype (const struct parser_table* entry, char **argv, int *arg_ptr) > { > @@ -2877,6 +2975,40 @@ check_path_safety (const char *action) > } while (splitstring (path, path_separators, false, &pos, &len)); > } > > +#ifdef HAVE_CAPABILITIES > +static bool > +insert_cap (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_cap, > rx); > + our_pred->need_stat = true; > + our_pred->need_type = false; > + re = xmalloc (sizeof (struct re_pattern_buffer)); > + our_pred->args.regex = re; > + re->allocated = 100; > + re->buffer = xmalloc (re->allocated); > + re->fastmap = NULL; > + > + re_set_syntax (regex_options); > + re->syntax = regex_options; > + re->translate = NULL; > + > + error_message = re_compile_pattern (rx, strlen (rx), re); > + if (error_message) > + error (EXIT_FAILURE, 0, "%s", error_message); > + our_pred->est_success_rate = estimate_pattern_match_rate (rx, 1); > + return true; > + } > + return false; > +} > +#endif > > /* handles both exec and ok predicate */ > static bool > diff --git a/find/pred.c b/find/pred.c > index 2014b5ab..08ad1b8a 100644 > --- a/find/pred.c > +++ b/find/pred.c > @@ -34,6 +34,14 @@ > #include <sys/wait.h> > #include <unistd.h> /* for unlinkat() */ > > +#ifdef HAVE_XATTR > +#include <attr/xattr.h> Nowadays you should probably use <sys/xattr.h>, and maybe fallback to <attr/xattr.h> (that header is provided by glibc-headers). > +#endif > + > +#ifdef HAVE_CAPABILITIES > +#include <sys/capability.h> /* for pred_cap() */ > +#endif > + > /* gnulib headers. */ > #include "areadlink.h" > #include "dirname.h" > @@ -74,6 +82,9 @@ struct pred_assoc pred_table[] = > {pred_and, "and "}, > {pred_anewer, "anewer "}, > {pred_atime, "atime "}, > +#ifdef HAVE_CAPABILITIES > + {pred_cap, "cap "}, > +#endif > {pred_closeparen, ") "}, > {pred_cmin, "cmin "}, > {pred_cnewer, "cnewer "}, > @@ -126,6 +137,9 @@ struct pred_assoc pred_table[] = > {pred_used, "used "}, > {pred_user, "user "}, > {pred_writable, "writable "}, > +#ifdef HAVE_XATTR > + {pred_xattr, "xattr "}, > +#endif > {pred_xtype, "xtype "}, > {pred_context, "context"}, > {0, "none "} > @@ -242,6 +256,34 @@ pred_atime (const char *pathname, struct stat *stat_buf, > struct predicate *pred_ > return pred_timewindow (get_stat_atime(stat_buf), pred_ptr, DAYSECS); > } > > +#ifdef HAVE_CAPABILITIES > +bool > +pred_cap (const char *pathname, struct stat *stat_buf, struct predicate > *pred_ptr) > +{ > + (void) pathname; > + char *cap_str; > + cap_t caps; > + bool ret = false; Looking how pred_context() has been implemented, I think that in pret_cap() this pattern ... #ifndef HAVE_CAPABILITIES errno = ENOTSUP; error (0, errno, _("can't get capabilities, not implemented: %s"), safely_quote_err_filename (0, pathname)); return (ret); #else ... the code calling cap_* stuff goes here ... #endif .. should be used. This way you can avoid a lot of the #ifdef hell since everything can be complied in. Findutils maintainers should review this, though. > + // get capabilities I think that only /* comments */ are allowed. > + caps = cap_get_file(pathname); > + cap_str = cap_to_text(caps, NULL); > + if ( cap_str == NULL ) { Style note, that should be `if (cap_str == NULL)`. > + cap_free(caps); > + return(ret); > + } > + > + // perform regex match > + if (regexec(pred_ptr->args.regex, cap_str, (size_t) 0, NULL, 0) == 0) > + ret = true; > + > + // clean up > + cap_free(caps); > + cap_free(cap_str); > + return(ret); > +} > +#endif > + > bool > pred_closeparen (const char *pathname, struct stat *stat_buf, struct > predicate *pred_ptr) > { > @@ -1184,6 +1226,61 @@ pred_user (const char *pathname, struct stat > *stat_buf, struct predicate *pred_p > return (false); > } > > +#ifdef HAVE_XATTR > +bool > +pred_xattr (const char *pathname, struct stat *stat_buf, struct predicate > *pred_ptr) > +{ > + (void) pathname; > + char empty[0]; > + char *list; > + char *substrings; > + ssize_t list_size; > + int i, j; > + bool ret = false; > + > + // get size of xattr list for the given path by passing an empty list > + if (options.symlink_handling == SYMLINK_NEVER_DEREF) { > + list_size = llistxattr(pathname, empty, 0); Function calls should look like `function<space>(param1, ...)`. > + } else { The 'else' keyword is always on it's own line. Thanks again! Pavel > + list_size = listxattr(pathname, empty, 0); > + } > + > + // allocate just enough memory to hold all xattrs > + list = malloc(list_size); > + > + // used to hold individual attributes (substrings) > + // allocate same size as list just in case there's only one xattr > + substrings = malloc(list_size); > + > + // retrieve the list of xattrs > + if (options.symlink_handling == SYMLINK_NEVER_DEREF) { > + llistxattr(pathname, list, list_size); > + } else { > + listxattr(pathname, list, list_size); > + } > + > + // break list into asciiz strings > + for (i = 0, j = 0; i < list_size; i++) { > + substrings[j] = list[i]; > + if (list[i] == 0) { > + // perform regex match against substring > + j = 0; > + if (regexec(pred_ptr->args.regex, substrings, (size_t) 0, NULL, 0) == > 0) { > + ret = true; > + } > + continue; > + } > + j++; > + } > + > + // clean up > + free(list); > + free(substrings); > + return(ret); > + > +} > +#endif > + > bool > pred_xtype (const char *pathname, struct stat *stat_buf, struct predicate > *pred_ptr) > { > diff --git a/find/testsuite/excuses.txt b/find/testsuite/excuses.txt > index ac4515a1..d4aee6d4 100644 > --- a/find/testsuite/excuses.txt > +++ b/find/testsuite/excuses.txt > @@ -22,6 +22,7 @@ > > ## Things that are hard to test because they rely on features of > ## the environment > +"cap", cap), /* GNU */ > "gid", gid), /* GNU */ > "uid", uid), /* GNU */ > "user", user), > @@ -34,6 +35,7 @@ > "ignore_readdir_race", ignore_race), /* GNU */ > "noignore_readdir_race", noignore_race), /* GNU */ > "noleaf", noleaf), /* GNU */ > +"xattr", xattr), /* GNU */ > > ## Things that might be testable but which I have not yet thought > ## about enough. > diff --git a/find/tree.c b/find/tree.c > index 5be88f2e..d5938a0c 100644 > --- a/find/tree.c > +++ b/find/tree.c > @@ -926,6 +926,9 @@ static struct pred_cost_lookup costlookup[] = > { pred_and , NeedsNothing, }, > { pred_anewer , NeedsStatInfo, }, > { pred_atime , NeedsStatInfo, }, > +#ifdef HAVE_CAPABILITIES > + { pred_cap , NeedsNothing, }, > +#endif > { pred_closeparen, NeedsNothing }, > { pred_cmin , NeedsStatInfo, }, > { pred_cnewer , NeedsStatInfo, }, > @@ -980,6 +983,9 @@ static struct pred_cost_lookup costlookup[] = > { pred_used , NeedsStatInfo }, > { pred_user , NeedsStatInfo }, > { pred_writable , NeedsAccessInfo }, > +#ifdef HAVE_XATTR > + { pred_xattr , NeedsNothing }, > +#endif > { pred_xtype , NeedsType } /* roughly correct unless > most files are symlinks */ > }; > static int pred_table_sorted = 0; > diff --git a/find/util.c b/find/util.c > index fa338074..0b2e8646 100644 > --- a/find/util.c > +++ b/find/util.c > @@ -181,15 +181,16 @@ 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")); > HTL (_("\ > -tests (N can be +N or -N or N): -amin N -anewer FILE -atime N -cmin N\n\ > - -cnewer FILE -ctime N -empty -false -fstype TYPE -gid N -group NAME\n\ > - -ilname PATTERN -iname PATTERN -inum N -iwholename PATTERN -iregex > PATTERN\n\ > - -links N -lname PATTERN -mmin N -mtime N -name PATTERN -newer FILE")); > +tests (N can be +N or -N or N): -amin N -anewer FILE -atime N -cap PATTERN\n\ > + -cmin N -cnewer FILE -ctime N -empty -false -fstype TYPE -gid N -group > NAME\n\ > + -icap PATTERN -ilname PATTERN -iname PATTERN -inum N -iwholename > PATTERN\n\ > + -iregex PATTERN -ixattr PATTERN -links N -lname PATTERN -mmin N -mtime > N\n\ > + -name PATTERN -newer FILE")); > HTL (_("\n\ > -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]")); > + -used N -user NAME -xattr PATTERN -xtype [bcdpfls]")); > HTL (_("\ > -context CONTEXT\n")); > HTL (_("\n\