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

systemd / systemd / 23722534442

29 Mar 2026 09:58PM UTC coverage: 72.208% (-0.2%) from 72.41%
23722534442

push

github

daandemeyer
repart: allow --el-torito= with any --empty= value

The restriction requiring --empty= to be require, force, or create
when using --el-torito= is unnecessary.
context_verify_eltorito_overlap() already validates that the ISO 9660
blocks don't collide with GPT partition entries or the first usable
LBA, which is sufficient to guarantee safety regardless of the empty
mode.

This is needed for two-stage image builds where the first stage creates
the usr and verity partitions, and the second stage adds --el-torito=
to produce a bootable ISO with a UKI containing usrhash= derived from
the verity hash of the first stage. In the second stage, repart runs
with --empty=allow since the image already exists.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>

317578 of 439810 relevant lines covered (72.21%)

1212802.04 hits per line

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

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

3
#include <fcntl.h>
4
#include <linux/magic.h>
5
#include <sys/statvfs.h>
6
#include <unistd.h>
7

8
#include "alloc-util.h"
9
#include "bitfield.h"
10
#include "chase.h"
11
#include "dirent-util.h"
12
#include "errno-util.h"
13
#include "fd-util.h"
14
#include "filesystems.h"
15
#include "fs-util.h"
16
#include "hash-funcs.h"
17
#include "log.h"
18
#include "mountpoint-util.h"
19
#include "path-util.h"
20
#include "siphash24.h"
21
#include "stat-util.h"
22
#include "string-util.h"
23
#include "time-util.h"
24

25
static int verify_stat_at(
2,167,715✔
26
                int fd,
27
                const char *path,
28
                bool follow,
29
                int (*verify_func)(const struct stat *st),
30
                bool verify) {
31

32
        struct stat st;
2,167,715✔
33
        int r;
2,167,715✔
34

35
        assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT));
2,167,715✔
36
        assert(!isempty(path) || !follow);
2,167,715✔
37
        assert(verify_func);
2,167,715✔
38

39
        _cleanup_free_ char *p = NULL;
2,167,715✔
40
        r = resolve_xat_fdroot(&fd, &path, &p);
2,167,715✔
41
        if (r < 0)
2,167,715✔
42
                return r;
43

