• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 15263807472

26 May 2025 08:53PM UTC coverage: 72.046% (-0.002%) from 72.048%
15263807472

push

github

yuwata
src/core/manager.c: log preset activity on first boot

This gives us a little more information about what units were enabled
or disabled on that first boot and will be useful for OS developers
tracking down the source of unit state.

An example with this enabled looks like:

```
NET: Registered PF_VSOCK protocol family
systemd[1]: Applying preset policy.
systemd[1]: Unit /etc/systemd/system/dnsmasq.service is masked, ignoring.
systemd[1]: Unit /etc/systemd/system/systemd-repart.service is masked, ignoring.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket'.
systemd[1]: Removed '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir.mount' → '/etc/systemd/system/var-mnt-workdir.mount'.
systemd[1]: Created symlink '/etc/systemd/system/multi-user.target.wants/var-mnt-workdir\x2dtmp.mount' → '/etc/systemd/system/var-mnt-workdir\x2dtmp.mount'.
systemd[1]: Created symlink '/etc/systemd/system/afterburn-sshkeys.target.requires/afterburn-sshkeys@core.service' → '/usr/lib/systemd/system/afterburn-sshkeys@.service'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-varlink.socket' → '/usr/lib/systemd/system/systemd-resolved-varlink.socket'.
systemd[1]: Created symlink '/etc/systemd/system/sockets.target.wants/systemd-resolved-monitor.socket' → '/usr/lib/systemd/system/systemd-resolved-monitor.socket'.
systemd[1]: Populated /etc with preset unit settings.
```

Considering it only happens on first boot and not on every boot I think
the extra information is worth the extra verbosity in the logs just for
that boot.

5 of 6 new or added lines in 1 file covered. (83.33%)

5463 existing lines in 165 files now uncovered.

299151 of 415222 relevant lines covered (72.05%)

702386.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.25
/src/basic/recurse-dir.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/stat.h>
4

5
#include "alloc-util.h"
6
#include "dirent-util.h"
7
#include "fd-util.h"
8
#include "fs-util.h"
9
#include "log.h"
10
#include "mountpoint-util.h"
11
#include "path-util.h"
12
#include "recurse-dir.h"
13
#include "sort-util.h"
14

15
#define DEFAULT_RECURSION_MAX 100
16

17
static int sort_func(struct dirent * const *a, struct dirent * const *b) {
339,745✔
18
        return strcmp((*a)->d_name, (*b)->d_name);
339,745✔
19
}
20

21
static bool ignore_dirent(const struct dirent *de, RecurseDirFlags flags) {
692,742✔
22
        assert(de);
692,742✔
23

24
        /* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */
25

26
        return FLAGS_SET(flags, RECURSE_DIR_IGNORE_DOT) ?
692,742✔
27
                de->d_name[0] == '.' :
234,326✔
28
                dot_or_dot_dot(de->d_name);
692,742✔
29
}
30

