]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/mount-util.c
license: LGPL-2.1+ -> LGPL-2.1-or-later
[thirdparty/systemd.git] / src / shared / mount-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <sys/mount.h>
6 #include <sys/stat.h>
7 #include <sys/statvfs.h>
8 #include <unistd.h>
9
10 #include "alloc-util.h"
11 #include "extract-word.h"
12 #include "fd-util.h"
13 #include "fileio.h"
14 #include "fs-util.h"
15 #include "hashmap.h"
16 #include "libmount-util.h"
17 #include "mount-util.h"
18 #include "mountpoint-util.h"
19 #include "parse-util.h"
20 #include "path-util.h"
21 #include "set.h"
22 #include "stat-util.h"
23 #include "stdio-util.h"
24 #include "string-util.h"
25 #include "strv.h"
26
27 int mount_fd(const char *source,
28 int target_fd,
29 const char *filesystemtype,
30 unsigned long mountflags,
31 const void *data) {
32
33 char path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
34
35 xsprintf(path, "/proc/self/fd/%i", target_fd);
36 if (mount(source, path, filesystemtype, mountflags, data) < 0) {
37 if (errno != ENOENT)
38 return -errno;
39
40 /* ENOENT can mean two things: either that the source is missing, or that /proc/ isn't
41 * mounted. Check for the latter to generate better error messages. */
42 if (proc_mounted() == 0)
43 return -ENOSYS;
44
45 return -ENOENT;
46 }
47
48 return 0;
49 }
50
51 int mount_nofollow(
52 const char *source,
53 const char *target,
54 const char *filesystemtype,
55 unsigned long mountflags,
56 const void *data) {
57
58 _cleanup_close_ int fd = -1;
59
60 /* In almost all cases we want to manipulate the mount table without following symlinks, hence
61 * mount_nofollow() is usually the way to go. The only exceptions are environments where /proc/ is
62 * not available yet, since we need /proc/self/fd/ for this logic to work. i.e. during the early
63 * initialization of namespacing/container stuff where /proc is not yet mounted (and maybe even the
64 * fs to mount) we can only use traditional mount() directly.
65 *
66 * Note that this disables following only for the final component of the target, i.e symlinks within
67 * the path of the target are honoured, as are symlinks in the source path everywhere. */
68
69 fd = open(target, O_PATH|O_CLOEXEC|O_NOFOLLOW);
70 if (fd < 0)
71 return -errno;
72
73 return mount_fd(source, fd, filesystemtype, mountflags, data);
74 }
75
76 int umount_recursive(const char *prefix, int flags) {
77 int n = 0, r;
78 bool again;
79
80 /* Try to umount everything recursively below a
81 * directory. Also, take care of stacked mounts, and keep
82 * unmounting them until they are gone. */
83
84 do {
85 _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL;
86 _cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL;
87
88 again = false;
89
90 r = libmount_parse("/proc/self/mountinfo", NULL, &table, &iter);
91 if (r < 0)
92 return log_debug_errno(r, "Failed to parse /proc/self/mountinfo: %m");
93
94 for (;;) {
95 struct libmnt_fs *fs;
96 const char *path;
97
98 r = mnt_table_next_fs(table, iter, &fs);
99 if (r == 1)
100 break;
101 if (r < 0)
102 return log_debug_errno(r, "Failed to get next entry from /proc/self/mountinfo: %m");
103
104 path = mnt_fs_get_target(fs);
105 if (!path)
106 continue;
107
108 if (!path_startswith(path, prefix))
109 continue;
110
111 if (umount2(path, flags | UMOUNT_NOFOLLOW) < 0) {
112 log_debug_errno(errno, "Failed to umount %s, ignoring: %m", path);
113 continue;
114 }
115
116 log_debug("Successfully unmounted %s", path);
117
118 again = true;
119 n++;
120
121 break;
122 }
123 } while (again);
124
125 return n;
126 }
127
128 static int get_mount_flags(
129 struct libmnt_table *table,
130 const char *path,
131 unsigned long *ret) {
132
133 _cleanup_close_ int fd = -1;
134 struct libmnt_fs *fs;
135 struct statvfs buf;
136 const char *opts;
137 int r;
138
139 /* Get the mount flags for the mountpoint at "path" from "table". We have a fallback using statvfs()
140 * in place (which provides us with mostly the same info), but it's just a fallback, since using it
141 * means triggering autofs or NFS mounts, which we'd rather avoid needlessly.
142 *
143 * This generally doesn't follow symlinks. */
144
145 fs = mnt_table_find_target(table, path, MNT_ITER_FORWARD);
146 if (!fs) {
147 log_debug("Could not find '%s' in mount table, ignoring.", path);
148 goto fallback;
149 }
150
151 opts = mnt_fs_get_vfs_options(fs);
152 if (!opts) {
153 *ret = 0;
154 return 0;
155 }
156
157 r = mnt_optstr_get_flags(opts, ret, mnt_get_builtin_optmap(MNT_LINUX_MAP));
158 if (r != 0) {
159 log_debug_errno(r, "Could not get flags for '%s', ignoring: %m", path);
160 goto fallback;
161 }
162
163 /* MS_RELATIME is default and trying to set it in an unprivileged container causes EPERM */
164 *ret &= ~MS_RELATIME;
165 return 0;
166
167 fallback:
168 fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
169 if (fd < 0)
170 return -errno;
171
172 if (fstatvfs(fd, &buf) < 0)
173 return -errno;
174
175 /* The statvfs() flags and the mount flags mostly have the same values, but for some cases do
176 * not. Hence map the flags manually. (Strictly speaking, ST_RELATIME/MS_RELATIME is the most
177 * prominent one that doesn't match, but that's the one we mask away anyway, see above.) */
178
179 *ret =
180 FLAGS_SET(buf.f_flag, ST_RDONLY) * MS_RDONLY |
181 FLAGS_SET(buf.f_flag, ST_NODEV) * MS_NODEV |
182 FLAGS_SET(buf.f_flag, ST_NOEXEC) * MS_NOEXEC |
183 FLAGS_SET(buf.f_flag, ST_NOSUID) * MS_NOSUID |
184 FLAGS_SET(buf.f_flag, ST_NOATIME) * MS_NOATIME |
185 FLAGS_SET(buf.f_flag, ST_NODIRATIME) * MS_NODIRATIME;
186
187 return 0;
188 }
189
190 /* Use this function only if you do not have direct access to /proc/self/mountinfo but the caller can open it
191 * for you. This is the case when /proc is masked or not mounted. Otherwise, use bind_remount_recursive. */
192 int bind_remount_recursive_with_mountinfo(
193 const char *prefix,
194 unsigned long new_flags,
195 unsigned long flags_mask,
196 char **deny_list,
197 FILE *proc_self_mountinfo) {
198
199 _cleanup_set_free_free_ Set *done = NULL;
200 _cleanup_free_ char *simplified = NULL;
201 int r;
202
203 assert(prefix);
204 assert(proc_self_mountinfo);
205
206 /* Recursively remount a directory (and all its submounts) read-only or read-write. If the directory is already
207 * mounted, we reuse the mount and simply mark it MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write
208 * operation). If it isn't we first make it one. Afterwards we apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to
209 * all submounts we can access, too. When mounts are stacked on the same mount point we only care for each
210 * individual "top-level" mount on each point, as we cannot influence/access the underlying mounts anyway. We
211 * do not have any effect on future submounts that might get propagated, they might be writable. This includes
212 * future submounts that have been triggered via autofs.
213 *
214 * If the "deny_list" parameter is specified it may contain a list of subtrees to exclude from the
215 * remount operation. Note that we'll ignore the deny list for the top-level path. */
216
217 simplified = strdup(prefix);
218 if (!simplified)
219 return -ENOMEM;
220
221 path_simplify(simplified, false);
222
223 done = set_new(&path_hash_ops);
224 if (!done)
225 return -ENOMEM;
226
227 for (;;) {
228 _cleanup_set_free_free_ Set *todo = NULL;
229 _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL;
230 _cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL;
231 bool top_autofs = false;
232 char *x;
233 unsigned long orig_flags;
234
235 todo = set_new(&path_hash_ops);
236 if (!todo)
237 return -ENOMEM;
238
239 rewind(proc_self_mountinfo);
240
241 r = libmount_parse("/proc/self/mountinfo", proc_self_mountinfo, &table, &iter);
242 if (r < 0)
243 return log_debug_errno(r, "Failed to parse /proc/self/mountinfo: %m");
244
245 for (;;) {
246 struct libmnt_fs *fs;
247 const char *path, *type;
248
249 r = mnt_table_next_fs(table, iter, &fs);
250 if (r == 1)
251 break;
252 if (r < 0)
253 return log_debug_errno(r, "Failed to get next entry from /proc/self/mountinfo: %m");
254
255 path = mnt_fs_get_target(fs);
256 type = mnt_fs_get_fstype(fs);
257 if (!path || !type)
258 continue;
259
260 if (!path_startswith(path, simplified))
261 continue;
262
263 /* Ignore this mount if it is deny-listed, but only if it isn't the top-level mount
264 * we shall operate on. */
265 if (!path_equal(path, simplified)) {
266 bool deny_listed = false;
267 char **i;
268
269 STRV_FOREACH(i, deny_list) {
270 if (path_equal(*i, simplified))
271 continue;
272
273 if (!path_startswith(*i, simplified))
274 continue;
275
276 if (path_startswith(path, *i)) {
277 deny_listed = true;
278 log_debug("Not remounting %s deny-listed by %s, called for %s",
279 path, *i, simplified);
280 break;
281 }
282 }
283 if (deny_listed)
284 continue;
285 }
286
287 /* Let's ignore autofs mounts. If they aren't
288 * triggered yet, we want to avoid triggering
289 * them, as we don't make any guarantees for
290 * future submounts anyway. If they are
291 * already triggered, then we will find
292 * another entry for this. */
293 if (streq(type, "autofs")) {
294 top_autofs = top_autofs || path_equal(path, simplified);
295 continue;
296 }
297
298 if (!set_contains(done, path)) {
299 r = set_put_strdup(&todo, path);
300 if (r < 0)
301 return r;
302 }
303 }
304
305 /* If we have no submounts to process anymore and if
306 * the root is either already done, or an autofs, we
307 * are done */
308 if (set_isempty(todo) &&
309 (top_autofs || set_contains(done, simplified)))
310 return 0;
311
312 if (!set_contains(done, simplified) &&
313 !set_contains(todo, simplified)) {
314 /* The prefix directory itself is not yet a mount, make it one. */
315 r = mount_nofollow(simplified, simplified, NULL, MS_BIND|MS_REC, NULL);
316 if (r < 0)
317 return r;
318
319 orig_flags = 0;
320 (void) get_mount_flags(table, simplified, &orig_flags);
321
322 r = mount_nofollow(NULL, simplified, NULL, (orig_flags & ~flags_mask)|MS_BIND|MS_REMOUNT|new_flags, NULL);
323 if (r < 0)
324 return r;
325
326 log_debug("Made top-level directory %s a mount point.", prefix);
327
328 r = set_put_strdup(&done, simplified);
329 if (r < 0)
330 return r;
331 }
332
333 while ((x = set_steal_first(todo))) {
334
335 r = set_consume(done, x);
336 if (IN_SET(r, 0, -EEXIST))
337 continue;
338 if (r < 0)
339 return r;
340
341 /* Deal with mount points that are obstructed by a later mount */
342 r = path_is_mount_point(x, NULL, 0);
343 if (IN_SET(r, 0, -ENOENT))
344 continue;
345 if (r < 0) {
346 if (!ERRNO_IS_PRIVILEGE(r))
347 return r;
348
349 /* Even if root user invoke this, submounts under private FUSE or NFS mount points
350 * may not be acceessed. E.g.,
351 *
352 * $ bindfs --no-allow-other ~/mnt/mnt ~/mnt/mnt
353 * $ bindfs --no-allow-other ~/mnt ~/mnt
354 *
355 * Then, root user cannot access the mount point ~/mnt/mnt.
356 * In such cases, the submounts are ignored, as we have no way to manage them. */
357 log_debug_errno(r, "Failed to determine '%s' is mount point or not, ignoring: %m", x);
358 continue;
359 }
360
361 /* Try to reuse the original flag set */
362 orig_flags = 0;
363 (void) get_mount_flags(table, x, &orig_flags);
364
365 r = mount_nofollow(NULL, x, NULL, (orig_flags & ~flags_mask)|MS_BIND|MS_REMOUNT|new_flags, NULL);
366 if (r < 0)
367 return r;
368
369 log_debug("Remounted %s read-only.", x);
370 }
371 }
372 }
373
374 int bind_remount_recursive(
375 const char *prefix,
376 unsigned long new_flags,
377 unsigned long flags_mask,
378 char **deny_list) {
379
380 _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
381 int r;
382
383 r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo);
384 if (r < 0)
385 return r;
386
387 return bind_remount_recursive_with_mountinfo(prefix, new_flags, flags_mask, deny_list, proc_self_mountinfo);
388 }
389
390 int bind_remount_one_with_mountinfo(
391 const char *path,
392 unsigned long new_flags,
393 unsigned long flags_mask,
394 FILE *proc_self_mountinfo) {
395
396 _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL;
397 unsigned long orig_flags = 0;
398 int r;
399
400 assert(path);
401 assert(proc_self_mountinfo);
402
403 rewind(proc_self_mountinfo);
404
405 table = mnt_new_table();
406 if (!table)
407 return -ENOMEM;
408
409 r = mnt_table_parse_stream(table, proc_self_mountinfo, "/proc/self/mountinfo");
410 if (r < 0)
411 return r;
412
413 /* Try to reuse the original flag set */
414 (void) get_mount_flags(table, path, &orig_flags);
415
416 r = mount_nofollow(NULL, path, NULL, (orig_flags & ~flags_mask)|MS_BIND|MS_REMOUNT|new_flags, NULL);
417 if (r < 0)
418 return r;
419
420 return 0;
421 }
422
423 int mount_move_root(const char *path) {
424 assert(path);
425
426 if (chdir(path) < 0)
427 return -errno;
428
429 if (mount(path, "/", NULL, MS_MOVE, NULL) < 0)
430 return -errno;
431
432 if (chroot(".") < 0)
433 return -errno;
434
435 if (chdir("/") < 0)
436 return -errno;
437
438 return 0;
439 }
440
441 int repeat_unmount(const char *path, int flags) {
442 bool done = false;
443
444 assert(path);
445
446 /* If there are multiple mounts on a mount point, this
447 * removes them all */
448
449 for (;;) {
450 if (umount2(path, flags) < 0) {
451
452 if (errno == EINVAL)
453 return done;
454
455 return -errno;
456 }
457
458 done = true;
459 }
460 }
461
462 int mode_to_inaccessible_node(
463 const char *runtime_dir,
464 mode_t mode,
465 char **ret) {
466
467 /* This function maps a node type to a corresponding inaccessible file node. These nodes are created
468 * during early boot by PID 1. In some cases we lacked the privs to create the character and block
469 * devices (maybe because we run in an userns environment, or miss CAP_SYS_MKNOD, or run with a
470 * devices policy that excludes device nodes with major and minor of 0), but that's fine, in that
471 * case we use an AF_UNIX file node instead, which is not the same, but close enough for most
472 * uses. And most importantly, the kernel allows bind mounts from socket nodes to any non-directory
473 * file nodes, and that's the most important thing that matters.
474 *
475 * Note that the runtime directory argument shall be the top-level runtime directory, i.e. /run/ if
476 * we operate in system context and $XDG_RUNTIME_DIR if we operate in user context. */
477
478 _cleanup_free_ char *d = NULL;
479 const char *node = NULL;
480
481 assert(ret);
482
483 if (!runtime_dir)
484 runtime_dir = "/run";
485
486 switch(mode & S_IFMT) {
487 case S_IFREG:
488 node = "/systemd/inaccessible/reg";
489 break;
490
491 case S_IFDIR:
492 node = "/systemd/inaccessible/dir";
493 break;
494
495 case S_IFCHR:
496 node = "/systemd/inaccessible/chr";
497 break;
498
499 case S_IFBLK:
500 node = "/systemd/inaccessible/blk";
501 break;
502
503 case S_IFIFO:
504 node = "/systemd/inaccessible/fifo";
505 break;
506
507 case S_IFSOCK:
508 node = "/systemd/inaccessible/sock";
509 break;
510 }
511 if (!node)
512 return -EINVAL;
513
514 d = path_join(runtime_dir, node);
515 if (!d)
516 return -ENOMEM;
517
518 /* On new kernels unprivileged users are permitted to create 0:0 char device nodes (because they also
519 * act as whiteout inode for overlayfs), but no other char or block device nodes. On old kernels no
520 * device node whatsoever may be created by unprivileged processes. Hence, if the caller asks for the
521 * inaccessible block device node let's see if the block device node actually exists, and if not,
522 * fall back to the character device node. From there fall back to the socket device node. This means
523 * in the best case we'll get the right device node type — but if not we'll hopefully at least get a
524 * device node at all. */
525
526 if (S_ISBLK(mode) &&
527 access(d, F_OK) < 0 && errno == ENOENT) {
528 free(d);
529 d = path_join(runtime_dir, "/systemd/inaccessible/chr");
530 if (!d)
531 return -ENOMEM;
532 }
533
534 if (IN_SET(mode & S_IFMT, S_IFBLK, S_IFCHR) &&
535 access(d, F_OK) < 0 && errno == ENOENT) {
536 free(d);
537 d = path_join(runtime_dir, "/systemd/inaccessible/sock");
538 if (!d)
539 return -ENOMEM;
540 }
541
542 *ret = TAKE_PTR(d);
543 return 0;
544 }
545
546 #define FLAG(name) (flags & name ? STRINGIFY(name) "|" : "")
547 static char* mount_flags_to_string(long unsigned flags) {
548 char *x;
549 _cleanup_free_ char *y = NULL;
550 long unsigned overflow;
551
552 overflow = flags & ~(MS_RDONLY |
553 MS_NOSUID |
554 MS_NODEV |
555 MS_NOEXEC |
556 MS_SYNCHRONOUS |
557 MS_REMOUNT |
558 MS_MANDLOCK |
559 MS_DIRSYNC |
560 MS_NOATIME |
561 MS_NODIRATIME |
562 MS_BIND |
563 MS_MOVE |
564 MS_REC |
565 MS_SILENT |
566 MS_POSIXACL |
567 MS_UNBINDABLE |
568 MS_PRIVATE |
569 MS_SLAVE |
570 MS_SHARED |
571 MS_RELATIME |
572 MS_KERNMOUNT |
573 MS_I_VERSION |
574 MS_STRICTATIME |
575 MS_LAZYTIME);
576
577 if (flags == 0 || overflow != 0)
578 if (asprintf(&y, "%lx", overflow) < 0)
579 return NULL;
580
581 x = strjoin(FLAG(MS_RDONLY),
582 FLAG(MS_NOSUID),
583 FLAG(MS_NODEV),
584 FLAG(MS_NOEXEC),
585 FLAG(MS_SYNCHRONOUS),
586 FLAG(MS_REMOUNT),
587 FLAG(MS_MANDLOCK),
588 FLAG(MS_DIRSYNC),
589 FLAG(MS_NOATIME),
590 FLAG(MS_NODIRATIME),
591 FLAG(MS_BIND),
592 FLAG(MS_MOVE),
593 FLAG(MS_REC),
594 FLAG(MS_SILENT),
595 FLAG(MS_POSIXACL),
596 FLAG(MS_UNBINDABLE),
597 FLAG(MS_PRIVATE),
598 FLAG(MS_SLAVE),
599 FLAG(MS_SHARED),
600 FLAG(MS_RELATIME),
601 FLAG(MS_KERNMOUNT),
602 FLAG(MS_I_VERSION),
603 FLAG(MS_STRICTATIME),
604 FLAG(MS_LAZYTIME),
605 y);
606 if (!x)
607 return NULL;
608 if (!y)
609 x[strlen(x) - 1] = '\0'; /* truncate the last | */
610 return x;
611 }
612
613 int mount_verbose_full(
614 int error_log_level,
615 const char *what,
616 const char *where,
617 const char *type,
618 unsigned long flags,
619 const char *options,
620 bool follow_symlink) {
621
622 _cleanup_free_ char *fl = NULL, *o = NULL;
623 unsigned long f;
624 int r;
625
626 r = mount_option_mangle(options, flags, &f, &o);
627 if (r < 0)
628 return log_full_errno(error_log_level, r,
629 "Failed to mangle mount options %s: %m",
630 strempty(options));
631
632 fl = mount_flags_to_string(f);
633
634 if ((f & MS_REMOUNT) && !what && !type)
635 log_debug("Remounting %s (%s \"%s\")...",
636 where, strnull(fl), strempty(o));
637 else if (!what && !type)
638 log_debug("Mounting %s (%s \"%s\")...",
639 where, strnull(fl), strempty(o));
640 else if ((f & MS_BIND) && !type)
641 log_debug("Bind-mounting %s on %s (%s \"%s\")...",
642 what, where, strnull(fl), strempty(o));
643 else if (f & MS_MOVE)
644 log_debug("Moving mount %s → %s (%s \"%s\")...",
645 what, where, strnull(fl), strempty(o));
646 else
647 log_debug("Mounting %s (%s) on %s (%s \"%s\")...",
648 strna(what), strna(type), where, strnull(fl), strempty(o));
649
650 if (follow_symlink)
651 r = mount(what, where, type, f, o) < 0 ? -errno : 0;
652 else
653 r = mount_nofollow(what, where, type, f, o);
654 if (r < 0)
655 return log_full_errno(error_log_level, r,
656 "Failed to mount %s (type %s) on %s (%s \"%s\"): %m",
657 strna(what), strna(type), where, strnull(fl), strempty(o));
658 return 0;
659 }
660
661 int umount_verbose(
662 int error_log_level,
663 const char *what,
664 int flags) {
665
666 assert(what);
667
668 log_debug("Umounting %s...", what);
669
670 if (umount2(what, flags) < 0)
671 return log_full_errno(error_log_level, errno,
672 "Failed to unmount %s: %m", what);
673
674 return 0;
675 }
676
677 int mount_option_mangle(
678 const char *options,
679 unsigned long mount_flags,
680 unsigned long *ret_mount_flags,
681 char **ret_remaining_options) {
682
683 const struct libmnt_optmap *map;
684 _cleanup_free_ char *ret = NULL;
685 const char *p;
686 int r;
687
688 /* This extracts mount flags from the mount options, and store
689 * non-mount-flag options to '*ret_remaining_options'.
690 * E.g.,
691 * "rw,nosuid,nodev,relatime,size=1630748k,mode=700,uid=1000,gid=1000"
692 * is split to MS_NOSUID|MS_NODEV|MS_RELATIME and
693 * "size=1630748k,mode=700,uid=1000,gid=1000".
694 * See more examples in test-mount-utils.c.
695 *
696 * Note that if 'options' does not contain any non-mount-flag options,
697 * then '*ret_remaining_options' is set to NULL instead of empty string.
698 * Note that this does not check validity of options stored in
699 * '*ret_remaining_options'.
700 * Note that if 'options' is NULL, then this just copies 'mount_flags'
701 * to '*ret_mount_flags'. */
702
703 assert(ret_mount_flags);
704 assert(ret_remaining_options);
705
706 map = mnt_get_builtin_optmap(MNT_LINUX_MAP);
707 if (!map)
708 return -EINVAL;
709
710 p = options;
711 for (;;) {
712 _cleanup_free_ char *word = NULL;
713 const struct libmnt_optmap *ent;
714
715 r = extract_first_word(&p, &word, ",", EXTRACT_UNQUOTE);
716 if (r < 0)
717 return r;
718 if (r == 0)
719 break;
720
721 for (ent = map; ent->name; ent++) {
722 /* All entries in MNT_LINUX_MAP do not take any argument.
723 * Thus, ent->name does not contain "=" or "[=]". */
724 if (!streq(word, ent->name))
725 continue;
726
727 if (!(ent->mask & MNT_INVERT))
728 mount_flags |= ent->id;
729 else if (mount_flags & ent->id)
730 mount_flags ^= ent->id;
731
732 break;
733 }
734
735 /* If 'word' is not a mount flag, then store it in '*ret_remaining_options'. */
736 if (!ent->name && !strextend_with_separator(&ret, ",", word, NULL))
737 return -ENOMEM;
738 }
739
740 *ret_mount_flags = mount_flags;
741 *ret_remaining_options = TAKE_PTR(ret);
742
743 return 0;
744 }