From nobody Thu Apr 23 13:49:08 2026 X-Original-To: dev-commits-src-all@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 4g1crj1zHBz6Zlgk for ; Thu, 23 Apr 2026 13:49:09 +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 "R13" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4g1crj00jkz3X0W for ; Thu, 23 Apr 2026 13:49:09 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1776952149; 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=pdvOAVRwQ1YJiMRnmQYRsoST0dVwJTPlXVx4v0QKKOw=; b=RQYUK4Wn2jOYR6hcpiPNyObB8T00cLPOvHRiPq76UpFuHrautHRx5yXIkXsFnoV3l/oQoC OLd+u7SgPqcSmpNLiU4FXgkqVGG866pTOJ6/Mdicm/xtrJHFo3gynyk4vrSOR5+i2xmJQk 1R1NL2erBH/jR/ARpyizTefywZQrwD/I+/VO7KUKNwYKOvj6CKJIFVKmrMjzndsSk7wduh fi9q3XgNZyJfENgXdzdDrcM4/Aj0XCzCg2+44iqnBdfFfHn8isfHwIexmrJw23KzBwcbva SLq+e/EU0uT9wtzrtcwfjliRemE/WijEfP1JUTJBNZJPEIFFxPfwChEYH36GCw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1776952149; a=rsa-sha256; cv=none; b=AaWXKzY40hW+JCLo6Cpw4SyAz2OKDsUlMgYOYU3ql6cyXj6vpbxOcfvv4kffRco2UN6qlQ JQGCK//hzLFJ+56oZgekutC235l2cYx6OoG02xLo4UuuwNXTl46PR+SQjeLKE3M7/XpEBZ JunSjC3kmGkiiHdH2vztzWquFZ9l+N6CVPpuifTq84EM5C9S5CUzZjJBpFC5hs9PaCpYXU O0sW4DVX0WyTf3IxRCiK+Q1QPqagVfHG6uouGwL5E0RbX9/N4OrM1k/Rtgq7/6JGvzjTHV 4dpDOpt73rz3gmmfUuCCFfy3rhSP1rK6MMqQARIRn13aznZ0XoQyG91KB8QBsw== 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=1776952149; 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=pdvOAVRwQ1YJiMRnmQYRsoST0dVwJTPlXVx4v0QKKOw=; b=Jd3MeJjxubzJYiBXkG7oX0c61/up7NUjhbYb+zaexPMPSni62jMB/GoVj/hzl9rbpc5Irq obfRHhcWdkdgQEuuYVTxETpoNkGWtN1hyYOAJYOSfcoWptPbX2FDk9D2/ZC7UkBBVGrWA5 tKaqKJ/l9aUp1k3FuzQYIyKhgmmzTj+AD0QnWVLgfmW5ifiraRpau9RWKHfD2EO4OO6jtw 29Gu/U2Cvdu9fXskYyGhqSSOKTk1QytAKXfQ/K0kdzlvLEu0OLAfsV4tFxT37+r0Bl+C95 X5HybVt0s/Q0QCCz0RQfOl1ZGvjEBY4jj2cpihcDBOYUk68m+dOFovALU31zsg== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4g1crh6cCRz3s8 for ; Thu, 23 Apr 2026 13:49:08 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 2153d by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Thu, 23 Apr 2026 13:49:08 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Kyle Evans Subject: git: 5cbb1e05086c - stable/15 - find: add -xattr and -xttrname List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@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/stable/15 X-Git-Reftype: branch X-Git-Commit: 5cbb1e05086c2cb510a9b77a6979dfb42c0cf215 Auto-Submitted: auto-generated Date: Thu, 23 Apr 2026 13:49:08 +0000 Message-Id: <69ea2354.2153d.493aa0dd@gitrepo.freebsd.org> The branch stable/15 has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=5cbb1e05086c2cb510a9b77a6979dfb42c0cf215 commit 5cbb1e05086c2cb510a9b77a6979dfb42c0cf215 Author: Kyle Evans AuthorDate: 2026-04-09 02:41:12 +0000 Commit: Kyle Evans CommitDate: 2026-04-23 13:48:44 +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. (cherry picked from commit 22fba3a9d64140d80a9e2093cfc02c9c503b2e19) --- 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 }