44
        if (fstatat(fd, strempty(path), &st,
3,013,583✔
45
                    (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
3,489,562✔
46
                return -errno;
8,445✔
47

48
        r = verify_func(&st);
2,159,270✔
49
        return verify ? r : r >= 0;
2,159,270✔
50
}
51

52
static int mode_verify_regular(mode_t mode) {
2,847,889✔
53
        if (S_ISDIR(mode))
2,847,889✔
54
                return -EISDIR;
55

56
        if (S_ISLNK(mode))
2,847,812✔
57
                return -ELOOP;
58

59
        if (!S_ISREG(mode))
2,847,776✔
60
                return -EBADFD;
20✔
61

62
        return 0;
63
}
64

65
int stat_verify_regular(const struct stat *st) {
2,847,833✔
66
        assert(st);
2,847,833✔
67

68
        /* Checks whether the specified stat() structure refers to a regular file. If not returns an
69
         * appropriate error code. */
70

71
        return mode_verify_regular(st->st_mode);
2,847,833✔
72
}
73

74
int statx_verify_regular(const struct statx *stx) {
56✔
75
        assert(stx);
56✔
76

77
        if (!FLAGS_SET(stx->stx_mask, STATX_TYPE))
56✔
78
                return -ENODATA;
79

80
        return mode_verify_regular(stx->stx_mode);
56✔
81
}
82

83
int verify_regular_at(int fd, const char *path, bool follow) {
840,109✔
84
        return verify_stat_at(fd, path, follow, stat_verify_regular, true);
840,109✔
85
}
86

87
int fd_verify_regular(int fd) {
839,828✔
88
        if (IN_SET(fd, AT_FDCWD, XAT_FDROOT))
839,828✔
89
                return -EISDIR;
90

91
        return verify_regular_at(fd, /* path= */ NULL, /* follow= */ false);
839,828✔
92
}
93

94
static int mode_verify_directory(mode_t mode) {
1,367,653✔
95
        if (S_ISLNK(mode))
1,367,653✔
96
                return -ELOOP;
97

98
        if (!S_ISDIR(mode))
1,367,643✔
99
                return -ENOTDIR;
7✔
100

101
        return 0;
102
}
103

104
int stat_verify_directory(const struct stat *st) {
1,300,559✔
105
        assert(st);
1,300,559✔
106

107
        return mode_verify_directory(st->st_mode);
1,300,559✔
108
}
109

110
int statx_verify_directory(const struct statx *stx) {
67,094✔
111
        assert(stx);
67,094✔
112

113
        if (!FLAGS_SET(stx->stx_mask, STATX_TYPE))
67,094✔
114
                return -ENODATA;
115

116
        return mode_verify_directory(stx->stx_mode);
67,094✔
117
}
118

119
int fd_verify_directory(int fd) {
168✔
120
        if (IN_SET(fd, AT_FDCWD, XAT_FDROOT))
168✔
121
                return 0;
122

123
        return verify_stat_at(fd, NULL, false, stat_verify_directory, true);
167✔
124
}
125

126
int is_dir_at(int fd, const char *path, bool follow) {
1,302,530✔
127
        return verify_stat_at(fd, path, follow, stat_verify_directory, false);
1,302,530✔
128
}
129

130
int is_dir(const char *path, bool follow) {
466,029✔
131
        assert(!isempty(path));
466,029✔
132
        return is_dir_at(AT_FDCWD, path, follow);
466,029✔
133
}
134

135
int stat_verify_symlink(const struct stat *st) {
24,730✔
136
        assert(st);
24,730✔
137

138
        if (S_ISDIR(st->st_mode))
24,730✔
139
                return -EISDIR;
140

141
        if (!S_ISLNK(st->st_mode))
24,712✔
142
                return -ENOLINK;
281✔
143

144
        return 0;
145
}
146

147
int fd_verify_symlink(int fd) {
5,826✔
148
        return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_symlink, /* verify= */ true);
5,826✔
149
}
150

151
int is_symlink(const char *path) {
18,904✔
152
        assert(!isempty(path));
18,904✔
153
        return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false);
18,904✔
154
}
155

156
static int mode_verify_socket(mode_t mode) {
74✔
157
        if (S_ISLNK(mode))
74✔
158
                return -ELOOP;
159

160
        if (S_ISDIR(mode))
74✔
161
                return -EISDIR;
162

163
        if (!S_ISSOCK(mode))
73✔
164
                return -ENOTSOCK;
×
165

166
        return 0;
167
}
168

169
int stat_verify_socket(const struct stat *st) {
2✔
170
        assert(st);
2✔
171

172
        return mode_verify_socket(st->st_mode);
2✔
173
}
174

175
int statx_verify_socket(const struct statx *stx) {
72✔
176
        assert(stx);
72✔
177

178
        return mode_verify_socket(stx->stx_mode);
72✔
179
}
180

181
int is_socket(const char *path) {
2✔
182
        assert(!isempty(path));
2✔
183
        return verify_stat_at(AT_FDCWD, path, /* follow= */ true, stat_verify_socket, /* verify= */ false);
2✔
184
}
185

186
int stat_verify_linked(const struct stat *st) {
1,936,024✔
187
        assert(st);
1,936,024✔
188

189
        if (st->st_nlink <= 0)
1,936,024✔
190
                return -EIDRM; /* recognizable error. */
2✔
191

192
        return 0;
193
}
194

195
int fd_verify_linked(int fd) {
48✔
196

197
        if (fd == XAT_FDROOT)
48✔
198
                return 0;
199

200
        return verify_stat_at(fd, NULL, false, stat_verify_linked, true);
47✔
201
}
202

203
int stat_verify_device_node(const struct stat *st) {
3,532✔
204
        assert(st);
3,532✔
205

206
        if (S_ISLNK(st->st_mode))
3,532✔
207
                return -ELOOP;
208

209
        if (S_ISDIR(st->st_mode))
3,532✔
210
                return -EISDIR;
211

212
        if (!S_ISBLK(st->st_mode) && !S_ISCHR(st->st_mode))
3,411✔
213
                return -ENOTTY;
5✔
214

215
        return 0;
216
}
217

