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

systemd / systemd / 13665439908

04 Mar 2025 09:54PM UTC coverage: 71.855% (+0.04%) from 71.819%
13665439908

push

github

poettering
dirent-util: add several assertions in posix_getdents()

Follow-up for e86a492ff.

4 of 4 new or added lines in 1 file covered. (100.0%)

8029 existing lines in 84 files now uncovered.

294783 of 410245 relevant lines covered (71.86%)

717882.44 hits per line

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

92.61
/src/basic/stat-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <errno.h>
4
#include <fcntl.h>
5
#include <sched.h>
6
#include <sys/statvfs.h>
7
#include <sys/types.h>
8
#include <unistd.h>
9

10
#include "alloc-util.h"
11
#include "chase.h"
12
#include "dirent-util.h"
13
#include "errno-util.h"
14
#include "fd-util.h"
15
#include "fileio.h"
16
#include "filesystems.h"
17
#include "fs-util.h"
18
#include "hash-funcs.h"
19
#include "macro.h"
20
#include "missing_fs.h"
21
#include "missing_magic.h"
22
#include "mountpoint-util.h"
23
#include "nulstr-util.h"
24
#include "parse-util.h"
25
#include "stat-util.h"
26
#include "string-util.h"
27

28
static int verify_stat_at(
2,179,461✔
29
                int fd,
30
                const char *path,
31
                bool follow,
32
                int (*verify_func)(const struct stat *st),
33
                bool verify) {
34

35
        struct stat st;
2,179,461✔
36
        int r;
2,179,461✔
37

38
        assert(fd >= 0 || fd == AT_FDCWD);
2,179,461✔
39
        assert(!isempty(path) || !follow);
2,179,461✔
40
        assert(verify_func);
2,179,461✔
41

42
        if (fstatat(fd, strempty(path), &st,
5,172,091✔
43
                    (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
2,179,461✔
44
                return -errno;
8,760✔
45

46
        r = verify_func(&st);
2,170,701✔
47
        return verify ? r : r >= 0;
2,170,701✔
48
}
49

50
int stat_verify_regular(const struct stat *st) {
2,650,880✔
51
        assert(st);
2,650,880✔
52

53
        /* Checks whether the specified stat() structure refers to a regular file. If not returns an
54
         * appropriate error code. */
55

56
        if (S_ISDIR(st->st_mode))
2,650,880✔
57
                return -EISDIR;
58

59
        if (S_ISLNK(st->st_mode))
2,648,692✔
60
                return -ELOOP;
61

62
        if (!S_ISREG(st->st_mode))
2,648,678✔
63
                return -EBADFD;
17✔
64

65
        return 0;
66
}
67

68
int verify_regular_at(int fd, const char *path, bool follow) {
813,420✔
69
        return verify_stat_at(fd, path, follow, stat_verify_regular, true);
813,420✔
70
}
71

72
int fd_verify_regular(int fd) {
813,153✔
73
        assert(fd >= 0);
813,153✔
74
        return verify_regular_at(fd, NULL, false);
813,153✔
75
}
76

77
int stat_verify_directory(const struct stat *st) {
1,397,417✔
78
        assert(st);
1,397,417✔
79

80
        if (S_ISLNK(st->st_mode))
1,397,417✔
81
                return -ELOOP;
82

83
        if (!S_ISDIR(st->st_mode))
1,397,408✔
84
                return -ENOTDIR;
8✔
85

86
        return 0;
87
}
88

89
int fd_verify_directory(int fd) {
13✔
90
        assert(fd >= 0);
13✔
91
        return verify_stat_at(fd, NULL, false, stat_verify_directory, true);
13✔
92
}
93

94
int is_dir_at(int fd, const char *path, bool follow) {
1,345,587✔
95
        return verify_stat_at(fd, path, follow, stat_verify_directory, false);
1,345,587✔
96
}
97

98
int is_dir(const char *path, bool follow) {
483,828✔
99
        assert(!isempty(path));
483,828✔
100
        return is_dir_at(AT_FDCWD, path, follow);
483,828✔
101
}
102

103
int stat_verify_symlink(const struct stat *st) {
20,297✔
104
        assert(st);
20,297✔
105

106
        if (S_ISDIR(st->st_mode))
20,297✔
107
                return -EISDIR;
108

109
        if (!S_ISLNK(st->st_mode))
20,230✔
110
                return -ENOLINK;
700✔
111

112
        return 0;
113
}
114

115
int is_symlink(const char *path) {
20,297✔
116
        assert(!isempty(path));
20,297✔
117
        return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false);
20,297✔
118
}
119

120
int stat_verify_linked(const struct stat *st) {
1,809,093✔
121
        assert(st);
1,809,093✔
122

123
        if (st->st_nlink <= 0)
1,809,093✔
124
                return -EIDRM; /* recognizable error. */
2✔
125

126
        return 0;
127
}
128

129
int fd_verify_linked(int fd) {
3✔
130
        assert(fd >= 0);
3✔
131
        return verify_stat_at(fd, NULL, false, stat_verify_linked, true);
3✔
132
}
133

134
int stat_verify_device_node(const struct stat *st) {
7,978✔
135
        assert(st);
7,978✔
136

137
        if (S_ISLNK(st->st_mode))
7,978✔
138
                return -ELOOP;
139

140
        if (S_ISDIR(st->st_mode))
7,978✔
141
                return -EISDIR;
142

143
        if (!S_ISBLK(st->st_mode) && !S_ISCHR(st->st_mode))
7,845✔
144
                return -ENOTTY;
5✔
145

146
        return 0;
147
}
148

149
int is_device_node(const char *path) {
141✔
150
        assert(!isempty(path));
141✔
151
        return verify_stat_at(AT_FDCWD, path, false, stat_verify_device_node, false);
141✔
152
}
153

154
int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) {
38,754✔
155
        _cleanup_close_ int fd = -EBADF;
38,754✔
156
        struct dirent *buf;
38,754✔
157
        size_t m;
38,754✔
158

159
        fd = xopenat(dir_fd, path, O_DIRECTORY|O_CLOEXEC);
38,754✔
160
        if (fd < 0)
38,754✔
161
                return fd;
162

163
        /* Allocate space for at least 3 full dirents, since every dir has at least two entries ("."  +
164
         * ".."), and only once we have seen if there's a third we know whether the dir is empty or not. If
165
         * 'ignore_hidden_or_backup' is true we'll allocate a bit more, since we might skip over a bunch of
166
         * entries that we end up ignoring. */
167
        m = (ignore_hidden_or_backup ? 16 : 3) * DIRENT_SIZE_MAX;
3,223✔
168
        buf = alloca(m);
3,223✔
169

170
        for (;;) {
5,418✔
171
                struct dirent *de;
5,418✔
172
                ssize_t n;
5,418✔
173

174
                n = posix_getdents(fd, buf, m, /* flags = */ 0);
5,418✔
175
                if (n < 0)
5,418✔
UNCOV
176
                        return -errno;
×
177
                if (n == 0)
5,418✔
178
                        break;
179

180
                assert((size_t) n <= m);
3,223✔
181
                msan_unpoison(buf, n);
3,223✔
182

183
                FOREACH_DIRENT_IN_BUFFER(de, buf, n)
8,819✔
184
                        if (!(ignore_hidden_or_backup ? hidden_or_backup_file(de->d_name) : dot_or_dot_dot(de->d_name)))
6,624✔
185
                                return 0;
186
        }
187

188
        return 1;
189
}
190

191
bool null_or_empty(struct stat *st) {
355,675✔
192
        assert(st);
355,675✔
193

194
        if (S_ISREG(st->st_mode) && st->st_size <= 0)
355,675✔
195
                return true;
196

197
        /* We don't want to hardcode the major/minor of /dev/null, hence we do a simpler "is this a character
198
         * device node?" check. */
199

200
        if (S_ISCHR(st->st_mode))
355,663✔
201
                return true;
849✔
202

203
        return false;
204
}
205

206
int null_or_empty_path_with_root(const char *fn, const char *root) {
253,628✔
207
        struct stat st;
253,628✔
208
        int r;
253,628✔
209

210
        assert(fn);
253,628✔
211

212
        /* A symlink to /dev/null or an empty file?
213
         * When looking under root_dir, we can't expect /dev/ to be mounted,
214
         * so let's see if the path is a (possibly dangling) symlink to /dev/null. */
215

216
        if (path_equal(path_startswith(fn, root ?: "/"), "dev/null"))
507,256✔
217
                return true;
253,628✔
218

219
        r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
253,466✔
220
        if (r < 0)
253,466✔
221
                return r;
222

223
        return null_or_empty(&st);
253,352✔
224
}
225

226
int fd_is_read_only_fs(int fd) {
2,031✔
227
        struct statvfs st;
2,031✔
228

229
        assert(fd >= 0);
2,031✔
230

231
        if (fstatvfs(fd, &st) < 0)
2,031✔
UNCOV
232
                return -errno;
×
233

234
        if (st.f_flag & ST_RDONLY)
2,031✔
235
                return true;
236

237
        /* On NFS, fstatvfs() might not reflect whether we can actually write to the remote share. Let's try
238
         * again with access(W_OK) which is more reliable, at least sometimes. */
239
        if (access_fd(fd, W_OK) == -EROFS)
1,671✔
UNCOV
240
                return true;
×
241

242
        return false;
243
}
244

245
int path_is_read_only_fs(const char *path) {
1,763✔
246
        _cleanup_close_ int fd = -EBADF;
1,763✔
247

248
        assert(path);
1,763✔
249

250
        fd = open(path, O_CLOEXEC | O_PATH);
1,763✔
251
        if (fd < 0)
1,763✔
252
                return -errno;
146✔
253

254
        return fd_is_read_only_fs(fd);
1,617✔
255
}
256

257
int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags) {
14,260✔
258
        struct stat sta, stb;
14,260✔
259
        int r;
14,260✔
260

261
        assert(fda >= 0 || fda == AT_FDCWD);
14,260✔
262
        assert(fdb >= 0 || fdb == AT_FDCWD);
14,260✔
263
        assert((flags & ~(AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT)) == 0);
14,260✔
264

265
        /* Refuse an unset filea or fileb early unless AT_EMPTY_PATH is set */
266
        if ((isempty(filea) || isempty(fileb)) && !FLAGS_SET(flags, AT_EMPTY_PATH))
28,333✔
267
                return -EINVAL;
14,260✔
268

269
        /* Shortcut: comparing the same fd with itself means we can return true */
270
        if (fda >= 0 && fda == fdb && isempty(filea) && isempty(fileb) && FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW))
