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