31
int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) {
16,045✔
32
        _cleanup_free_ DirectoryEntries *de = NULL;
16,045✔
33
        DirectoryEntries *nde;
16,045✔
34
        int r;
16,045✔
35

36
        assert(dir_fd >= 0);
16,045✔
37

38
        /* Returns an array with pointers to "struct dirent" directory entries, optionally sorted. Free the
39
         * array with readdir_all_freep().
40
         *
41
         * Start with space for up to 8 directory entries. We expect at least 2 ("." + ".."), hence hopefully
42
         * 8 will cover most cases comprehensively. (Note that most likely a lot more entries will actually
43
         * fit in the buffer, given we calculate maximum file name length here.) */
44
        de = malloc(offsetof(DirectoryEntries, buffer) + DIRENT_SIZE_MAX * 8);
16,045✔
45
        if (!de)
16,045✔
46
                return -ENOMEM;
47

48
        de->buffer_size = 0;
16,045✔
49
        for (;;) {
33,124✔
50
                size_t bs;
33,124✔
51
                ssize_t n;
33,124✔
52

53
                bs = MIN(MALLOC_SIZEOF_SAFE(de) - offsetof(DirectoryEntries, buffer), (size_t) SSIZE_MAX);
33,124✔
54
                assert(bs > de->buffer_size);
33,124✔
55

56
                n = posix_getdents(dir_fd, (uint8_t*) de->buffer + de->buffer_size, bs - de->buffer_size, /* flags = */ 0);
33,124✔
57
                if (n < 0)
33,124✔
UNCOV
58
                        return -errno;
×
59
                if (n == 0)
33,124✔
60
                        break;
61

62
                msan_unpoison((uint8_t*) de->buffer + de->buffer_size, n);
17,079✔
63

64
                de->buffer_size += n;
17,079✔
65

66
                if (de->buffer_size < bs - DIRENT_SIZE_MAX) /* Still room for one more entry, then try to
17,079✔
67
                                                             * fill it up without growing the structure. */
68
                        continue;
15,991✔
69

70
                if (bs >= SSIZE_MAX - offsetof(DirectoryEntries, buffer))
1,088✔
71
                        return -EFBIG;
72
                bs = bs >= (SSIZE_MAX - offsetof(DirectoryEntries, buffer))/2 ? SSIZE_MAX - offsetof(DirectoryEntries, buffer) : bs * 2;
1,088✔
73

74
                nde = realloc(de, bs);
1,088✔
75
                if (!nde)
1,088✔
76
                        return -ENOMEM;
77
                de = nde;
1,088✔
78
        }
79

80
        de->n_entries = 0;
16,045✔
81
        struct dirent *entry;
16,045✔
82
        FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) {
362,416✔
83
                if (ignore_dirent(entry, flags))
346,371✔
84
                        continue;
32,090✔
85

86
                if (FLAGS_SET(flags, RECURSE_DIR_ENSURE_TYPE)) {
314,281✔
87
                        r = dirent_ensure_type(dir_fd, entry);
238✔
88
                        if (r == -ENOENT)
238✔
89
                                /* dentry gone by now? no problem, let's just suppress it */
UNCOV
90
                                continue;
×
91
                        if (r < 0)
238✔
92
                                return r;
93
                }
94

95
                de->n_entries++;
314,281✔
96
        }
97

98
        size_t sz, j;
16,045✔
99

100
        sz = ALIGN(offsetof(DirectoryEntries, buffer) + de->buffer_size);
16,045✔
101
        if (!INC_SAFE(&sz, sizeof(struct dirent*) * de->n_entries))
16,045✔
102
                return -ENOMEM;
103

104
        nde = realloc(de, sz);
16,045✔
105
        if (!nde)
16,045✔
106
                return -ENOMEM;
107
        de = nde;
16,045✔
108

109
        de->entries = (struct dirent**) ((uint8_t*) de + ALIGN(offsetof(DirectoryEntries, buffer) + de->buffer_size));
16,045✔
110

111
        j = 0;
16,045✔
112
        FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) {
362,416✔
113
                if (ignore_dirent(entry, flags))
346,371✔
114
                        continue;
32,090✔
115

116
                /* If d_type == DT_UNKNOWN that means we failed to ensure the type in the earlier loop and
117
                 * didn't include the dentry in de->n_entries and as such should skip it here as well. */
118
                if (FLAGS_SET(flags, RECURSE_DIR_ENSURE_TYPE) && entry->d_type == DT_UNKNOWN)
314,281✔
UNCOV
119
                        continue;
×
120

121
                de->entries[j++] = entry;
314,281✔
122
        }
123
        assert(j == de->n_entries);
16,045✔
124

125
        if (FLAGS_SET(flags, RECURSE_DIR_SORT))
16,045✔
126
                typesafe_qsort(de->entries, de->n_entries, sort_func);
3,326✔
127

128
        if (ret)
16,045✔
129
                *ret = TAKE_PTR(de);
16,045✔
130

131
        return 0;
132
}
133

134
int readdir_all_at(int fd, const char *path, RecurseDirFlags flags, DirectoryEntries **ret) {
40✔
135
        _cleanup_close_ int dir_fd = -EBADF;
40✔
136

137
        assert(fd >= 0 || fd == AT_FDCWD);
40✔
138

139
        dir_fd = xopenat(fd, path, O_DIRECTORY|O_CLOEXEC);
40✔
140
        if (dir_fd < 0)
40✔
141
                return dir_fd;
142

143
        return readdir_all(dir_fd, flags, ret);
35✔
144
}
145