14,262✔
271
                return true;
272

273
        _cleanup_close_ int pin_a = -EBADF, pin_b = -EBADF;
28,519✔
274
        if (!FLAGS_SET(flags, AT_NO_AUTOMOUNT)) {
14,259✔
275
                /* Let's try to use the name_to_handle_at() AT_HANDLE_FID API to identify identical
276
                 * inodes. We have to issue multiple calls on the same file for that (first, to acquire the
277
                 * FID, and then to check if .st_dev is actually the same). Hence let's pin the inode in
278
                 * between via O_PATH, unless we already have an fd for it. */
279

280
                if (!isempty(filea)) {
14,259✔
281
                        pin_a = openat(fda, filea, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0));
14,073✔
282
                        if (pin_a < 0)
14,073✔
283
                                return -errno;
14,108✔
284

285
                        fda = pin_a;
13,535✔
286
                        filea = NULL;
13,535✔
287
                        flags |= AT_EMPTY_PATH;
13,535✔
288
                }
289

290
                if (!isempty(fileb)) {
13,721✔
291
                        pin_b = openat(fdb, fileb, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0));
13,533✔
292
                        if (pin_b < 0)
13,533✔
293
                                return -errno;
4✔
294

295
                        fdb = pin_b;
13,529✔
296
                        fileb = NULL;
