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

systemd / systemd / 24697298032

20 Apr 2026 09:22PM UTC coverage: 72.23% (+1.6%) from 70.661%
24697298032

push

github

bluca
sysupdate: Emit READY=1 status when installing

`READY=1` is already correctly emitted when acquiring an update, but was
forgotten to be emitted when subsequently installing that update.

That meant that the state tracking code in `sysupdated` and hence
`updatectl` could not properly report the progress of the install
operation, and hence printed “Already up-to-date” after a successful
update installation, rather than “Done”.

Add a test to catch this in future.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>

Fixes: https://github.com/systemd/systemd/issues/41502

322667 of 446720 relevant lines covered (72.23%)

1191594.78 hits per line

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

83.14
/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,154,386✔
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,154,386✔
33
        int r;
2,154,386✔
34

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

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

44
        if (fstatat(fd, strempty(path), &st,
2,998,792✔
45
                    (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
3,464,366✔
46
                return -errno;
8,332✔
47

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

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

56
        if (S_ISLNK(mode))
2,763,974✔
57
                return -ELOOP;
58

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

62
        return 0;
63
}
64

65
int stat_verify_regular(const struct stat *st) {
2,764,007✔
66
        assert(st);
2,764,007✔
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,764,007✔
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) {
838,463✔
84
        return verify_stat_at(fd, path, follow, stat_verify_regular, true);
838,463✔
85
}
86

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

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

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

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

101
        return 0;
102
}
103

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

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

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

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

116
        return mode_verify_directory(stx->stx_mode);
61,077✔
117
}
118

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

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

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

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

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

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

141
        if (!S_ISLNK(st->st_mode))
26,638✔
142
                return -ENOLINK;
287✔
143

144
        return 0;
145
}
146

147
int fd_verify_symlink(int fd) {
5,291✔
148
        if (IN_SET(fd, AT_FDCWD, XAT_FDROOT))
5,291✔
149
                return -EISDIR;
150

151
        return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_symlink, /* verify= */ true);
5,291✔
152
}
153

154
int is_symlink(const char *path) {
21,365✔
155
        assert(!isempty(path));
21,365✔
156
        return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false);
21,365✔
157
}
158

159
static int mode_verify_socket(mode_t mode) {
256✔
160
        if (S_ISDIR(mode))
256✔
161
                return -EISDIR;
162

163
        if (S_ISLNK(mode))
255✔
164
                return -ELOOP;
165

166
        if (!S_ISSOCK(mode))
255✔
167
                return -ENOTSOCK;
1✔
168

169
        return 0;
170
}
171

172
int stat_verify_socket(const struct stat *st) {
181✔
173
        assert(st);
181✔
174

175
        return mode_verify_socket(st->st_mode);
181✔
176
}
177

178
int statx_verify_socket(const struct statx *stx) {
75✔
179
        assert(stx);
75✔
180

181
        return mode_verify_socket(stx->stx_mode);
75✔
182
}
183

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

189
int stat_verify_linked(const struct stat *st) {
1,857,438✔
190
        assert(st);
1,857,438✔
191

192
        if (st->st_nlink <= 0)
1,857,438✔
193
                return -EIDRM; /* recognizable error. */
2✔
194

195
        return 0;
196
}
197

198
int fd_verify_linked(int fd) {
50✔
199

200
        if (fd == XAT_FDROOT)
50✔
201
                return 0;
202

203
        return verify_stat_at(fd, NULL, false, stat_verify_linked, true);
49✔
204
}
205

206
int stat_verify_block(const struct stat *st) {
183✔
207
        assert(st);
183✔
208

209
        if (S_ISDIR(st->st_mode))
183✔
210
                return -EISDIR;
211

212
        if (S_ISLNK(st->st_mode))
183✔
213
                return -ELOOP;
214

215
        if (!S_ISBLK(st->st_mode))
183✔
216
                return -ENOTBLK;
×
217

218
        return 0;
219
}
220

