]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
4349cd7c | 2 | |
11c3a366 | 3 | #include <errno.h> |
35bbbf85 | 4 | #include <stdio_ext.h> |
11c3a366 | 5 | #include <stdlib.h> |
4349cd7c LP |
6 | #include <string.h> |
7 | #include <sys/mount.h> | |
11c3a366 | 8 | #include <sys/stat.h> |
4349cd7c | 9 | #include <sys/statvfs.h> |
11c3a366 | 10 | #include <unistd.h> |
4349cd7c | 11 | |
9e7f941a YW |
12 | /* Include later */ |
13 | #include <libmount.h> | |
14 | ||
b5efdb8a | 15 | #include "alloc-util.h" |
4349cd7c | 16 | #include "escape.h" |
9e7f941a | 17 | #include "extract-word.h" |
4349cd7c LP |
18 | #include "fd-util.h" |
19 | #include "fileio.h" | |
e1873695 | 20 | #include "fs-util.h" |
93cc7779 | 21 | #include "hashmap.h" |
4349cd7c | 22 | #include "mount-util.h" |
049af8ad | 23 | #include "mountpoint-util.h" |
4349cd7c LP |
24 | #include "parse-util.h" |
25 | #include "path-util.h" | |
26 | #include "set.h" | |
15a5e950 | 27 | #include "stdio-util.h" |
4349cd7c | 28 | #include "string-util.h" |
6b7c9f8b | 29 | #include "strv.h" |
4349cd7c | 30 | |
4349cd7c LP |
31 | int umount_recursive(const char *prefix, int flags) { |
32 | bool again; | |
33 | int n = 0, r; | |
34 | ||
35 | /* Try to umount everything recursively below a | |
36 | * directory. Also, take care of stacked mounts, and keep | |
37 | * unmounting them until they are gone. */ | |
38 | ||
39 | do { | |
40 | _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; | |
41 | ||
42 | again = false; | |
43 | r = 0; | |
44 | ||
45 | proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); | |
46 | if (!proc_self_mountinfo) | |
47 | return -errno; | |
48 | ||
35bbbf85 LP |
49 | (void) __fsetlocking(proc_self_mountinfo, FSETLOCKING_BYCALLER); |
50 | ||
4349cd7c LP |
51 | for (;;) { |
52 | _cleanup_free_ char *path = NULL, *p = NULL; | |
53 | int k; | |
54 | ||
55 | k = fscanf(proc_self_mountinfo, | |
56 | "%*s " /* (1) mount id */ | |
57 | "%*s " /* (2) parent id */ | |
58 | "%*s " /* (3) major:minor */ | |
59 | "%*s " /* (4) root */ | |
60 | "%ms " /* (5) mount point */ | |
61 | "%*s" /* (6) mount options */ | |
62 | "%*[^-]" /* (7) optional fields */ | |
63 | "- " /* (8) separator */ | |
64 | "%*s " /* (9) file system type */ | |
65 | "%*s" /* (10) mount source */ | |
66 | "%*s" /* (11) mount options 2 */ | |
67 | "%*[^\n]", /* some rubbish at the end */ | |
68 | &path); | |
69 | if (k != 1) { | |
70 | if (k == EOF) | |
71 | break; | |
72 | ||
73 | continue; | |
74 | } | |
75 | ||
76 | r = cunescape(path, UNESCAPE_RELAX, &p); | |
77 | if (r < 0) | |
78 | return r; | |
79 | ||
80 | if (!path_startswith(p, prefix)) | |
81 | continue; | |
82 | ||
83 | if (umount2(p, flags) < 0) { | |
6b7c9f8b | 84 | r = log_debug_errno(errno, "Failed to umount %s: %m", p); |
4349cd7c LP |
85 | continue; |
86 | } | |
87 | ||
6b7c9f8b LP |
88 | log_debug("Successfully unmounted %s", p); |
89 | ||
4349cd7c LP |
90 | again = true; |
91 | n++; | |
92 | ||
93 | break; | |
94 | } | |
95 | ||
96 | } while (again); | |
97 | ||
98 | return r ? r : n; | |
99 | } | |
100 | ||
101 | static int get_mount_flags(const char *path, unsigned long *flags) { | |
102 | struct statvfs buf; | |
103 | ||
104 | if (statvfs(path, &buf) < 0) | |
105 | return -errno; | |
106 | *flags = buf.f_flag; | |
107 | return 0; | |
108 | } | |
109 | ||
ac9de0b3 TR |
110 | /* Use this function only if do you have direct access to /proc/self/mountinfo |
111 | * and need the caller to open it for you. This is the case when /proc is | |
112 | * masked or not mounted. Otherwise, use bind_remount_recursive. */ | |
113 | int bind_remount_recursive_with_mountinfo(const char *prefix, bool ro, char **blacklist, FILE *proc_self_mountinfo) { | |
4349cd7c LP |
114 | _cleanup_set_free_free_ Set *done = NULL; |
115 | _cleanup_free_ char *cleaned = NULL; | |
116 | int r; | |
117 | ||
ac9de0b3 TR |
118 | assert(proc_self_mountinfo); |
119 | ||
6b7c9f8b LP |
120 | /* Recursively remount a directory (and all its submounts) read-only or read-write. If the directory is already |
121 | * mounted, we reuse the mount and simply mark it MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write | |
122 | * operation). If it isn't we first make it one. Afterwards we apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to | |
123 | * all submounts we can access, too. When mounts are stacked on the same mount point we only care for each | |
124 | * individual "top-level" mount on each point, as we cannot influence/access the underlying mounts anyway. We | |
125 | * do not have any effect on future submounts that might get propagated, they migt be writable. This includes | |
126 | * future submounts that have been triggered via autofs. | |
127 | * | |
128 | * If the "blacklist" parameter is specified it may contain a list of subtrees to exclude from the | |
129 | * remount operation. Note that we'll ignore the blacklist for the top-level path. */ | |
4349cd7c LP |
130 | |
131 | cleaned = strdup(prefix); | |
132 | if (!cleaned) | |
133 | return -ENOMEM; | |
134 | ||
858d36c1 | 135 | path_simplify(cleaned, false); |
4349cd7c | 136 | |
548f6937 | 137 | done = set_new(&path_hash_ops); |
4349cd7c LP |
138 | if (!done) |
139 | return -ENOMEM; | |
140 | ||
141 | for (;;) { | |
4349cd7c LP |
142 | _cleanup_set_free_free_ Set *todo = NULL; |
143 | bool top_autofs = false; | |
144 | char *x; | |
145 | unsigned long orig_flags; | |
146 | ||
548f6937 | 147 | todo = set_new(&path_hash_ops); |
4349cd7c LP |
148 | if (!todo) |
149 | return -ENOMEM; | |
150 | ||
ac9de0b3 | 151 | rewind(proc_self_mountinfo); |
4349cd7c LP |
152 | |
153 | for (;;) { | |
154 | _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL; | |
155 | int k; | |
156 | ||
157 | k = fscanf(proc_self_mountinfo, | |
158 | "%*s " /* (1) mount id */ | |
159 | "%*s " /* (2) parent id */ | |
160 | "%*s " /* (3) major:minor */ | |
161 | "%*s " /* (4) root */ | |
162 | "%ms " /* (5) mount point */ | |
163 | "%*s" /* (6) mount options (superblock) */ | |
164 | "%*[^-]" /* (7) optional fields */ | |
165 | "- " /* (8) separator */ | |
166 | "%ms " /* (9) file system type */ | |
167 | "%*s" /* (10) mount source */ | |
168 | "%*s" /* (11) mount options (bind mount) */ | |
169 | "%*[^\n]", /* some rubbish at the end */ | |
170 | &path, | |
171 | &type); | |
172 | if (k != 2) { | |
173 | if (k == EOF) | |
174 | break; | |
175 | ||
176 | continue; | |
177 | } | |
178 | ||
179 | r = cunescape(path, UNESCAPE_RELAX, &p); | |
180 | if (r < 0) | |
181 | return r; | |
182 | ||
6b7c9f8b LP |
183 | if (!path_startswith(p, cleaned)) |
184 | continue; | |
185 | ||
186 | /* Ignore this mount if it is blacklisted, but only if it isn't the top-level mount we shall | |
187 | * operate on. */ | |
188 | if (!path_equal(cleaned, p)) { | |
189 | bool blacklisted = false; | |
190 | char **i; | |
191 | ||
192 | STRV_FOREACH(i, blacklist) { | |
193 | ||
194 | if (path_equal(*i, cleaned)) | |
195 | continue; | |
196 | ||
197 | if (!path_startswith(*i, cleaned)) | |
198 | continue; | |
199 | ||
200 | if (path_startswith(p, *i)) { | |
201 | blacklisted = true; | |
c745d2bf | 202 | log_debug("Not remounting %s blacklisted by %s, called for %s", p, *i, cleaned); |
6b7c9f8b LP |
203 | break; |
204 | } | |
205 | } | |
206 | if (blacklisted) | |
207 | continue; | |
208 | } | |
209 | ||
4349cd7c LP |
210 | /* Let's ignore autofs mounts. If they aren't |
211 | * triggered yet, we want to avoid triggering | |
212 | * them, as we don't make any guarantees for | |
213 | * future submounts anyway. If they are | |
214 | * already triggered, then we will find | |
215 | * another entry for this. */ | |
216 | if (streq(type, "autofs")) { | |
217 | top_autofs = top_autofs || path_equal(cleaned, p); | |
218 | continue; | |
219 | } | |
220 | ||
6b7c9f8b | 221 | if (!set_contains(done, p)) { |
4349cd7c LP |
222 | r = set_consume(todo, p); |
223 | p = NULL; | |
4349cd7c LP |
224 | if (r == -EEXIST) |
225 | continue; | |
226 | if (r < 0) | |
227 | return r; | |
228 | } | |
229 | } | |
230 | ||
231 | /* If we have no submounts to process anymore and if | |
232 | * the root is either already done, or an autofs, we | |
233 | * are done */ | |
234 | if (set_isempty(todo) && | |
235 | (top_autofs || set_contains(done, cleaned))) | |
236 | return 0; | |
237 | ||
238 | if (!set_contains(done, cleaned) && | |
239 | !set_contains(todo, cleaned)) { | |
6b7c9f8b | 240 | /* The prefix directory itself is not yet a mount, make it one. */ |
4349cd7c LP |
241 | if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) |
242 | return -errno; | |
243 | ||
244 | orig_flags = 0; | |
245 | (void) get_mount_flags(cleaned, &orig_flags); | |
246 | orig_flags &= ~MS_RDONLY; | |
247 | ||
ef454fd1 | 248 | if (mount(NULL, cleaned, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) |
4349cd7c LP |
249 | return -errno; |
250 | ||
6b7c9f8b LP |
251 | log_debug("Made top-level directory %s a mount point.", prefix); |
252 | ||
4349cd7c LP |
253 | x = strdup(cleaned); |
254 | if (!x) | |
255 | return -ENOMEM; | |
256 | ||
257 | r = set_consume(done, x); | |
258 | if (r < 0) | |
259 | return r; | |
260 | } | |
261 | ||
262 | while ((x = set_steal_first(todo))) { | |
263 | ||
264 | r = set_consume(done, x); | |
4c701096 | 265 | if (IN_SET(r, 0, -EEXIST)) |
4349cd7c LP |
266 | continue; |
267 | if (r < 0) | |
268 | return r; | |
269 | ||
6b7c9f8b | 270 | /* Deal with mount points that are obstructed by a later mount */ |
e1873695 | 271 | r = path_is_mount_point(x, NULL, 0); |
4c701096 | 272 | if (IN_SET(r, 0, -ENOENT)) |
98df8089 | 273 | continue; |
ef454fd1 | 274 | if (IN_SET(r, -EACCES, -EPERM)) { |
53c442ef YW |
275 | /* Even if root user invoke this, submounts under private FUSE or NFS mount points |
276 | * may not be acceessed. E.g., | |
277 | * | |
278 | * $ bindfs --no-allow-other ~/mnt/mnt ~/mnt/mnt | |
279 | * $ bindfs --no-allow-other ~/mnt ~/mnt | |
280 | * | |
281 | * Then, root user cannot access the mount point ~/mnt/mnt. | |
282 | * In such cases, the submounts are ignored, as we have no way to manage them. */ | |
ef454fd1 YW |
283 | log_debug_errno(r, "Failed to determine '%s' is mount point or not, ignoring: %m", x); |
284 | continue; | |
285 | } | |
98df8089 AC |
286 | if (r < 0) |
287 | return r; | |
288 | ||
289 | /* Try to reuse the original flag set */ | |
4349cd7c LP |
290 | orig_flags = 0; |
291 | (void) get_mount_flags(x, &orig_flags); | |
292 | orig_flags &= ~MS_RDONLY; | |
293 | ||
98df8089 AC |
294 | if (mount(NULL, x, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) |
295 | return -errno; | |
4349cd7c | 296 | |
6b7c9f8b | 297 | log_debug("Remounted %s read-only.", x); |
4349cd7c LP |
298 | } |
299 | } | |
300 | } | |
301 | ||
ac9de0b3 TR |
302 | int bind_remount_recursive(const char *prefix, bool ro, char **blacklist) { |
303 | _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; | |
304 | ||
305 | proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); | |
306 | if (!proc_self_mountinfo) | |
307 | return -errno; | |
308 | ||
35bbbf85 LP |
309 | (void) __fsetlocking(proc_self_mountinfo, FSETLOCKING_BYCALLER); |
310 | ||
ac9de0b3 TR |
311 | return bind_remount_recursive_with_mountinfo(prefix, ro, blacklist, proc_self_mountinfo); |
312 | } | |
313 | ||
4349cd7c LP |
314 | int mount_move_root(const char *path) { |
315 | assert(path); | |
316 | ||
317 | if (chdir(path) < 0) | |
318 | return -errno; | |
319 | ||
320 | if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) | |
321 | return -errno; | |
322 | ||
323 | if (chroot(".") < 0) | |
324 | return -errno; | |
325 | ||
326 | if (chdir("/") < 0) | |
327 | return -errno; | |
328 | ||
329 | return 0; | |
330 | } | |
4e036b7a | 331 | |
3f2c0bec LP |
332 | int repeat_unmount(const char *path, int flags) { |
333 | bool done = false; | |
334 | ||
335 | assert(path); | |
336 | ||
337 | /* If there are multiple mounts on a mount point, this | |
338 | * removes them all */ | |
339 | ||
340 | for (;;) { | |
341 | if (umount2(path, flags) < 0) { | |
342 | ||
343 | if (errno == EINVAL) | |
344 | return done; | |
345 | ||
346 | return -errno; | |
347 | } | |
348 | ||
349 | done = true; | |
350 | } | |
351 | } | |
c4b41707 AP |
352 | |
353 | const char* mode_to_inaccessible_node(mode_t mode) { | |
fe80fcc7 LP |
354 | /* This function maps a node type to a corresponding inaccessible file node. These nodes are created during |
355 | * early boot by PID 1. In some cases we lacked the privs to create the character and block devices (maybe | |
356 | * because we run in an userns environment, or miss CAP_SYS_MKNOD, or run with a devices policy that excludes | |
357 | * device nodes with major and minor of 0), but that's fine, in that case we use an AF_UNIX file node instead, | |
358 | * which is not the same, but close enough for most uses. And most importantly, the kernel allows bind mounts | |
359 | * from socket nodes to any non-directory file nodes, and that's the most important thing that matters. */ | |
360 | ||
c4b41707 AP |
361 | switch(mode & S_IFMT) { |
362 | case S_IFREG: | |
363 | return "/run/systemd/inaccessible/reg"; | |
fe80fcc7 | 364 | |
c4b41707 AP |
365 | case S_IFDIR: |
366 | return "/run/systemd/inaccessible/dir"; | |
fe80fcc7 | 367 | |
c4b41707 | 368 | case S_IFCHR: |
b3d1d516 AP |
369 | if (access("/run/systemd/inaccessible/chr", F_OK) == 0) |
370 | return "/run/systemd/inaccessible/chr"; | |
371 | return "/run/systemd/inaccessible/sock"; | |
fe80fcc7 | 372 | |
c4b41707 | 373 | case S_IFBLK: |
b3d1d516 AP |
374 | if (access("/run/systemd/inaccessible/blk", F_OK) == 0) |
375 | return "/run/systemd/inaccessible/blk"; | |
376 | return "/run/systemd/inaccessible/sock"; | |
fe80fcc7 | 377 | |
c4b41707 AP |
378 | case S_IFIFO: |
379 | return "/run/systemd/inaccessible/fifo"; | |
fe80fcc7 | 380 | |
c4b41707 AP |
381 | case S_IFSOCK: |
382 | return "/run/systemd/inaccessible/sock"; | |
383 | } | |
384 | return NULL; | |
385 | } | |
60e76d48 ZJS |
386 | |
387 | #define FLAG(name) (flags & name ? STRINGIFY(name) "|" : "") | |
388 | static char* mount_flags_to_string(long unsigned flags) { | |
389 | char *x; | |
390 | _cleanup_free_ char *y = NULL; | |
391 | long unsigned overflow; | |
392 | ||
393 | overflow = flags & ~(MS_RDONLY | | |
394 | MS_NOSUID | | |
395 | MS_NODEV | | |
396 | MS_NOEXEC | | |
397 | MS_SYNCHRONOUS | | |
398 | MS_REMOUNT | | |
399 | MS_MANDLOCK | | |
400 | MS_DIRSYNC | | |
401 | MS_NOATIME | | |
402 | MS_NODIRATIME | | |
403 | MS_BIND | | |
404 | MS_MOVE | | |
405 | MS_REC | | |
406 | MS_SILENT | | |
407 | MS_POSIXACL | | |
408 | MS_UNBINDABLE | | |
409 | MS_PRIVATE | | |
410 | MS_SLAVE | | |
411 | MS_SHARED | | |
412 | MS_RELATIME | | |
413 | MS_KERNMOUNT | | |
414 | MS_I_VERSION | | |
415 | MS_STRICTATIME | | |
416 | MS_LAZYTIME); | |
417 | ||
418 | if (flags == 0 || overflow != 0) | |
419 | if (asprintf(&y, "%lx", overflow) < 0) | |
420 | return NULL; | |
421 | ||
422 | x = strjoin(FLAG(MS_RDONLY), | |
423 | FLAG(MS_NOSUID), | |
424 | FLAG(MS_NODEV), | |
425 | FLAG(MS_NOEXEC), | |
426 | FLAG(MS_SYNCHRONOUS), | |
427 | FLAG(MS_REMOUNT), | |
428 | FLAG(MS_MANDLOCK), | |
429 | FLAG(MS_DIRSYNC), | |
430 | FLAG(MS_NOATIME), | |
431 | FLAG(MS_NODIRATIME), | |
432 | FLAG(MS_BIND), | |
433 | FLAG(MS_MOVE), | |
434 | FLAG(MS_REC), | |
435 | FLAG(MS_SILENT), | |
436 | FLAG(MS_POSIXACL), | |
437 | FLAG(MS_UNBINDABLE), | |
438 | FLAG(MS_PRIVATE), | |
439 | FLAG(MS_SLAVE), | |
440 | FLAG(MS_SHARED), | |
441 | FLAG(MS_RELATIME), | |
442 | FLAG(MS_KERNMOUNT), | |
443 | FLAG(MS_I_VERSION), | |
444 | FLAG(MS_STRICTATIME), | |
445 | FLAG(MS_LAZYTIME), | |
605405c6 | 446 | y); |
60e76d48 ZJS |
447 | if (!x) |
448 | return NULL; | |
449 | if (!y) | |
450 | x[strlen(x) - 1] = '\0'; /* truncate the last | */ | |
451 | return x; | |
452 | } | |
453 | ||
454 | int mount_verbose( | |
455 | int error_log_level, | |
456 | const char *what, | |
457 | const char *where, | |
458 | const char *type, | |
459 | unsigned long flags, | |
460 | const char *options) { | |
461 | ||
6ef8df2b YW |
462 | _cleanup_free_ char *fl = NULL, *o = NULL; |
463 | unsigned long f; | |
464 | int r; | |
465 | ||
466 | r = mount_option_mangle(options, flags, &f, &o); | |
467 | if (r < 0) | |
468 | return log_full_errno(error_log_level, r, | |
469 | "Failed to mangle mount options %s: %m", | |
470 | strempty(options)); | |
60e76d48 | 471 | |
6ef8df2b | 472 | fl = mount_flags_to_string(f); |
60e76d48 | 473 | |
6ef8df2b | 474 | if ((f & MS_REMOUNT) && !what && !type) |
60e76d48 | 475 | log_debug("Remounting %s (%s \"%s\")...", |
6ef8df2b | 476 | where, strnull(fl), strempty(o)); |
60e76d48 ZJS |
477 | else if (!what && !type) |
478 | log_debug("Mounting %s (%s \"%s\")...", | |
6ef8df2b YW |
479 | where, strnull(fl), strempty(o)); |
480 | else if ((f & MS_BIND) && !type) | |
60e76d48 | 481 | log_debug("Bind-mounting %s on %s (%s \"%s\")...", |
6ef8df2b YW |
482 | what, where, strnull(fl), strempty(o)); |
483 | else if (f & MS_MOVE) | |
afe682bc | 484 | log_debug("Moving mount %s → %s (%s \"%s\")...", |
6ef8df2b | 485 | what, where, strnull(fl), strempty(o)); |
60e76d48 ZJS |
486 | else |
487 | log_debug("Mounting %s on %s (%s \"%s\")...", | |
6ef8df2b YW |
488 | strna(type), where, strnull(fl), strempty(o)); |
489 | if (mount(what, where, type, f, o) < 0) | |
60e76d48 | 490 | return log_full_errno(error_log_level, errno, |
3ccf6126 LP |
491 | "Failed to mount %s (type %s) on %s (%s \"%s\"): %m", |
492 | strna(what), strna(type), where, strnull(fl), strempty(o)); | |
60e76d48 ZJS |
493 | return 0; |
494 | } | |
495 | ||
496 | int umount_verbose(const char *what) { | |
497 | log_debug("Umounting %s...", what); | |
498 | if (umount(what) < 0) | |
499 | return log_error_errno(errno, "Failed to unmount %s: %m", what); | |
500 | return 0; | |
501 | } | |
83555251 | 502 | |
9e7f941a YW |
503 | int mount_option_mangle( |
504 | const char *options, | |
505 | unsigned long mount_flags, | |
506 | unsigned long *ret_mount_flags, | |
507 | char **ret_remaining_options) { | |
508 | ||
509 | const struct libmnt_optmap *map; | |
510 | _cleanup_free_ char *ret = NULL; | |
511 | const char *p; | |
512 | int r; | |
513 | ||
514 | /* This extracts mount flags from the mount options, and store | |
515 | * non-mount-flag options to '*ret_remaining_options'. | |
516 | * E.g., | |
517 | * "rw,nosuid,nodev,relatime,size=1630748k,mode=700,uid=1000,gid=1000" | |
518 | * is split to MS_NOSUID|MS_NODEV|MS_RELATIME and | |
519 | * "size=1630748k,mode=700,uid=1000,gid=1000". | |
520 | * See more examples in test-mount-utils.c. | |
521 | * | |
522 | * Note that if 'options' does not contain any non-mount-flag options, | |
523 | * then '*ret_remaining_options' is set to NULL instread of empty string. | |
524 | * Note that this does not check validity of options stored in | |
525 | * '*ret_remaining_options'. | |
526 | * Note that if 'options' is NULL, then this just copies 'mount_flags' | |
527 | * to '*ret_mount_flags'. */ | |
528 | ||
529 | assert(ret_mount_flags); | |
530 | assert(ret_remaining_options); | |
531 | ||
532 | map = mnt_get_builtin_optmap(MNT_LINUX_MAP); | |
533 | if (!map) | |
534 | return -EINVAL; | |
535 | ||
536 | p = options; | |
537 | for (;;) { | |
538 | _cleanup_free_ char *word = NULL; | |
539 | const struct libmnt_optmap *ent; | |
540 | ||
541 | r = extract_first_word(&p, &word, ",", EXTRACT_QUOTES); | |
542 | if (r < 0) | |
543 | return r; | |
544 | if (r == 0) | |
545 | break; | |
546 | ||
547 | for (ent = map; ent->name; ent++) { | |
548 | /* All entries in MNT_LINUX_MAP do not take any argument. | |
549 | * Thus, ent->name does not contain "=" or "[=]". */ | |
550 | if (!streq(word, ent->name)) | |
551 | continue; | |
552 | ||
553 | if (!(ent->mask & MNT_INVERT)) | |
554 | mount_flags |= ent->id; | |
555 | else if (mount_flags & ent->id) | |
556 | mount_flags ^= ent->id; | |
557 | ||
558 | break; | |
559 | } | |
560 | ||
561 | /* If 'word' is not a mount flag, then store it in '*ret_remaining_options'. */ | |
562 | if (!ent->name && !strextend_with_separator(&ret, ",", word, NULL)) | |
563 | return -ENOMEM; | |
564 | } | |
565 | ||
566 | *ret_mount_flags = mount_flags; | |
ae2a15bc | 567 | *ret_remaining_options = TAKE_PTR(ret); |
9e7f941a YW |
568 | |
569 | return 0; | |
570 | } |