146
int recurse_dir(
15,281✔
147
                int dir_fd,
148
                const char *path,
149
                unsigned statx_mask,
150
                unsigned n_depth_max,
151
                RecurseDirFlags flags,
152
                recurse_dir_func_t func,
153
                void *userdata) {
154

155
        _cleanup_free_ DirectoryEntries *de = NULL;
15,281✔
156
        struct statx root_sx;
15,281✔
157
        int r;
15,281✔
158

159
        assert(dir_fd >= 0);
15,281✔
160
        assert(func);
15,281✔
161

162
        /* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx()/O_PATH,
163
         * and under the assumption that fds are not as 'expensive' as they used to be. */
164

165
        if (n_depth_max == 0)
15,281✔
166
                return -EOVERFLOW;
167
        if (n_depth_max == UINT_MAX) /* special marker for "default" */
15,281✔
168
                n_depth_max = DEFAULT_RECURSION_MAX;
3,096✔
169

170
        if (FLAGS_SET(flags, RECURSE_DIR_TOPLEVEL)) {
15,281✔
171
                if (statx_mask != 0) {
186✔
172
                        if (statx(dir_fd, "", AT_EMPTY_PATH, statx_mask, &root_sx) < 0)
10✔
UNCOV
173
                                return -errno;
×
174
                }
175

176
                r = func(RECURSE_DIR_ENTER,
186✔
177
                         path,
178
                         -1, /* we have no parent fd */
179
                         dir_fd,
180
                         NULL, /* we have no dirent */
181
                         statx_mask != 0 ? &root_sx : NULL,
182
                         userdata);
183
                if (IN_SET(r, RECURSE_DIR_LEAVE_DIRECTORY, RECURSE_DIR_SKIP_ENTRY))
186✔
184
                        return 0;
185
                if (r != RECURSE_DIR_CONTINUE)
186✔
186
                        return r;
187
        }
188

189
        /* Mask out RECURSE_DIR_ENSURE_TYPE so we can do it ourselves and avoid an extra statx() call. */
190
        r = readdir_all(dir_fd, flags & ~RECURSE_DIR_ENSURE_TYPE, &de);
15,281✔
191
        if (r < 0)
15,281✔
192
                return r;
193

194
        for (size_t i = 0; i < de->n_entries; i++) {
307,060✔
195
                _cleanup_close_ int inode_fd = -EBADF, subdir_fd = -EBADF;
587,698✔
196
                _cleanup_free_ char *joined = NULL;
293,849✔
197
                struct statx sx;
293,849✔
198
                bool sx_valid = false;
293,849✔
199
                const char *p;
293,849✔
200

201
                /* For each directory entry we'll do one of the following:
202
                 *
203
                 * 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)
204
                 * 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)
205
                 * 3) Otherwise, we'll statx() the directory entry via the directory fd we are currently looking at (if requested)
206
                 */
207

208
                if (path) {
293,849✔
209
                        joined = path_join(path, de->entries[i]->d_name);
293,811✔
210
                        if (!joined)
293,811✔
211
                                return -ENOMEM;
212

213
                        p = joined;
214
                } else
215
                        p = de->entries[i]->d_name;
38✔
216

217
                if (IN_SET(de->entries[i]->d_type, DT_UNKNOWN, DT_DIR)) {
293,849✔
218
                        subdir_fd = openat(dir_fd, de->entries[i]->d_name, O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
13,918✔
219
                        if (subdir_fd < 0) {
13,918✔
UNCOV
220
                                if (errno == ENOENT) /* Vanished by now, go for next file immediately */
×
UNCOV
221
                                        continue;
×
222

223
                                /* If it is a subdir but we failed to open it, then fail */
UNCOV
224
                                if (!IN_SET(errno, ENOTDIR, ELOOP)) {
×
225
                                        log_debug_errno(errno, "Failed to open directory '%s': %m", p);
×
226

227
                                        assert(errno <= RECURSE_DIR_SKIP_OPEN_DIR_ERROR_MAX - RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE);
×
228

UNCOV
229
                                        r = func(RECURSE_DIR_SKIP_OPEN_DIR_ERROR_BASE + errno,
×
230
                                                 p,
231
                                                 dir_fd,
232
                                                 -1,
UNCOV
233
                                                 de->entries[i],
×
234
                                                 NULL,
235
                                                 userdata);
236
                                        if (r == RECURSE_DIR_LEAVE_DIRECTORY)
×
237
                                                break;
UNCOV
238
                                        if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
×
239
                                                return r;
240

UNCOV
241
                                        continue;
×
242
                                }
243

244
                                /* If it's not a subdir, then let's handle it like a regular inode below */
245

246
                        } else {
247
                                /* If we managed to get a DIR* off the inode, it's definitely a directory. */
248
                                de->entries[i]->d_type = DT_DIR;
13,918✔
249

250
                                if (statx_mask != 0 || (flags & RECURSE_DIR_SAME_MOUNT)) {
13,918✔
251
                                        if (statx(subdir_fd, "", AT_EMPTY_PATH, statx_mask, &sx) < 0)
2,340✔
UNCOV
252
                                                return -errno;
×
253

254
                                        sx_valid = true;
255
                                }
256
                        }
257
                }
258

259
                if (subdir_fd < 0) {
13,918✔
260
                        /* It's not a subdirectory. */
261

262
                        if (flags & RECURSE_DIR_INODE_FD) {
279,931✔
263

264
                                inode_fd = openat(dir_fd, de->entries[i]->d_name, O_PATH|O_NOFOLLOW|O_CLOEXEC);
39,613✔
265
                                if (inode_fd < 0) {
39,613✔
UNCOV
266
                                        if (errno == ENOENT) /* Vanished by now, go for next file immediately */
×
267
                                                continue;
×
268

269
                                        log_debug_errno(errno, "Failed to open directory entry '%s': %m", p);
×
270

271
                                        assert(errno <= RECURSE_DIR_SKIP_OPEN_INODE_ERROR_MAX - RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE);
×
272

UNCOV
273
                                        r = func(RECURSE_DIR_SKIP_OPEN_INODE_ERROR_BASE + errno,
×
274
                                                 p,
275
                                                 dir_fd,
276
                                                 -1,
UNCOV
277
                                                 de->entries[i],
×
278
                                                 NULL,
279
                                                 userdata);
280
                                        if (r == RECURSE_DIR_LEAVE_DIRECTORY)
×
281
                                                break;
UNCOV
282
                                        if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
×
283
                                                return r;
284

UNCOV
285
                                        continue;
×
286
                                }
287

288
                                /* If we open the inode, then verify it's actually a non-directory, like we
289
                                 * assume. Let's guarantee that we never pass statx data of a directory where
290
                                 * caller expects a non-directory */
291

292
                                if (statx(inode_fd, "", AT_EMPTY_PATH, statx_mask | STATX_TYPE, &sx) < 0)
39,613✔
UNCOV
293
                                        return -errno;
×
294

295
                                assert(sx.stx_mask & STATX_TYPE);
39,613✔
296
                                sx_valid = true;
39,613✔
297

298
                                if (S_ISDIR(sx.stx_mode)) {
39,613✔
299
                                        /* What? It's a directory now? Then someone must have quickly
300
                                         * replaced it. Let's handle that gracefully: convert it to a
301
                                         * directory fd — which should be riskless now that we pinned the
302
                                         * inode. */
303

UNCOV
304
                                        subdir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_CLOEXEC);
×
UNCOV
305
                                        if (subdir_fd < 0)
×
306
                                                return subdir_fd;
307

UNCOV
308
                                        inode_fd = safe_close(inode_fd);
×
309
                                }
310

311
                        } else if (statx_mask != 0 || (de->entries[i]->d_type == DT_UNKNOWN && (flags & RECURSE_DIR_ENSURE_TYPE))) {
240,318✔
312

313
                                if (statx(dir_fd, de->entries[i]->d_name, AT_SYMLINK_NOFOLLOW, statx_mask | STATX_TYPE, &sx) < 0) {
×
UNCOV
314
                                        if (errno == ENOENT) /* Vanished by now? Go for next file immediately */
×
315
                                                continue;
×
316

317
                                        log_debug_errno(errno, "Failed to stat directory entry '%s': %m", p);
×
318

319
                                        assert(errno <= RECURSE_DIR_SKIP_STAT_INODE_ERROR_MAX - RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE);
×
320

UNCOV
321
                                        r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + errno,
×
322
                                                 p,
323
                                                 dir_fd,
324
                                                 -1,
UNCOV
325
                                                 de->entries[i],
×
326
                                                 NULL,
327
                                                 userdata);
328
                                        if (r == RECURSE_DIR_LEAVE_DIRECTORY)
×
329
                                                break;
UNCOV
330
                                        if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
×
331
                                                return r;
332

UNCOV
333
                                        continue;
×
334
                                }
335

UNCOV
336
                                assert(sx.stx_mask & STATX_TYPE);
×
337
                                sx_valid = true;
×
338

UNCOV
339
                                if (S_ISDIR(sx.stx_mode)) {
×
340
                                        /* So it suddenly is a directory, but we couldn't open it as such
341
                                         * earlier?  That is weird, and probably means somebody is racing
342
                                         * against us. We could of course retry and open it as a directory
343
                                         * again, but the chance to win here is limited. Hence, let's
344
                                         * propagate this as EISDIR error instead. That way we make this
345
                                         * something that can be reasonably handled, even though we give the
346
                                         * guarantee that RECURSE_DIR_ENTRY is strictly issued for
347
                                         * non-directory dirents. */
348

349
                                        log_debug("Non-directory entry '%s' suddenly became a directory.", p);
×
350

UNCOV
351
                                        r = func(RECURSE_DIR_SKIP_STAT_INODE_ERROR_BASE + EISDIR,
×
352
                                                 p,
353
                                                 dir_fd,
354
                                                 -1,
UNCOV
355
                                                 de->entries[i],
×
356
                                                 NULL,
357
                                                 userdata);
358
                                        if (r == RECURSE_DIR_LEAVE_DIRECTORY)
×
359
                                                break;
UNCOV
360
                                        if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
×
361
                                                return r;
362

UNCOV
363
                                        continue;
×
364
                                }
365
                        }
366
                }
367

368
                if (sx_valid) {
254,236✔
369
                        /* Copy over the data we acquired through statx() if we acquired any */
370
                        if (sx.stx_mask & STATX_TYPE) {
41,953✔
371
                                assert((subdir_fd < 0) == !S_ISDIR(sx.stx_mode));
41,953✔
372
                                de->entries[i]->d_type = IFTODT(sx.stx_mode);
41,953✔
373
                        }
374

375
                        if (sx.stx_mask & STATX_INO)
41,953✔
376
                                de->entries[i]->d_ino = sx.stx_ino;
41,953✔
377
                }
378

379
                if (subdir_fd >= 0) {
293,849✔
380
                        if (FLAGS_SET(flags, RECURSE_DIR_SAME_MOUNT)) {
13,918✔
381
                                bool is_mount;
143✔
382

383
                                if (sx_valid && FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT))
143✔
384
                                        is_mount = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT);
143✔
385
                                else {
386
                                        r = is_mount_point_at(dir_fd, de->entries[i]->d_name, 0);
×
UNCOV
387
                                        if (r < 0)
×
388
                                                log_debug_errno(r, "Failed to determine whether %s is a submount, assuming not: %m", p);
×
389

UNCOV
390
                                        is_mount = r > 0;
×
391
                                }
392

393
                                if (is_mount) {
143✔
UNCOV
394
                                        r = func(RECURSE_DIR_SKIP_MOUNT,
×
395
                                                 p,
396
                                                 dir_fd,
397
                                                 subdir_fd,
UNCOV
398
                                                 de->entries[i],
×
399
                                                 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
400
                                                 userdata);
401
                                        if (r == RECURSE_DIR_LEAVE_DIRECTORY)
×
402
                                                break;
UNCOV
403
                                        if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
×
404
                                                return r;
405

UNCOV
406
                                        continue;
×
407
                                }
408
                        }
