chiark / gitweb /
Fix service file to match installed elogind binary location
[elogind.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
41 static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) {
42         char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
43         _cleanup_free_ char *fdinfo = NULL;
44         _cleanup_close_ int subfd = -1;
45         char *p;
46         int r;
47
48         if ((flags & AT_EMPTY_PATH) && isempty(filename))
49                 xsprintf(path, "/proc/self/fdinfo/%i", fd);
50         else {
51                 subfd = openat(fd, filename, O_CLOEXEC|O_PATH);
52                 if (subfd < 0)
53                         return -errno;
54
55                 xsprintf(path, "/proc/self/fdinfo/%i", subfd);
56         }
57
58         r = read_full_file(path, &fdinfo, NULL);
59         if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
60                 return -EOPNOTSUPP;
61         if (r < 0)
62                 return -errno;
63
64         p = startswith(fdinfo, "mnt_id:");
65         if (!p) {
66                 p = strstr(fdinfo, "\nmnt_id:");
67                 if (!p) /* The mnt_id field is a relatively new addition */
68                         return -EOPNOTSUPP;
69
70                 p += 8;
71         }
72
73         p += strspn(p, WHITESPACE);
74         p[strcspn(p, WHITESPACE)] = 0;
75
76         return safe_atoi(p, mnt_id);
77 }
78
79 int fd_is_mount_point(int fd, const char *filename, int flags) {
80         union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT;
81         int mount_id = -1, mount_id_parent = -1;
82         bool nosupp = false, check_st_dev = true;
83         struct stat a, b;
84         int r;
85
86         assert(fd >= 0);
87         assert(filename);
88
89         /* First we will try the name_to_handle_at() syscall, which
90          * tells us the mount id and an opaque file "handle". It is
91          * not supported everywhere though (kernel compile-time
92          * option, not all file systems are hooked up). If it works
93          * the mount id is usually good enough to tell us whether
94          * something is a mount point.
95          *
96          * If that didn't work we will try to read the mount id from
97          * /proc/self/fdinfo/<fd>. This is almost as good as
98          * name_to_handle_at(), however, does not return the
99          * opaque file handle. The opaque file handle is pretty useful
100          * to detect the root directory, which we should always
101          * consider a mount point. Hence we use this only as
102          * fallback. Exporting the mnt_id in fdinfo is a pretty recent
103          * kernel addition.
104          *
105          * As last fallback we do traditional fstat() based st_dev
106          * comparisons. This is how things were traditionally done,
107          * but unionfs breaks this since it exposes file
108          * systems with a variety of st_dev reported. Also, btrfs
109          * subvolumes have different st_dev, even though they aren't
110          * real mounts of their own. */
111
112         r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags);
113         if (r < 0) {
114                 if (IN_SET(errno, ENOSYS, EACCES, EPERM))
115                         /* This kernel does not support name_to_handle_at() at all, or the syscall was blocked (maybe
116                          * through seccomp, because we are running inside of a container?): fall back to simpler
117                          * logic. */
118                         goto fallback_fdinfo;
119                 else if (errno == EOPNOTSUPP)
120                         /* This kernel or file system does not support
121                          * name_to_handle_at(), hence let's see if the
122                          * upper fs supports it (in which case it is a
123                          * mount point), otherwise fallback to the
124                          * traditional stat() logic */
125                         nosupp = true;
126                 else
127                         return -errno;
128         }
129
130         r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH);
131         if (r < 0) {
132                 if (errno == EOPNOTSUPP) {
133                         if (nosupp)
134                                 /* Neither parent nor child do name_to_handle_at()?
135                                    We have no choice but to fall back. */
136                                 goto fallback_fdinfo;
137                         else
138                                 /* The parent can't do name_to_handle_at() but the
139                                  * directory we are interested in can?
140                                  * If so, it must be a mount point. */
141                                 return 1;
142                 } else
143                         return -errno;
144         }
145
146         /* The parent can do name_to_handle_at() but the
147          * directory we are interested in can't? If so, it
148          * must be a mount point. */
149         if (nosupp)
150                 return 1;
151
152         /* If the file handle for the directory we are
153          * interested in and its parent are identical, we
154          * assume this is the root directory, which is a mount
155          * point. */
156
157         if (h.handle.handle_bytes == h_parent.handle.handle_bytes &&
158             h.handle.handle_type == h_parent.handle.handle_type &&
159             memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0)
160                 return 1;
161
162         return mount_id != mount_id_parent;
163
164 fallback_fdinfo:
165         r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
166         if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM))
167                 goto fallback_fstat;
168         if (r < 0)
169                 return r;
170
171         r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
172         if (r < 0)
173                 return r;
174
175         if (mount_id != mount_id_parent)
176                 return 1;
177
178         /* Hmm, so, the mount ids are the same. This leaves one
179          * special case though for the root file system. For that,
180          * let's see if the parent directory has the same inode as we
181          * are interested in. Hence, let's also do fstat() checks now,
182          * too, but avoid the st_dev comparisons, since they aren't
183          * that useful on unionfs mounts. */
184         check_st_dev = false;
185
186 fallback_fstat:
187         /* yay for fstatat() taking a different set of flags than the other
188          * _at() above */
189         if (flags & AT_SYMLINK_FOLLOW)
190                 flags &= ~AT_SYMLINK_FOLLOW;
191         else
192                 flags |= AT_SYMLINK_NOFOLLOW;
193         if (fstatat(fd, filename, &a, flags) < 0)
194                 return -errno;
195
196         if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0)
197                 return -errno;
198
199         /* A directory with same device and inode as its parent? Must
200          * be the root directory */
201         if (a.st_dev == b.st_dev &&
202             a.st_ino == b.st_ino)
203                 return 1;
204
205         return check_st_dev && (a.st_dev != b.st_dev);
206 }
207
208 /* flags can be AT_SYMLINK_FOLLOW or 0 */
209 int path_is_mount_point(const char *t, const char *root, int flags) {
210         _cleanup_free_ char *canonical = NULL, *parent = NULL;
211         _cleanup_close_ int fd = -1;
212         int r;
213
214         assert(t);
215
216         if (path_equal(t, "/"))
217                 return 1;
218
219         /* we need to resolve symlinks manually, we can't just rely on
220          * fd_is_mount_point() to do that for us; if we have a structure like
221          * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
222          * look at needs to be /usr, not /. */
223         if (flags & AT_SYMLINK_FOLLOW) {
224                 r = chase_symlinks(t, root, 0, &canonical);
225                 if (r < 0)
226                         return r;
227
228                 t = canonical;
229         }
230
231         parent = dirname_malloc(t);
232         if (!parent)
233                 return -ENOMEM;
234
235         fd = openat(AT_FDCWD, parent, O_DIRECTORY|O_CLOEXEC|O_PATH);
236         if (fd < 0)
237                 return -errno;
238
239         return fd_is_mount_point(fd, basename(t), flags);
240 }
241
242 #if 0 /// UNNEEDED by elogind
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 int repeat_unmount(const char *path, int flags) {
556         bool done = false;
557
558         assert(path);
559
560         /* If there are multiple mounts on a mount point, this
561          * removes them all */
562
563         for (;;) {
564                 if (umount2(path, flags) < 0) {
565
566                         if (errno == EINVAL)
567                                 return done;
568
569                         return -errno;
570                 }
571
572                 done = true;
573         }
574 }
575 #endif // 0
576
577 const char* mode_to_inaccessible_node(mode_t mode) {
578         /* This function maps a node type to the correspondent inaccessible node type.
579          * Character and block inaccessible devices may not be created (because major=0 and minor=0),
580          * in such case we map character and block devices to the inaccessible node type socket. */
581         switch(mode & S_IFMT) {
582                 case S_IFREG:
583                         return "/run/systemd/inaccessible/reg";
584                 case S_IFDIR:
585                         return "/run/systemd/inaccessible/dir";
586                 case S_IFCHR:
587                         if (access("/run/systemd/inaccessible/chr", F_OK) == 0)
588                                 return "/run/systemd/inaccessible/chr";
589                         return "/run/systemd/inaccessible/sock";
590                 case S_IFBLK:
591                         if (access("/run/systemd/inaccessible/blk", F_OK) == 0)
592                                 return "/run/systemd/inaccessible/blk";
593                         return "/run/systemd/inaccessible/sock";
594                 case S_IFIFO:
595                         return "/run/systemd/inaccessible/fifo";
596                 case S_IFSOCK:
597                         return "/run/systemd/inaccessible/sock";
598         }
599         return NULL;
600 }
601
602 #if 0 /// UNNEEDED by elogind
603 #define FLAG(name) (flags & name ? STRINGIFY(name) "|" : "")
604 static char* mount_flags_to_string(long unsigned flags) {
605         char *x;
606         _cleanup_free_ char *y = NULL;
607         long unsigned overflow;
608
609         overflow = flags & ~(MS_RDONLY |
610                              MS_NOSUID |
611                              MS_NODEV |
612                              MS_NOEXEC |
613                              MS_SYNCHRONOUS |
614                              MS_REMOUNT |
615                              MS_MANDLOCK |
616                              MS_DIRSYNC |
617                              MS_NOATIME |
618                              MS_NODIRATIME |
619                              MS_BIND |
620                              MS_MOVE |
621                              MS_REC |
622                              MS_SILENT |
623                              MS_POSIXACL |
624                              MS_UNBINDABLE |
625                              MS_PRIVATE |
626                              MS_SLAVE |
627                              MS_SHARED |
628                              MS_RELATIME |
629                              MS_KERNMOUNT |
630                              MS_I_VERSION |
631                              MS_STRICTATIME |
632                              MS_LAZYTIME);
633
634         if (flags == 0 || overflow != 0)
635                 if (asprintf(&y, "%lx", overflow) < 0)
636                         return NULL;
637
638         x = strjoin(FLAG(MS_RDONLY),
639                     FLAG(MS_NOSUID),
640                     FLAG(MS_NODEV),
641                     FLAG(MS_NOEXEC),
642                     FLAG(MS_SYNCHRONOUS),
643                     FLAG(MS_REMOUNT),
644                     FLAG(MS_MANDLOCK),
645                     FLAG(MS_DIRSYNC),
646                     FLAG(MS_NOATIME),
647                     FLAG(MS_NODIRATIME),
648                     FLAG(MS_BIND),
649                     FLAG(MS_MOVE),
650                     FLAG(MS_REC),
651                     FLAG(MS_SILENT),
652                     FLAG(MS_POSIXACL),
653                     FLAG(MS_UNBINDABLE),
654                     FLAG(MS_PRIVATE),
655                     FLAG(MS_SLAVE),
656                     FLAG(MS_SHARED),
657                     FLAG(MS_RELATIME),
658                     FLAG(MS_KERNMOUNT),
659                     FLAG(MS_I_VERSION),
660                     FLAG(MS_STRICTATIME),
661                     FLAG(MS_LAZYTIME),
662                     y);
663         if (!x)
664                 return NULL;
665         if (!y)
666                 x[strlen(x) - 1] = '\0'; /* truncate the last | */
667         return x;
668 }
669
670 int mount_verbose(
671                 int error_log_level,
672                 const char *what,
673                 const char *where,
674                 const char *type,
675                 unsigned long flags,
676                 const char *options) {
677
678         _cleanup_free_ char *fl = NULL;
679
680         fl = mount_flags_to_string(flags);
681
682         if ((flags & MS_REMOUNT) && !what && !type)
683                 log_debug("Remounting %s (%s \"%s\")...",
684                           where, strnull(fl), strempty(options));
685         else if (!what && !type)
686                 log_debug("Mounting %s (%s \"%s\")...",
687                           where, strnull(fl), strempty(options));
688         else if ((flags & MS_BIND) && !type)
689                 log_debug("Bind-mounting %s on %s (%s \"%s\")...",
690                           what, where, strnull(fl), strempty(options));
691         else if (flags & MS_MOVE)
692                 log_debug("Moving mount %s → %s (%s \"%s\")...",
693                           what, where, strnull(fl), strempty(options));
694         else
695                 log_debug("Mounting %s on %s (%s \"%s\")...",
696                           strna(type), where, strnull(fl), strempty(options));
697         if (mount(what, where, type, flags, options) < 0)
698                 return log_full_errno(error_log_level, errno,
699                                       "Failed to mount %s on %s (%s \"%s\"): %m",
700                                       strna(type), where, strnull(fl), strempty(options));
701         return 0;
702 }
703
704 int umount_verbose(const char *what) {
705         log_debug("Umounting %s...", what);
706         if (umount(what) < 0)
707                 return log_error_errno(errno, "Failed to unmount %s: %m", what);
708         return 0;
709 }
710
711 const char *mount_propagation_flags_to_string(unsigned long flags) {
712
713         switch (flags & (MS_SHARED|MS_SLAVE|MS_PRIVATE)) {
714         case 0:
715                 return "";
716         case MS_SHARED:
717                 return "shared";
718         case MS_SLAVE:
719                 return "slave";
720         case MS_PRIVATE:
721                 return "private";
722         }
723
724         return NULL;
725 }
726
727
728 int mount_propagation_flags_from_string(const char *name, unsigned long *ret) {
729
730         if (isempty(name))
731                 *ret = 0;
732         else if (streq(name, "shared"))
733                 *ret = MS_SHARED;
734         else if (streq(name, "slave"))
735                 *ret = MS_SLAVE;
736         else if (streq(name, "private"))
737                 *ret = MS_PRIVATE;
738         else
739                 return -EINVAL;
740         return 0;
741 }
742 #endif // 0