218
int is_device_node(const char *path) {
130✔
219
        assert(!isempty(path));
130✔
220
        return verify_stat_at(AT_FDCWD, path, false, stat_verify_device_node, false);
130✔
221
}
222

223
int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) {
39,261✔
224
        _cleanup_close_ int fd = -EBADF;
39,261✔
225
        struct dirent *buf;
39,261✔
226
        size_t m;
39,261✔
227

228
        fd = xopenat(dir_fd, path, O_DIRECTORY|O_CLOEXEC);
39,261✔
229
        if (fd < 0)
39,261✔
230
                return fd;
231

232
        /* Allocate space for at least 3 full dirents, since every dir has at least two entries ("."  +
233
         * ".."), and only once we have seen if there's a third we know whether the dir is empty or not. If
234
         * 'ignore_hidden_or_backup' is true we'll allocate a bit more, since we might skip over a bunch of
235
         * entries that we end up ignoring. */
236
        m = (ignore_hidden_or_backup ? 16 : 3) * DIRENT_SIZE_MAX;
2,318✔
237
        buf = alloca(m);
2,318✔
238

239
        for (;;) {
3,613✔
240
                struct dirent *de;
3,613✔
241
                ssize_t n;
3,613✔
242

243
                n = getdents64(fd, buf, m);
3,613✔
244
                if (n < 0)
3,613✔
245
                        return -errno;
×
246
                if (n == 0)
3,613✔
247
                        break;
248

249
                assert((size_t) n <= m);
2,318✔
250
                msan_unpoison(buf, n);
2,318✔
251

252
                FOREACH_DIRENT_IN_BUFFER(de, buf, n)
6,327✔
253
                        if (!(ignore_hidden_or_backup ? hidden_or_backup_file(de->d_name) : dot_or_dot_dot(de->d_name)))
5,032✔
254
                                return 0;
255
        }
256

257
        return 1;
258
}
259

260
bool stat_may_be_dev_null(struct stat *st) {
475,082✔
261
        assert(st);
475,082✔
262

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

266
        return S_ISCHR(st->st_mode);
475,082✔
267
}
268

269
bool stat_is_empty(struct stat *st) {
458,037✔
270
        assert(st);
458,037✔
271

272
        return S_ISREG(st->st_mode) && st->st_size <= 0;
458,037✔
273
}
274

275
int null_or_empty_path_with_root(const char *fn, const char *root) {
335,469✔
276
        struct stat st;
335,469✔
277
        int r;
335,469✔
278

279
        assert(fn);
335,469✔
280

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

285
        if (path_equal(path_startswith(fn, root ?: "/"), "dev/null"))
670,938✔
286
                return true;
335,469✔
287

288
        r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
335,299✔
289
        if (r < 0)
335,299✔
290
                return r;
291

292
        return null_or_empty(&st);
335,183✔
293
}
294

295
static const char* statx_mask_one_to_name(unsigned mask);
296
static const char* statx_attribute_to_name(uint64_t attr);
297

298
#include "statx-attribute-to-name.inc"
299
#include "statx-mask-to-name.inc"
300

301
#define DEFINE_STATX_BITS_TO_STRING(prefix, type, func, format_str)             \
302
        static char* prefix##_to_string(type v) {                               \
303
                if (v == 0)                                                     \
304
                        return strdup("");                                      \
305
                                                                                \
306
                _cleanup_free_ char *s = NULL;                                  \
307
                                                                                \
308
                BIT_FOREACH(i, v) {                                             \
309
                        type f = 1 << i;                                        \
310
                                                                                \
311
                        const char *n = func(f);                                \
312
                        if (!n)                                                 \
313
                                continue;                                       \
314
                                                                                \
315
                        if (!strextend_with_separator(&s, "|", n))              \
316
                                return NULL;                                    \
317
                        v &= ~f;                                                \
318
                }                                                               \
319
                                                                                \
320
                if (v != 0 && strextendf_with_separator(&s, "|", format_str, v) < 0) \
321
                        return NULL;                                            \
322
                                                                                \
323
                return TAKE_PTR(s);                                             \
324
        }
325