221
int fd_verify_block(int fd) {
×
222
        if (IN_SET(fd, AT_FDCWD, XAT_FDROOT))
×
223
                return -EISDIR;
224

225
        return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_block, /* verify= */ true);
×
226
}
227

228
int stat_verify_char(const struct stat *st) {
×
229
        assert(st);
×
230

231
        if (S_ISDIR(st->st_mode))
×
232
                return -EISDIR;
233

234
        if (S_ISLNK(st->st_mode))
×
235
                return -ELOOP;
236

237
        if (!S_ISCHR(st->st_mode))
×
238
                return -EBADFD;
×
239

240
        return 0;
241
}
242

243
int stat_verify_device_node(const struct stat *st) {
11,922✔
244
        assert(st);
11,922✔
245

246
        if (S_ISDIR(st->st_mode))
11,922✔
247
                return -EISDIR;
248

249
        if (S_ISLNK(st->st_mode))
11,799✔
250
                return -ELOOP;
251

252
        if (!S_ISBLK(st->st_mode) && !S_ISCHR(st->st_mode))
11,799✔
253
                return -ENOTTY;
5✔
254

255
        return 0;
256
}
257

258
int is_device_node(const char *path) {
132✔
259
        assert(!isempty(path));
132✔
260
        return verify_stat_at(AT_FDCWD, path, false, stat_verify_device_node, false);
132✔
261
}
262

263
int stat_verify_regular_or_block(const struct stat *st) {
234✔
264
        assert(st);
234✔
265

266
        if (S_ISDIR(st->st_mode))
234✔
267
                return -EISDIR;
268

269
        if (S_ISLNK(st->st_mode))
234✔
270
                return -ELOOP;
271

272
        if (!S_ISREG(st->st_mode) && !S_ISBLK(st->st_mode))
234✔
273
                return -EBADFD;
×
274

275
        return 0;
276
}
277

278
int fd_verify_regular_or_block(int fd) {
5✔
279
        if (IN_SET(fd, AT_FDCWD, XAT_FDROOT))
5✔
280
                return -EISDIR;
281

282
        return verify_stat_at(fd, /* path= */ NULL, /* follow= */ false, stat_verify_regular_or_block, /* verify= */ true);
5✔
283
}
284

285
int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) {
40,084✔
286
        _cleanup_close_ int fd = -EBADF;
40,084✔
287
        struct dirent *buf;
40,084✔
288
        size_t m;
40,084✔
289

290
        fd = xopenat(dir_fd, path, O_DIRECTORY|O_CLOEXEC);
40,084✔
291
        if (fd < 0)
40,084✔
292
                return fd;
293

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

301
        for (;;) {
3,725✔
302
                struct dirent *de;
3,725✔
303
                ssize_t n;
3,725✔
304

305
                n = getdents64(fd, buf, m);
3,725✔
306
                if (n < 0)
3,725✔
307
                        return -errno;
×
308
                if (n == 0)
3,725✔
309
                        break;
310

311
                assert((size_t) n <= m);
2,375✔
312
                msan_unpoison(buf, n);
2,375✔
313

314
                FOREACH_DIRENT_IN_BUFFER(de, buf, n)
6,549✔
315
                        if (!(ignore_hidden_or_backup ? hidden_or_backup_file(de->d_name) : dot_or_dot_dot(de->d_name)))
5,199✔
316
                                return 0;
317
        }
318

319
        return 1;
320
}
321

322
bool stat_may_be_dev_null(struct stat *st) {
525,162✔
323
        assert(st);
525,162✔
324

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

328
        return S_ISCHR(st->st_mode);
525,162✔
329
}
330

331
bool stat_is_empty(struct stat *st) {
507,340✔
332
        assert(st);
507,340✔
333

334
        return S_ISREG(st->st_mode) && st->st_size <= 0;
507,340✔
335
}
336