409

410
                        if (n_depth_max <= 1) {
13,918✔
411
                                /* When we reached max depth, generate a special event */
412

413
                                r = func(RECURSE_DIR_SKIP_DEPTH,
1,793✔
414
                                         p,
415
                                         dir_fd,
416
                                         subdir_fd,
417
                                         de->entries[i],
1,793✔
418
                                         statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
419
                                         userdata);
420
                                if (r == RECURSE_DIR_LEAVE_DIRECTORY)
1,793✔
421
                                        break;
422
                                if (!IN_SET(r, RECURSE_DIR_CONTINUE, RECURSE_DIR_SKIP_ENTRY))
1,793✔
423
                                        return r;
424

425
                                continue;
1,793✔
426
                        }
427

428
                        r = func(RECURSE_DIR_ENTER,
12,125✔
429
                                 p,
430
                                 dir_fd,
431
                                 subdir_fd,
432
                                 de->entries[i],
12,125✔
433
                                 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
434
                                 userdata);
435
                        if (r == RECURSE_DIR_LEAVE_DIRECTORY)
12,125✔
436
                                break;
437
                        if (r == RECURSE_DIR_SKIP_ENTRY)
12,125✔
UNCOV
438
                                continue;
×
439
                        if (r != RECURSE_DIR_CONTINUE)