13,529✔
297
                        flags |= AT_EMPTY_PATH;
13,529✔
298
                }
299

300
                int ntha_flags = at_flags_normalize_follow(flags) & (AT_EMPTY_PATH|AT_SYMLINK_FOLLOW);
13,717✔
301
                _cleanup_free_ struct file_handle *ha = NULL, *hb = NULL;
13,717✔
302
                int mntida = -1, mntidb = -1;
13,717✔
303

304
                r = name_to_handle_at_try_fid(
13,717✔
305
                                fda,
306
                                filea,
307
                                &ha,
308
                                &mntida,
309
                                ntha_flags);
310
                if (r < 0) {
13,717✔
UNCOV
311
                        if (is_name_to_handle_at_fatal_error(r))
×
312
                                return r;
313

UNCOV
314
                        goto fallback;
×
315
                }
316

317
                r = name_to_handle_at_try_fid(
13,717✔
318
                                fdb,
319
                                fileb,
320
                                &hb,
321
                                &mntidb,
322
                                ntha_flags);
323
                if (r < 0) {
13,717✔
UNCOV
324
                        if (is_name_to_handle_at_fatal_error(r))
×
325
                                return r;
326

UNCOV
327
                        goto fallback;
×
328
                }
329

330
                /* Now compare the two file handles */