337
int null_or_empty_path_with_root(const char *fn, const char *root) {
379,159✔
338
        struct stat st;
379,159✔
339
        int r;
379,159✔
340

341
        assert(fn);
379,159✔
342

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

347
        if (path_equal(path_startswith(fn, root ?: "/"), "dev/null"))
758,318✔
348
                return true;
379,159✔
349

350
        r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
378,989✔
351
        if (r < 0)
378,989✔
352
                return r;
353

354
        return null_or_empty(&st);
378,872✔
355
}
356

357
static const char* statx_mask_one_to_name(unsigned mask);
358
static const char* statx_attribute_to_name(uint64_t attr);
359

360
#include "statx-attribute-to-name.inc"
361
#include "statx-mask-to-name.inc"
362

363
#define DEFINE_STATX_BITS_TO_STRING(prefix, type, func, format_str)             \
364
        static char* prefix##_to_string(type v) {                               \
365
                if (v == 0)                                                     \
366
                        return strdup("");                                      \
367
                                                                                \
368
                _cleanup_free_ char *s = NULL;                                  \
369
                                                                                \
370
                BIT_FOREACH(i, v) {                                             \
371
                        type f = 1 << i;                                        \
372
                                                                                \
373
                        const char *n = func(f);                                \
374
                        if (!n)                                                 \
375
                                continue;                                       \
376
                                                                                \
377
                        if (!strextend_with_separator(&s, "|", n))              \
378
                                return NULL;                                    \
379
                        v &= ~f;                                                \
380
                }                                                               \
381
                                                                                \
382
                if (v != 0 && strextendf_with_separator(&s, "|", format_str, v) < 0) \
383
                        return NULL;                                            \
384
                                                                                \
385
                return TAKE_PTR(s);                                             \
386
        }
387

388
DEFINE_STATX_BITS_TO_STRING(statx_mask,       unsigned, statx_mask_one_to_name,  "0x%x");
×
389
DEFINE_STATX_BITS_TO_STRING(statx_attributes, uint64_t, statx_attribute_to_name, "0x%" PRIx64);
×
390

391
int xstatx_full(int fd,
32,712,516✔
392
                const char *path,
393
                int statx_flags,
394
                XStatXFlags xstatx_flags,
395
                unsigned mandatory_mask,
396
                unsigned optional_mask,
397
                uint64_t mandatory_attributes,
398
                struct statx *ret) {
399

400
        struct statx sx = {}; /* explicitly initialize the struct to make msan silent. */
32,712,516✔
401
        int r;
32,712,516✔
402

403
        /* Wrapper around statx(), with additional bells and whistles:
404
         *
405
         * 1. AT_EMPTY_PATH is implied on empty path
406
         * 2. Supports XAT_FDROOT
407
         * 3. Takes separate mandatory and optional mask params, plus mandatory attributes.
408
         *    Returns -EUNATCH if statx() does not return all masks specified as mandatory,
409
         *    > 0 if all optional masks are supported, 0 otherwise.
410
         * 4. Supports a new flag XSTATX_MNT_ID_BEST which acquires STATX_MNT_ID_UNIQUE if available and
411
         *    STATX_MNT_ID if not.
412
         */
413

414
        assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT));
32,712,516✔
415
        assert((mandatory_mask & optional_mask) == 0);
32,712,516✔
416
        assert(!FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) || !((mandatory_mask|optional_mask) & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE)));
32,712,516✔
417
        assert(ret);
32,712,516✔
418

419
        _cleanup_free_ char *p = NULL;
32,712,516✔
420
        r = resolve_xat_fdroot(&fd, &path, &p);
32,712,516✔
421
        if (r < 0)
32,712,516✔
422
                return r;
423

424
        unsigned request_mask = mandatory_mask|optional_mask;
32,712,516✔
425
        if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST))
32,712,516✔
426
                request_mask |= STATX_MNT_ID|STATX_MNT_ID_UNIQUE;
