From 564142a029fda86b2d87f8f39c12acea34241098 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Aug 2020 10:02:29 -0400 Subject: Implement -xattrname From macOS find. --- RELEASES.md | 2 ++ bfs.1 | 4 +++ eval.c | 13 ++++++++ eval.h | 1 + fsade.c | 44 ++++++++++++++++++++++++++ fsade.h | 12 ++++++++ parse.c | 20 ++++++++++++ tests.sh | 77 ++++++++++++++++++++++++++++++++++------------ tests/test_L_xattr.out | 1 + tests/test_L_xattrname.out | 2 ++ tests/test_xattr.out | 1 + tests/test_xattrname.out | 2 ++ 12 files changed, 159 insertions(+), 20 deletions(-) create mode 100644 tests/test_L_xattrname.out create mode 100644 tests/test_xattrname.out diff --git a/RELEASES.md b/RELEASES.md index e8e0b71..d80f1ee 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -38,6 +38,8 @@ - Fixed an optimizer bug that could skip `-empty`/`-xtype` if they didn't always lead to an action +- Implemented `-xattrname` to find files with a particular extended attribute (from macOS find) + [#8]: https://github.com/tavianator/bfs/issues/8 [#30]: https://github.com/tavianator/bfs/issues/30 diff --git a/bfs.1 b/bfs.1 index 4c78bde..abb4189 100644 --- a/bfs.1 +++ b/bfs.1 @@ -576,6 +576,10 @@ days after they were changed. Find files with extended attributes .RB ( xattr (7)). .TP +\fB\-xattrname\fR \fINAME\fR +Find files with the extended attribute +.IR NAME . +.TP \fB\-xtype\fR [\fIbcdlpfswD\fR] Find files of the given type, following links when .B \-type diff --git a/eval.c b/eval.c index dc859b7..f8b9a6d 100644 --- a/eval.c +++ b/eval.c @@ -863,6 +863,19 @@ bool eval_xattr(const struct expr *expr, struct eval_state *state) { } } +/** + * -xattr test. + */ +bool eval_xattrname(const struct expr *expr, struct eval_state *state) { + int ret = bfs_check_xattr_named(state->ftwbuf, expr->sdata); + if (ret >= 0) { + return ret; + } else { + eval_report_error(state); + return false; + } +} + /** * -xtype test. */ diff --git a/eval.h b/eval.h index b31c0d5..a95c3ac 100644 --- a/eval.h +++ b/eval.h @@ -33,6 +33,7 @@ bool eval_acl(const struct expr *expr, struct eval_state *state); bool eval_capable(const struct expr *expr, struct eval_state *state); bool eval_perm(const struct expr *expr, struct eval_state *state); bool eval_xattr(const struct expr *expr, struct eval_state *state); +bool eval_xattrname(const struct expr *expr, struct eval_state *state); bool eval_newer(const struct expr *expr, struct eval_state *state); bool eval_time(const struct expr *expr, struct eval_state *state); diff --git a/fsade.c b/fsade.c index acd39bb..efe5092 100644 --- a/fsade.c +++ b/fsade.c @@ -330,6 +330,45 @@ int bfs_check_xattrs(const struct BFTW *ftwbuf) { } } +int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { + const char *path = fake_at(ftwbuf); + ssize_t len; + +#if BFS_HAS_SYS_EXTATTR + ssize_t (*extattr_get)(const char *, int, const char *, void*, size_t) = + ftwbuf->type == BFTW_LNK ? extattr_get_link : extattr_get_file; + + len = extattr_get(path, EXTATTR_NAMESPACE_SYSTEM, name, NULL, 0); + if (len < 0) { + len = extattr_get(path, EXTATTR_NAMESPACE_USER, name, NULL, 0); + } +#elif __APPLE__ + int options = ftwbuf->type == BFTW_LNK ? XATTR_NOFOLLOW : 0; + len = getxattr(path, name, NULL, 0, 0, options); +#else + if (ftwbuf->type == BFTW_LNK) { + len = lgetxattr(path, name, NULL, 0); + } else { + len = getxattr(path, name, NULL, 0); + } +#endif + + int error = errno; + + free_fake_at(ftwbuf, path); + + if (len >= 0) { + return 1; + } else if (is_absence_error(error)) { + return 0; + } else if (error == E2BIG) { + return 1; + } else { + errno = error; + return -1; + } +} + #else // !BFS_CAN_CHECK_XATTRS int bfs_check_xattrs(const struct BFTW *ftwbuf) { @@ -337,4 +376,9 @@ int bfs_check_xattrs(const struct BFTW *ftwbuf) { return -1; } +int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { + errno = ENOTSUP; + return -1; +} + #endif diff --git a/fsade.h b/fsade.h index fedc8ed..3136608 100644 --- a/fsade.h +++ b/fsade.h @@ -67,4 +67,16 @@ int bfs_check_capabilities(const struct BFTW *ftwbuf); */ int bfs_check_xattrs(const struct BFTW *ftwbuf); +/** + * Check if a file has an extended attribute with the given name. + * + * @param ftwbuf + * The file to check. + * @param name + * The name of the xattr to check. + * @return + * 1 if it does, 0 if it doesn't, or -1 if an error occurred. + */ +int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name); + #endif // BFS_FSADE_H diff --git a/parse.c b/parse.c index 42dfc2e..155f10b 100644 --- a/parse.c +++ b/parse.c @@ -2504,6 +2504,23 @@ static struct expr *parse_xattr(struct parser_state *state, int arg1, int arg2) #endif } +/** + * Parse -xattrname. + */ +static struct expr *parse_xattrname(struct parser_state *state, int arg1, int arg2) { +#if BFS_CAN_CHECK_XATTRS + struct expr *expr = parse_unary_test(state, eval_xattrname); + if (expr) { + expr->cost = STAT_COST; + expr->probability = 0.01; + } + return expr; +#else + parse_error(state, "${blu}%s${rs} is missing platform support.\n", state->argv[0]); + return NULL; +#endif +} + /** * Parse -xdev. */ @@ -2814,6 +2831,8 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS cfprintf(cout, " ${blu}-xattr${rs}\n"); cfprintf(cout, " Find files with extended attributes\n"); + cfprintf(cout, " ${blu}-xattrname${rs} ${bld}NAME${rs}\n"); + cfprintf(cout, " Find files with the extended attribute ${bld}NAME${rs}\n"); #endif cfprintf(cout, " ${blu}-xtype${rs} ${bld}[bcdlpfswD]${rs}\n"); cfprintf(cout, " Find files of the given type, following links when ${blu}-type${rs} would not, and\n"); @@ -3017,6 +3036,7 @@ static const struct table_entry parse_table[] = { {"-writable", T_TEST, parse_access, W_OK}, {"-x", T_FLAG, parse_xdev}, {"-xattr", T_TEST, parse_xattr}, + {"-xattrname", T_TEST, parse_xattrname}, {"-xdev", T_OPTION, parse_xdev}, {"-xtype", T_TEST, parse_type, true}, {0}, diff --git a/tests.sh b/tests.sh index 88b6988..bbc823d 100755 --- a/tests.sh +++ b/tests.sh @@ -695,12 +695,18 @@ case "$UNAME" in bsd_tests+=( test_xattr test_L_xattr + + test_xattrname + test_L_xattrname ) ;; *) sudo_tests+=( test_xattr test_L_xattr + + test_xattrname + test_L_xattrname ) ;; esac @@ -2562,53 +2568,84 @@ function test_L_capable() { bfs_diff -L scratch -capable } -function set_xattr() { +function make_xattrs() { + rm -rf scratch/* + + touch scratch/{normal,xattr,xattr_2} + ln -s xattr scratch/link + ln -s normal scratch/xattr_link + case "$UNAME" in Darwin) - xattr -w bfs_test true "$1" - xattr -s -w bfs_test true "$2" + xattr -w bfs_test true scratch/xattr + xattr -w bfs_test_2 true scratch/xattr_2 + xattr -s -w bfs_test true scratch/xattr_link ;; FreeBSD) - setextattr user bfs_test true "$1" - setextattr -h user bfs_test true "$2" + setextattr user bfs_test true scratch/xattr + setextattr user bfs_test_2 true scratch/xattr_2 + setextattr -h user bfs_test true scratch/xattr_link ;; *) # Linux tmpfs doesn't support the user.* namespace, so we use the security.* # namespace, which is writable by root and readable by others - sudo setfattr -n security.bfs_test "$1" - sudo setfattr -h -n security.bfs_test "$2" + sudo setfattr -n security.bfs_test scratch/xattr + sudo setfattr -n security.bfs_test_2 scratch/xattr_2 + sudo setfattr -h -n security.bfs_test scratch/xattr_link ;; esac } function test_xattr() { - rm -rf scratch/* - if ! invoke_bfs scratch -quit -xattr 2>/dev/null; then return 0 fi - touch scratch/{normal,xattr} - ln -s xattr scratch/link - ln -s normal scratch/xattr_link - set_xattr scratch/xattr scratch/xattr_link - + make_xattrs bfs_diff scratch -xattr } function test_L_xattr() { - rm -rf scratch/* + if ! invoke_bfs scratch -quit -xattr 2>/dev/null; then + return 0 + fi + make_xattrs + bfs_diff -L scratch -xattr +} + +function test_xattrname() { if ! invoke_bfs scratch -quit -xattr 2>/dev/null; then return 0 fi - touch scratch/{normal,xattr} - ln -s xattr scratch/link - ln -s normal scratch/xattr_link - set_xattr scratch/xattr scratch/xattr_link + make_xattrs - bfs_diff -L scratch -xattr + case "$UNAME" in + Darwin|FreeBSD) + bfs_diff scratch -xattrname bfs_test + ;; + *) + bfs_diff scratch -xattrname security.bfs_test + ;; + esac +} + +function test_L_xattrname() { + if ! invoke_bfs scratch -quit -xattr 2>/dev/null; then + return 0 + fi + + make_xattrs + + case "$UNAME" in + Darwin|FreeBSD) + bfs_diff -L scratch -xattrname bfs_test + ;; + *) + bfs_diff -L scratch -xattrname security.bfs_test + ;; + esac } function test_help() { diff --git a/tests/test_L_xattr.out b/tests/test_L_xattr.out index 4dc4836..12fac95 100644 --- a/tests/test_L_xattr.out +++ b/tests/test_L_xattr.out @@ -1,2 +1,3 @@ scratch/link scratch/xattr +scratch/xattr_2 diff --git a/tests/test_L_xattrname.out b/tests/test_L_xattrname.out new file mode 100644 index 0000000..4dc4836 --- /dev/null +++ b/tests/test_L_xattrname.out @@ -0,0 +1,2 @@ +scratch/link +scratch/xattr diff --git a/tests/test_xattr.out b/tests/test_xattr.out index 0285ac1..109e7c9 100644 --- a/tests/test_xattr.out +++ b/tests/test_xattr.out @@ -1,2 +1,3 @@ scratch/xattr +scratch/xattr_2 scratch/xattr_link diff --git a/tests/test_xattrname.out b/tests/test_xattrname.out new file mode 100644 index 0000000..0285ac1 --- /dev/null +++ b/tests/test_xattrname.out @@ -0,0 +1,2 @@ +scratch/xattr +scratch/xattr_link -- cgit v1.2.3