12,125✔
440
                                return r;
441

442
                        r = recurse_dir(subdir_fd,
24,250✔
443
                                        p,
444
                                        statx_mask,
445
                                        n_depth_max - 1,
446
                                        flags &~ RECURSE_DIR_TOPLEVEL, /* we already called the callback for this entry */
12,125✔
447
                                        func,
448
                                        userdata);
449
                        if (r != 0)
12,125✔
450
                                return r;
451

452
                        r = func(RECURSE_DIR_LEAVE,
11,112✔
453
                                 p,
454
                                 dir_fd,
455
                                 subdir_fd,
456
                                 de->entries[i],
11,112✔
457
                                 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
458
                                 userdata);
459
                } else
460
                        /* Non-directory inode */
461
                        r = func(RECURSE_DIR_ENTRY,
279,931✔
462
                                 p,
463
                                 dir_fd,
464
                                 inode_fd,
465
                                 de->entries[i],
279,931✔
466
                                 statx_mask != 0 ? &sx : NULL, /* only pass sx if user asked for it */
467
                                 userdata);
468

469
                if (r == RECURSE_DIR_LEAVE_DIRECTORY)
291,043✔
470
                        break;
471
                if (!IN_SET(r, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE))
290,523✔
472
                        return r;
473
        }
474

