]> Sergey Matveev's repositories - bfs.git/blob - src/fsade.c
Skip mtab
[bfs.git] / src / fsade.c
1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
3
4 #include "fsade.h"
5 #include "atomic.h"
6 #include "bfstd.h"
7 #include "bftw.h"
8 #include "config.h"
9 #include "dir.h"
10 #include "dstring.h"
11 #include "sanity.h"
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <stddef.h>
15 #include <unistd.h>
16
17 #if BFS_CAN_CHECK_ACL
18 #  include <sys/acl.h>
19 #endif
20
21 #if BFS_CAN_CHECK_CAPABILITIES
22 #  include <sys/capability.h>
23 #endif
24
25 #if BFS_USE_SYS_EXTATTR_H
26 #  include <sys/extattr.h>
27 #elif BFS_USE_SYS_XATTR_H
28 #  include <sys/xattr.h>
29 #endif
30
31 #if BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS
32
33 /**
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.
36  */
37 static const char *fake_at(const struct BFTW *ftwbuf) {
38         static atomic int proc_works = -1;
39
40         dchar *path = NULL;
41         if (ftwbuf->at_fd == AT_FDCWD || load(&proc_works, relaxed) == 0) {
42                 goto fail;
43         }
44
45         path = dstrprintf("/proc/self/fd/%d/", ftwbuf->at_fd);
46         if (!path) {
47                 goto fail;
48         }
49
50         if (load(&proc_works, relaxed) < 0) {
51                 if (xfaccessat(AT_FDCWD, path, F_OK) != 0) {
52                         store(&proc_works, 0, relaxed);
53                         goto fail;
54                 } else {
55                         store(&proc_works, 1, relaxed);
56                 }
57         }
58
59         if (dstrcat(&path, ftwbuf->at_path) != 0) {
60                 goto fail;
61         }
62
63         return path;
64
65 fail:
66         dstrfree(path);
67         return ftwbuf->path;
68 }
69
70 static void free_fake_at(const struct BFTW *ftwbuf, const char *path) {
71         if (path != ftwbuf->path) {
72                 dstrfree((dchar *)path);
73         }
74 }
75
76 /**
77  * Check if an error was caused by the absence of support or data for a feature.
78  */
79 static bool is_absence_error(int error) {
80         // If the OS doesn't support the feature, it's obviously not enabled for
81         // any files
82         if (error == ENOTSUP) {
83                 return true;
84         }
85
86         // On Linux, ACLs and capabilities are implemented in terms of extended
87         // attributes, which report ENODATA/ENOATTR when missing
88
89 #ifdef ENODATA
90         if (error == ENODATA) {
91                 return true;
92         }
93 #endif
94
95 #if defined(ENOATTR) && ENOATTR != ENODATA
96         if (error == ENOATTR) {
97                 return true;
98         }
99 #endif
100
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) {
104                 return true;
105         }
106
107 #if __APPLE__
108         // On macOS, ENOENT can also signal that a file has no ACLs
109         if (error == ENOENT) {
110                 return true;
111         }
112 #endif
113
114         return false;
115 }
116
117 #endif // BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS
118
119 #if BFS_CAN_CHECK_ACL
120
121 /** Check if a POSIX.1e ACL is non-trivial. */
122 static int bfs_check_posix1e_acl(acl_t acl, bool ignore_required) {
123         int ret = 0;
124
125         acl_entry_t entry;
126         for (int status = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
127 #if __APPLE__
128              // POSIX.1e specifies a return value of 1 for success, but macOS
129              // returns 0 instead
130              status == 0;
131 #else
132              status > 0;
133 #endif
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) {
137                         acl_tag_t tag;
138                         if (acl_get_tag_type(entry, &tag) != 0) {
139                                 ret = -1;
140                                 continue;
141                         }
142                         if (tag == ACL_USER_OBJ || tag == ACL_GROUP_OBJ || tag == ACL_OTHER) {
143                                 continue;
144                         }
145                 }
146 #endif
147
148                 ret = 1;
149                 break;
150         }
151
152         return ret;
153 }
154
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);
160         }
161
162 #if __FreeBSD__
163         int trivial;
164         int ret = acl_is_trivial_np(acl, &trivial);
165
166         // msan seems to be missing an interceptor for acl_is_trivial_np()
167         sanitize_init(&trivial);
168
169         if (ret < 0) {
170                 return -1;
171         } else if (trivial) {
172                 return 0;
173         } else {
174                 return 1;
175         }
176 #else // !__FreeBSD__
177         return bfs_check_posix1e_acl(acl, true);
178 #endif
179 }
180
181 int bfs_check_acl(const struct BFTW *ftwbuf) {
182         static const acl_type_t acl_types[] = {
183 #if __APPLE__
184                 // macOS gives EINVAL for either of the two standard ACL types,
185                 // supporting only ACL_TYPE_EXTENDED
186                 ACL_TYPE_EXTENDED,
187 #else
188                 // The two standard POSIX.1e ACL types
189                 ACL_TYPE_ACCESS,
190                 ACL_TYPE_DEFAULT,
191 #endif
192
193 #ifdef ACL_TYPE_NFS4
194                 ACL_TYPE_NFS4,
195 #endif
196         };
197
198         if (ftwbuf->type == BFS_LNK) {
199                 return 0;
200         }
201
202         const char *path = fake_at(ftwbuf);
203
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];
207
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
211                         continue;
212                 }
213
214                 acl_t acl = acl_get_file(path, type);
215                 if (!acl) {
216                         error = errno;
217                         if (is_absence_error(error)) {
218                                 ret = 0;
219                         }
220                         continue;
221                 }
222
223                 ret = bfs_check_acl_type(acl, type);
224                 error = errno;
225                 acl_free(acl);
226         }
227
228         free_fake_at(ftwbuf, path);
229         errno = error;
230         return ret;
231 }
232
233 #else // !BFS_CAN_CHECK_ACL
234
235 int bfs_check_acl(const struct BFTW *ftwbuf) {
236         errno = ENOTSUP;
237         return -1;
238 }
239
240 #endif
241
242 #if BFS_CAN_CHECK_CAPABILITIES
243
244 int bfs_check_capabilities(const struct BFTW *ftwbuf) {
245         if (ftwbuf->type == BFS_LNK) {
246                 return 0;
247         }
248
249         int ret = -1, error;
250         const char *path = fake_at(ftwbuf);
251
252         cap_t caps = cap_get_file(path);
253         if (!caps) {
254                 error = errno;
255                 if (is_absence_error(error)) {
256                         ret = 0;
257                 }
258                 goto out_path;
259         }
260
261         // TODO: Any better way to check for a non-empty capability set?
262         char *text = cap_to_text(caps, NULL);
263         if (!text) {
264                 error = errno;
265                 goto out_caps;
266         }
267         ret = text[0] ? 1 : 0;
268
269         error = errno;
270         cap_free(text);
271 out_caps:
272         cap_free(caps);
273 out_path:
274         free_fake_at(ftwbuf, path);
275         errno = error;
276         return ret;
277 }
278
279 #else // !BFS_CAN_CHECK_CAPABILITIES
280
281 int bfs_check_capabilities(const struct BFTW *ftwbuf) {
282         errno = ENOTSUP;
283         return -1;
284 }
285
286 #endif
287
288 #if BFS_CAN_CHECK_XATTRS
289
290 int bfs_check_xattrs(const struct BFTW *ftwbuf) {
291         const char *path = fake_at(ftwbuf);
292         ssize_t len;
293
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;
297
298         len = extattr_list(path, EXTATTR_NAMESPACE_SYSTEM, NULL, 0);
299         if (len <= 0) {
300                 len = extattr_list(path, EXTATTR_NAMESPACE_USER, NULL, 0);
301         }
302 #elif __APPLE__
303         int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0;
304         len = listxattr(path, NULL, 0, options);
305 #else
306         if (ftwbuf->type == BFS_LNK) {
307                 len = llistxattr(path, NULL, 0);
308         } else {
309                 len = listxattr(path, NULL, 0);
310         }
311 #endif
312
313         int error = errno;
314
315         free_fake_at(ftwbuf, path);
316
317         if (len > 0) {
318                 return 1;
319         } else if (len == 0 || is_absence_error(error)) {
320                 return 0;
321         } else if (error == E2BIG) {
322                 return 1;
323         } else {
324                 errno = error;
325                 return -1;
326         }
327 }
328
329 int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) {
330         const char *path = fake_at(ftwbuf);
331         ssize_t len;
332
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;
336
337         len = extattr_get(path, EXTATTR_NAMESPACE_SYSTEM, name, NULL, 0);
338         if (len < 0) {
339                 len = extattr_get(path, EXTATTR_NAMESPACE_USER, name, NULL, 0);
340         }
341 #elif __APPLE__
342         int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0;
343         len = getxattr(path, name, NULL, 0, 0, options);
344 #else
345         if (ftwbuf->type == BFS_LNK) {
346                 len = lgetxattr(path, name, NULL, 0);
347         } else {
348                 len = getxattr(path, name, NULL, 0);
349         }
350 #endif
351
352         int error = errno;
353
354         free_fake_at(ftwbuf, path);
355
356         if (len >= 0) {
357                 return 1;
358         } else if (is_absence_error(error)) {
359                 return 0;
360         } else if (error == E2BIG) {
361                 return 1;
362         } else {
363                 errno = error;
364                 return -1;
365         }
366 }
367
368 #else // !BFS_CAN_CHECK_XATTRS
369
370 int bfs_check_xattrs(const struct BFTW *ftwbuf) {
371         errno = ENOTSUP;
372         return -1;
373 }
374
375 int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) {
376         errno = ENOTSUP;
377         return -1;
378 }
379
380 #endif