24,556,308✔
427

428
        if (statx(fd,
32,712,516✔
429
                  strempty(path),
32,712,516✔
430
                  statx_flags|(isempty(path) ? AT_EMPTY_PATH : 0),
36,865,896✔
431
                  request_mask,
432
                  &sx) < 0)
433
                return negative_errno();
2,544✔
434

435
        if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) &&
32,709,972✔
436
            !(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE)))
24,556,308✔
437
                return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "statx() did not return either STATX_MNT_ID or STATX_MNT_ID_UNIQUE.");
×
438

439
        if (!FLAGS_SET(sx.stx_mask, mandatory_mask)) {
32,709,972✔
440
                if (DEBUG_LOGGING) {
×
441
                        _cleanup_free_ char *mask_str = statx_mask_to_string(mandatory_mask & ~sx.stx_mask);
×
442
                        log_debug("statx() does not support '%s' mask (running on an old kernel?)", strnull(mask_str));
×
443
                }
444

445
                return -EUNATCH;
×
446
        }
447

448
        if (!FLAGS_SET(sx.stx_attributes_mask, mandatory_attributes)) {
32,709,972✔
449
                if (DEBUG_LOGGING) {
×
450
                        _cleanup_free_ char *attr_str = statx_attributes_to_string(mandatory_attributes & ~sx.stx_attributes_mask);
×
451
                        log_debug("statx() does not support '%s' attribute (running on an old kernel?)", strnull(attr_str));
×
452
                }
453

454
                return -EUNATCH;
×
455
        }
456

457
        *ret = sx;
32,709,972✔
458
        return FLAGS_SET(sx.stx_mask, optional_mask);
32,709,972✔
459
}
460

461
static int xfstatfs(int fd, struct statfs *ret) {
8,656,270✔
462
        assert(ret);
8,656,270✔
463

464
        if (fd == AT_FDCWD)
8,656,270✔
465
                return RET_NERRNO(statfs(".", ret));
×
466
        if (fd == XAT_FDROOT)
8,656,270✔
467
                return RET_NERRNO(statfs("/", ret));
2✔
468

469
        assert(fd >= 0);
8,656,268✔
470
        return RET_NERRNO(fstatfs(fd, ret));
8,656,268✔
471
}
472

473
int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
8,632,411✔
474
        _cleanup_close_ int fd = -EBADF;
8,632,411✔
475

476
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
8,632,411✔
477
        assert(ret);
8,632,411✔
478

479
        if (!isempty(path)) {
8,632,411✔
480
                fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC);
26,486✔
481
                if (fd < 0)
26,486✔
482
                        return fd;
483
                dir_fd = fd;
484
        }
485

486
        return xfstatfs(dir_fd, ret);
8,632,410✔
487
}
488

489
int fd_is_read_only_fs(int fd) {
1,846✔
490
        int r;
1,846✔
491

492
        struct statfs st;
1,846✔
493
        r = xfstatfs(fd, &st);
1,846✔
494
        if (r < 0)
1,846✔
495
                return r;
1,846✔
496

497
        if (st.f_flags & ST_RDONLY)
1,846✔
498
                return true;
499

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

505
        return false;
506
}
507

508
int path_is_read_only_fs(const char *path) {
1,551✔
509
        _cleanup_close_ int fd = -EBADF;
1,551✔
510

511
        assert(path);
1,551✔
512

513
        fd = open(path, O_CLOEXEC | O_PATH);
1,551✔
514
        if (fd < 0)
1,551✔
515
                return -errno;
158✔
516

517
        return fd_is_read_only_fs(fd);
1,393✔
518
}
519