475
        if (FLAGS_SET(flags, RECURSE_DIR_TOPLEVEL)) {
13,731✔
476

477
                r = func(RECURSE_DIR_LEAVE,
362✔
478
                         path,
479
                         -1,
480
                         dir_fd,
481
                         NULL,
482
                         statx_mask != 0 ? &root_sx : NULL,
483
                         userdata);
484
                if (!IN_SET(r, RECURSE_DIR_LEAVE_DIRECTORY, RECURSE_DIR_SKIP_ENTRY, RECURSE_DIR_CONTINUE))
186✔
UNCOV
485
                        return r;
×
486
        }
487

488
        return 0;
489
}
490

491
int recurse_dir_at(
7,414✔
492
                int atfd,
493
                const char *path,
494
                unsigned statx_mask,
495
                unsigned n_depth_max,
496
                RecurseDirFlags flags,
497
                recurse_dir_func_t func,
498
                void *userdata) {
499

500
        _cleanup_close_ int fd = -EBADF;
7,414✔
501

502
        assert(atfd >= 0 || atfd == AT_FDCWD);
7,414✔
503
        assert(func);
7,414✔
504

505
        fd = openat(atfd, path ?: ".", O_DIRECTORY|O_CLOEXEC);
7,414✔
506
        if (fd < 0)
7,414✔
507
                return -errno;
4,448✔
508

509
        return recurse_dir(fd, path, statx_mask, n_depth_max, flags, func, userdata);
2,966✔
510
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc