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

systemd / systemd / 20977275812

13 Jan 2026 03:18AM UTC coverage: 72.685% (+0.3%) from 72.39%
20977275812

push

github

web-flow
Bump kernel requirements to >= 5.10, and recommend >= 5.14 (#38977)

Then, this drops several unnecessary code for older kernels.

83 of 98 new or added lines in 14 files covered. (84.69%)

267 existing lines in 44 files now uncovered.

310058 of 426578 relevant lines covered (72.68%)

1143071.97 hits per line

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

79.69
/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

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

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

37
        assert(err < 0);
3,658✔
38

39
        if (ERRNO_IS_NEG_NOT_SUPPORTED(err))
3,658✔
40
                return false;
41
        if (ERRNO_IS_NEG_PRIVILEGE(err))
×
42
                return false;
43

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

47
int name_to_handle_at_loop(
29,127✔
48
                int fd,
49
                const char *path,
50
                struct file_handle **ret_handle,
51
                int *ret_mnt_id,
52
                int flags) {
53

54
        size_t n = ORIGINAL_MAX_HANDLE_SZ;
29,127✔
55

56
        assert(fd >= 0 || fd == AT_FDCWD);
29,127✔
57
        assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH|AT_HANDLE_FID)) == 0);
29,127✔
58

59
        /* We need to invoke name_to_handle_at() in a loop, given that it might return EOVERFLOW when the specified
60
         * buffer is too small. Note that in contrast to what the docs might suggest, MAX_HANDLE_SZ is only good as a
61
         * start value, it is not an upper bound on the buffer size required.
62
         *
63
         * This improves on raw name_to_handle_at() also in one other regard: ret_handle and ret_mnt_id can be passed
64
         * as NULL if there's no interest in either. */
65

66
        for (;;) {
29,127✔
67
                _cleanup_free_ struct file_handle *h = NULL;
×
68
                int mnt_id = -1;
29,127✔
69

70
                h = malloc0(offsetof(struct file_handle, f_handle) + n);
29,127✔
71
                if (!h)
29,127✔
72
                        return -ENOMEM;
73

74
                h->handle_bytes = n;
29,127✔
75

76
                if (name_to_handle_at(fd, strempty(path), h, &mnt_id, flags) >= 0) {
58,245✔
77

78
                        if (ret_handle)
29,127✔
79
                                *ret_handle = TAKE_PTR(h);
29,127✔
80

81
                        if (ret_mnt_id)
29,127✔
82
                                *ret_mnt_id = mnt_id;
29,127✔
83

84
                        return 0;
29,127✔
85
                }
86
                if (errno != EOVERFLOW)
×
87
                        return -errno;
×
88

89
                if (!ret_handle && ret_mnt_id && mnt_id >= 0) {
×
90

91
                        /* As it appears, name_to_handle_at() fills in mnt_id even when it returns EOVERFLOW when the
92
                         * buffer is too small, but that's undocumented. Hence, let's make use of this if it appears to
93
                         * be filled in, and the caller was interested in only the mount ID an nothing else. */
94

95
                        *ret_mnt_id = mnt_id;
×
96
                        return 0;
×
97
                }
98

99
                /* If name_to_handle_at() didn't increase the byte size, then this EOVERFLOW is caused by
100
                 * something else (apparently EOVERFLOW is returned for untriggered nfs4 autofs mounts
101
                 * sometimes), not by the too small buffer. In that case propagate EOVERFLOW */
102
                if (h->handle_bytes <= n)
×
103
                        return -EOVERFLOW;
104

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

108
                /* paranoia: check for overflow (note that .handle_bytes is unsigned only) */
109
                if (n > UINT_MAX - offsetof(struct file_handle, f_handle))
×
110
                        return -EOVERFLOW;
111
        }
112
}
113

114
int name_to_handle_at_try_fid(
29,127✔
115
                int fd,
116
                const char *path,
117
                struct file_handle **ret_handle,
118
                int *ret_mnt_id,
119
                int flags) {
120

121
        int r;
29,127✔
122

123
        assert(fd >= 0 || fd == AT_FDCWD);
29,127✔
124

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

129
        r = name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, flags | AT_HANDLE_FID);
29,127✔
130
        if (r >= 0 || is_name_to_handle_at_fatal_error(r))
29,127✔
131
                return r;
29,127✔
132

133
        return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, flags & ~AT_HANDLE_FID);
×
134
}
135

136
bool file_handle_equal(const struct file_handle *a, const struct file_handle *b) {
13,597✔
137
        if (a == b)
13,597✔
138
                return true;
139
        if (!a != !b)
13,597✔
140
                return false;
141
        if (a->handle_type != b->handle_type)
13,597✔
142
                return false;
143

144
        return memcmp_nn(a->f_handle, a->handle_bytes, b->f_handle, b->handle_bytes) == 0;
13,578✔
145
}
146

147
int is_mount_point_at(int dir_fd, const char *path, int flags) {
83,539✔
148
        int r;
83,539✔
149

150
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
83,539✔
151
        assert((flags & ~AT_SYMLINK_FOLLOW) == 0);
83,539✔
152

153
        if (path_equal(path, "/"))
83,539✔
154
                return true;
83,539✔
155

156
        if (isempty(path)) {
83,479✔
157
                if (dir_fd == AT_FDCWD)
7,819✔
158
                        path = ".";
159
                else {
160
                        flags |= AT_EMPTY_PATH;
7,817✔
161
                        path = "";
7,817✔
162
                }
163
        }
164

165
        struct statx sx = {}; /* explicitly initialize the struct to make msan silent. */
83,479✔
166
        if (statx(dir_fd, path,
83,479✔
167
                  at_flags_normalize_nofollow(flags) |
83,479✔
168
                  AT_NO_AUTOMOUNT |            /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */
169
                  AT_STATX_DONT_SYNC,          /* don't go to the network for this – for similar reasons */
170
                  STATX_TYPE|STATX_INO,
171
                  &sx) < 0)
172
                return -errno;
2,462✔
173

174
        r = statx_warn_mount_root(&sx, LOG_DEBUG);
81,017✔
175
        if (r < 0)
81,017✔
176
                return r;
177

178
        if (FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT))
81,017✔
179
                return true;
180

181
        /* When running on chroot environment, the root may not be a mount point, but we unconditionally
182
         * return true when the input is "/" in the above, but the shortcut may not work e.g. when the path
183
         * is relative. */
184
        struct statx sx2 = {}; /* explicitly initialize the struct to make msan silent. */
72,509✔
185
        if (statx(AT_FDCWD, "/", AT_STATX_DONT_SYNC, STATX_TYPE|STATX_INO, &sx2) < 0)
72,509✔
UNCOV
186
                return -errno;
×
187

188
        return statx_inode_same(&sx, &sx2);
72,509✔
189
}
190

191
/* flags can be AT_SYMLINK_FOLLOW or 0 */
192
int path_is_mount_point_full(const char *path, const char *root, int flags) {
26,855✔
193
        _cleanup_close_ int dir_fd = -EBADF;
26,855✔
194
        int r;
26,855✔
195

196
        assert(path);
26,855✔
197
        assert((flags & ~AT_SYMLINK_FOLLOW) == 0);
26,855✔
198

199
        if (empty_or_root(root))
26,855✔
200
                return is_mount_point_at(AT_FDCWD, path, flags);
17,821✔
201

202
        r = chase(path, root,
9,034✔
203
                  FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : CHASE_NOFOLLOW,
9,034✔
204
                  /* ret_path= */ NULL, &dir_fd);
205
        if (r < 0)
9,034✔
206
                return r;
207

208
        return is_mount_point_at(dir_fd, /* path= */ NULL, flags);
7,597✔
209
}
210