331
                if (!file_handle_equal(ha, hb))
13,717✔
332
                        return false;
333

334
                /* If the file handles are the same and they come from the same mount ID? Great, then we are
335
                 * good, they are definitely the same */
336
                if (mntida == mntidb)
13,452✔
337
                        return true;
338

339
                /* File handles are the same, they are not on the same mount id. This might either be because
340
                 * they are on two entirely different file systems, that just happen to have the same FIDs
341
                 * (because they originally where created off the same disk images), or it could be because
342
                 * they are located on two distinct bind mounts of the same fs. To check that, let's look at
343
                 * .st_rdev of the inode. We simply reuse the fallback codepath for that, since it checks
344
                 * exactly that (it checks slightly more, but we don't care.) */
345
        }
346

UNCOV
347
fallback:
×
348
        if (fstatat(fda, strempty(filea), &sta, flags) < 0)
302✔
UNCOV
349
                return log_debug_errno(errno, "Cannot stat %s: %m", strna(filea));
×
350

351
        if (fstatat(fdb, strempty(fileb), &stb, flags) < 0)
302✔
UNCOV
352
                return log_debug_errno(errno, "Cannot stat %s: %m", strna(fileb));
×
353

354
        return stat_inode_same(&sta, &stb);
151✔
355
}
356

357
bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) {
9,450,737✔
358
        assert(s);
9,450,737✔
359
        assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type));
9,450,737✔
360

361
        return F_TYPE_EQUAL(s->f_type, magic_value);
9,450,737✔
362
}
363

364
int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value) {
9,143,776✔
365
        struct statfs s;
9,143,776✔
366
        int r;
9,143,776✔
367

368
        r = xstatfsat(dir_fd, path, &s);
9,143,776✔
369
        if (r < 0)
9,143,776✔
370
                return r;
9,143,776✔
371

372
        return is_fs_type(&s, magic_value);
9,143,775✔
373
}
374

375
bool is_temporary_fs(const struct statfs *s) {
43,541✔
376
        return fs_in_group(s, FILESYSTEM_SET_TEMPORARY);
43,541✔
377
}
378

379
bool is_network_fs(const struct statfs *s) {
15,667✔
380
        return fs_in_group(s, FILESYSTEM_SET_NETWORK);
15,667✔
381
}
382

383
int fd_is_temporary_fs(int fd) {
199✔
384
        struct statfs s;
199✔
385

386
        if (fstatfs(fd, &s) < 0)
199✔
UNCOV
387
                return -errno;
×
388

389
        return is_temporary_fs(&s);
199✔
390
}
391

392
int fd_is_network_fs(int fd) {
15,564✔
393
        struct statfs s;
15,564✔
394

395
        if (fstatfs(fd, &s) < 0)
15,564✔
UNCOV
396
                return -errno;
×
397

398
        return is_network_fs(&s);
15,564✔
399
}
400

401
int path_is_temporary_fs(const char *path) {
11✔
402
        struct statfs s;
11✔
403

404
        if (statfs(path, &s) < 0)
11✔
405
                return -errno;
2✔
406

407
        return is_temporary_fs(&s);
9✔
408
}
409

UNCOV
410
int path_is_network_fs(const char *path) {
×
411
        struct statfs s;
×
412

UNCOV
413
        if (statfs(path, &s) < 0)
×
414
                return -errno;
×
415

UNCOV
416
        return is_network_fs(&s);
×
417
}
418

419
int proc_mounted(void) {
17,958✔
420
        int r;
17,958✔
421

422
        /* A quick check of procfs is properly mounted */
423

424
        r = path_is_fs_type("/proc/", PROC_SUPER_MAGIC);
17,958✔
425
        if (r == -ENOENT) /* not mounted at all */
17,958✔
UNCOV
426
                return false;
×
427

428
        return r;
429
}
430

431
bool stat_inode_same(const struct stat *a, const struct stat *b) {
1,301,889✔
432

433
        /* Returns if the specified stat structure references the same (though possibly modified) inode. Does
434
         * a thorough check, comparing inode nr, backing device and if the inode is still of the same type. */
435

436
        return stat_is_set(a) && stat_is_set(b) &&
2,601,767✔
437
                ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 &&  /* same inode type */
1,258,319✔
438
                a->st_dev == b->st_dev &&
2,514,593✔
439
                a->st_ino == b->st_ino;
1,212,704✔
440
}
441

442
bool stat_inode_unmodified(const struct stat *a, const struct stat *b) {
120,941✔
443

444
        /* Returns if the specified stat structures reference the same, unmodified inode. This check tries to
445
         * be reasonably careful when detecting changes: we check both inode and mtime, to cater for file
446
         * systems where mtimes are fixed to 0 (think: ostree/nixos type installations). We also check file
447
         * size, backing device, inode type and if this refers to a device not the major/minor.
448
         *
449
         * Note that we don't care if file attributes such as ownership or access mode change, this here is
450
         * about contents of the file. The purpose here is to detect file contents changes, and nothing
451
         * else. */
452

453
        return stat_inode_same(a, b) &&
120,941✔
454
                a->st_mtim.tv_sec == b->st_mtim.tv_sec &&
70,824✔
455
                a->st_mtim.tv_nsec == b->st_mtim.tv_nsec &&
70,689✔
456
                (!S_ISREG(a->st_mode) || a->st_size == b->st_size) && /* if regular file, compare file size */
191,615✔
457
                (!(S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) || a->st_rdev == b->st_rdev); /* if device node, also compare major/minor, because we can */
70,674✔
458
}
459

460
bool statx_inode_same(const struct statx *a, const struct statx *b) {
3,617,757✔
461

462
        /* Same as stat_inode_same() but for struct statx */
463

464
        return statx_is_set(a) && statx_is_set(b) &&
7,235,514✔
465
                FLAGS_SET(a->stx_mask, STATX_TYPE|STATX_INO) && FLAGS_SET(b->stx_mask, STATX_TYPE|STATX_INO) &&
3,617,757✔
466
                ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 &&
3,617,757✔
467
                a->stx_dev_major == b->stx_dev_major &&
3,617,757✔
468
                a->stx_dev_minor == b->stx_dev_minor &&
6,937,995✔
469
                a->stx_ino == b->stx_ino;
3,320,238✔
470
}
471

