chiark / gitweb /
mount-util: add fusectl to list of API VFS
[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 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                 "fusectl\0"
568                 "hugetlbfs\0"
569                 "mqueue\0"
570                 "proc\0"
571                 "pstore\0"
572                 "ramfs\0"
573                 "securityfs\0"
574                 "sysfs\0"
575                 "tmpfs\0"
576                 "tracefs\0"
577                 ;
578
579         return nulstr_contains(table, fstype);
580 }
581
582 bool fstype_is_ro(const char *fstype) {
583
584         /* All Linux file systems that are necessarily read-only */
585
586         static const char table[] =
587                 "DM_verity_hash\0"
588                 "iso9660\0"
589                 "squashfs\0"
590                 ;
591
592         return nulstr_contains(table, fstype);
593 }
594
595 bool fstype_can_discard(const char *fstype) {
596
597         static const char table[] =
598                 "btrfs\0"
599                 "ext4\0"
600                 "vfat\0"
601                 "xfs\0"
602                 ;
603
604         return nulstr_contains(table, fstype);
605 }
606
607 int repeat_unmount(const char *path, int flags) {
608         bool done = false;
609
610         assert(path);
611
612         /* If there are multiple mounts on a mount point, this
613          * removes them all */
614
615         for (;;) {
616                 if (umount2(path, flags) < 0) {
617
618                         if (errno == EINVAL)
619                                 return done;
620
621                         return -errno;
622                 }
623
624                 done = true;
625         }
626 }
627 #endif // 0
628
629 const char* mode_to_inaccessible_node(mode_t mode) {
630         /* This function maps a node type to the correspondent inaccessible node type.
631          * Character and block inaccessible devices may not be created (because major=0 and minor=0),
632          * in such case we map character and block devices to the inaccessible node type socket. */
633         switch(mode & S_IFMT) {
634                 case S_IFREG:
635                         return "/run/systemd/inaccessible/reg";
636                 case S_IFDIR:
637                         return "/run/systemd/inaccessible/dir";
638                 case S_IFCHR:
639                         if (access("/run/systemd/inaccessible/chr", F_OK) == 0)
640                                 return "/run/systemd/inaccessible/chr";
641                         return "/run/systemd/inaccessible/sock";
642                 case S_IFBLK:
643                         if (access("/run/systemd/inaccessible/blk", F_OK) == 0)
644                                 return "/run/systemd/inaccessible/blk";
645                         return "/run/systemd/inaccessible/sock";
646                 case S_IFIFO:
647                         return "/run/systemd/inaccessible/fifo";
648                 case S_IFSOCK:
649                         return "/run/systemd/inaccessible/sock";
650         }
651         return NULL;
652 }
653
654 #if 0 /// UNNEEDED by elogind
655 #define FLAG(name) (flags & name ? STRINGIFY(name) "|" : "")
656 static char* mount_flags_to_string(long unsigned flags) {
657         char *x;
658         _cleanup_free_ char *y = NULL;
659         long unsigned overflow;
660
661         overflow = flags & ~(MS_RDONLY |
662                              MS_NOSUID |
663                              MS_NODEV |
664                              MS_NOEXEC |
665                              MS_SYNCHRONOUS |
666                              MS_REMOUNT |
667                              MS_MANDLOCK |
668                              MS_DIRSYNC |
669                              MS_NOATIME |
670                              MS_NODIRATIME |
671                              MS_BIND |
672                              MS_MOVE |
673                              MS_REC |
674                              MS_SILENT |
675                              MS_POSIXACL |
676                              MS_UNBINDABLE |
677                              MS_PRIVATE |
678                              MS_SLAVE |
679                              MS_SHARED |
680                              MS_RELATIME |
681                              MS_KERNMOUNT |
682                              MS_I_VERSION |
683                              MS_STRICTATIME |
684                              MS_LAZYTIME);
685
686         if (flags == 0 || overflow != 0)
687                 if (asprintf(&y, "%lx", overflow) < 0)
688                         return NULL;
689
690         x = strjoin(FLAG(MS_RDONLY),
691                     FLAG(MS_NOSUID),
692                     FLAG(MS_NODEV),
693                     FLAG(MS_NOEXEC),
694                     FLAG(MS_SYNCHRONOUS),
695                     FLAG(MS_REMOUNT),
696                     FLAG(MS_MANDLOCK),
697                     FLAG(MS_DIRSYNC),
698                     FLAG(MS_NOATIME),
699                     FLAG(MS_NODIRATIME),
700                     FLAG(MS_BIND),
701                     FLAG(MS_MOVE),
702                     FLAG(MS_REC),
703                     FLAG(MS_SILENT),
704                     FLAG(MS_POSIXACL),
705                     FLAG(MS_UNBINDABLE),
706                     FLAG(MS_PRIVATE),
707                     FLAG(MS_SLAVE),
708                     FLAG(MS_SHARED),
709                     FLAG(MS_RELATIME),
710                     FLAG(MS_KERNMOUNT),
711                     FLAG(MS_I_VERSION),
712                     FLAG(MS_STRICTATIME),
713                     FLAG(MS_LAZYTIME),
714                     y);
715         if (!x)
716                 return NULL;
717         if (!y)
718                 x[strlen(x) - 1] = '\0'; /* truncate the last | */
719         return x;
720 }
721
722 int mount_verbose(
723                 int error_log_level,
724                 const char *what,
725                 const char *where,
726                 const char *type,
727                 unsigned long flags,
728                 const char *options) {
729
730         _cleanup_free_ char *fl = NULL;
731
732         fl = mount_flags_to_string(flags);
733
734         if ((flags & MS_REMOUNT) && !what && !type)
735                 log_debug("Remounting %s (%s \"%s\")...",
736                           where, strnull(fl), strempty(options));
737         else if (!what && !type)
738                 log_debug("Mounting %s (%s \"%s\")...",
739                           where, strnull(fl), strempty(options));
740         else if ((flags & MS_BIND) && !type)
741                 log_debug("Bind-mounting %s on %s (%s \"%s\")...",
742                           what, where, strnull(fl), strempty(options));
743         else if (flags & MS_MOVE)
744                 log_debug("Moving mount %s → %s (%s \"%s\")...",
745                           what, where, strnull(fl), strempty(options));
746         else
747                 log_debug("Mounting %s on %s (%s \"%s\")...",
748                           strna(type), where, strnull(fl), strempty(options));
749         if (mount(what, where, type, flags, options) < 0)
750                 return log_full_errno(error_log_level, errno,
751                                       "Failed to mount %s on %s (%s \"%s\"): %m",
752                                       strna(type), where, strnull(fl), strempty(options));
753         return 0;
754 }
755
756 int umount_verbose(const char *what) {
757         log_debug("Umounting %s...", what);
758         if (umount(what) < 0)
759                 return log_error_errno(errno, "Failed to unmount %s: %m", what);
760         return 0;
761 }
762 #endif // 0
763
764 const char *mount_propagation_flags_to_string(unsigned long flags) {
765
766         switch (flags & (MS_SHARED|MS_SLAVE|MS_PRIVATE)) {
767         case 0:
768                 return "";
769         case MS_SHARED:
770                 return "shared";
771         case MS_SLAVE:
772                 return "slave";
773         case MS_PRIVATE:
774                 return "private";
775         }
776
777         return NULL;
778 }
779
780
781 int mount_propagation_flags_from_string(const char *name, unsigned long *ret) {
782
783         if (isempty(name))
784                 *ret = 0;
785         else if (streq(name, "shared"))
786                 *ret = MS_SHARED;
787         else if (streq(name, "slave"))
788                 *ret = MS_SLAVE;
789         else if (streq(name, "private"))
790                 *ret = MS_PRIVATE;
791         else
792                 return -EINVAL;
793         return 0;
794 }