326
DEFINE_STATX_BITS_TO_STRING(statx_mask,       unsigned, statx_mask_one_to_name,  "0x%x");
×
327
DEFINE_STATX_BITS_TO_STRING(statx_attributes, uint64_t, statx_attribute_to_name, "0x%" PRIx64);
×
328

329
int xstatx_full(int fd,
32,268,855✔
330
                const char *path,
331
                int statx_flags,
332
                XStatXFlags xstatx_flags,
333
                unsigned mandatory_mask,
334
                unsigned optional_mask,
335
                uint64_t mandatory_attributes,
336
                struct statx *ret) {
337

338
        struct statx sx = {}; /* explicitly initialize the struct to make msan silent. */
32,268,855✔
339
        int r;
32,268,855✔
340

341
        /* Wrapper around statx(), with additional bells and whistles:
342
         *
343
         * 1. AT_EMPTY_PATH is implied on empty path
344
         * 2. Supports XAT_FDROOT
345
         * 3. Takes separate mandatory and optional mask params, plus mandatory attributes.
346
         *    Returns -EUNATCH if statx() does not return all masks specified as mandatory,
347
         *    > 0 if all optional masks are supported, 0 otherwise.
348
         * 4. Supports a new flag XSTATX_MNT_ID_BEST which acquires STATX_MNT_ID_UNIQUE if available and
349
         *    STATX_MNT_ID if not.
350
         */
351

352
        assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT));
32,268,855✔
353
        assert((mandatory_mask & optional_mask) == 0);
32,268,855✔
354
        assert(!FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) || !((mandatory_mask|optional_mask) & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE)));
32,268,855✔
355
        assert(ret);
32,268,855✔
356

357
        _cleanup_free_ char *p = NULL;
32,268,855✔
358
        r = resolve_xat_fdroot(&fd, &path, &p);
32,268,855✔
359
        if (r < 0)
32,268,855✔
360
                return r;
361

362
        unsigned request_mask = mandatory_mask|optional_mask;
32,268,855✔
363
        if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST))
32,268,855✔
364
                request_mask |= STATX_MNT_ID|STATX_MNT_ID_UNIQUE;
24,237,625✔
365

366
        if (statx(fd,
32,268,855✔
367
                  strempty(path),
32,268,855✔
368
                  statx_flags|(isempty(path) ? AT_EMPTY_PATH : 0),
36,358,678✔
369
                  request_mask,
370
                  &sx) < 0)
371
                return negative_errno();
2,507✔
372

373
        if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) &&
32,266,348✔
374
            !(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE)))
24,237,625✔
375
                return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "statx() did not return either STATX_MNT_ID or STATX_MNT_ID_UNIQUE.");
×
376

377
        if (!FLAGS_SET(sx.stx_mask, mandatory_mask)) {
32,266,348✔
378
                if (DEBUG_LOGGING) {
×
379
                        _cleanup_free_ char *mask_str = statx_mask_to_string(mandatory_mask & ~sx.stx_mask);
×
380
                        log_debug("statx() does not support '%s' mask (running on an old kernel?)", strnull(mask_str));
×
381
                }
382

383
                return -EUNATCH;
×
384
        }
385

386
        if (!FLAGS_SET(sx.stx_attributes_mask, mandatory_attributes)) {
32,266,348✔
387
                if (DEBUG_LOGGING) {
×
388
                        _cleanup_free_ char *attr_str = statx_attributes_to_string(mandatory_attributes & ~sx.stx_attributes_mask);
×
389
                        log_debug("statx() does not support '%s' attribute (running on an old kernel?)", strnull(attr_str));
×
390
                }
391

392
                return -EUNATCH;
×
393
        }
394

395
        *ret = sx;
32,266,348✔
396
        return FLAGS_SET(sx.stx_mask, optional_mask);
32,266,348✔
397
}
398

399
static int xfstatfs(int fd, struct statfs *ret) {
8,716,581✔
400
        assert(ret);
8,716,581✔
401

402
        if (fd == AT_FDCWD)
8,716,581✔
403
                return RET_NERRNO(statfs(".", ret));
×
404
        if (fd == XAT_FDROOT)
8,716,581✔
405
                return RET_NERRNO(statfs("/", ret));
2✔
406

407
        assert(fd >= 0);
8,716,579✔
408
        return RET_NERRNO(fstatfs(fd, ret));
8,716,579✔
409
}
410

