The branch main has been updated by kevans:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=22fba3a9d64140d80a9e2093cfc02c9c503b2e19

commit 22fba3a9d64140d80a9e2093cfc02c9c503b2e19
Author:     Kyle Evans <[email protected]>
AuthorDate: 2026-04-09 02:41:12 +0000
Commit:     Kyle Evans <[email protected]>
CommitDate: 2026-04-09 02:41:12 +0000

    find: add -xattr and -xttrname
    
    We use -xattr in our openrsync tests for convenience, and it seems like
    a good addition to FreeBSD.  -xattr and -xattrname will both consult all
    available namespaces by default, but -xattrname allows filtering by
    namespace using a "user:" or "system:" prefix.
    
    Inspired by:    https://github.com/apple-oss-distributions/shell_cmds
    Reviewed by:    kib, rmacklem
    Sponsored by:   Klara, Inc.
    Differential Revision:  https://reviews.freebsd.org/D55286
---
 usr.bin/find/extern.h           |  2 +
 usr.bin/find/find.1             | 23 +++++++++-
 usr.bin/find/function.c         | 94 +++++++++++++++++++++++++++++++++++++++++
 usr.bin/find/option.c           |  2 +
 usr.bin/find/tests/find_test.sh | 80 +++++++++++++++++++++++++++++++++++
 5 files changed, 199 insertions(+), 2 deletions(-)

diff --git a/usr.bin/find/extern.h b/usr.bin/find/extern.h
index 02c85d06a34c..250b5fb6689c 100644
--- a/usr.bin/find/extern.h
+++ b/usr.bin/find/extern.h
@@ -122,6 +122,8 @@ exec_f      f_sparse;
 exec_f f_type;
 exec_f f_user;
 exec_f f_writable;
+exec_f f_xattr;
+exec_f f_xattrname;
 
 extern int ftsoptions, ignore_readdir_race, isdepth, isoutput;
 extern int issort, isxargs;
diff --git a/usr.bin/find/find.1 b/usr.bin/find/find.1
index 98521a98762d..afe10a36607d 100644
--- a/usr.bin/find/find.1
+++ b/usr.bin/find/find.1
@@ -28,7 +28,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd July 26, 2025
+.Dd February 14, 2026
 .Dt FIND 1
 .Os
 .Sh NAME
@@ -976,6 +976,23 @@ This test makes use of the
 .Xr access 2
 system call, and so can be fooled by NFS servers which do UID mapping (or 
root-squashing).
 This is a GNU find extension.
+.It Ic -xattr
+Matches files which have extended attributes set in any supported namespace.
+.It Ic -xattrname Ar xattr
+Matches files which have the specified
+.Ar xattr
+extended attribute set.
+All supported namespaces are searched by default, but
+.Ar xattr
+may be prefixed with
+.Dq user:
+or
+.Dq system:
+to filter by namespace.
+.Pp
+Note that named attributes are not supported, only extended attributes as set
+by, e.g.,
+.Xr setextattr 8 .
 .El
 .Sh OPERATORS
 The primaries may be combined using the following operators.
@@ -1245,6 +1262,7 @@ section below for details.
 .Xr whereis 1 ,
 .Xr which 1 ,
 .Xr xargs 1 ,
+.Xr extattr 2 ,
 .Xr stat 2 ,
 .Xr acl 3 ,
 .Xr fts 3 ,
@@ -1253,7 +1271,8 @@ section below for details.
 .Xr strmode 3 ,
 .Xr ascii 7 ,
 .Xr re_format 7 ,
-.Xr symlink 7
+.Xr symlink 7 ,
+.Xr setextattr 8
 .Sh STANDARDS
 The
 .Nm
diff --git a/usr.bin/find/function.c b/usr.bin/find/function.c
index b260a71ef4a9..c62ac39a9e82 100644
--- a/usr.bin/find/function.c
+++ b/usr.bin/find/function.c
@@ -37,6 +37,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/acl.h>
+#include <sys/extattr.h>
 #include <sys/wait.h>
 #include <sys/mount.h>
 
