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