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

systemd / systemd / 26546993077

27 May 2026 08:34PM UTC coverage: 72.995% (+0.3%) from 72.667%
26546993077

push

github

bluca
test-pressure: set timeout to make not wait forever

If this runs on a slow or busy machine, then we may not get enough
pressure to trigger the event sources. In such case, the test does not
finish. It is problematic when the test is _not_ run with 'meson test',
e.g. debian/ubuntu CIs.

Let's introduce a timeout for each event loop, and skip test cases
gracefully.

8 of 12 new or added lines in 1 file covered. (66.67%)

19671 existing lines in 226 files now uncovered.

337119 of 461841 relevant lines covered (72.99%)

1326365.62 hits per line

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

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

3
#include <fcntl.h>
4
#include <sys/mount.h>
5

6
#include "alloc-util.h"
7
#include "chase.h"
8
#include "errno-util.h"
9
#include "fd-util.h"
10
#include "fileio.h"
11
#include "filesystems.h"
12
#include "fs-util.h"
13
#include "log.h"
14
#include "mountpoint-util.h"
15
#include "nulstr-util.h"
16
#include "path-util.h"
17
#include "stat-util.h"
18
#include "string-util.h"
19
#include "strv.h"
20
#include "unaligned.h"
21

22
/* This is the original MAX_HANDLE_SZ definition from the kernel, when the API was introduced. We use that in place of
23
 * any more currently defined value to future-proof things: if the size is increased in the API headers, and our code
24
 * is recompiled then it would cease working on old kernels, as those refuse any sizes larger than this value with
25
 * EINVAL right-away. Hence, let's disconnect ourselves from any such API changes, and stick to the original definition
26
 * from when it was introduced. We use it as a start value only anyway (see below), and hence should be able to deal
27
 * with large file handles anyway. */
28
#define ORIGINAL_MAX_HANDLE_SZ 128
29

30
bool is_name_to_handle_at_fatal_error(int err) {
×
31
        /* name_to_handle_at() can return "acceptable" errors that are due to the context. For example
32
         * the file system does not support name_to_handle_at() (EOPNOTSUPP), or the syscall was blocked
33
         * (EACCES/EPERM; maybe through seccomp, because we are running inside of a container), or
34
         * the mount point is not triggered yet (EOVERFLOW, think autofs+nfs4), or some general name_to_handle_at()
35
         * flakiness (EINVAL). However other errors are not supposed to happen and therefore are considered
36
         * fatal ones. */
37

38
        assert(err < 0);
×
39

40
        if (ERRNO_IS_NEG_NOT_SUPPORTED(err))
×
41
                return false;
42
        if (ERRNO_IS_NEG_PRIVILEGE(err))
×
43
                return false;
44

45
        return !IN_SET(err, -EOVERFLOW, -EINVAL);
×
46
}
47

48
int name_to_handle_at_loop(
112,551✔
49
                int fd,
50
                const char *path,
51
                struct file_handle **ret_handle,
52
                int *ret_mnt_id,
53
                uint64_t *ret_unique_mnt_id,
54
                int flags) {
55

56
        int r;
112,551✔
57

58
        assert(fd >= 0 || fd == AT_FDCWD);
112,551✔
59
        assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH|AT_HANDLE_FID)) == 0);
112,551✔
60

61
        /* We need to invoke name_to_handle_at() in a loop, given that it might return EOVERFLOW when the specified
62
         * buffer is too small. Note that in contrast to what the docs might suggest, MAX_HANDLE_SZ is only good as a
63
         * start value, it is not an upper bound on the buffer size required.
64
         *
65
         * This improves on raw name_to_handle_at() also in one other regard: ret_handle and ret_mnt_id can be passed
66
         * as NULL if there's no interest in either.
67
         *
68
         * If unique mount id is requested via ret_unique_mnt_id, try AT_HANDLE_MNT_ID_UNIQUE flag first
69
         * (needs kernel v6.12), and fall back to statx() if not supported. If neither worked, and caller
70
         * also specifies ret_mnt_id, then the old-style mount id is returned, -EUNATCH otherwise. */