520
int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags) {
17,421✔
521
        struct stat sta, stb;
17,421✔
522
        int r;
17,421✔
523

524
        assert(fda >= 0 || fda == AT_FDCWD);
17,421✔
525
        assert(fdb >= 0 || fdb == AT_FDCWD);
17,421✔
526
        assert((flags & ~(AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT)) == 0);
17,421✔
527

528
        /* Refuse an unset filea or fileb early unless AT_EMPTY_PATH is set */
529
        if ((isempty(filea) || isempty(fileb)) && !FLAGS_SET(flags, AT_EMPTY_PATH))
32,971✔
530
                return -EINVAL;
17,421✔
531

532
        /* Shortcut: comparing the same fd with itself means we can return true */
533
        if (fda >= 0 && fda == fdb && isempty(filea) && isempty(fileb) && FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW))
17,423✔
534
                return true;
535

536
        _cleanup_close_ int pin_a = -EBADF, pin_b = -EBADF;
34,841✔
537
        if (!FLAGS_SET(flags, AT_NO_AUTOMOUNT)) {
17,420✔
538
                /* Let's try to use the name_to_handle_at() AT_HANDLE_FID API to identify identical
539
                 * inodes. We have to issue multiple calls on the same file for that (first, to acquire the
540
                 * FID, and then to check if .st_dev is actually the same). Hence let's pin the inode in
541
                 * between via O_PATH, unless we already have an fd for it. */
542

543
                if (!isempty(filea)) {
17,420✔
544
                        pin_a = openat(fda, filea, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0));
15,550✔
545
                        if (pin_a < 0)
15,550✔
546
                                return -errno;
17,172✔
547

548
                        fda = pin_a;
15,084✔
549
                        filea = NULL;
15,084✔
550
                        flags |= AT_EMPTY_PATH;
15,084✔
551
                }
552

553
                if (!isempty(fileb)) {
16,954✔
554
                        pin_b = openat(fdb, fileb, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, AT_SYMLINK_NOFOLLOW) ? O_NOFOLLOW : 0));
15,082✔
555
                        if (pin_b < 0)
15,082✔
556
                                return -errno;
4✔
557

558
                        fdb = pin_b;
15,078✔
559
                        fileb = NULL;
15,078✔
560
                        flags |= AT_EMPTY_PATH;
15,078✔
561
                }
562

563
                int ntha_flags = at_flags_normalize_follow(flags) & (AT_EMPTY_PATH|AT_SYMLINK_FOLLOW);
16,950✔
564
                _cleanup_free_ struct file_handle *ha = NULL, *hb = NULL;
16,950✔
565
                uint64_t mntida, mntidb;
16,950✔
566
                int _mntida, _mntidb;
16,950✔
567

568
                r = name_to_handle_at_try_fid(
16,950✔
569
                                fda,
570
                                filea,
571
                                &ha,
572
                                &_mntida,
573
                                &mntida,
574
                                ntha_flags);
575
                if (r < 0) {
16,950✔
576
                        if (is_name_to_handle_at_fatal_error(r))
×
577
                                return r;
578

579
                        goto fallback;
×
580
                }
581
                bool have_unique_mntid = r > 0;
16,950✔
582

583
                if (!have_unique_mntid)
16,950✔
584
                        mntida = _mntida;
×
585

586
                r = name_to_handle_at_try_fid(
16,950✔
587
                                fdb,
588
                                fileb,
589
                                &hb,
590
                                have_unique_mntid ? NULL : &_mntidb, /* if we managed to get unique mnt id for a, insist on that for b */
591
                                have_unique_mntid ? &mntidb : NULL,
592
                                ntha_flags);
593
                if (r < 0) {
16,950✔
594
                        if (is_name_to_handle_at_fatal_error(r))
×
595
                                return r;
596

597
                        goto fallback;
×
598
                }
599
                if (r == 0) {
16,950✔
600
                        assert(!have_unique_mntid); /* _mntidb was initialized by name_to_handle_at_try_fid() */
×
601
                        mntidb = _mntidb;
×
602
                }
603

604
                /* Now compare the two file handles */
605
                if (!file_handle_equal(ha, hb))
16,950✔
606
                        return false;
607

