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