71

72
        if (isempty(path)) {
112,551✔
73
                flags |= AT_EMPTY_PATH;
100,510✔
74
                path = "";
100,510✔
75
        }
76

77
        for (size_t n = ORIGINAL_MAX_HANDLE_SZ;;) {
112,551✔
78
                _cleanup_free_ struct file_handle *h = NULL;
112,551✔
79

80
                h = malloc0(offsetof(struct file_handle, f_handle) + n);
112,551✔
81
                if (!h)
112,551✔
82
                        return -ENOMEM;
83

84
                h->handle_bytes = n;
112,551✔
85

86
                if (ret_unique_mnt_id) {
112,551✔
87
                        /* Here, explicitly initialize mnt_id, otherwise valgrind complains:
88
                         *
89
                         * ==175708== Conditional jump or move depends on uninitialised value(s)
90
                         * ==175708==    at 0x4BC33D1: inode_same_at (stat-util.c:610)
91
                         * ==175708==    by 0x4BF1972: inode_same (stat-util.h:86)
92
                         */
93
                        uint64_t mnt_id = 0;
52,244✔
94

95
                        /* The kernel will still use this as uint64_t pointer */
96
                        r = name_to_handle_at(fd, path, h, (int *) &mnt_id, flags|AT_HANDLE_MNT_ID_UNIQUE);
52,244✔
97
                        if (r >= 0) {
52,244✔
98
                                if (ret_handle)
52,244✔
99
                                        *ret_handle = TAKE_PTR(h);
52,244✔
100

101
                                *ret_unique_mnt_id = mnt_id;
52,244✔
102

103
                                if (ret_mnt_id)
52,244✔
104
                                        *ret_mnt_id = -1;
30,683✔
105

106
                                return 1;
52,244✔
107
                        }
108
                        if (errno == EOVERFLOW)
×
109
                                goto grow;
×
110
                        if (errno != EINVAL)
×
111
                                return -errno;
×
112
                }
113

114
                int mnt_id;
60,307✔
115
                r = name_to_handle_at(fd, path, h, &mnt_id, flags);
60,307✔
116
                if (r >= 0) {
60,307✔
117
                        if (ret_unique_mnt_id) {
60,307✔
118
                                /* Hmm, AT_HANDLE_MNT_ID_UNIQUE is not supported? Let's try to acquire
119
                                 * the unique mount id from statx() then, which has a slightly lower
120
                                 * kernel version requirement (6.8 vs 6.12). */
121

122
                                struct statx sx;
×
123
                                r = xstatx(fd, path,
×
124
                                           at_flags_normalize_nofollow(flags & (AT_SYMLINK_FOLLOW|AT_EMPTY_PATH))|AT_STATX_DONT_SYNC,
×
125
                                           STATX_MNT_ID_UNIQUE,
126
                                           &sx);
127
                                if (r >= 0) {
×
128
                                        if (ret_handle)
×
129
                                                *ret_handle = TAKE_PTR(h);
×
130

131
                                        *ret_unique_mnt_id = sx.stx_mnt_id;
×
132

133
                                        if (ret_mnt_id)
×
134
                                                *ret_mnt_id = -1;
×
135

136
                                        return 1;
×
137
                                }
138
                                if (r != -EUNATCH || !ret_mnt_id)
×
139
                                        return r;
140

141
                                *ret_unique_mnt_id = 0;
×
142
                        }
143

144
                        if (ret_handle)
60,307✔
145
                                *ret_handle = TAKE_PTR(h);
60,307✔
146

147
                        if (ret_mnt_id)
60,307✔
148
                                *ret_mnt_id = mnt_id;
9✔
149

150
                        return 0;
151
                }
152
                if (errno != EOVERFLOW)
×
153
                        return -errno;
×
154

155
        grow:
×
156
                /* If name_to_handle_at() didn't increase the byte size, then this EOVERFLOW is caused by
157
                 * something else (apparently EOVERFLOW is returned for untriggered nfs4 autofs mounts
158
                 * sometimes), not by the too small buffer. In that case propagate EOVERFLOW */
159
                if (h->handle_bytes <= n)
×
160
                        return -EOVERFLOW;
161

162
                /* The buffer was too small. Size the new buffer by what name_to_handle_at() returned. */
163
                n = h->handle_bytes;
×
164

165
                /* paranoia: check for overflow (note that .handle_bytes is unsigned only) */
166
                if (n > UINT_MAX - offsetof(struct file_handle, f_handle))
×
167
                        return -EOVERFLOW;
168
        }