211
int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) {
6,915✔
212
        struct statx sx;
6,915✔
213
        int r;
6,915✔
214

215
        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
6,915✔
216
        assert(ret);
6,915✔
217

218
        if (statx(dir_fd,
13,830✔
219
                  strempty(path),
6,915✔
220
                  (isempty(path) ? AT_EMPTY_PATH : AT_SYMLINK_NOFOLLOW) |
6,915✔
221
                  AT_NO_AUTOMOUNT |    /* don't trigger automounts, mnt_id is a local concept */
222
                  AT_STATX_DONT_SYNC,  /* don't go to the network, mnt_id is a local concept */
223
                  STATX_MNT_ID,
224
                  &sx) < 0)
225
                return -errno;
1✔
226

227
        r = statx_warn_mount_id(&sx, LOG_DEBUG);
6,914✔
228
        if (r < 0)
6,914✔
229
                return r;
230

231
        *ret = sx.stx_mnt_id;
6,914✔
232
        return 0;
6,914✔
233
}
234

235
bool fstype_is_network(const char *fstype) {
1,748✔
236
        const char *x;
1,748✔
237

238
        x = startswith(fstype, "fuse.");
1,748✔
239
        if (x)
1,748✔
240
                fstype = x;
×
241

242
        if (nulstr_contains(filesystem_sets[FILESYSTEM_SET_NETWORK].value, fstype))
1,748✔
243
                return true;
1,748✔
244

245
        /* Filesystems not present in the internal database */
246
        return STR_IN_SET(fstype,
1,744✔
247
                          "davfs",
248
                          "glusterfs",
249
                          "lustre",
250
                          "sshfs");
251
}
252

253
bool fstype_needs_quota(const char *fstype) {
×
254
       /* 1. quotacheck needs to be run for some filesystems after they are mounted
255
        *    if the filesystem was not unmounted cleanly.
256
        * 2. You may need to run quotaon to enable quota usage tracking and/or
257
        *    enforcement.
258
        * ext2     - needs 1) and 2)
259
        * ext3     - needs 2) if configured using usrjquota/grpjquota mount options
260
        * ext4     - needs 1) if created without journal, needs 2) if created without QUOTA
261
        *            filesystem feature
262
        * reiserfs - needs 2).
263
        * jfs      - needs 2)
264
        * f2fs     - needs 2) if configured using usrjquota/grpjquota/prjjquota mount options
265
        * xfs      - nothing needed
266
        * gfs2     - nothing needed
267
        * ocfs2    - nothing needed
268
        * btrfs    - nothing needed
269
        * for reference see filesystem and quota manpages */
270
        return STR_IN_SET(fstype,
×
271
                          "ext2",
272
                          "ext3",
273
                          "ext4",
274
                          "reiserfs",
275
                          "jfs",
276
                          "f2fs");
277
}
278

279
bool fstype_is_api_vfs(const char *fstype) {
58✔
280
        assert(fstype);
58✔
281

282
        const FilesystemSet *fs;
58✔
283
        FOREACH_ARGUMENT(fs,
263✔
284
                         filesystem_sets + FILESYSTEM_SET_BASIC_API,
285
                         filesystem_sets + FILESYSTEM_SET_AUXILIARY_API,
286
                         filesystem_sets + FILESYSTEM_SET_PRIVILEGED_API,
287
                         filesystem_sets + FILESYSTEM_SET_TEMPORARY)
288
                if (nulstr_contains(fs->value, fstype))
232✔
289
                    return true;
27✔
290

291
        /* Filesystems not present in the internal database */
292
        return STR_IN_SET(fstype,
31✔
293
                          "autofs",
294
                          "cpuset",
295
                          "devtmpfs");
296
}
297

298
bool fstype_is_blockdev_backed(const char *fstype) {
29✔
299
        const char *x;
29✔
300

301
        x = startswith(fstype, "fuse.");
29✔
302
        if (x)
29✔
303
                fstype = x;
×
304

305
        return !streq(fstype, "9p") && !fstype_is_network(fstype) && !fstype_is_api_vfs(fstype);
29✔
306
}
307

308
bool fstype_is_ro(const char *fstype) {
3,773✔
309
        /* All Linux file systems that are necessarily read-only */
310
        return STR_IN_SET(fstype,
3,773✔
311
                          "DM_verity_hash",
312
                          "cramfs",
313
                          "erofs",
314
                          "iso9660",
315
                          "squashfs");
316
}
317

318
bool fstype_can_discard(const char *fstype) {
6✔
319
        assert(fstype);
6✔
320

321
        /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might
322
         * not be allowed in our MAC context. */
323
        if (STR_IN_SET(fstype, "btrfs", "f2fs", "ext4", "vfat", "xfs"))
6✔
324
                return true;
3✔
325

326
        /* On new kernels we can just ask the kernel */
327
        return mount_option_supported(fstype, "discard", NULL) > 0;
3✔
328
}
329

330
const char* fstype_norecovery_option(const char *fstype) {
191✔
331
        int r;
191✔
332

333
        assert(fstype);
191✔
334

335
        /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might
336
         * not be allowed in our MAC context. */
337
        if (STR_IN_SET(fstype, "ext3", "ext4", "xfs"))
191✔
338
                return "norecovery";
18✔
339

340
        /* btrfs dropped support for the "norecovery" option in 6.8
341
         * (https://github.com/torvalds/linux/commit/a1912f712188291f9d7d434fba155461f1ebef66) and replaced
342
         * it with rescue=nologreplay, so we check for the new name first and fall back to checking for the
343
         * old name if the new name doesn't work. */
344
        if (streq(fstype, "btrfs")) {
173✔
345
                r = mount_option_supported(fstype, "rescue=nologreplay", NULL);
×
346
                if (r == -EAGAIN) {
×
347
                        log_debug_errno(r, "Failed to check for btrfs 'rescue=nologreplay' option, assuming old kernel with 'norecovery': %m");
×
348
                        return "norecovery";
×
349
                }
350
                if (r < 0)
×
351
                        log_debug_errno(r, "Failed to check for btrfs 'rescue=nologreplay' option, assuming it is not supported: %m");
×
352
                if (r > 0)
×
353
                        return "rescue=nologreplay";
354
        }
355

356
        /* On new kernels we can just ask the kernel */
357
        return mount_option_supported(fstype, "norecovery", NULL) > 0 ? "norecovery" : NULL;
173✔
358
}
359

360
bool fstype_can_fmask_dmask(const char *fstype) {
57✔
361
        assert(fstype);
57✔
362

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

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

376
        return STR_IN_SET(fstype,
1✔
377
                          "adfs",
378
                          "exfat",
379
                          "fat",
380
                          "hfs",
381
                          "hpfs",
382
                          "iso9660",
383
                          "msdos",
384
                          "ntfs",
385
                          "vfat");
386
}
387

388
int dev_is_devtmpfs(void) {
316✔
389
        _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
316✔
390
        int mount_id, r;
316✔
391
        char *e;
316✔
392

393
        r = path_get_mnt_id("/dev", &mount_id);
316✔
394
        if (r < 0)
316✔
395
                return r;
396

397
        r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo);
316✔
398
        if (r == -ENOENT)
316✔
399
                return proc_mounted() > 0 ? -ENOENT : -ENOSYS;
×
400
        if (r < 0)
316✔
401
                return r;
402

403
        for (;;) {
14,379✔
404
                _cleanup_free_ char *line = NULL;
14,080✔
405
                int mid;
14,379✔
406

407
                r = read_line(proc_self_mountinfo, LONG_LINE_MAX, &line);
14,379✔
408
                if (r < 0)
14,379✔
409
                        return r;
410
                if (r == 0)
14,379✔
411
                        break;
412

413
                if (sscanf(line, "%i", &mid) != 1)
14,080✔
414
                        continue;
×
415

416
                if (mid != mount_id)
14,080✔
417
                        continue;
13,764✔
418

419
                e = strstrafter(line, " - ");
316✔
420
                if (!e)
316✔
421
                        continue;
×
422

423
                /* accept any name that starts with the currently expected type */
424
                if (startswith(e, "devtmpfs"))
316✔
425
                        return true;
426
        }
427

428
        return false;
299✔
429
}
430