411
int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
8,693,062✔
412
        _cleanup_close_ int fd = -EBADF;
8,693,062✔
413

414
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
8,693,062✔
415
        assert(ret);
8,693,062✔
416

417
        if (!isempty(path)) {
8,693,062✔
418
                fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC);
27,945✔
419
                if (fd < 0)
27,945✔
420
                        return fd;
421
                dir_fd = fd;
422
        }
423

424
        return xfstatfs(dir_fd, ret);
8,693,050✔
425
}
426

427
int fd_is_read_only_fs(int fd) {
1,843✔
428
        int r;
1,843✔
429

430
        struct statfs st;
1,843✔
431
        r = xfstatfs(fd, &st);
1,843✔
432
        if (r < 0)
1,843✔
433
                return r;
1,843✔
434

435
        if (st.f_flags & ST_RDONLY)
1,843✔
436
                return true;
437

438
        if (is_network_fs(&st))
1,470✔
439
                /* On NFS, fstatfs() might not reflect whether we can actually write to the remote share.
440
                 * Let's try again with access(W_OK) which is more reliable, at least sometimes. */
441
                return access_fd(fd, W_OK) == -EROFS;
×
442

443
        return false;
444
}
445

446
int path_is_read_only_fs(const char *path) {
1,548✔
447
        _cleanup_close_ int fd = -EBADF;
1,548✔
448

449
        assert(path);
1,548✔
450

451
        fd = open(path, O_CLOEXEC | O_PATH);
1,548✔
452
        if (fd < 0)
1,548✔
453
                return -errno;
158✔
454

455
        return fd_is_read_only_fs(fd);
1,390✔
456
}
457

458
int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags) {
16,491✔
459
        struct stat sta, stb;
16,491✔
460
        int r;
16,491✔
461

462
        assert(fda >= 0 || fda == AT_FDCWD);
16,491✔
463
        assert(fdb >= 0 || fdb == AT_FDCWD);
16,491✔
464
        assert((flags & ~(AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT)) == 0);
16,491✔
465

466
        /* Refuse an unset filea or fileb early unless AT_EMPTY_PATH is set */
467
        if ((isempty(filea) || isempty(fileb)) && !FLAGS_SET(flags, AT_EMPTY_PATH))
31,069✔
468
                return -EINVAL;
16,491✔
469

470
        /* Shortcut: comparing the same fd with itself means we can return true */
471
        if (fda >= 0 && fda == fdb && isempty(filea) && isempty(fileb) && FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW))
16,493✔
472
                return true;
473

474
        _cleanup_close_ int pin_a = -EBADF, pin_b = -EBADF;
32,981✔
475
        if (!FLAGS_SET(flags, AT_NO_AUTOMOUNT)) {
16,490✔
476
                /* Let's try to use the name_to_handle_at() AT_HANDLE_FID API to identify identical
477
                 * inodes. We have to issue multiple calls on the same file for that (first, to acquire the
478
                 * FID, and then to check if .st_dev is actually the same). Hence let's pin the inode in
479
                 * between via O_PATH, unless we already have an fd for it. */
480

481
                if (!isempty(filea)) {
16,490✔
482
                        pin_a = openat(fda, filea, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0));
14,578✔
483
                        if (pin_a < 0)
14,578✔
484
                                return -errno;
16,246✔
485

486
                        fda = pin_a;
14,172✔
487
                        filea = NULL;
14,172✔
488
                        flags |= AT_EMPTY_PATH;
14,172✔
489
                }
490

491
                if (!isempty(fileb)) {
16,084✔
492
                        pin_b = openat(fdb, fileb, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0));
14,170✔
493
                        if (pin_b < 0)
14,170✔
494
                                return -errno;
4✔
495

496
                        fdb = pin_b;
14,166✔
497
                        fileb = NULL;
14,166✔
498
                        flags |= AT_EMPTY_PATH;
14,166✔
499
                }
500

501
                int ntha_flags = at_flags_normalize_follow(flags) & (AT_EMPTY_PATH|AT_SYMLINK_FOLLOW);
16,080✔
502
                _cleanup_free_ struct file_handle *ha = NULL, *hb = NULL;
