From nobody Thu Apr 9 02:41:22 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4frkhf2Kcdz6YGQL for ; Thu, 09 Apr 2026 02:41:22 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R12" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4frkhf0tPzz3XB7 for ; Thu, 09 Apr 2026 02:41:22 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1775702482; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=erWZrasvZVEncoBLqovACiFzm/c4R+iV8a0xHu7g3yM=; b=tdrawWH/NhShIen1loFl/GheHrNyWL9B8e54f8TXOu4pFfD4DzHvJlVsXkGe4Pze2npPME EAvqYR9ywhkD3lOYczXT3w+m7mifa085lG+gSH4h5fB/z3bgp3JPysgnf53gw6ifJv2zsz Rz6mbBC4dCk2PFYHxwPCgHaQ2qSjfcJXBnVWOheylepH42Tu/3d5hGvlIGqyNjeqoUF3xS QZ2mQW8SOemSjQGaeY9EkCWUfDHawtUoVbzqL0I8rsS4EkILL4RO4vCXhwjzrSk20ogMsn Xg5EuHvByhCqtoLc9mQyqO+3dbsBtFw+u1DYEDQ9Aq+pk4geroK9tAURaOGdlA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1775702482; a=rsa-sha256; cv=none; b=ChBTSAuPy50DDRpOGtVqbuYo5J57ZwuXC/1r0NdmzUCCLD9VsYhVxf4xE+VLLLk31bUOxQ HfBgpqVsh2PqgalCR6Q912+UbNdkZWCn8PWNmur2BB6A7VxB2YCwwgtvAWQZx5jAm2PMWg AYSswc8iSlLzpqq9Vj4lvLgegXYP/9k+ifGimWzdhk2vL2wVwD8WEtOAYtAUQf44lCOxGW 3k+wqfZaAQK+K6DZbBml72vqfucdC3Htfq1/zt/dshfs9zcOEdCzHI9VyvFb0KPrHrDhPo gE64g3G8A1QQYEMPemp2FbmXaEV/LaXJVRJCJmjmwWQnnMD4fFmB4lhh5V+2wg== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1775702482; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=erWZrasvZVEncoBLqovACiFzm/c4R+iV8a0xHu7g3yM=; b=fAcPbP+PjE1acz8JoT08LFyCmzP6NTJlJVU5vSzzwkeRcbv+I/CuyZ2s1c0GA1HDIKaQYF TYfm4fF7k7mmcrm3lr74+upoItfsg8gu9KlbTnerVe4t6pV1uZjUWYc8ypzbn/lUHUC7/j 142XwRmc6Oy3lphfdOh9cExiMK0DPS0TIY6OejmwFAhGhu9P5KQwPQexvIKSLzNixSJf6d pBwXa+5fsV1EWt0hBOSNm7mxZmMxhtXklLsmX52akxmZfADBjK/m1n73gWg2vC0OiNM6P6 dyHokKSznCeEhCwzCwFfnwMXde6EF2YSE01Gewy4DhEv3b3PpBvti5Or07aYmw== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4frkhf0Rmdztln for ; Thu, 09 Apr 2026 02:41:22 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 4174b by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Thu, 09 Apr 2026 02:41:22 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Kyle Evans Subject: git: 22fba3a9d641 - main - find: add -xattr and -xttrname List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: kevans X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 22fba3a9d64140d80a9e2093cfc02c9c503b2e19 Auto-Submitted: auto-generated Date: Thu, 09 Apr 2026 02:41:22 +0000 Message-Id: <69d711d2.4174b.38e1f303@gitrepo.freebsd.org> The branch main has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=22fba3a9d64140d80a9e2093cfc02c9c503b2e19 commit 22fba3a9d64140d80a9e2093cfc02c9c503b2e19 Author: Kyle Evans AuthorDate: 2026-04-09 02:41:12 +0000 Commit: Kyle Evans 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 #include #include +#include #include #include @@ -49,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -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 }