]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/mount-util.c
Merge pull request #6580 from poettering/nspawn-dm-deviceallow
[thirdparty/systemd.git] / src / basic / mount-util.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2010 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <errno.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/mount.h>
24 #include <sys/stat.h>
25 #include <sys/statvfs.h>
26 #include <unistd.h>
27
28 #include "alloc-util.h"
29 #include "escape.h"
30 #include "fd-util.h"
31 #include "fileio.h"
32 #include "fs-util.h"
33 #include "hashmap.h"
34 #include "mount-util.h"
35 #include "parse-util.h"
36 #include "path-util.h"
37 #include "set.h"
38 #include "stdio-util.h"
39 #include "string-util.h"
40 #include "strv.h"
41
42 static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) {
43 char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
44 _cleanup_free_ char *fdinfo = NULL;
45 _cleanup_close_ int subfd = -1;
46 char *p;
47 int r;
48
49 if ((flags & AT_EMPTY_PATH) && isempty(filename))
50 xsprintf(path, "/proc/self/fdinfo/%i", fd);
51 else {
52 subfd = openat(fd, filename, O_CLOEXEC|O_PATH);
53 if (subfd < 0)
54 return -errno;
55
56 xsprintf(path, "/proc/self/fdinfo/%i", subfd);
57 }
58
59 r = read_full_file(path, &fdinfo, NULL);
60 if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
61 return -EOPNOTSUPP;
62 if (r < 0)
63 return -errno;
64
65 p = startswith(fdinfo, "mnt_id:");
66 if (!p) {
67 p = strstr(fdinfo, "\nmnt_id:");
68 if (!p) /* The mnt_id field is a relatively new addition */
69 return -EOPNOTSUPP;
70
71 p += 8;
72 }
73
74 p += strspn(p, WHITESPACE);
75 p[strcspn(p, WHITESPACE)] = 0;
76
77 return safe_atoi(p, mnt_id);
78 }
79
80 int fd_is_mount_point(int fd, const char *filename, int flags) {
81 union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT;
82 int mount_id = -1, mount_id_parent = -1;
83 bool nosupp = false, check_st_dev = true;
84 struct stat a, b;
85 int r;
86
87 assert(fd >= 0);
88 assert(filename);
89
90 /* First we will try the name_to_handle_at() syscall, which
91 * tells us the mount id and an opaque file "handle". It is
92 * not supported everywhere though (kernel compile-time
93 * option, not all file systems are hooked up). If it works
94 * the mount id is usually good enough to tell us whether
95 * something is a mount point.
96 *
97 * If that didn't work we will try to read the mount id from
98 * /proc/self/fdinfo/<fd>. This is almost as good as
99 * name_to_handle_at(), however, does not return the
100 * opaque file handle. The opaque file handle is pretty useful
101 * to detect the root directory, which we should always
102 * consider a mount point. Hence we use this only as
103 * fallback. Exporting the mnt_id in fdinfo is a pretty recent
104 * kernel addition.
105 *
106 * As last fallback we do traditional fstat() based st_dev
107 * comparisons. This is how things were traditionally done,
108 * but unionfs breaks this since it exposes file
109 * systems with a variety of st_dev reported. Also, btrfs
110 * subvolumes have different st_dev, even though they aren't
111 * real mounts of their own. */
112
113 r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags);
114 if (r < 0) {
115 if (IN_SET(errno, ENOSYS, EACCES, EPERM))
116 /* This kernel does not support name_to_handle_at() at all, or the syscall was blocked (maybe
117 * through seccomp, because we are running inside of a container?): fall back to simpler
118 * logic. */
119 goto fallback_fdinfo;
120 else if (errno == EOPNOTSUPP)
121 /* This kernel or file system does not support
122 * name_to_handle_at(), hence let's see if the
123 * upper fs supports it (in which case it is a
124 * mount point), otherwise fallback to the
125 * traditional stat() logic */
126 nosupp = true;
127 else
128 return -errno;
129 }
130
131 r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH);
132 if (r < 0) {
133 if (errno == EOPNOTSUPP) {
134 if (nosupp)
135 /* Neither parent nor child do name_to_handle_at()?
136 We have no choice but to fall back. */
137 goto fallback_fdinfo;
138 else
139 /* The parent can't do name_to_handle_at() but the
140 * directory we are interested in can?
141 * If so, it must be a mount point. */
142 return 1;
143 } else
144 return -errno;
145 }
146
147 /* The parent can do name_to_handle_at() but the
148 * directory we are interested in can't? If so, it
149 * must be a mount point. */
150 if (nosupp)
151 return 1;
152
153 /* If the file handle for the directory we are
154 * interested in and its parent are identical, we
155 * assume this is the root directory, which is a mount
156 * point. */
157
158 if (h.handle.handle_bytes == h_parent.handle.handle_bytes &&
159 h.handle.handle_type == h_parent.handle.handle_type &&
160 memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0)
161 return 1;
162
163 return mount_id != mount_id_parent;
164
165 fallback_fdinfo:
166 r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
167 if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM))
168 goto fallback_fstat;
169 if (r < 0)
170 return r;
171
172 r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
173 if (r < 0)
174 return r;
175
176 if (mount_id != mount_id_parent)
177 return 1;
178
179 /* Hmm, so, the mount ids are the same. This leaves one
180 * special case though for the root file system. For that,
181 * let's see if the parent directory has the same inode as we
182 * are interested in. Hence, let's also do fstat() checks now,
183 * too, but avoid the st_dev comparisons, since they aren't
184 * that useful on unionfs mounts. */
185 check_st_dev = false;
186
187 fallback_fstat:
188 /* yay for fstatat() taking a different set of flags than the other
189 * _at() above */
190 if (flags & AT_SYMLINK_FOLLOW)
191 flags &= ~AT_SYMLINK_FOLLOW;
192 else
193 flags |= AT_SYMLINK_NOFOLLOW;
194 if (fstatat(fd, filename, &a, flags) < 0)
195 return -errno;
196
197 if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0)
198 return -errno;
199
200 /* A directory with same device and inode as its parent? Must
201 * be the root directory */
202 if (a.st_dev == b.st_dev &&
203 a.st_ino == b.st_ino)
204 return 1;
205
206 return check_st_dev && (a.st_dev != b.st_dev);
207 }
208
209 /* flags can be AT_SYMLINK_FOLLOW or 0 */
210 int path_is_mount_point(const char *t, const char *root, int flags) {
211 _cleanup_free_ char *canonical = NULL, *parent = NULL;
212 _cleanup_close_ int fd = -1;
213 int r;
214
215 assert(t);
216
217 if (path_equal(t, "/"))
218 return 1;
219
220 /* we need to resolve symlinks manually, we can't just rely on
221 * fd_is_mount_point() to do that for us; if we have a structure like
222 * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
223 * look at needs to be /usr, not /. */
224 if (flags & AT_SYMLINK_FOLLOW) {
225 r = chase_symlinks(t, root, 0, &canonical);
226 if (r < 0)
227 return r;
228
229 t = canonical;
230 }
231
232 parent = dirname_malloc(t);
233 if (!parent)
234 return -ENOMEM;
235
236 fd = openat(AT_FDCWD, parent, O_DIRECTORY|O_CLOEXEC|O_PATH);
237 if (fd < 0)
238 return -errno;
239
240 return fd_is_mount_point(fd, basename(t), flags);
241 }
242
243 int umount_recursive(const char *prefix, int flags) {
244 bool again;
245 int n = 0, r;
246
247 /* Try to umount everything recursively below a
248 * directory. Also, take care of stacked mounts, and keep
249 * unmounting them until they are gone. */
250
251 do {
252 _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
253
254 again = false;
255 r = 0;
256
257 proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
258 if (!proc_self_mountinfo)
259 return -errno;
260
261 for (;;) {
262 _cleanup_free_ char *path = NULL, *p = NULL;
263 int k;
264
265 k = fscanf(proc_self_mountinfo,
266 "%*s " /* (1) mount id */
267 "%*s " /* (2) parent id */
268 "%*s " /* (3) major:minor */
269 "%*s " /* (4) root */
270 "%ms " /* (5) mount point */
271 "%*s" /* (6) mount options */
272 "%*[^-]" /* (7) optional fields */
273 "- " /* (8) separator */
274 "%*s " /* (9) file system type */
275 "%*s" /* (10) mount source */
276 "%*s" /* (11) mount options 2 */
277 "%*[^\n]", /* some rubbish at the end */
278 &path);
279 if (k != 1) {
280 if (k == EOF)
281 break;
282
283 continue;
284 }
285
286 r = cunescape(path, UNESCAPE_RELAX, &p);
287 if (r < 0)
288 return r;
289
290 if (!path_startswith(p, prefix))
291 continue;
292
293 if (umount2(p, flags) < 0) {
294 r = log_debug_errno(errno, "Failed to umount %s: %m", p);
295 continue;
296 }
297
298 log_debug("Successfully unmounted %s", p);
299
300 again = true;
301 n++;
302
303 break;
304 }
305
306 } while (again);
307
308 return r ? r : n;
309 }
310
311 static int get_mount_flags(const char *path, unsigned long *flags) {
312 struct statvfs buf;
313
314 if (statvfs(path, &buf) < 0)
315 return -errno;
316 *flags = buf.f_flag;
317 return 0;
318 }
319
320 /* Use this function only if do you have direct access to /proc/self/mountinfo
321 * and need the caller to open it for you. This is the case when /proc is
322 * masked or not mounted. Otherwise, use bind_remount_recursive. */
323 int bind_remount_recursive_with_mountinfo(const char *prefix, bool ro, char **blacklist, FILE *proc_self_mountinfo) {
324 _cleanup_set_free_free_ Set *done = NULL;
325 _cleanup_free_ char *cleaned = NULL;
326 int r;
327
328 assert(proc_self_mountinfo);
329
330 /* Recursively remount a directory (and all its submounts) read-only or read-write. If the directory is already
331 * mounted, we reuse the mount and simply mark it MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write
332 * operation). If it isn't we first make it one. Afterwards we apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to
333 * all submounts we can access, too. When mounts are stacked on the same mount point we only care for each
334 * individual "top-level" mount on each point, as we cannot influence/access the underlying mounts anyway. We
335 * do not have any effect on future submounts that might get propagated, they migt be writable. This includes
336 * future submounts that have been triggered via autofs.
337 *
338 * If the "blacklist" parameter is specified it may contain a list of subtrees to exclude from the
339 * remount operation. Note that we'll ignore the blacklist for the top-level path. */
340
341 cleaned = strdup(prefix);
342 if (!cleaned)
343 return -ENOMEM;
344
345 path_kill_slashes(cleaned);
346
347 done = set_new(&string_hash_ops);
348 if (!done)
349 return -ENOMEM;
350
351 for (;;) {
352 _cleanup_set_free_free_ Set *todo = NULL;
353 bool top_autofs = false;
354 char *x;
355 unsigned long orig_flags;
356
357 todo = set_new(&string_hash_ops);
358 if (!todo)
359 return -ENOMEM;
360
361 rewind(proc_self_mountinfo);
362
363 for (;;) {
364 _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL;
365 int k;
366
367 k = fscanf(proc_self_mountinfo,
368 "%*s " /* (1) mount id */
369 "%*s " /* (2) parent id */
370 "%*s " /* (3) major:minor */
371 "%*s " /* (4) root */
372 "%ms " /* (5) mount point */
373 "%*s" /* (6) mount options (superblock) */
374 "%*[^-]" /* (7) optional fields */
375 "- " /* (8) separator */
376 "%ms " /* (9) file system type */
377 "%*s" /* (10) mount source */
378 "%*s" /* (11) mount options (bind mount) */
379 "%*[^\n]", /* some rubbish at the end */
380 &path,
381 &type);
382 if (k != 2) {
383 if (k == EOF)
384 break;
385
386 continue;
387 }
388
389 r = cunescape(path, UNESCAPE_RELAX, &p);
390 if (r < 0)
391 return r;
392
393 if (!path_startswith(p, cleaned))
394 continue;
395
396 /* Ignore this mount if it is blacklisted, but only if it isn't the top-level mount we shall
397 * operate on. */
398 if (!path_equal(cleaned, p)) {
399 bool blacklisted = false;
400 char **i;
401
402 STRV_FOREACH(i, blacklist) {
403
404 if (path_equal(*i, cleaned))
405 continue;
406
407 if (!path_startswith(*i, cleaned))
408 continue;
409
410 if (path_startswith(p, *i)) {
411 blacklisted = true;
412 log_debug("Not remounting %s, because blacklisted by %s, called for %s", p, *i, cleaned);
413 break;
414 }
415 }
416 if (blacklisted)
417 continue;
418 }
419
420 /* Let's ignore autofs mounts. If they aren't
421 * triggered yet, we want to avoid triggering
422 * them, as we don't make any guarantees for
423 * future submounts anyway. If they are
424 * already triggered, then we will find
425 * another entry for this. */
426 if (streq(type, "autofs")) {
427 top_autofs = top_autofs || path_equal(cleaned, p);
428 continue;
429 }
430
431 if (!set_contains(done, p)) {
432 r = set_consume(todo, p);
433 p = NULL;
434 if (r == -EEXIST)
435 continue;
436 if (r < 0)
437 return r;
438 }
439 }
440
441 /* If we have no submounts to process anymore and if
442 * the root is either already done, or an autofs, we
443 * are done */
444 if (set_isempty(todo) &&
445 (top_autofs || set_contains(done, cleaned)))
446 return 0;
447
448 if (!set_contains(done, cleaned) &&
449 !set_contains(todo, cleaned)) {
450 /* The prefix directory itself is not yet a mount, make it one. */
451 if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0)
452 return -errno;
453
454 orig_flags = 0;
455 (void) get_mount_flags(cleaned, &orig_flags);
456 orig_flags &= ~MS_RDONLY;
457
458 if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0)
459 return -errno;
460
461 log_debug("Made top-level directory %s a mount point.", prefix);
462
463 x = strdup(cleaned);
464 if (!x)
465 return -ENOMEM;
466
467 r = set_consume(done, x);
468 if (r < 0)
469 return r;
470 }
471
472 while ((x = set_steal_first(todo))) {
473
474 r = set_consume(done, x);
475 if (r == -EEXIST || r == 0)
476 continue;
477 if (r < 0)
478 return r;
479
480 /* Deal with mount points that are obstructed by a later mount */
481 r = path_is_mount_point(x, NULL, 0);
482 if (r == -ENOENT || r == 0)
483 continue;
484 if (r < 0)
485 return r;
486
487 /* Try to reuse the original flag set */
488 orig_flags = 0;
489 (void) get_mount_flags(x, &orig_flags);
490 orig_flags &= ~MS_RDONLY;
491
492 if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0)
493 return -errno;
494
495 log_debug("Remounted %s read-only.", x);
496 }
497 }
498 }
499
500 int bind_remount_recursive(const char *prefix, bool ro, char **blacklist) {
501 _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
502
503 proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
504 if (!proc_self_mountinfo)
505 return -errno;
506
507 return bind_remount_recursive_with_mountinfo(prefix, ro, blacklist, proc_self_mountinfo);
508 }
509
510 int mount_move_root(const char *path) {
511 assert(path);
512
513 if (chdir(path) < 0)
514 return -errno;
515
516 if (mount(path, "/", NULL, MS_MOVE, NULL) < 0)
517 return -errno;
518
519 if (chroot(".") < 0)
520 return -errno;
521
522 if (chdir("/") < 0)
523 return -errno;
524
525 return 0;
526 }
527
528 bool fstype_is_network(const char *fstype) {
529 static const char table[] =
530 "afs\0"
531 "cifs\0"
532 "smbfs\0"
533 "sshfs\0"
534 "ncpfs\0"
535 "ncp\0"
536 "nfs\0"
537 "nfs4\0"
538 "gfs\0"
539 "gfs2\0"
540 "glusterfs\0"
541 "pvfs2\0" /* OrangeFS */
542 "ocfs2\0"
543 "lustre\0"
544 ;
545
546 const char *x;
547
548 x = startswith(fstype, "fuse.");
549 if (x)
550 fstype = x;
551
552 return nulstr_contains(table, fstype);
553 }
554
555 bool fstype_is_api_vfs(const char *fstype) {
556 static const char table[] =
557 "autofs\0"
558 "bpf\0"
559 "cgroup\0"
560 "cgroup2\0"
561 "configfs\0"
562 "cpuset\0"
563 "debugfs\0"
564 "devpts\0"
565 "devtmpfs\0"
566 "efivarfs\0"
567 "hugetlbfs\0"
568 "mqueue\0"
569 "proc\0"
570 "pstore\0"
571 "ramfs\0"
572 "securityfs\0"
573 "sysfs\0"
574 "tmpfs\0"
575 "tracefs\0"
576 ;
577
578 return nulstr_contains(table, fstype);
579 }
580
581 int repeat_unmount(const char *path, int flags) {
582 bool done = false;
583
584 assert(path);
585
586 /* If there are multiple mounts on a mount point, this
587 * removes them all */
588
589 for (;;) {
590 if (umount2(path, flags) < 0) {
591
592 if (errno == EINVAL)
593 return done;
594
595 return -errno;
596 }
597
598 done = true;
599 }
600 }
601
602 const char* mode_to_inaccessible_node(mode_t mode) {
603 /* This function maps a node type to the correspondent inaccessible node type.
604 * Character and block inaccessible devices may not be created (because major=0 and minor=0),
605 * in such case we map character and block devices to the inaccessible node type socket. */
606 switch(mode & S_IFMT) {
607 case S_IFREG:
608 return "/run/systemd/inaccessible/reg";
609 case S_IFDIR:
610 return "/run/systemd/inaccessible/dir";
611 case S_IFCHR:
612 if (access("/run/systemd/inaccessible/chr", F_OK) == 0)
613 return "/run/systemd/inaccessible/chr";
614 return "/run/systemd/inaccessible/sock";
615 case S_IFBLK:
616 if (access("/run/systemd/inaccessible/blk", F_OK) == 0)
617 return "/run/systemd/inaccessible/blk";
618 return "/run/systemd/inaccessible/sock";
619 case S_IFIFO:
620 return "/run/systemd/inaccessible/fifo";
621 case S_IFSOCK:
622 return "/run/systemd/inaccessible/sock";
623 }
624 return NULL;
625 }
626
627 #define FLAG(name) (flags & name ? STRINGIFY(name) "|" : "")
628 static char* mount_flags_to_string(long unsigned flags) {
629 char *x;
630 _cleanup_free_ char *y = NULL;
631 long unsigned overflow;
632
633 overflow = flags & ~(MS_RDONLY |
634 MS_NOSUID |
635 MS_NODEV |
636 MS_NOEXEC |
637 MS_SYNCHRONOUS |
638 MS_REMOUNT |
639 MS_MANDLOCK |
640 MS_DIRSYNC |
641 MS_NOATIME |
642 MS_NODIRATIME |
643 MS_BIND |
644 MS_MOVE |
645 MS_REC |
646 MS_SILENT |
647 MS_POSIXACL |
648 MS_UNBINDABLE |
649 MS_PRIVATE |
650 MS_SLAVE |
651 MS_SHARED |
652 MS_RELATIME |
653 MS_KERNMOUNT |
654 MS_I_VERSION |
655 MS_STRICTATIME |
656 MS_LAZYTIME);
657
658 if (flags == 0 || overflow != 0)
659 if (asprintf(&y, "%lx", overflow) < 0)
660 return NULL;
661
662 x = strjoin(FLAG(MS_RDONLY),
663 FLAG(MS_NOSUID),
664 FLAG(MS_NODEV),
665 FLAG(MS_NOEXEC),
666 FLAG(MS_SYNCHRONOUS),
667 FLAG(MS_REMOUNT),
668 FLAG(MS_MANDLOCK),
669 FLAG(MS_DIRSYNC),
670 FLAG(MS_NOATIME),
671 FLAG(MS_NODIRATIME),
672 FLAG(MS_BIND),
673 FLAG(MS_MOVE),
674 FLAG(MS_REC),
675 FLAG(MS_SILENT),
676 FLAG(MS_POSIXACL),
677 FLAG(MS_UNBINDABLE),
678 FLAG(MS_PRIVATE),
679 FLAG(MS_SLAVE),
680 FLAG(MS_SHARED),
681 FLAG(MS_RELATIME),
682 FLAG(MS_KERNMOUNT),
683 FLAG(MS_I_VERSION),
684 FLAG(MS_STRICTATIME),
685 FLAG(MS_LAZYTIME),
686 y);
687 if (!x)
688 return NULL;
689 if (!y)
690 x[strlen(x) - 1] = '\0'; /* truncate the last | */
691 return x;
692 }
693
694 int mount_verbose(
695 int error_log_level,
696 const char *what,
697 const char *where,
698 const char *type,
699 unsigned long flags,
700 const char *options) {
701
702 _cleanup_free_ char *fl = NULL;
703
704 fl = mount_flags_to_string(flags);
705
706 if ((flags & MS_REMOUNT) && !what && !type)
707 log_debug("Remounting %s (%s \"%s\")...",
708 where, strnull(fl), strempty(options));
709 else if (!what && !type)
710 log_debug("Mounting %s (%s \"%s\")...",
711 where, strnull(fl), strempty(options));
712 else if ((flags & MS_BIND) && !type)
713 log_debug("Bind-mounting %s on %s (%s \"%s\")...",
714 what, where, strnull(fl), strempty(options));
715 else if (flags & MS_MOVE)
716 log_debug("Moving mount %s → %s (%s \"%s\")...",
717 what, where, strnull(fl), strempty(options));
718 else
719 log_debug("Mounting %s on %s (%s \"%s\")...",
720 strna(type), where, strnull(fl), strempty(options));
721 if (mount(what, where, type, flags, options) < 0)
722 return log_full_errno(error_log_level, errno,
723 "Failed to mount %s on %s (%s \"%s\"): %m",
724 strna(type), where, strnull(fl), strempty(options));
725 return 0;
726 }
727
728 int umount_verbose(const char *what) {
729 log_debug("Umounting %s...", what);
730 if (umount(what) < 0)
731 return log_error_errno(errno, "Failed to unmount %s: %m", what);
732 return 0;
733 }
734
735 const char *mount_propagation_flags_to_string(unsigned long flags) {
736
737 switch (flags & (MS_SHARED|MS_SLAVE|MS_PRIVATE)) {
738 case 0:
739 return "";
740 case MS_SHARED:
741 return "shared";
742 case MS_SLAVE:
743 return "slave";
744 case MS_PRIVATE:
745 return "private";
746 }
747
748 return NULL;
749 }
750
751
752 int mount_propagation_flags_from_string(const char *name, unsigned long *ret) {
753
754 if (isempty(name))
755 *ret = 0;
756 else if (streq(name, "shared"))
757 *ret = MS_SHARED;
758 else if (streq(name, "slave"))
759 *ret = MS_SLAVE;
760 else if (streq(name, "private"))
761 *ret = MS_PRIVATE;
762 else
763 return -EINVAL;
764 return 0;
765 }