Something went wrong. Try again.
A breadth-first version of the UNIX find command tavianator.com/projects/bfs.html
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522// Copyright © Tavian Barnes <tavianator@tavianator.com>// SPDX-License-Identifier: 0BSD
#include "fsade.h"
#include "atomic.h"#include "bfs.h"#include "bfstd.h"#include "bftw.h"#include "dir.h"#include "dstring.h"#include "sanity.h"
#include <errno.h>#include <fcntl.h>#include <stddef.h>#include <unistd.h>
#if BFS_CAN_CHECK_ACL# include <sys/acl.h>#endif
#if BFS_CAN_CHECK_CAPABILITIES# include <sys/capability.h>#endif
#if BFS_CAN_CHECK_CONTEXT# include <selinux/selinux.h>#endif
#if __has_include(<sys/extattr.h>)# include <sys/extattr.h># define BFS_USE_EXTATTR true#elif __has_include(<sys/xattr.h>)# include <sys/xattr.h># define BFS_USE_XATTR true#endif
#ifndef BFS_USE_EXTATTR# define BFS_USE_EXTATTR false#endif#ifndef BFS_USE_XATTR# define BFS_USE_XATTR false#endif
/** * Many of the APIs used here don't have *at() variants, but we can try to * emulate something similar if /proc/self/fd is available. */[[_maybe_unused]]static const char *fake_at(const struct BFTW *ftwbuf) { static atomic int proc_works = -1;
dchar *path = NULL; if (ftwbuf->at_fd == (int)AT_FDCWD || load(&proc_works, relaxed) == 0) { goto fail; }
path = dstrprintf("/proc/self/fd/%d/", ftwbuf->at_fd); if (!path) { goto fail; }
if (load(&proc_works, relaxed) < 0) { if (xfaccessat(AT_FDCWD, path, F_OK) != 0) { store(&proc_works, 0, relaxed); goto fail; } else { store(&proc_works, 1, relaxed); } }
if (dstrcat(&path, ftwbuf->at_path) != 0) { goto fail; }
return path;
fail: dstrfree(path); return ftwbuf->path;}
[[_maybe_unused]]static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { dstrfree((dchar *)path); }}
/** * Check if an error was caused by the absence of support or data for a feature. */[[_maybe_unused]]static bool is_absence_error(int error) { // If the OS doesn't support the feature, it's obviously not enabled for // any files if (error == ENOTSUP) { return true; }
// On Linux, ACLs and capabilities are implemented in terms of extended // attributes, which report ENODATA/ENOATTR when missing
#ifdef ENODATA if (error == ENODATA) { return true; }#endif
#if defined(ENOATTR) && ENOATTR != ENODATA if (error == ENOATTR) { return true; }#endif
// On at least FreeBSD and macOS, EINVAL is returned when the requested // ACL type is not supported for that file if (error == EINVAL) { return true; }
#if __APPLE__ // On macOS, ENOENT can also signal that a file has no ACLs if (error == ENOENT) { return true; }#endif
return false;}
#if BFS_CAN_CHECK_ACL
#if BFS_HAS_ACL_GET_FILE
/** Unified interface for incompatible acl_get_entry() implementations. */static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) {#if BFS_HAS_ACL_GET_ENTRY int ret = acl_get_entry(acl, which, entry);# if __APPLE__ // POSIX.1e specifies a return value of 1 for success, but macOS returns 0 instead return !ret;# else return ret;# endif#elif __DragonFly__# if !defined(ACL_FIRST_ENTRY) && !defined(ACL_NEXT_ENTRY)# define ACL_FIRST_ENTRY 0# define ACL_NEXT_ENTRY 1# endif
switch (which) { case ACL_FIRST_ENTRY: *entry = &acl->acl_entry[0]; break; case ACL_NEXT_ENTRY: ++*entry; break; default: errno = EINVAL; return -1; }
acl_entry_t last = &acl->acl_entry[acl->acl_cnt]; return *entry == last;#else errno = ENOTSUP; return -1;#endif}
/** Unified interface for acl_get_tag_type(). */[[_maybe_unused]]static int bfs_acl_tag_type(acl_entry_t entry, acl_tag_t *tag) {#if BFS_HAS_ACL_GET_TAG_TYPE return acl_get_tag_type(entry, tag);#elif __DragonFly__ *tag = entry->ae_tag; return 0;#else errno = ENOTSUP; return -1;#endif}
/** Check if a POSIX.1e ACL is non-trivial. */static int bfs_check_posix1e_acl(acl_t acl, bool ignore_required) { int ret = 0;
acl_entry_t entry; for (int status = bfs_acl_entry(acl, ACL_FIRST_ENTRY, &entry); status > 0; status = bfs_acl_entry(acl, ACL_NEXT_ENTRY, &entry)) {#if defined(ACL_USER_OBJ) && defined(ACL_GROUP_OBJ) && defined(ACL_OTHER) if (ignore_required) { acl_tag_t tag; if (bfs_acl_tag_type(entry, &tag) != 0) { ret = -1; continue; } if (tag == ACL_USER_OBJ || tag == ACL_GROUP_OBJ || tag == ACL_OTHER) { continue; } }#endif
ret = 1; break; }
return ret;}
/** Check if an ACL of the given type is non-trivial. */static int bfs_check_acl_type(acl_t acl, acl_type_t type) { if (type == ACL_TYPE_DEFAULT) { // For directory default ACLs, any entries make them non-trivial return bfs_check_posix1e_acl(acl, false); }
#if BFS_HAS_ACL_IS_TRIVIAL_NP int trivial; int ret = acl_is_trivial_np(acl, &trivial);
// msan seems to be missing an interceptor for acl_is_trivial_np() sanitize_init(&trivial);
if (ret < 0) { return -1; } else if (trivial) { return 0; } else { return 1; }#else return bfs_check_posix1e_acl(acl, true);#endif}
#endif // BFS_HAS_ACL_GET_FILE
int bfs_check_acl(const struct BFTW *ftwbuf) { if (ftwbuf->type == BFS_LNK) { return 0; }
const char *path = fake_at(ftwbuf);
#if BFS_HAS_ACL_TRIVIAL int ret = acl_trivial(path); int error = errno;#elif BFS_HAS_ACL_GET_FILE static const acl_type_t acl_types[] = {# if __APPLE__ // macOS gives EINVAL for either of the two standard ACL types, // supporting only ACL_TYPE_EXTENDED ACL_TYPE_EXTENDED,# else // The two standard POSIX.1e ACL types ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT,# endif
# ifdef ACL_TYPE_NFS4 ACL_TYPE_NFS4,# endif };
int ret = -1, error = 0; for (size_t i = 0; i < countof(acl_types) && ret <= 0; ++i) { acl_type_t type = acl_types[i];
if (type == ACL_TYPE_DEFAULT && ftwbuf->type != BFS_DIR) { // ACL_TYPE_DEFAULT is supported only for directories, // otherwise acl_get_file() gives EACCESS continue; }
acl_t acl = acl_get_file(path, type); if (!acl) { error = errno; if (is_absence_error(error)) { ret = 0; } continue; }
ret = bfs_check_acl_type(acl, type); error = errno; acl_free(acl); }#endif
free_fake_at(ftwbuf, path); errno = error; return ret;}
#else // !BFS_CAN_CHECK_ACL
int bfs_check_acl(const struct BFTW *ftwbuf) { errno = ENOTSUP; return -1;}
#endif
#if BFS_CAN_CHECK_CAPABILITIES
int bfs_check_capabilities(const struct BFTW *ftwbuf) { if (ftwbuf->type == BFS_LNK) { return 0; }
int ret = -1, error; const char *path = fake_at(ftwbuf);
cap_t caps = cap_get_file(path); if (!caps) { error = errno; if (is_absence_error(error)) { ret = 0; } goto out_path; }
// TODO: Any better way to check for a non-empty capability set? char *text = cap_to_text(caps, NULL); if (!text) { error = errno; goto out_caps; } ret = text[0] ? 1 : 0;
error = errno; cap_free(text);out_caps: cap_free(caps);out_path: free_fake_at(ftwbuf, path); errno = error; return ret;}
#else // !BFS_CAN_CHECK_CAPABILITIES
int bfs_check_capabilities(const struct BFTW *ftwbuf) { errno = ENOTSUP; return -1;}
#endif
#if BFS_CAN_CHECK_XATTRS
#if BFS_USE_EXTATTR
/** Wrapper for extattr_list_{file,link}. */static ssize_t bfs_extattr_list(const char *path, enum bfs_type type, int namespace) { if (type == BFS_LNK) {#if BFS_HAS_EXTATTR_LIST_LINK return extattr_list_link(path, namespace, NULL, 0);#elif BFS_HAS_EXTATTR_GET_LINK return extattr_get_link(path, namespace, "", NULL, 0);#else return 0;#endif }
#if BFS_HAS_EXTATTR_LIST_FILE return extattr_list_file(path, namespace, NULL, 0);#elif BFS_HAS_EXTATTR_GET_FILE // From man extattr(2): // // In earlier versions of this API, passing an empty string for the // attribute name to extattr_get_file() would return the list of attributes // defined for the target object. This interface has been deprecated in // preference to using the explicit list API, and should not be used. return extattr_get_file(path, namespace, "", NULL, 0);#else return 0;#endif}
/** Wrapper for extattr_get_{file,link}. */static ssize_t bfs_extattr_get(const char *path, enum bfs_type type, int namespace, const char *name) { if (type == BFS_LNK) {#if BFS_HAS_EXTATTR_GET_LINK return extattr_get_link(path, namespace, name, NULL, 0);#else return 0;#endif }
#if BFS_HAS_EXTATTR_GET_FILE return extattr_get_file(path, namespace, name, NULL, 0);#else return 0;#endif}
#endif // BFS_USE_EXTATTR
int bfs_check_xattrs(const struct BFTW *ftwbuf) { const char *path = fake_at(ftwbuf); ssize_t len;
#if BFS_USE_EXTATTR len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM); if (len <= 0) { len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_USER); }#elif __APPLE__ int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0; len = listxattr(path, NULL, 0, options);#else if (ftwbuf->type == BFS_LNK) { len = llistxattr(path, NULL, 0); } else { len = listxattr(path, NULL, 0); }#endif
int error = errno;
free_fake_at(ftwbuf, path);
if (len > 0) { return 1; } else if (len == 0 || is_absence_error(error)) { return 0; } else if (error == E2BIG) { return 1; } else { errno = error; return -1; }}
int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { const char *path = fake_at(ftwbuf); ssize_t len;
#if BFS_USE_EXTATTR len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM, name); if (len < 0) { len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_USER, name); }#elif __APPLE__ int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0; len = getxattr(path, name, NULL, 0, 0, options);#else if (ftwbuf->type == BFS_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) { errno = ENOTSUP; return -1;}
int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { errno = ENOTSUP; return -1;}
#endif
char *bfs_getfilecon(const struct BFTW *ftwbuf) {#if BFS_CAN_CHECK_CONTEXT const char *path = fake_at(ftwbuf);
char *con; int ret; if (ftwbuf->type == BFS_LNK) { ret = lgetfilecon(path, &con); } else { ret = getfilecon(path, &con); }
free_fake_at(ftwbuf, path);
if (ret >= 0) { return con; } else { return NULL; }#else errno = ENOTSUP; return NULL;#endif}
void bfs_freecon(char *con) {#if BFS_CAN_CHECK_CONTEXT freecon(con);#endif}