608
                /* If the file handles are the same and they come from the same mount ID? Great, then we are
609
                 * good, they are definitely the same */
610
                if (mntida == mntidb)
15,724✔
611
                        return true;
612

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

621
fallback:
×
622
        if (fstatat(fda, strempty(filea), &sta, flags) < 0)
496✔
623
                return log_debug_errno(errno, "Cannot stat %s: %m", strna(filea));
×
624

625
        if (fstatat(fdb, strempty(fileb), &stb, flags) < 0)
496✔
626
                return log_debug_errno(errno, "Cannot stat %s: %m", strna(fileb));
×
627

628
        return stat_inode_same(&sta, &stb);
248✔
629
}
630

631
bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) {
8,640,139✔
632
        assert(s);
8,640,139✔
633
        assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type));
8,640,139✔
634

635
        return F_TYPE_EQUAL(s->f_type, magic_value);
8,640,139✔
636
}
637

638
int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value) {
8,632,411✔
639
        int r;
8,632,411✔
640

641
        struct statfs s;
8,632,411✔
642
        r = xstatfsat(dir_fd, path, &s);
8,632,411✔
643
        if (r < 0)
8,632,411✔
644
                return r;
8,632,411✔
645

646
        return is_fs_type(&s, magic_value);
8,632,410✔
647
}
648

649
bool is_temporary_fs(const struct statfs *s) {
10,941✔
650
        return fs_in_group(s, FILESYSTEM_SET_TEMPORARY);
10,941✔
651
}
652

653
bool is_network_fs(const struct statfs *s) {
23,431✔
654
        return fs_in_group(s, FILESYSTEM_SET_NETWORK);
23,431✔
655
}
656

657
int fd_is_temporary_fs(int fd) {
157✔
658
        int r;
157✔
659

660
        struct statfs s;
157✔
661
        r = xfstatfs(fd, &s);
157✔
662
        if (r < 0)
157✔
663
                return r;
157✔
664

665
        return is_temporary_fs(&s);
157✔
666
}
667

668
int fd_is_network_fs(int fd) {
21,857✔
669
        int r;
21,857✔
670

671
        struct statfs s;
21,857✔
672
        r = xfstatfs(fd, &s);
21,857✔
673
        if (r < 0)
21,857✔
674
                return r;
21,857✔
675

676
        return is_network_fs(&s);
21,857✔
677
}
678

679
int path_is_temporary_fs(const char *path) {
11✔
680
        struct statfs s;
11✔
681

682
        if (statfs(path, &s) < 0)
11✔
683
                return -errno;
2✔
684

685
        return is_temporary_fs(&s);
9✔
686
}
687

688
int path_is_network_fs(const char *path) {
×
689
        struct statfs s;
×
690

691
        if (statfs(path, &s) < 0)
×
692
                return -errno;
×
693

694
        return is_network_fs(&s);
×
695
}
696

697
int proc_mounted(void) {
26,358✔
698
        /* This is typically used in error path. So, it is better to not overwrite the original errno. */
699
        PROTECT_ERRNO;
26,358✔
700
        int r;
26,358✔
701

702
        /* A quick check of procfs is properly mounted */
703

704
        r = path_is_fs_type("/proc/", PROC_SUPER_MAGIC);
26,358✔
705
        if (r == -ENOENT) /* not mounted at all */
26,358✔
706
                return false;
×
707

708
        return r;
709
}
710

711
bool stat_inode_same(const struct stat *a, const struct stat *b) {
357,145✔
712

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

716
        return stat_is_set(a) && stat_is_set(b) &&
712,043✔
717
                ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 &&  /* same inode type */
298,450✔
718
                a->st_dev == b->st_dev &&
620,808✔
719
                a->st_ino == b->st_ino;
263,663✔
720
}
721

722
bool stat_inode_unmodified(const struct stat *a, const struct stat *b) {
158,998✔
723

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

733
        return stat_inode_same(a, b) &&
158,998✔
734
                a->st_mtim.tv_sec == b->st_mtim.tv_sec &&
93,242✔
735
                a->st_mtim.tv_nsec == b->st_mtim.tv_nsec &&
92,882✔
736
                (!S_ISREG(a->st_mode) || a->st_size == b->st_size) && /* if regular file, compare file size */
251,855✔
737
                (!(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 */
92,857✔
738
}
739

740
bool statx_inode_same(const struct statx *a, const struct statx *b) {
4,785,988✔
741

742
        /* Same as stat_inode_same() but for struct statx */
743

744
        if (!statx_is_set(a) || !statx_is_set(b))
9,571,976✔
745
                return false;
746

747
        assert(FLAGS_SET(a->stx_mask, STATX_TYPE|STATX_INO));
4,785,988✔
748
        assert(FLAGS_SET(b->stx_mask, STATX_TYPE|STATX_INO));
4,785,988✔
749

750
        return
4,785,988✔
751
                ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 &&
9,566,165✔
752
                a->stx_dev_major == b->stx_dev_major &&
4,780,177✔
753
                a->stx_dev_minor == b->stx_dev_minor &&
14,312,854✔
754
                a->stx_ino == b->stx_ino;
4,732,916✔
755
}
756

757
int statx_mount_same(const struct statx *a, const struct statx *b) {
3,983,985✔
758
        if (!statx_is_set(a) || !statx_is_set(b))
7,967,970✔
759
                return false;
760

761
        if ((FLAGS_SET(a->stx_mask, STATX_MNT_ID) && FLAGS_SET(b->stx_mask, STATX_MNT_ID)) ||
3,983,985✔
762
            (FLAGS_SET(a->stx_mask, STATX_MNT_ID_UNIQUE) && FLAGS_SET(b->stx_mask, STATX_MNT_ID_UNIQUE)))
×
763
                return a->stx_mnt_id == b->stx_mnt_id;
3,983,985✔
764

765
        return -ENODATA;
766
}
767

768
usec_t statx_timestamp_load(const struct statx_timestamp *ts) {
6,511✔
769
        return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
6,511✔
770
}
771
nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) {
878✔
772
        return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec });
878✔
773
}
774

775
void inode_hash_func(const struct stat *q, struct siphash *state) {
33,522✔
776
        siphash24_compress_typesafe(q->st_dev, state);
33,522✔
777
        siphash24_compress_typesafe(q->st_ino, state);
33,522✔
778

779
        /* Also include inode type, to mirror stat_inode_same() */
780
        mode_t type = q->st_mode & S_IFMT;
33,522✔
781
        siphash24_compress_typesafe(type, state);
33,522✔
782
}
33,522✔
783

784
int inode_compare_func(const struct stat *a, const struct stat *b) {
27,784✔
785
        int r;
27,784✔
786

787
        r = CMP(a->st_dev, b->st_dev);
27,784✔
788
        if (r != 0)
23,206✔
789
                return r;
4,743✔
790

791
        r = CMP(a->st_ino, b->st_ino);
23,041✔
792
        if (r != 0)
7,955✔
793
                return r;
22,950✔
794

795
        return CMP(a->st_mode & S_IFMT, b->st_mode & S_IFMT);
91✔
796
}
797

798
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
976✔
799

800
void inode_unmodified_hash_func(const struct stat *q, struct siphash *state) {
7✔
801
        inode_hash_func(q, state);
7✔
802

803
        siphash24_compress_typesafe(q->st_mtim.tv_sec, state);
7✔
804
        siphash24_compress_typesafe(q->st_mtim.tv_nsec, state);
7✔
805

806
        if (S_ISREG(q->st_mode))
7✔
807
                siphash24_compress_typesafe(q->st_size, state);
7✔
808
        else {
809
                uint64_t invalid = UINT64_MAX;
×
810
                siphash24_compress_typesafe(invalid, state);
×
811
        }
812

813
        if (S_ISCHR(q->st_mode) || S_ISBLK(q->st_mode))
7✔
814
                siphash24_compress_typesafe(q->st_rdev, state);
×
815
        else {
816
                dev_t invalid = (dev_t) -1;
7✔
817
                siphash24_compress_typesafe(invalid, state);
7✔
818
        }
819
}
7✔
820