472
bool statx_mount_same(const struct statx *a, const struct statx *b) {
3,290,901✔
473
        if (!statx_is_set(a) || !statx_is_set(b))
6,581,802✔
474
                return false;
475

476
        /* if we have the mount ID, that's all we need */
477
        if (FLAGS_SET(a->stx_mask, STATX_MNT_ID) && FLAGS_SET(b->stx_mask, STATX_MNT_ID))
3,290,901✔
478
                return a->stx_mnt_id == b->stx_mnt_id;
3,290,901✔
479

480
        /* Otherwise, major/minor of backing device must match */
UNCOV
481
        return a->stx_dev_major == b->stx_dev_major &&
×
482
                a->stx_dev_minor == b->stx_dev_minor;
483
}
484

485
int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
9,144,160✔
486
        _cleanup_close_ int fd = -EBADF;
9,144,160✔
487

488
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
9,144,160✔
489
        assert(ret);
9,144,160✔
490

491
        fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY);
9,144,160✔
492
        if (fd < 0)
9,144,160✔
493
                return fd;
494

495
        return RET_NERRNO(fstatfs(fd, ret));
9,144,160✔
496
}
497

498
void inode_hash_func(const struct stat *q, struct siphash *state) {
37,326✔
499
        siphash24_compress_typesafe(q->st_dev, state);
37,326✔
500
        siphash24_compress_typesafe(q->st_ino, state);
37,326✔
501
}
37,326✔
502

503
int inode_compare_func(const struct stat *a, const struct stat *b) {
31,157✔
504
        int r;
31,157✔
505

506
        r = CMP(a->st_dev, b->st_dev);
31,157✔
507
        if (r != 0)
26,304✔
508
                return r;
4,928✔
509

510
        return CMP(a->st_ino, b->st_ino);
26,229✔
511
}
512

513
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
761✔
514

515
const char* inode_type_to_string(mode_t m) {
10,016✔
516

517
        /* Returns a short string for the inode type. We use the same name as the underlying macros for each
518
         * inode type. */
519

520
        switch (m & S_IFMT) {
10,016✔
521
        case S_IFREG:
522
                return "reg";
523
        case S_IFDIR:
3,955✔
524
                return "dir";
3,955✔
525
        case S_IFLNK:
1✔
526
                return "lnk";
1✔
527
        case S_IFCHR:
932✔
528
                return "chr";
932✔
529
        case S_IFBLK:
487✔
530
                return "blk";
487✔
531
        case S_IFIFO:
487✔
532
                return "fifo";
487✔
533
        case S_IFSOCK:
639✔
534
                return "sock";
639✔
535
        }
536

537
        /* Note anonymous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will
538
         * return an .st_mode where we'll return NULL here! */
539
        return NULL;
3✔
540
}
541

542
mode_t inode_type_from_string(const char *s) {
13✔
543
        if (!s)
13✔
544
                return MODE_INVALID;
545

546
        if (streq(s, "reg"))
13✔
547
                return S_IFREG;
548
        if (streq(s, "dir"))
10✔
549
                return S_IFDIR;
550
        if (streq(s, "lnk"))
7✔
551
                return S_IFLNK;
552
        if (streq(s, "chr"))
6✔
553
                return S_IFCHR;
554
        if (streq(s, "blk"))
5✔
555
                return S_IFBLK;
556
        if (streq(s, "fifo"))
4✔
557
                return S_IFIFO;
558
        if (streq(s, "sock"))
2✔
559
                return S_IFSOCK;
2✔
560

561
        return MODE_INVALID;
562
}
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