16,080✔
503
                uint64_t mntida, mntidb;
16,080✔
504
                int _mntida, _mntidb;
16,080✔
505

506
                r = name_to_handle_at_try_fid(
16,080✔
507
                                fda,
508
                                filea,
509
                                &ha,
510
                                &_mntida,
511
                                &mntida,
512
                                ntha_flags);
513
                if (r < 0) {
16,080✔
514
                        if (is_name_to_handle_at_fatal_error(r))
×
515
                                return r;
516

517
                        goto fallback;
×
518
                }
519
                bool have_unique_mntid = r > 0;
16,080✔
520

521
                if (!have_unique_mntid)
16,080✔
522
                        mntida = _mntida;
×
523

524
                r = name_to_handle_at_try_fid(
16,080✔
525
                                fdb,
526
                                fileb,
527
                                &hb,
528
                                have_unique_mntid ? NULL : &_mntidb, /* if we managed to get unique mnt id for a, insist on that for b */
529
                                have_unique_mntid ? &mntidb : NULL,
530
                                ntha_flags);
531
                if (r < 0) {
16,080✔
532
                        if (is_name_to_handle_at_fatal_error(r))
×
533
                                return r;
534

535
                        goto fallback;
×
536
                }
537
                if (r == 0) {
16,080✔
538
                        assert(!have_unique_mntid); /* _mntidb was initialized by name_to_handle_at_try_fid() */
×
539
                        mntidb = _mntidb;
×
540
                }
541

542
                /* Now compare the two file handles */
543
                if (!file_handle_equal(ha, hb))
16,080✔
544
                        return false;
545

546
                /* If the file handles are the same and they come from the same mount ID? Great, then we are
547
                 * good, they are definitely the same */
548
                if (mntida == mntidb)
14,834✔
549
                        return true;
550

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

559
fallback:
×
560
        if (fstatat(fda, strempty(filea), &sta, flags) < 0)
488✔
561
                return log_debug_errno(errno, "Cannot stat %s: %m", strna(filea));
×
562

563
        if (fstatat(fdb, strempty(fileb), &stb, flags) < 0)
488✔
564
                return log_debug_errno(errno, "Cannot stat %s: %m", strna(fileb));
×
565

566
        return stat_inode_same(&sta, &stb);
244✔
567
}
568

569
bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) {
8,697,831✔
570
        assert(s);
8,697,831✔
571
        assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type));
8,697,831✔
572

573
        return F_TYPE_EQUAL(s->f_type, magic_value);
8,697,831✔
574
}
575

576
int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value) {
8,692,552✔
577
        int r;
8,692,552✔
578

579
        struct statfs s;
8,692,552✔
580
        r = xstatfsat(dir_fd, path, &s);
8,692,552✔
581
        if (r < 0)
8,692,552✔
582
                return r;
8,692,552✔
583

584
        return is_fs_type(&s, magic_value);
8,692,551✔
585
}
586

587
bool is_temporary_fs(const struct statfs *s) {
10,947✔
588
        return fs_in_group(s, FILESYSTEM_SET_TEMPORARY);
10,947✔
589
}
590

591
bool is_network_fs(const struct statfs *s) {
23,112✔
592
        return fs_in_group(s, FILESYSTEM_SET_NETWORK);
23,112✔
593
}
594

595
int fd_is_temporary_fs(int fd) {
155✔
596
        int r;
155✔
597

598
        struct statfs s;
155✔
599
        r = xfstatfs(fd, &s);
155✔
600
        if (r < 0)
155✔
601
                return r;
155✔
602

603
        return is_temporary_fs(&s);
155✔
604
}
605

606
int fd_is_network_fs(int fd) {
21,533✔
607
        int r;
21,533✔
608

609
        struct statfs s;
21,533✔
610
        r = xfstatfs(fd, &s);
21,533✔
611
        if (r < 0)
21,533✔
612
                return r;
21,533✔
613

614
        return is_network_fs(&s);
21,533✔
615
}
616

617
int path_is_temporary_fs(const char *path) {
11✔
618
        struct statfs s;
11✔
619

620
        if (statfs(path, &s) < 0)
11✔
621
                return -errno;
2✔
622

623
        return is_temporary_fs(&s);
9✔
624
}
625

