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