431
static int mount_fd(
52,564✔
432
                const char *source,
433
                int target_fd,
434
                const char *filesystemtype,
435
                unsigned long mountflags,
436
                const void *data) {
437

438
        assert(target_fd >= 0);
52,564✔
439

440
        if (mount(source, FORMAT_PROC_FD_PATH(target_fd), filesystemtype, mountflags, data) < 0) {
52,564✔
441
                if (errno != ENOENT)
1,128✔
442
                        return -errno;
1,128✔
443

444
                /* ENOENT can mean two things: either that the source is missing, or that /proc/ isn't
445
                 * mounted. Check for the latter to generate better error messages. */
446
                if (proc_mounted() == 0)
555✔
447
                        return -ENOSYS;
448

449
                return -ENOENT;
555✔
450
        }
451

452
        return 0;
51,436✔
453
}
454

455
int mount_nofollow(
54,040✔
456
                const char *source,
457
                const char *target,
458
                const char *filesystemtype,
459
                unsigned long mountflags,
460
                const void *data) {
461

462
        _cleanup_close_ int fd = -EBADF;
54,040✔
463

464
        assert(target);
54,040✔
465

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

475
        fd = open(target, O_PATH|O_CLOEXEC|O_NOFOLLOW);
54,040✔
476
        if (fd < 0)
54,040✔
477
                return -errno;
1,476✔
478

479
        return mount_fd(source, fd, filesystemtype, mountflags, data);
52,564✔
480
}
481

482
const char* mount_propagation_flag_to_string(unsigned long flags) {
56✔
483

484
        switch (flags & (MS_SHARED|MS_SLAVE|MS_PRIVATE)) {
56✔
485
        case 0:
486
                return "";
487
        case MS_SHARED:
1✔
488
                return "shared";
1✔
489
        case MS_SLAVE:
1✔
490
                return "slave";
1✔
491
        case MS_PRIVATE:
3✔
492
                return "private";
3✔
493
        }
494

495
        return NULL;
×
496
}
497

498
int mount_propagation_flag_from_string(const char *name, unsigned long *ret) {
11✔
499

500
        if (isempty(name))
11✔
501
                *ret = 0;
2✔
502
        else if (streq(name, "shared"))
9✔
503
                *ret = MS_SHARED;
2✔
504
        else if (streq(name, "slave"))
7✔
505
                *ret = MS_SLAVE;
2✔
506
        else if (streq(name, "private"))
5✔
507
                *ret = MS_PRIVATE;
3✔
508
        else
509
                return -EINVAL;
510
        return 0;
511
}
512

513
bool mount_propagation_flag_is_valid(unsigned long flag) {
2,238✔
514
        return IN_SET(flag, 0, MS_SHARED, MS_PRIVATE, MS_SLAVE);
2,238✔
515
}
516

517
bool mount_new_api_supported(void) {
7,393✔
518
        static int cache = -1;
7,393✔
519
        int r;
7,393✔
520

521
        if (cache >= 0)
7,393✔
522
                return cache;
2,461✔
523

524
        /* This is the newer API among the ones we use, so use it as boundary */
525
        r = RET_NERRNO(mount_setattr(-EBADF, NULL, 0, NULL, 0));
4,932✔
526
        if (r == 0 || ERRNO_IS_NOT_SUPPORTED(r)) /* This should return an error if it is working properly */
4,932✔
527
                return (cache = false);
×
528

529
        return (cache = true);
4,932✔
530
}
531

532
unsigned long ms_nosymfollow_supported(void) {
3,160✔
533
        _cleanup_close_ int fsfd = -EBADF, mntfd = -EBADF;
3,160✔
534
        static int cache = -1;
3,160✔
535

536
        /* Returns MS_NOSYMFOLLOW if it is supported, zero otherwise. */
537

538
        if (cache >= 0)
3,160✔
539
                return cache ? MS_NOSYMFOLLOW : 0;
674✔
540

541
        if (!mount_new_api_supported())
2,486✔
542
                goto not_supported;
×
543

544
        /* Checks if MS_NOSYMFOLLOW is supported (which was added in 5.10). We use the new mount API's
545
         * mount_setattr() call for that, which was added in 5.12, which is close enough. */
546

547
        fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC);