626
int path_is_network_fs(const char *path) {
×
627
        struct statfs s;
×
628

629
        if (statfs(path, &s) < 0)
×
630
                return -errno;
×
631

632
        return is_network_fs(&s);
×
633
}
634

635
int proc_mounted(void) {
27,309✔
636
        /* This is typically used in error path. So, it is better to not overwrite the original errno. */
637
        PROTECT_ERRNO;
27,309✔
638
        int r;
27,309✔
639

640
        /* A quick check of procfs is properly mounted */
641

642
        r = path_is_fs_type("/proc/", PROC_SUPER_MAGIC);
27,309✔
643
        if (r == -ENOENT) /* not mounted at all */
27,309✔
644
                return false;
×
645

646
        return r;
647
}
648

649
bool stat_inode_same(const struct stat *a, const struct stat *b) {
289,233✔
650

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

654
        return stat_is_set(a) && stat_is_set(b) &&
576,276✔
655
                ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 &&  /* same inode type */
250,045✔
656
                a->st_dev == b->st_dev &&
504,931✔
657
                a->st_ino == b->st_ino;
215,698✔
658
}
659

660
bool stat_inode_unmodified(const struct stat *a, const struct stat *b) {
132,001✔
661

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

671
        return stat_inode_same(a, b) &&
132,001✔
672
                a->st_mtim.tv_sec == b->st_mtim.tv_sec &&
85,697✔
673
                a->st_mtim.tv_nsec == b->st_mtim.tv_nsec &&
85,403✔
674
                (!S_ISREG(a->st_mode) || a->st_size == b->st_size) && /* if regular file, compare file size */
217,374✔
675
                (!(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 */
85,373✔
676
}
677

678
bool statx_inode_same(const struct statx *a, const struct statx *b) {
4,785,789✔
679

680
        /* Same as stat_inode_same() but for struct statx */
681

682
        if (!statx_is_set(a) || !statx_is_set(b))
9,571,578✔
683
                return false;
684

685
        assert(FLAGS_SET(a->stx_mask, STATX_TYPE|STATX_INO));
4,785,789✔
686
        assert(FLAGS_SET(b->stx_mask, STATX_TYPE|STATX_INO));
4,785,789✔
687

688
        return
4,785,789✔
689
                ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 &&
9,565,841✔
690
                a->stx_dev_major == b->stx_dev_major &&
4,780,052✔
691
                a->stx_dev_minor == b->stx_dev_minor &&
14,312,960✔
692
                a->stx_ino == b->stx_ino;
4,733,421✔
693
}
694

695
int statx_mount_same(const struct statx *a, const struct statx *b) {
3,922,809✔
696
        if (!statx_is_set(a) || !statx_is_set(b))
7,845,618✔
697
                return false;
698

699
        if ((FLAGS_SET(a->stx_mask, STATX_MNT_ID) && FLAGS_SET(b->stx_mask, STATX_MNT_ID)) ||
3,922,809✔
700
            (FLAGS_SET(a->stx_mask, STATX_MNT_ID_UNIQUE) && FLAGS_SET(b->stx_mask, STATX_MNT_ID_UNIQUE)))
×
701
                return a->stx_mnt_id == b->stx_mnt_id;
3,922,809✔
702

703
        return -ENODATA;
704
}
705

706
usec_t statx_timestamp_load(const struct statx_timestamp *ts) {
6,971✔
707
        return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
6,971✔
708
}
709
nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) {
820✔
710
        return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
820✔
711
}
712

713
void inode_hash_func(const struct stat *q, struct siphash *state) {
34,404✔
714
        siphash24_compress_typesafe(q->st_dev, state);
34,404✔
715
        siphash24_compress_typesafe(q->st_ino, state);
34,404✔
716

717
        /* Also include inode type, to mirror stat_inode_same() */
718
        mode_t type = q->st_mode & S_IFMT;
34,404✔
719
        siphash24_compress_typesafe(type, state);
34,404✔
720
}
34,404✔
721

722
int inode_compare_func(const struct stat *a, const struct stat *b) {
28,472✔
723
        int r;
28,472✔
724

725
        r = CMP(a->st_dev, b->st_dev);
28,472✔
726
        if (r != 0)
23,488✔
727
                return r;
5,045✔
728

729
        r = CMP(a->st_ino, b->st_ino);
23,427✔
730
        if (r != 0)
8,541✔
731
                return r;
23,336✔
732

733
        return CMP(a->st_mode & S_IFMT, b->st_mode & S_IFMT);
91✔
734
}
735

736
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
976✔
737

738
void inode_unmodified_hash_func(const struct stat *q, struct siphash *state) {
7✔
739
        inode_hash_func(q, state);
7✔
740

741
        siphash24_compress_typesafe(q->st_mtim.tv_sec, state);
7✔
742
        siphash24_compress_typesafe(q->st_mtim.tv_nsec, state);
7✔
743

744
        if (S_ISREG(q->st_mode))
7✔
745
                siphash24_compress_typesafe(q->st_size, state);
7✔
746
        else {
747
                uint64_t invalid = UINT64_MAX;
×
748
                siphash24_compress_typesafe(invalid, state);
×
749
        }
750

751
        if (S_ISCHR(q->st_mode) || S_ISBLK(q->st_mode))
7✔
752
                siphash24_compress_typesafe(q->st_rdev, state);
×
753
        else {
754
                dev_t invalid = (dev_t) -1;
7✔
755
                siphash24_compress_typesafe(invalid, state);
7✔
756
        }
757
}
7✔
758

759
int inode_unmodified_compare_func(const struct stat *a, const struct stat *b) {
4✔
760
        int r;
4✔
761

762
        r = inode_compare_func(a, b);
4✔
763
        if (r != 0)
4✔
764
                return r;
765

766
        r = CMP(a->st_mtim.tv_sec, b->st_mtim.tv_sec);
×
767
        if (r != 0)
×
768
                return r;
×
769

770
        r = CMP(a->st_mtim.tv_nsec, b->st_mtim.tv_nsec);
×
771
        if (r != 0)
×
772
                return r;
×
773

774
        if (S_ISREG(a->st_mode)) {
×
775
                r = CMP(a->st_size, b->st_size);
×
776
                if (r != 0)
×
777
                        return r;
×
778
        }
779

780
        if (S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) {
×
781
                r = CMP(a->st_rdev, b->st_rdev);
×
782
                if (r != 0)
×
783
                        return r;
×
784
        }
785

786
        return 0;
787
}
788

789
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_unmodified_hash_ops, struct stat, inode_unmodified_hash_func, inode_unmodified_compare_func, free);
4✔
790

