1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
21 #if BFS_CAN_CHECK_CAPABILITIES
22 # include <sys/capability.h>
25 #if BFS_USE_SYS_EXTATTR_H
26 # include <sys/extattr.h>
27 #elif BFS_USE_SYS_XATTR_H
28 # include <sys/xattr.h>
31 #if BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS
34 * Many of the APIs used here don't have *at() variants, but we can try to
35 * emulate something similar if /proc/self/fd is available.
37 static const char *fake_at(const struct BFTW *ftwbuf) {
38 static atomic int proc_works = -1;
41 if (ftwbuf->at_fd == AT_FDCWD || load(&proc_works, relaxed) == 0) {
45 path = dstrprintf("/proc/self/fd/%d/", ftwbuf->at_fd);
50 if (load(&proc_works, relaxed) < 0) {
51 if (xfaccessat(AT_FDCWD, path, F_OK) != 0) {
52 store(&proc_works, 0, relaxed);
55 store(&proc_works, 1, relaxed);
59 if (dstrcat(&path, ftwbuf->at_path) != 0) {
70 static void free_fake_at(const struct BFTW *ftwbuf, const char *path) {
71 if (path != ftwbuf->path) {
72 dstrfree((dchar *)path);
77 * Check if an error was caused by the absence of support or data for a feature.
79 static bool is_absence_error(int error) {
80 // If the OS doesn't support the feature, it's obviously not enabled for
82 if (error == ENOTSUP) {
86 // On Linux, ACLs and capabilities are implemented in terms of extended
87 // attributes, which report ENODATA/ENOATTR when missing
90 if (error == ENODATA) {
95 #if defined(ENOATTR) && ENOATTR != ENODATA
96 if (error == ENOATTR) {
101 // On at least FreeBSD and macOS, EINVAL is returned when the requested
102 // ACL type is not supported for that file
103 if (error == EINVAL) {
108 // On macOS, ENOENT can also signal that a file has no ACLs
109 if (error == ENOENT) {
117 #endif // BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS
119 #if BFS_CAN_CHECK_ACL
121 /** Check if a POSIX.1e ACL is non-trivial. */
122 static int bfs_check_posix1e_acl(acl_t acl, bool ignore_required) {
126 for (int status = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
128 // POSIX.1e specifies a return value of 1 for success, but macOS
134 status = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) {
135 #if defined(ACL_USER_OBJ) && defined(ACL_GROUP_OBJ) && defined(ACL_OTHER)
136 if (ignore_required) {
138 if (acl_get_tag_type(entry, &tag) != 0) {
142 if (tag == ACL_USER_OBJ || tag == ACL_GROUP_OBJ || tag == ACL_OTHER) {
155 /** Check if an ACL of the given type is non-trivial. */
156 static int bfs_check_acl_type(acl_t acl, acl_type_t type) {
157 if (type == ACL_TYPE_DEFAULT) {
158 // For directory default ACLs, any entries make them non-trivial
159 return bfs_check_posix1e_acl(acl, false);
164 int ret = acl_is_trivial_np(acl, &trivial);
166 // msan seems to be missing an interceptor for acl_is_trivial_np()
167 sanitize_init(&trivial);
171 } else if (trivial) {
176 #else // !__FreeBSD__
177 return bfs_check_posix1e_acl(acl, true);
181 int bfs_check_acl(const struct BFTW *ftwbuf) {
182 static const acl_type_t acl_types[] = {
184 // macOS gives EINVAL for either of the two standard ACL types,
185 // supporting only ACL_TYPE_EXTENDED
188 // The two standard POSIX.1e ACL types
198 if (ftwbuf->type == BFS_LNK) {
202 const char *path = fake_at(ftwbuf);
204 int ret = -1, error = 0;
205 for (size_t i = 0; i < countof(acl_types) && ret <= 0; ++i) {
206 acl_type_t type = acl_types[i];
208 if (type == ACL_TYPE_DEFAULT && ftwbuf->type != BFS_DIR) {
209 // ACL_TYPE_DEFAULT is supported only for directories,
210 // otherwise acl_get_file() gives EACCESS
214 acl_t acl = acl_get_file(path, type);
217 if (is_absence_error(error)) {
223 ret = bfs_check_acl_type(acl, type);
228 free_fake_at(ftwbuf, path);
233 #else // !BFS_CAN_CHECK_ACL
235 int bfs_check_acl(const struct BFTW *ftwbuf) {
242 #if BFS_CAN_CHECK_CAPABILITIES
244 int bfs_check_capabilities(const struct BFTW *ftwbuf) {
245 if (ftwbuf->type == BFS_LNK) {
250 const char *path = fake_at(ftwbuf);
252 cap_t caps = cap_get_file(path);
255 if (is_absence_error(error)) {
261 // TODO: Any better way to check for a non-empty capability set?
262 char *text = cap_to_text(caps, NULL);
267 ret = text[0] ? 1 : 0;
274 free_fake_at(ftwbuf, path);
279 #else // !BFS_CAN_CHECK_CAPABILITIES
281 int bfs_check_capabilities(const struct BFTW *ftwbuf) {
288 #if BFS_CAN_CHECK_XATTRS
290 int bfs_check_xattrs(const struct BFTW *ftwbuf) {
291 const char *path = fake_at(ftwbuf);
294 #if BFS_USE_SYS_EXTATTR_H
295 ssize_t (*extattr_list)(const char *, int, void *, size_t) =
296 ftwbuf->type == BFS_LNK ? extattr_list_link : extattr_list_file;
298 len = extattr_list(path, EXTATTR_NAMESPACE_SYSTEM, NULL, 0);
300 len = extattr_list(path, EXTATTR_NAMESPACE_USER, NULL, 0);
303 int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0;
304 len = listxattr(path, NULL, 0, options);
306 if (ftwbuf->type == BFS_LNK) {
307 len = llistxattr(path, NULL, 0);
309 len = listxattr(path, NULL, 0);
315 free_fake_at(ftwbuf, path);
319 } else if (len == 0 || is_absence_error(error)) {
321 } else if (error == E2BIG) {
329 int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) {
330 const char *path = fake_at(ftwbuf);
333 #if BFS_USE_SYS_EXTATTR_H
334 ssize_t (*extattr_get)(const char *, int, const char *, void *, size_t) =
335 ftwbuf->type == BFS_LNK ? extattr_get_link : extattr_get_file;
337 len = extattr_get(path, EXTATTR_NAMESPACE_SYSTEM, name, NULL, 0);
339 len = extattr_get(path, EXTATTR_NAMESPACE_USER, name, NULL, 0);
342 int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0;
343 len = getxattr(path, name, NULL, 0, 0, options);
345 if (ftwbuf->type == BFS_LNK) {
346 len = lgetxattr(path, name, NULL, 0);
348 len = getxattr(path, name, NULL, 0);
354 free_fake_at(ftwbuf, path);
358 } else if (is_absence_error(error)) {
360 } else if (error == E2BIG) {
368 #else // !BFS_CAN_CHECK_XATTRS
370 int bfs_check_xattrs(const struct BFTW *ftwbuf) {
375 int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) {