1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "alloc-util.h"
4 #include "dirent-util.h"
5 #include "fd-util.h"
6 #include "fileio.h"
7 #include "missing_syscall.h"
8 #include "mountpoint-util.h"
9 #include "recurse-dir.h"
10 #include "sort-util.h"
11
12 #define DEFAULT_RECURSION_MAX 100
13
sort_func(struct dirent * const * a,struct dirent * const * b)14 static int sort_func(struct dirent * const *a, struct dirent * const *b) {
15 return strcmp((*a)->d_name, (*b)->d_name);
16 }
17
ignore_dirent(const struct dirent * de,RecurseDirFlags flags)18 static bool ignore_dirent(const struct dirent *de, RecurseDirFlags flags) {
19 assert(de);
20
21 /* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
22
23 return FLAGS_SET(flags, RECURSE_DIR_IGNORE_DOT) ?
24 de->d_name[0] == '.' :
25 dot_or_dot_dot(de->d_name);
26 }
27
readdir_all(int dir_fd,RecurseDirFlags flags,DirectoryEntries ** ret)28 int readdir_all(int dir_fd,
29 RecurseDirFlags flags,
30 DirectoryEntries **ret) {
31
32 _cleanup_free_ DirectoryEntries *de = NULL;
33 struct dirent *entry;
34 DirectoryEntries *nde;
35 size_t add, sz, j;
36
37 assert(dir_fd >= 0);
38
39 /* Returns an array with pointers to "struct dirent" directory entries, optionally sorted. Free the
40 * array with readdir_all_freep().
41 *
42 * Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully
43 * 8 will cover most cases comprehensively. (Note that most likely a lot more entries will actually
44 * fit in the buffer, given we calculate maximum file name length here.) */
45 de = malloc(offsetof(DirectoryEntries, buffer) + DIRENT_SIZE_MAX * 8);
46 if (!de)
47 return -ENOMEM;
48
49 de->buffer_size = 0;
50 for (;;) {
51 size_t bs;
52 ssize_t n;
53
54 bs = MIN(MALLOC_SIZEOF_SAFE(de) - offsetof(DirectoryEntries, buffer), (size_t) SSIZE_MAX);
55 assert(bs > de->buffer_size);
56
57 n = getdents64(dir_fd, (uint8_t*) de->buffer + de->buffer_size, bs - de->buffer_size);
58 if (n < 0)
59 return -errno;
60 if (n == 0)
61 break;
62
63 msan_unpoison((uint8_t*) de->buffer + de->buffer_size, n);
64
65 de->buffer_size += n;
66
67 if (de->buffer_size < bs - DIRENT_SIZE_MAX) /* Still room for one more entry, then try to
68 * fill it up without growing the structure. */
69 continue;
70
71 if (bs >= SSIZE_MAX - offsetof(DirectoryEntries, buffer))
72 return -EFBIG;
73 bs = bs >= (SSIZE_MAX - offsetof(DirectoryEntries, buffer))/2 ? SSIZE_MAX - offsetof(DirectoryEntries, buffer) : bs * 2;
74
75 nde = realloc(de, bs);
76 if (!nde)
77 return -ENOMEM;
78
79 de = nde;
80 }
81
82 de->n_entries = 0;
83 FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) {
84 if (ignore_dirent(entry, flags))
85 continue;
86
87 de->n_entries++;
88 }
89
90 sz = ALIGN(offsetof(DirectoryEntries, buffer) + de->buffer_size);
91 add = sizeof(struct dirent*) * de->n_entries;
92 if (add > SIZE_MAX - add)
93 return -ENOMEM;
94
95 nde = realloc(de, sz + add);
96 if (!nde)
97 return -ENOMEM;
98
99 de = nde;
100 de->entries = (struct dirent**) ((uint8_t*) de + ALIGN(offsetof(DirectoryEntries, buffer) + de->buffer_size));
101
102 j = 0;
103 FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) {
104 if (ignore_dirent(entry, flags))
105 continue;
106
107 de->entries[j++] = entry;
108 }
109
110 if (FLAGS_SET(flags, RECURSE_DIR_SORT))
111 typesafe_qsort(de->entries, de->n_entries, sort_func);
112
113 if (ret)
114 *ret = TAKE_PTR(de);
115
116 return 0;
117 }
118
recurse_dir(int dir_fd,const char * path,unsigned statx_mask,unsigned n_depth_max,RecurseDirFlags flags,recurse_dir_func_t func,void * userdata)119 int recurse_dir(
120 int dir_fd,
121 const char *path,
122 unsigned statx_mask,
123 unsigned n_depth_max,
124 RecurseDirFlags flags,
125 recurse_dir_func_t func,
126 void *userdata) {
127
128 _cleanup_free_ DirectoryEntries *de = NULL;
129 int r;
130
131 assert(dir_fd >= 0);
132 assert(func);
133
134 /* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx()/O_PATH,
135 * and under the assumption that fds are not as 'expensive' as they used to be. */
136
137 if (n_depth_max == 0)
138 return -EOVERFLOW;
139 if (n_depth_max == UINT_MAX) /* special marker for "default" */
140 n_depth_max = DEFAULT_RECURSION_MAX;
141
142 r = readdir_all(dir_fd, flags, &de);
143 if (r < 0)
144 return r;
145
146 for (size_t i = 0; i < de->n_entries; i++) {
147 _cleanup_close_ int inode_fd = -1, subdir_fd = -1;
148 _cleanup_free_ char *joined = NULL;
149 STRUCT_STATX_DEFINE(sx);
150 bool sx_valid = false;
151 const char *p;
152
153 /* For each directory entry we'll do one of the following:
154 *
155 * 1) If the entry refers to a directory, we'll open it as O_DIRECTORY 'subdir_fd' and then statx() the opened directory via that new fd (if requested)
156 * 2) Otherwise, if RECURSE_DIR_INODE_FD is set we'll open it as O_PATH 'inode_fd' and then statx() the opened inode via that new fd (if requested)
157 * 3) Otherwise, we'll statx() the directory entry via the directory fd we are currently looking at (if requested)
158 */
159
160 if (path) {
161 joined = path_join(path, de->entries[i]->d_name);
162 if (!joined)
163 return -ENOMEM;
164
165 p = joined;
166 } else
167 p = de->entries[i]->d_name;
168
169 if (IN_SET(de->entries[i]->d_type, DT_UNKNOWN, DT_DIR)) {
170 subdir_fd = openat(dir_fd, de->entries[i]->d_name, O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
171 if (subdir_fd < 0) {
172 if (errno == ENOENT) /* Vanished by now, go for next file immediately */
173 continue;
174
175 /* If it is a subdir but we failed to open it, then fail */
176 if (!IN_SET(errno, ENOTDIR, ELOOP)) {
177 log_debug_errno(errno, "Failed to open directory '%s': %m", p);
178
179 assert(errno <= RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX - RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE);
180
181 r = func(RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE + errno,
182 p,
183 dir_fd,
184 -1,
185 de->entries[i],
186 NULL,
187 userdata);
188 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
189 break;
190 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
191 return r;
192
193 continue;
194 }
195
196 /* If it's not a subdir, then let's handle it like a regular inode below */
197
198 } else {
199 /* If we managed to get a DIR* off the inode, it's definitely a directory. */
200 de->entries[i]->d_type = DT_DIR;
201
202 if (statx_mask != 0 || (flags & RECURSE_DIR_SAME_MOUNT)) {
203 r = statx_fallback(subdir_fd, "", AT_EMPTY_PATH, statx_mask, &sx);
204 if (r < 0)
205 return r;
206
207 sx_valid = true;
208 }
209 }
210 }
211
212 if (subdir_fd < 0) {
213 /* It's not a subdirectory. */
214
215 if (flags & RECURSE_DIR_INODE_FD) {
216
217 inode_fd = openat(dir_fd, de->entries[i]->d_name, O_PATH|O_NOFOLLOW|O_CLOEXEC);
218 if (inode_fd < 0) {
219 if (errno == ENOENT) /* Vanished by now, go for next file immediately */
220 continue;
221
222 log_debug_errno(errno, "Failed to open directory entry '%s': %m", p);
223
224 assert(errno <= RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX - RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE);
225
226 r = func(RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE + errno,
227 p,
228 dir_fd,
229 -1,
230 de->entries[i],
231 NULL,
232 userdata);
233 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
234 break;
235 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
236 return r;
237
238 continue;
239 }
240
241 /* If we open the inode, then verify it's actually a non-directory, like we
242 * assume. Let's guarantee that we never pass statx data of a directory where
243 * caller expects a non-directory */
244
245 r = statx_fallback(inode_fd, "", AT_EMPTY_PATH, statx_mask | STATX_TYPE, &sx);
246 if (r < 0)
247 return r;
248
249 assert(sx.stx_mask & STATX_TYPE);
250 sx_valid = true;
251
252 if (S_ISDIR(sx.stx_mode)) {
253 /* What? It's a directory now? Then someone must have quickly
254 * replaced it. Let's handle that gracefully: convert it to a
255 * directory fd — which should be riskless now that we pinned the
256 * inode. */
257
258 subdir_fd = openat(AT_FDCWD, FORMAT_PROC_FD_PATH(inode_fd), O_DIRECTORY|O_CLOEXEC);
259 if (subdir_fd < 0)
260 return -errno;
261
262 inode_fd = safe_close(inode_fd);
263 }
264
265 } else if (statx_mask != 0 || (de->entries[i]->d_type == DT_UNKNOWN && (flags & RECURSE_DIR_ENSURE_TYPE))) {
266
267 r = statx_fallback(dir_fd, de->entries[i]->d_name, AT_SYMLINK_NOFOLLOW, statx_mask | STATX_TYPE, &sx);
268 if (r == -ENOENT) /* Vanished by now? Go for next file immediately */
269 continue;
270 if (r < 0) {
271 log_debug_errno(r, "Failed to stat directory entry '%s': %m", p);
272
273 assert(errno <= RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX - RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE);
274
275 r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + -r,
276 p,
277 dir_fd,
278 -1,
279 de->entries[i],
280 NULL,
281 userdata);
282 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
283 break;
284 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
285 return r;
286
287 continue;
288 }
289
290 assert(sx.stx_mask & STATX_TYPE);
291 sx_valid = true;
292
293 if (S_ISDIR(sx.stx_mode)) {
294 /* So it suddenly is a directory, but we couldn't open it as such
295 * earlier? That is weird, and probably means somebody is racing
296 * against us. We could of course retry and open it as a directory
297 * again, but the chance to win here is limited. Hence, let's
298 * propagate this as EISDIR error instead. That way we make this
299 * something that can be reasonably handled, even though we give the
300 * guarantee that RECURSE_DIR_ENTRY is strictly issued for
301 * non-directory dirents. */
302
303 log_debug_errno(r, "Non-directory entry '%s' suddenly became a directory: %m", p);
304
305 r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + EISDIR,
306 p,
307 dir_fd,
308 -1,
309 de->entries[i],
310 NULL,
311 userdata);
312 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
313 break;
314 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
315 return r;
316
317 continue;
318 }
319 }
320 }
321
322 if (sx_valid) {
323 /* Copy over the data we acquired through statx() if we acquired any */
324 if (sx.stx_mask & STATX_TYPE) {
325 assert((subdir_fd < 0) == !S_ISDIR(sx.stx_mode));
326 de->entries[i]->d_type = IFTODT(sx.stx_mode);
327 }
328
329 if (sx.stx_mask & STATX_INO)
330 de->entries[i]->d_ino = sx.stx_ino;
331 }
332
333 if (subdir_fd >= 0) {
334 if (FLAGS_SET(flags, RECURSE_DIR_SAME_MOUNT)) {
335 bool is_mount;
336
337 if (sx_valid && FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT))
338 is_mount = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT);
339 else {
340 r = fd_is_mount_point(dir_fd, de->entries[i]->d_name, 0);
341 if (r < 0)
342 log_debug_errno(r, "Failed to determine whether %s is a submount, assuming not: %m", p);
343
344 is_mount = r > 0;
345 }
346
347 if (is_mount) {
348 r = func(RECURSE_DIR_SKIP_MOUNT,
349 p,
350 dir_fd,
351 subdir_fd,
352 de->entries[i],
353 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
354 userdata);
355 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
356 break;
357 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
358 return r;
359
360 continue;
361 }
362 }
363
364 if (n_depth_max <= 1) {
365 /* When we reached max depth, generate a special event */
366
367 r = func(RECURSE_DIR_SKIP_DEPTH,
368 p,
369 dir_fd,
370 subdir_fd,
371 de->entries[i],
372 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
373 userdata);
374 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
375 break;
376 if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
377 return r;
378
379 continue;
380 }
381
382 r = func(RECURSE_DIR_ENTER,
383 p,
384 dir_fd,
385 subdir_fd,
386 de->entries[i],
387 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
388 userdata);
389 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
390 break;
391 if (r == RECURSE_DIR_SKIP_ENTRY)
392 continue;
393 if (r != RECURSE_DIR_CONTINUE)
394 return r;
395
396 r = recurse_dir(subdir_fd,
397 p,
398 statx_mask,
399 n_depth_max - 1,
400 flags,
401 func,
402 userdata);
403 if (r != 0)
404 return r;
405
406 r = func(RECURSE_DIR_LEAVE,
407 p,
408 dir_fd,
409 subdir_fd,
410 de->entries[i],
411 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
412 userdata);
413 } else
414 /* Non-directory inode */
415 r = func(RECURSE_DIR_ENTRY,
416 p,
417 dir_fd,
418 inode_fd,
419 de->entries[i],
420 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
421 userdata);
422
423
424 if (r == RECURSE_DIR_LEAVE_DIRECTORY)
425 break;
426 if (!IN_SET(r, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE))
427 return r;
428 }
429
430 return 0;
431 }
432
recurse_dir_at(int atfd,const char * path,unsigned statx_mask,unsigned n_depth_max,RecurseDirFlags flags,recurse_dir_func_t func,void * userdata)433 int recurse_dir_at(
434 int atfd,
435 const char *path,
436 unsigned statx_mask,
437 unsigned n_depth_max,
438 RecurseDirFlags flags,
439 recurse_dir_func_t func,
440 void *userdata) {
441
442 _cleanup_close_ int fd = -1;
443
444 assert(atfd >= 0 || atfd == AT_FDCWD);
445 assert(func);
446
447 fd = openat(atfd, path ?: ".", O_DIRECTORY|O_CLOEXEC);
448 if (fd < 0)
449 return -errno;
450
451 return recurse_dir(fd, path, statx_mask, n_depth_max, flags, func, userdata);
452 }
453