791
const char* inode_type_to_string(mode_t m) {
19,719✔
792

793
        /* Returns a short string for the inode type. We use the same name as the underlying macros for each
794
         * inode type. */
795

796
        switch (m & S_IFMT) {
19,719✔
797
        case S_IFREG:
798
                return "reg";
799
        case S_IFDIR:
6,769✔
800
                return "dir";
6,769✔
801
        case S_IFLNK:
1✔
802
                return "lnk";
1✔
803
        case S_IFCHR:
1,017✔
804
                return "chr";
1,017✔
805
        case S_IFBLK:
468✔
806
                return "blk";
468✔
807
        case S_IFIFO:
468✔
808
                return "fifo";
468✔
809
        case S_IFSOCK:
612✔
810
                return "sock";
612✔
811
        }
812

813
        /* Note anonymous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will
814
         * return an .st_mode where we'll return NULL here! */
815
        return NULL;
4✔
816
}
817

818
mode_t inode_type_from_string(const char *s) {
13✔
819
        if (!s)
13✔
820
                return MODE_INVALID;
821

822
        if (streq(s, "reg"))
13✔
823
                return S_IFREG;
824
        if (streq(s, "dir"))
10✔
825
                return S_IFDIR;
826
        if (streq(s, "lnk"))
7✔
827
                return S_IFLNK;
828
        if (streq(s, "chr"))
6✔
829
                return S_IFCHR;
830
        if (streq(s, "blk"))
5✔
831
                return S_IFBLK;
832
        if (streq(s, "fifo"))
4✔
833
                return S_IFIFO;
834
        if (streq(s, "sock"))
2✔
835
                return S_IFSOCK;
2✔
836

837
        return MODE_INVALID;
838
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc