Page MenuHomeFreeBSD

D55286.diff
No OneTemporary

D55286.diff

diff --git a/usr.bin/find/extern.h b/usr.bin/find/extern.h
--- a/usr.bin/find/extern.h
+++ b/usr.bin/find/extern.h
@@ -122,6 +122,8 @@
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
--- 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 @@
.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 @@
.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 @@
.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
--- 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 @@
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
--- a/usr.bin/find/option.c
+++ b/usr.bin/find/option.c
@@ -162,6 +162,8 @@
{ "-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
--- a/usr.bin/find/tests/find_test.sh
+++ b/usr.bin/find/tests/find_test.sh
@@ -174,9 +174,89 @@
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
}

File Metadata

Mime Type
text/plain
Expires
Fri, Feb 20, 6:34 PM (6 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
28902788
Default Alt Text
D55286.diff (7 KB)

Event Timeline