@@ -49,6 +50,7 @@
 #include <limits.h>
 #include <pwd.h>
 #include <regex.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -57,6 +59,8 @@
 
 #include "find.h"
 
+static const char * const xattr_ns[] = EXTATTR_NAMESPACE_NAMES;
+
 static PLAN *palloc(OPTION *);
 static long long find_parsenum(PLAN *, const char *, char *, char *);
 static long long find_parsetime(PLAN *, const char *, char *);
@@ -1752,6 +1756,96 @@ c_user(OPTION *option, char ***argvp)
        return new;
 }
 
+/*
+ * -xattr functions --
+ *
+ *     True if the entry has any extended attribute in any namespace.
+ */
+int
+f_xattr(PLAN *plan __unused, FTSENT *entry)
+{
+       ssize_t asz;
+       bool deref_link;
+
+       deref_link = (ftsoptions & FTS_LOGICAL) != 0;
+       if (entry->fts_level == 0 && (ftsoptions & FTS_COMFOLLOW) != 0)
+               deref_link = true;
+
+       for (size_t ns = 0; ns < nitems(xattr_ns); ns++) {
+               if (ns == EXTATTR_NAMESPACE_EMPTY)
+                       continue;
+
+               if (deref_link)
+                       asz = extattr_list_file(entry->fts_accpath, ns, NULL, 
0);
+               else
+                       asz = extattr_list_link(entry->fts_accpath, ns, NULL, 
0);
+               if (asz > 0)
+                       return 1;
+       }
+
+       return 0;
+}
+
+static bool
+find_has_xattr(const char *path, int ns, const char *aname, bool deref_link)
+{
+       size_t asz;
+
+       if (deref_link)
+               asz = extattr_get_file(path, ns, aname, NULL, 0);
+       else
+               asz = extattr_get_link(path, ns, aname, NULL, 0);
+
+       return asz != (size_t)-1;
+}
+
+/*
+ * -xattrname xattr functions --
+ *
+ *     True if the entry has the given extended attribute xattr.  The xattr
+ *     may be prefixed with "user:" or "system:" to scope the search
+ *     explicitly, otherwise we assume the user namespace is requested.
+ */
+int
+f_xattrname(PLAN *plan, FTSENT *entry)
+{
+       const char *aname;
+       bool deref_link;
+
+       deref_link = (ftsoptions & FTS_LOGICAL) != 0;
+       if (entry->fts_level == 0 && (ftsoptions & FTS_COMFOLLOW) != 0)
+               deref_link = true;
+
+       aname = plan->c_data;
+       for (size_t ns = 0; ns < nitems(xattr_ns); ns++) {
+               const char *name;
+               size_t namelen;
+
+               if (ns == EXTATTR_NAMESPACE_EMPTY)
+                       continue;
+
+               name = xattr_ns[ns];
+               namelen = strlen(xattr_ns[ns]);
+               if (strncmp(aname, name, namelen) == 0 &&
+                   aname[namelen] == ':') {
+                       aname += namelen + 1;
+                       return find_has_xattr(entry->fts_accpath, ns, aname,
+                           deref_link);
+               }
+       }
+
+       for (size_t ns = 0; ns < nitems(xattr_ns); ns++) {
+               if (ns == EXTATTR_NAMESPACE_EMPTY)
+                       continue;
+
+               if (find_has_xattr(entry->fts_accpath, ns, aname,
+                   deref_link))
+                       return 1;
+       }
+
+       return 0;
+}
+
 /*
  * -xdev functions --
  *
diff --git a/usr.bin/find/option.c b/usr.bin/find/option.c
index fa09231a3152..fe3d9b00f90f 100644
--- a/usr.bin/find/option.c
+++ b/usr.bin/find/option.c
@@ -162,6 +162,8 @@ static OPTION const options[] = {
        { "-user",      c_user,         f_user,         0 },
        { "-wholename", c_name,         f_path,         0 },
        { "-writable",  c_simple,       f_writable,     0 },
+       { "-xattr",     c_simple,       f_xattr,        0 },
+       { "-xattrname", c_name,         f_xattrname,    0 },
        { "-xdev",      c_xdev,         f_always_true,  0 },
 // -xtype
 };
diff --git a/usr.bin/find/tests/find_test.sh b/usr.bin/find/tests/find_test.sh
index 99d2f6af4d45..deb6a66a8dfb 100755
--- a/usr.bin/find/tests/find_test.sh
+++ b/usr.bin/find/tests/find_test.sh
@@ -174,9 +174,89 @@ find_printf_body()
            find -s dir -printf '%Te\n'
 }
 
+atf_test_case find_xattr
+find_xattr_head()
+{
+       atf_set "descr" "Test the -xattr primary"
+}
+find_xattr_body()
+{
+       mkdir dir
+       ln -s dir dirlink
+
+       # No xattrs here
+       atf_check find dir -xattr
+       atf_check find dirlink -xattr
+
+       # Set one on the directory and be sure that we also dereference symlinks
+       # as appropriate with -H/-L.
+       if ! setextattr user find_test.attr val dir; then
+               atf_skip "Failed to set xattr (not supported on this fs?)"
+       fi
+
+       atf_check -o match:"dir$" find dir -xattr
+       atf_check -o match:"dirlink$" find -H dirlink -xattr
+       atf_check -o match:"dirlink$" find -L dirlink -xattr
+
+       atf_check -o match:"dir$" -o match:"dirlink" find -sL . -xattr
+       atf_check -o match:"dir$" -o not-match:"dirlink$" find -sH . -xattr
+       atf_check -o match:"dir$" -o not-match:"dirlink$" find -s . -xattr
+}
+
+atf_test_case find_xattrname
+find_xattrname_head()
+{
+       atf_set "descr" "Test the -xattrname primary"
+       atf_set "require.user" "root"
+}
+find_xattrname_body()
+{
+       touch foo bar baz none
+
+       ln -s foo link
+       if ! setextattr user find_test.special1 val foo; then
+               atf_skip "Failed to set xattr (not supported on this fs?)"
+       fi
+
+       atf_check setextattr user find_test.special2 val bar
+       atf_check setextattr user find_test.special2 val baz
+
+       # We want an unqualified 'find_test.special2' search to find all three
+       # of these, while 'user:' and 'system:' filter appropriately.
+       atf_check setextattr system find_test.special2 val foo
+
+       atf_check find . -xattrname 'find_test.special3'
+
+       # Be sure that we get symlink dereferencing right, so that one can use
+       # -H/-L/-P to get the right behavior.
+       atf_check -o match:foo -o not-match:"bar|baz|link|none" \
+           find . -xattrname 'find_test.special1'
+       atf_check -o match:foo -o match:link \
+           find -H foo link -xattrname 'find_test.special1'
+       atf_check -o match:foo -o match:link -o not-match:"bar|baz|none" \
+           find -L . -xattrname 'find_test.special1'
+
+       atf_check -o match:foo -o match:bar -o match:baz \
+           -o not-match:"none|link" find . -xattrname 'find_test.special2'
+       atf_check -o not-match:"foo|none|link" -o match:bar -o match:baz \
+           find . -xattrname 'user:find_test.special2'
+       atf_check -o match:foo -o not-match:"bar|baz|none|link" \
+           find . -xattrname 'system:find_test.special2'
+
+       # Now set an extattr on the link itself and be sure that find(1) can
+       # detect it.  With -L, we shouldn't see anything with a special3 xattr
+       # as symlinks are dereferenced.
+       atf_check setextattr -h user find_test.special3 val link
+       atf_check -o match:link find . -xattrname "find_test.special3"
+       atf_check find -L . -xattrname "find_test.special3"
+       atf_check find -H link -xattrname "find_test.special3"
+}
+
 atf_init_test_cases()
 {
        atf_add_test_case find_newer_link
        atf_add_test_case find_samefile_link
        atf_add_test_case find_printf
+       atf_add_test_case find_xattr
+       atf_add_test_case find_xattrname
 }

Reply via email to