169
}
170

171
int name_to_handle_at_try_fid(
52,253✔
172
                int fd,
173
                const char *path,
174
                struct file_handle **ret_handle,
175
                int *ret_mnt_id,
176
                uint64_t *ret_unique_mnt_id,
177
                int flags) {
178

179
        int r;
52,253✔
180

181
        assert(fd >= 0 || fd == AT_FDCWD);
52,253✔
182

183
        /* First issues name_to_handle_at() with AT_HANDLE_FID. If this fails and this is not a fatal error
184
         * we'll try without the flag, in order to support older kernels that didn't have AT_HANDLE_FID
185
         * (i.e. older than Linux 6.5). */
186

187
        r = name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, ret_unique_mnt_id, flags | AT_HANDLE_FID);
52,253✔
188
        if (r >= 0 || is_name_to_handle_at_fatal_error(r))
52,253✔
189
                return r;
190

191
        return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, ret_unique_mnt_id, flags & ~AT_HANDLE_FID);
×
192
}
193

194
int name_to_handle_at_u64(int fd, const char *path, uint64_t *ret) {
60,298✔
195
        _cleanup_free_ struct file_handle *h = NULL;
60,298✔
196
        int r;
60,298✔
197

198
        assert(fd >= 0 || fd == AT_FDCWD);
60,298✔
199

200
        /* This provides the first 64bit of the file handle. */
201

202
        r = name_to_handle_at_loop(fd, path, &h, /* ret_mnt_id= */ NULL, /* ret_unique_mnt_id= */ NULL, /* flags= */ 0);
60,298✔
203
        if (r < 0)
60,298✔
204
                return r;
205
        if (h->handle_bytes < sizeof(uint64_t))
60,298✔
206
                return -EBADMSG;
207

208
        if (ret)
60,298✔
209
                /* Note, "struct file_handle" is 32bit aligned usually, but we need to read a 64bit value from it */
210
                *ret = unaligned_read_ne64(h->f_handle);
60,297✔
211

212
        return 0;
213
}
214

215
bool file_handle_equal(const struct file_handle *a, const struct file_handle *b) {
21,468✔
216
        if (a == b)
21,468✔
217
                return true;
218
        if (!a != !b)
21,468✔
219
                return false;
220
        if (a->handle_type != b->handle_type)
21,468✔
221
                return false;
222

223
        return memcmp_nn(a->f_handle, a->handle_bytes, b->f_handle, b->handle_bytes) == 0;
21,446✔
224
}
225

226
struct file_handle* file_handle_dup(const struct file_handle *fh) {
9,135✔
227
        _cleanup_free_ struct file_handle *fh_copy = NULL;
18,270✔
228

229
        assert(fh);
9,135✔
230

231
        fh_copy = malloc0(offsetof(struct file_handle, f_handle) + fh->handle_bytes);
9,135✔
232
        if (!fh_copy)
9,135✔
233
                return NULL;
234

235
        fh_copy->handle_bytes = fh->handle_bytes;
9,135✔
236
        fh_copy->handle_type = fh->handle_type;
9,135✔
237
        memcpy(fh_copy->f_handle, fh->f_handle, fh->handle_bytes);
9,135✔
238

239
        return TAKE_PTR(fh_copy);
9,135✔
240
}
241

242
int is_mount_point_at(int dir_fd, const char *path, int flags) {
84,066✔
243
        int r;
84,066✔
244

245
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
84,066✔
246
        assert((flags & ~AT_SYMLINK_FOLLOW) == 0);
84,066✔
247

248
        if (path_equal(path, "/"))
84,066✔
249
                return true;
84,066✔
250

251
        if (dir_fd == XAT_FDROOT && isempty(path))
84,005✔
252
                return true;
253

254
        struct statx sx;
84,005✔
255
        r = xstatx_full(dir_fd, path,
84,005✔
256
                        at_flags_normalize_nofollow(flags) |
84,005✔
257
                        AT_NO_AUTOMOUNT |            /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */
258
                        AT_STATX_DONT_SYNC,          /* don't go to the network for this – for similar reasons */
259
                        /* xstatx_flags = */ 0,
260
                        STATX_TYPE|STATX_INO,
261
                        /* optional_mask = */ 0,
262
                        STATX_ATTR_MOUNT_ROOT,
263
                        &sx);
264
        if (r < 0)
84,005✔
265
                return r;
266

267
        if (FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT))
81,363✔
268
                return true;
269

270
        /* When running on chroot environment, the root may not be a mount point, but we unconditionally
271
         * return true when the input is "/" in the above, but the shortcut may not work e.g. when the path
272
         * is relative. */
273
        struct statx sx2;
71,846✔
274
        r = xstatx(AT_FDCWD,
71,846✔
275
                   "/",
276
                   AT_STATX_DONT_SYNC,
277
                   STATX_TYPE|STATX_INO,
278
                   &sx2);
279
        if (r < 0)
71,846✔
280
                return r;
281

282
        return statx_inode_same(&sx, &sx2);
71,846✔
283
}
284

285
/* flags can be AT_SYMLINK_FOLLOW or 0 */
286
int path_is_mount_point_full(const char *path, const char *root, int flags) {
31,092✔
287
        _cleanup_close_ int dir_fd = -EBADF;
31,092✔
288
        int r;
31,092✔
289

290
        assert(path);
31,092✔
291
        assert((flags & ~AT_SYMLINK_FOLLOW) == 0);
31,092✔
292

293
        if (empty_or_root(root))
31,092✔
294
                return is_mount_point_at(AT_FDCWD, path, flags);
21,573✔
295

296
        r = chase(path, root,
9,519✔
297
                  FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : CHASE_NOFOLLOW,
9,519✔
298
                  /* ret_path= */ NULL, &dir_fd);
299
        if (r < 0)
9,519✔
300
                return r;
301

302
        return is_mount_point_at(dir_fd, /* path= */ NULL, flags);
8,005✔
303
}
304

305
static int path_get_mnt_id_at_internal(int dir_fd, const char *path, bool unique, uint64_t *ret) {
10,047✔
306
        struct statx sx;
10,047✔
307
        int r;
10,047✔
308

309
        assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
10,047✔
310
        assert(ret);
10,047✔
311

312
        r = xstatx(dir_fd, path,
10,047✔
313
                   AT_SYMLINK_NOFOLLOW |
314
                   AT_NO_AUTOMOUNT |    /* don't trigger automounts, mnt_id is a local concept */
315
                   AT_STATX_DONT_SYNC,  /* don't go to the network, mnt_id is a local concept */
316
                   unique ? STATX_MNT_ID_UNIQUE : STATX_MNT_ID,
317
                   &sx);
318
        if (r < 0)
10,047✔
319
                return r;
10,047✔
320

321
        *ret = sx.stx_mnt_id;
10,046✔
322
        return 0;
10,046✔
323
}
324

325
int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) {
10,047✔
326
        uint64_t mnt_id;
10,047✔
327
        int r;
10,047✔
328

329
        assert(ret);
10,047✔
330

331
        r = path_get_mnt_id_at_internal(dir_fd, path, /* unique = */ false, &mnt_id);
10,047✔
332
        if (r < 0)
10,047✔
333
                return r;
10,047✔
334

335
        assert(mnt_id <= INT_MAX);
10,046✔
336
        *ret = (int) mnt_id;
10,046✔
337
        return 0;
10,046✔
338
}
339

340
int path_get_unique_mnt_id_at(int dir_fd, const char *path, uint64_t *ret) {
×
341
        return path_get_mnt_id_at_internal(dir_fd, path, /* unique = */ true, ret);
×
342
}
343

344
bool fstype_is_network(const char *fstype) {
2,848✔
345
        const char *x;
2,848✔
346

347
        x = startswith(fstype, "fuse.");
2,848✔
348
        if (x)
2,848✔
349
                fstype = x;
×
350

351
        if (nulstr_contains(filesystem_sets[FILESYSTEM_SET_NETWORK].value, fstype))
2,848✔
352
                return true;
2,848✔
353

354
        /* Filesystems not present in the internal database */
355
        return STR_IN_SET(fstype,
2,834✔
356
                          "davfs",
357
                          "glusterfs",
358
                          "lustre",
359
                          "sshfs");
360
}
361

362
bool fstype_needs_quota(const char *fstype) {
×
363
       /* 1. quotacheck needs to be run for some filesystems after they are mounted
364
        *    if the filesystem was not unmounted cleanly.
365
        * 2. You may need to run quotaon to enable quota usage tracking and/or
366
        *    enforcement.
367
        * ext2     - needs 1) and 2)
368
        * ext3     - needs 2) if configured using usrjquota/grpjquota mount options
369
        * ext4     - needs 1) if created without journal, needs 2) if created without QUOTA
370
        *            filesystem feature
371
        * reiserfs - needs 2).
372
        * jfs      - needs 2)
373
        * f2fs     - needs 2) if configured using usrjquota/grpjquota/prjjquota mount options
374
        * xfs      - nothing needed
375
        * gfs2     - nothing needed
376
        * ocfs2    - nothing needed
377
        * btrfs    - nothing needed
378
        * for reference see filesystem and quota manpages */
379
        return STR_IN_SET(fstype,
×
380
                          "ext2",
381
                          "ext3",
382
                          "ext4",
383
                          "reiserfs",
384
                          "jfs",
385
                          "f2fs");
386
}
387

UNCOV
388
bool fstype_has_internal_quota(const char *fstype) {
×
389
        /* These filesystems have built-in quota support and do not need
390
         * external quotacheck/quotaon services - see the "nothing needed"
391
         * entries in fstype_needs_quota() above. */
UNCOV
392
        return STR_IN_SET(fstype,
×
393
                          "xfs",
394
                          "gfs2",
395
                          "ocfs2",
396
                          "btrfs");
397
}
398

399
bool fstype_is_api_vfs(const char *fstype) {
54✔
400
        assert(fstype);
54✔
401

402
        const FilesystemSet *fs;
54✔
403
        FOREACH_ARGUMENT(fs,
243✔
404
                         filesystem_sets + FILESYSTEM_SET_BASIC_API,
405
                         filesystem_sets + FILESYSTEM_SET_AUXILIARY_API,
406
                         filesystem_sets + FILESYSTEM_SET_PRIVILEGED_API,
407
                         filesystem_sets + FILESYSTEM_SET_TEMPORARY)
408
                if (nulstr_contains(fs->value, fstype))
216✔
409
                    return true;
27✔
410

411
        /* Filesystems not present in the internal database */
412
        return STR_IN_SET(fstype,
27✔
413
                          "autofs",
414
                          "cpuset");
415
}
416

417
bool fstype_is_blockdev_backed(const char *fstype) {
29✔
418
        const char *x;
29✔
419

420
        x = startswith(fstype, "fuse.");
29✔
421
        if (x)
29✔
UNCOV
422
                fstype = x;
×
423

424
        return !STR_IN_SET(fstype, "9p", "overlay") && !fstype_is_network(fstype) && !fstype_is_api_vfs(fstype);
29✔
425
}
426

427
bool fstype_is_ro(const char *fstype) {
3,095✔
428
        /* All Linux file systems that are necessarily read-only */
429
        return STR_IN_SET(fstype,
3,095✔
430
                          "DM_verity_hash",
431
                          "cramfs",
432
                          "erofs",
433
                          "iso9660",
434
                          "squashfs");
435
}
436

437
bool fstype_can_discard(const char *fstype) {
11✔
438
        assert(fstype);
11✔
439

440
        /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might
441
         * not be allowed in our MAC context. */
442
        if (STR_IN_SET(fstype, "btrfs", "f2fs", "ext4", "vfat", "xfs"))
11✔
443
                return true;
8✔
444

445
        /* On new kernels we can just ask the kernel */
446
        return mount_option_supported(fstype, "discard", NULL) > 0;
3✔
447
}
448

449
const char* fstype_norecovery_option(const char *fstype) {
261✔
450
        int r;
261✔
451

452
        assert(fstype);
261✔
453

454
        /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might
455
         * not be allowed in our MAC context. */
456
        if (STR_IN_SET(fstype, "ext3", "ext4", "xfs"))
261✔
457
                return "norecovery";
21✔
458

459
        /* btrfs dropped support for the "norecovery" option in 6.8
460
         * (https://github.com/torvalds/linux/commit/a1912f712188291f9d7d434fba155461f1ebef66) and replaced
461
         * it with rescue=nologreplay, so we check for the new name first and fall back to checking for the
462
         * old name if the new name doesn't work. */
463
        if (streq(fstype, "btrfs")) {
240✔
UNCOV
464
                r = mount_option_supported(fstype, "rescue=nologreplay", NULL);
×
UNCOV
465
                if (r == -EAGAIN) {
×
UNCOV
466
                        log_debug_errno(r, "Failed to check for btrfs 'rescue=nologreplay' option, assuming old kernel with 'norecovery': %m");
×
467
                        return "norecovery";
468
                }
UNCOV
469
                if (r < 0)
×
UNCOV
470
                        log_debug_errno(r, "Failed to check for btrfs 'rescue=nologreplay' option, assuming it is not supported: %m");
×
UNCOV
471
                if (r > 0)
×
472
                        return "rescue=nologreplay";
473
        }
474

475
        /* On new kernels we can just ask the kernel */
476
        return mount_option_supported(fstype, "norecovery", NULL) > 0 ? "norecovery" : NULL;
240✔
477
}
478

479
bool fstype_can_fmask_dmask(const char *fstype) {
59✔
480
        assert(fstype);
59✔
481

482
        /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might
483
         * not be allowed in our MAC context. If we don't know ourselves, on new kernels we can just ask the
484
         * kernel. */
485
        return streq(fstype, "vfat") || (mount_option_supported(fstype, "fmask", "0177") > 0 && mount_option_supported(fstype, "dmask", "0077") > 0);
59✔
486
}
487

488
bool fstype_can_uid_gid(const char *fstype) {
1✔
489
        /* All file systems that have a uid=/gid= mount option that fixates the owners of all files and
490
         * directories, current and future. Note that this does *not* ask the kernel via
491
         * mount_option_supported() here because the uid=/gid= setting of various file systems mean different
492
         * things: some apply it only to the root dir inode, others to all inodes in the file system. Thus we
493
         * maintain the curated list below. 😢 */
494

495
        return STR_IN_SET(fstype,
1✔
496
                          "adfs",
497
                          "exfat",
498
                          "fat",
499
                          "hfs",
500
                          "hpfs",
501
                          "iso9660",
502
                          "msdos",
503
                          "ntfs",
504
                          "vfat");
505
}
506

507
int dev_is_devtmpfs(void) {
324✔
508
        _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
324✔
509
        int mount_id, r;
324✔
510
        char *e;
324✔
511

512
        r = path_get_mnt_id("/dev", &mount_id);
324✔
513
        if (r < 0)
324✔
514
                return r;
515

516
        r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo);
324✔
517
        if (r == -ENOENT)
324✔
UNCOV
518
                return proc_mounted() > 0 ? -ENOENT : -ENOSYS;
×
519
        if (r < 0)
324✔
520
                return r;
521

522
        for (;;) {
15,530✔
523
                _cleanup_free_ char *line = NULL;
15,231✔
524
                int mid;
15,530✔
525

526
                r = read_line(proc_self_mountinfo, LONG_LINE_MAX, &line);
15,530✔
527
                if (r < 0)
15,530✔
528
                        return r;
529
                if (r == 0)
15,530✔
530
                        break;
531

532
                if (sscanf(line, "%i", &mid) != 1)
15,231✔
UNCOV
533
                        continue;
×
534

535
                if (mid != mount_id)
15,231✔
536
                        continue;
14,907✔
537

538
                e = strstrafter(line, " - ");
324✔
539
                if (!e)
324✔
UNCOV
540
                        continue;
×
541

542
                /* accept any name that starts with the currently expected type */
543
                if (startswith(e, "devtmpfs"))
324✔
544
                        return true;
545
        }
546

547
        return false;
299✔
548
}
549

550
static int mount_fd(
57,556✔
551
                const char *source,
552
                int target_fd,
553
                const char *filesystemtype,
554
                unsigned long mountflags,
555
                const void *data) {
556

557
        assert(target_fd >= 0);
57,556✔
558

559
        if (mount(source, FORMAT_PROC_FD_PATH(target_fd), filesystemtype, mountflags, data) < 0) {
57,556✔
UNCOV
560
                if (errno != ENOENT)
×
UNCOV
561
                        return -errno;
×
562

563
                /* ENOENT can mean two things: either that the source is missing, or that /proc/ isn't
564
                 * mounted. Check for the latter to generate better error messages. */
UNCOV
565
                if (proc_mounted() == 0)
×
566
                        return -ENOSYS;
567

UNCOV
568
                return -ENOENT;
×
569
        }
570

571
        return 0;
57,556✔
572
}
573

574
int mount_nofollow(
59,097✔
575
                const char *source,
576
                const char *target,
577
                const char *filesystemtype,
578
                unsigned long mountflags,
579
                const void *data) {
580

581
        _cleanup_close_ int fd = -EBADF;
59,097✔
582

583
        assert(target);
59,097✔
584

585
        /* In almost all cases we want to manipulate the mount table without following symlinks, hence
586
         * mount_nofollow() is usually the way to go. The only exceptions are environments where /proc/ is
587
         * not available yet, since we need /proc/self/fd/ for this logic to work. i.e. during the early
588
         * initialization of namespacing/container stuff where /proc is not yet mounted (and maybe even the
589
         * fs to mount) we can only use traditional mount() directly.
590
         *
591
         * Note that this disables following only for the final component of the target, i.e symlinks within
592
         * the path of the target are honoured, as are symlinks in the source path everywhere. */
593

594
        fd = open(target, O_PATH|O_CLOEXEC|O_NOFOLLOW);
59,097✔
595
        if (fd < 0)
59,097✔
596
                return -errno;
1,541✔
597

598
        return mount_fd(source, fd, filesystemtype, mountflags, data);
57,556✔
599
}
600

601
const char* mount_propagation_flag_to_string(unsigned long flags) {
3,674✔
602

603
        switch (flags & (MS_SHARED|MS_SLAVE|MS_PRIVATE)) {
3,674✔
604
        case 0:
605
                return "";
606
        case MS_SHARED:
1✔
607
                return "shared";
1✔
608
        case MS_SLAVE:
1✔
609
                return "slave";
1✔
610
        case MS_PRIVATE:
3✔
611
                return "private";
3✔
612
        }
613

UNCOV
614
        return NULL;
×
615
}
616

617
int mount_propagation_flag_from_string(const char *name, unsigned long *ret) {
11✔
618

619
        POINTER_MAY_BE_NULL(name);
11✔
620
        assert(ret);
11✔
621

622
        if (isempty(name))
11✔
623
                *ret = 0;
2✔
624
        else if (streq(name, "shared"))
9✔
625
                *ret = MS_SHARED;
2✔
626
        else if (streq(name, "slave"))
7✔
627
                *ret = MS_SLAVE;
2✔
628
        else if (streq(name, "private"))
5✔
629
                *ret = MS_PRIVATE;
3✔
630
        else
631
                return -EINVAL;
632
        return 0;
633
}
634

635
bool mount_propagation_flag_is_valid(unsigned long flag) {
2,366✔
636
        return IN_SET(flag, 0, MS_SHARED, MS_PRIVATE, MS_SLAVE);
2,366✔
637
}
638

639
bool mount_new_api_supported(void) {
5,146✔
640
        static int cache = -1;
5,146✔
641
        int r;
5,146✔
642

643
        if (cache >= 0)
5,146✔
644
                return cache;
1,761✔
645

646
        /* This is the newest API among the ones we use, so use it as boundary */
647
        r = RET_NERRNO(mount_setattr(-EBADF, NULL, 0, NULL, 0));
3,385✔
648
        if (r == 0 || ERRNO_IS_NOT_SUPPORTED(r)) /* This should return an error if it is working properly */
3,385✔
UNCOV
649
                return (cache = false);
×
650

651
        return (cache = true);
3,385✔
652
}
653

654
int mount_option_supported(const char *fstype, const char *key, const char *value) {
1,001✔
655
        _cleanup_close_ int fd = -EBADF;
1,001✔
656
        int r;
1,001✔
657

658
        /* Checks if the specified file system supports a mount option. Returns > 0 if it supports it, == 0 if
659
         * it does not. Return -EAGAIN if we can't determine it. And any other error otherwise. */
660

661
        assert(fstype);
1,001✔
662
        assert(key);
1,001✔
663

664
        fd = fsopen(fstype, FSOPEN_CLOEXEC);
1,001✔
665
        if (fd < 0)
1,001✔
UNCOV
666
                return log_debug_errno(errno, "Failed to open superblock context for '%s': %m", fstype);
×
667

668
        /* Various file systems support fs context only in recent kernels (e.g. btrfs). For older kernels
669
         * fsconfig() with FSCONFIG_SET_STRING/FSCONFIG_SET_FLAG never fail. Which sucks, because we want to
670
         * use it for testing support, after all. Let's hence do a check if the file system got converted yet
671
         * first. */
672
        if (fsconfig(fd, FSCONFIG_SET_FD, "adefinitelynotexistingmountoption", NULL, fd) < 0) {
1,001✔
673
                /* If FSCONFIG_SET_FD is not supported for the fs, then the file system was not converted to
674
                 * the new mount API yet. If it returns EINVAL the mount option doesn't exist, but the fstype
675
                 * is converted. */
676
                if (errno == EOPNOTSUPP)
1,001✔
677
                        return -EAGAIN; /* fs not converted to new mount API → don't know */
678
                if (errno != EINVAL)
1,001✔
UNCOV
679
                        return log_debug_errno(errno, "Failed to check if file system '%s' has been converted to new mount API: %m", fstype);
×
680

681
                /* So FSCONFIG_SET_FD worked, but the option didn't exist (we got EINVAL), this means the fs
682
                 * is converted. Let's now ask the actual question we wonder about. */
683
        } else
UNCOV
684
                return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "FSCONFIG_SET_FD worked unexpectedly for '%s', whoa!", fstype);
×
685

686
        if (value)
1,001✔
687
                r = fsconfig(fd, FSCONFIG_SET_STRING, key, value, 0);
30✔
688
        else
689
                r = fsconfig(fd, FSCONFIG_SET_FLAG, key, NULL, 0);
971✔
690
        if (r < 0) {
1,001✔
691
                if (errno == EINVAL)
304✔
692
                        return false; /* EINVAL means option not supported. */
693

UNCOV
694
                return log_debug_errno(errno, "Failed to set '%s%s%s' on '%s' superblock context: %m",
×
695
                                       key, value ? "=" : "", strempty(value), fstype);
696
        }
697

698
        return true; /* works! */
699
}
700

701
bool path_below_api_vfs(const char *p) {
15,667✔
702
        assert(p);
15,667✔
703

704
        /* API VFS are either directly mounted on any of these three paths, or below it. */
705
        return PATH_STARTSWITH_SET(p, "/dev", "/sys", "/proc");
15,667✔
706
}
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