2,486✔
548
        if (fsfd < 0) {
2,486✔
549
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
550
                        goto not_supported;
×
551

552
                log_debug_errno(errno, "Failed to open superblock context for tmpfs: %m");
×
553
                return 0;
×
554
        }
555

556
        if (fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0) {
2,486✔
557
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
558
                        goto not_supported;
×
559

560
                log_debug_errno(errno, "Failed to create tmpfs superblock: %m");
×
561
                return 0;
×
562
        }
563

564
        mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
2,486✔
565
        if (mntfd < 0) {
2,486✔
566
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
567
                        goto not_supported;
×
568

569
                log_debug_errno(errno, "Failed to turn superblock fd into mount fd: %m");
×
570
                return 0;
×
571
        }
572

573
        if (mount_setattr(mntfd, "", AT_EMPTY_PATH|AT_RECURSIVE,
2,486✔
574
                          &(struct mount_attr) {
2,486✔
575
                                  .attr_set = MOUNT_ATTR_NOSYMFOLLOW,
576
                          }, sizeof(struct mount_attr)) < 0) {
577
                if (ERRNO_IS_NOT_SUPPORTED(errno))
×
578
                        goto not_supported;
×
579

580
                log_debug_errno(errno, "Failed to set MOUNT_ATTR_NOSYMFOLLOW mount attribute: %m");
×
581
                return 0;
×
582
        }
583

584
        cache = true;
2,486✔
585
        return MS_NOSYMFOLLOW;
2,486✔
586

587
not_supported:
×
588
        cache = false;
×
589
        return 0;
×
590
}
591

592
int mount_option_supported(const char *fstype, const char *key, const char *value) {
1,681✔
593
        _cleanup_close_ int fd = -EBADF;
1,681✔
594
        int r;
1,681✔
595

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

599
        assert(fstype);
1,681✔
600
        assert(key);
1,681✔
601

602
        fd = fsopen(fstype, FSOPEN_CLOEXEC);
1,681✔
603
        if (fd < 0)
1,681✔
604
                return log_debug_errno(errno, "Failed to open superblock context for '%s': %m", fstype);
×
605

606
        /* Various file systems support fs context only in recent kernels (e.g. btrfs). For older kernels
607
         * fsconfig() with FSCONFIG_SET_STRING/FSCONFIG_SET_FLAG never fail. Which sucks, because we want to
608
         * use it for testing support, after all. Let's hence do a check if the file system got converted yet
609
         * first. */
610
        if (fsconfig(fd, FSCONFIG_SET_FD, "adefinitelynotexistingmountoption", NULL, fd) < 0) {
1,681✔
611
                /* If FSCONFIG_SET_FD is not supported for the fs, then the file system was not converted to
612
                 * the new mount API yet. If it returns EINVAL the mount option doesn't exist, but the fstype
613
                 * is converted. */
614
                if (errno == EOPNOTSUPP)
1,681✔
615
                        return -EAGAIN; /* fs not converted to new mount API → don't know */
616
                if (errno != EINVAL)
1,681✔
617
                        return log_debug_errno(errno, "Failed to check if file system '%s' has been converted to new mount API: %m", fstype);
×
618

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

624
        if (value)
1,681✔
625
                r = fsconfig(fd, FSCONFIG_SET_STRING, key, value, 0);
828✔
626
        else
627
                r = fsconfig(fd, FSCONFIG_SET_FLAG, key, NULL, 0);
853✔
628
        if (r < 0) {
1,681✔
629
                if (errno == EINVAL)
229✔
630
                        return false; /* EINVAL means option not supported. */
631

632
                return log_debug_errno(errno, "Failed to set '%s%s%s' on '%s' superblock context: %m",
×
633
                                       key, value ? "=" : "", strempty(value), fstype);
634
        }
635

636
        return true; /* works! */
637
}
638

639
bool path_below_api_vfs(const char *p) {
11,151✔
640
        assert(p);
11,151✔
641

642
        /* API VFS are either directly mounted on any of these three paths, or below it. */
643
        return PATH_STARTSWITH_SET(p, "/dev", "/sys", "/proc");
11,151✔
644
}
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