821
int inode_unmodified_compare_func(const struct stat *a, const struct stat *b) {
×
822
        int r;
×
823

824
        r = inode_compare_func(a, b);
×
825
        if (r != 0)
×
826
                return r;
827

828
        r = CMP(a->st_mtim.tv_sec, b->st_mtim.tv_sec);
×
829
        if (r != 0)
×
830
                return r;
×
831

832
        r = CMP(a->st_mtim.tv_nsec, b->st_mtim.tv_nsec);
×
833
        if (r != 0)
×
834
                return r;
×
835

836
        if (S_ISREG(a->st_mode)) {
×
837
                r = CMP(a->st_size, b->st_size);
×
838
                if (r != 0)
×
839
                        return r;
×
840
        }
841

842
        if (S_ISCHR(a->st_mode) || S_ISBLK(a->st_mode)) {
×
843
                r = CMP(a->st_rdev, b->st_rdev);
×
844
                if (r != 0)
×
845
                        return r;
×
846
        }
847

848
        return 0;
849
}
850

851
DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_unmodified_hash_ops, struct stat, inode_unmodified_hash_func, inode_unmodified_compare_func, free);
4✔
852

853
const char* inode_type_to_string(mode_t m) {
19,789✔
854

855
        /* Returns a short string for the inode type. We use the same name as the underlying macros for each
856
         * inode type. */
857

858
        switch (m & S_IFMT) {
19,789✔
859
        case S_IFREG:
860
                return "reg";
861
        case S_IFDIR:
6,799✔
862
                return "dir";
6,799✔
863
        case S_IFLNK:
1✔
864
                return "lnk";
1✔
865
        case S_IFCHR:
1,023✔
866
                return "chr";
1,023✔
867
        case S_IFBLK:
471✔
868
                return "blk";
471✔
869
        case S_IFIFO:
471✔
870
                return "fifo";
471✔
871
        case S_IFSOCK:
615✔
872
                return "sock";
615✔
873
        }
874

875
        /* Note anonymous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will
876
         * return an .st_mode where we'll return NULL here! */
877
        return NULL;
4✔
878
}
879

880
mode_t inode_type_from_string(const char *s) {
13✔
881
        if (!s)
13✔
882
                return MODE_INVALID;
883

884
        if (streq(s, "reg"))
13✔
885
                return S_IFREG;
886
        if (streq(s, "dir"))
10✔
887
                return S_IFDIR;
888
        if (streq(s, "lnk"))
7✔
889
                return S_IFLNK;
890
        if (streq(s, "chr"))
6✔
891
                return S_IFCHR;
892
        if (streq(s, "blk"))
5✔
893
                return S_IFBLK;
894
        if (streq(s, "fifo"))
4✔
895
                return S_IFIFO;
896
        if (streq(s, "sock"))
2✔
897
                return S_IFSOCK;
2✔
898

899
        return MODE_INVALID;
900
}
901

902
int vfs_free_bytes(int fd, uint64_t *ret) {
×
903
        assert(fd >= 0);
×
904
        assert(ret);
×
905

906
        /* Safely returns the current available disk space (for root, i.e. including any space reserved for
907
         * root) of the disk referenced by the fd, converted to bytes. */
908

909
        struct statvfs sv;
×
910
        if (fstatvfs(fd, &sv) < 0)
×
911
                return -errno;
×
912

913
        if (!MUL_SAFE(ret, (uint64_t) sv.f_frsize, (uint64_t) sv.f_bfree))
×
914
                return -ERANGE;
×
915

916
        return 0;
917
}
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