1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <sys/mount.h>
23 #include <linux/magic.h>
25 #include "alloc-util.h"
26 #include "cgroup-util.h"
31 #include "mount-util.h"
32 #include "nspawn-mount.h"
33 #include "parse-util.h"
34 #include "path-util.h"
37 #include "stat-util.h"
38 #include "string-util.h"
40 #include "user-util.h"
43 CustomMount
* custom_mount_add(CustomMount
**l
, unsigned *n
, CustomMountType t
) {
49 assert(t
< _CUSTOM_MOUNT_TYPE_MAX
);
51 c
= realloc(*l
, (*n
+ 1) * sizeof(CustomMount
));
59 *ret
= (CustomMount
) { .type
= t
};
64 void custom_mount_free_all(CustomMount
*l
, unsigned n
) {
67 for (i
= 0; i
< n
; i
++) {
68 CustomMount
*m
= l
+ i
;
75 (void) rm_rf(m
->work_dir
, REMOVE_ROOT
|REMOVE_PHYSICAL
);
85 int custom_mount_compare(const void *a
, const void *b
) {
86 const CustomMount
*x
= a
, *y
= b
;
89 r
= path_compare(x
->destination
, y
->destination
);
93 if (x
->type
< y
->type
)
95 if (x
->type
> y
->type
)
101 int bind_mount_parse(CustomMount
**l
, unsigned *n
, const char *s
, bool read_only
) {
102 _cleanup_free_
char *source
= NULL
, *destination
= NULL
, *opts
= NULL
;
110 r
= extract_many_words(&p
, ":", EXTRACT_DONT_COALESCE_SEPARATORS
, &source
, &destination
, NULL
);
117 destination
= strdup(source
);
122 if (r
== 2 && !isempty(p
)) {
128 if (!path_is_absolute(source
))
131 if (!path_is_absolute(destination
))
134 m
= custom_mount_add(l
, n
, CUSTOM_MOUNT_BIND
);
139 m
->destination
= destination
;
140 m
->read_only
= read_only
;
143 source
= destination
= opts
= NULL
;
147 int tmpfs_mount_parse(CustomMount
**l
, unsigned *n
, const char *s
) {
148 _cleanup_free_
char *path
= NULL
, *opts
= NULL
;
157 r
= extract_first_word(&p
, &path
, ":", EXTRACT_DONT_COALESCE_SEPARATORS
);
164 opts
= strdup("mode=0755");
170 if (!path_is_absolute(path
))
173 m
= custom_mount_add(l
, n
, CUSTOM_MOUNT_TMPFS
);
177 m
->destination
= path
;
184 static int tmpfs_patch_options(
186 bool userns
, uid_t uid_shift
, uid_t uid_range
,
187 const char *selinux_apifs_context
,
192 if (userns
&& uid_shift
!= 0) {
193 assert(uid_shift
!= UID_INVALID
);
196 (void) asprintf(&buf
, "%s,uid=" UID_FMT
",gid=" UID_FMT
, options
, uid_shift
, uid_shift
);
198 (void) asprintf(&buf
, "uid=" UID_FMT
",gid=" UID_FMT
, uid_shift
, uid_shift
);
206 if (selinux_apifs_context
) {
210 t
= strjoin(options
, ",context=\"", selinux_apifs_context
, "\"", NULL
);
212 t
= strjoin("context=\"", selinux_apifs_context
, "\"", NULL
);
227 int mount_sysfs(const char *dest
) {
228 const char *full
, *top
, *x
;
231 top
= prefix_roota(dest
, "/sys");
232 r
= path_check_fstype(top
, SYSFS_MAGIC
);
234 return log_error_errno(r
, "Failed to determine filesystem type of %s: %m", top
);
235 /* /sys might already be mounted as sysfs by the outer child in the
236 * !netns case. In this case, it's all good. Don't touch it because we
237 * don't have the right to do so, see https://github.com/systemd/systemd/issues/1555.
242 full
= prefix_roota(top
, "/full");
244 (void) mkdir(full
, 0755);
246 if (mount("sysfs", full
, "sysfs", MS_RDONLY
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
, NULL
) < 0)
247 return log_error_errno(errno
, "Failed to mount sysfs to %s: %m", full
);
249 FOREACH_STRING(x
, "block", "bus", "class", "dev", "devices", "kernel") {
250 _cleanup_free_
char *from
= NULL
, *to
= NULL
;
252 from
= prefix_root(full
, x
);
256 to
= prefix_root(top
, x
);
260 (void) mkdir(to
, 0755);
262 if (mount(from
, to
, NULL
, MS_BIND
, NULL
) < 0)
263 return log_error_errno(errno
, "Failed to mount /sys/%s into place: %m", x
);
265 if (mount(NULL
, to
, NULL
, MS_BIND
|MS_RDONLY
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
|MS_REMOUNT
, NULL
) < 0)
266 return log_error_errno(errno
, "Failed to mount /sys/%s read-only: %m", x
);
269 if (umount(full
) < 0)
270 return log_error_errno(errno
, "Failed to unmount %s: %m", full
);
273 return log_error_errno(errno
, "Failed to remove %s: %m", full
);
275 x
= prefix_roota(top
, "/fs/kdbus");
276 (void) mkdir(x
, 0755);
278 if (mount(NULL
, top
, NULL
, MS_BIND
|MS_RDONLY
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
|MS_REMOUNT
, NULL
) < 0)
279 return log_error_errno(errno
, "Failed to make %s read-only: %m", top
);
284 int mount_all(const char *dest
,
285 bool use_userns
, bool in_userns
,
287 uid_t uid_shift
, uid_t uid_range
,
288 const char *selinux_apifs_context
) {
290 typedef struct MountPoint
{
301 static const MountPoint mount_table
[] = {
302 { "proc", "/proc", "proc", NULL
, MS_NOSUID
|MS_NOEXEC
|MS_NODEV
, true, true, false },
303 { "/proc/sys", "/proc/sys", NULL
, NULL
, MS_BIND
, true, true, false }, /* Bind mount first */
304 { NULL
, "/proc/sys", NULL
, NULL
, MS_BIND
|MS_RDONLY
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
|MS_REMOUNT
, true, true, false }, /* Then, make it r/o */
305 { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID
|MS_NOEXEC
|MS_NODEV
, true, false, true },
306 { "sysfs", "/sys", "sysfs", NULL
, MS_RDONLY
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
, true, false, false },
307 { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID
|MS_STRICTATIME
, true, false, false },
308 { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID
|MS_NODEV
|MS_STRICTATIME
, true, false, false },
309 { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID
|MS_NODEV
|MS_STRICTATIME
, true, false, false },
310 { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME
, true, false, false },
312 { "/sys/fs/selinux", "/sys/fs/selinux", NULL
, NULL
, MS_BIND
, false, false, false }, /* Bind mount first */
313 { NULL
, "/sys/fs/selinux", NULL
, NULL
, MS_BIND
|MS_RDONLY
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
|MS_REMOUNT
, false, false, false }, /* Then, make it r/o */
320 for (k
= 0; k
< ELEMENTSOF(mount_table
); k
++) {
321 _cleanup_free_
char *where
= NULL
, *options
= NULL
;
324 if (in_userns
!= mount_table
[k
].in_userns
)
327 if (!use_netns
&& mount_table
[k
].use_netns
)
330 where
= prefix_root(dest
, mount_table
[k
].where
);
334 r
= path_is_mount_point(where
, AT_SYMLINK_FOLLOW
);
335 if (r
< 0 && r
!= -ENOENT
)
336 return log_error_errno(r
, "Failed to detect whether %s is a mount point: %m", where
);
338 /* Skip this entry if it is not a remount. */
339 if (mount_table
[k
].what
&& r
> 0)
342 r
= mkdir_p(where
, 0755);
344 if (mount_table
[k
].fatal
)
345 return log_error_errno(r
, "Failed to create directory %s: %m", where
);
347 log_warning_errno(r
, "Failed to create directory %s: %m", where
);
351 o
= mount_table
[k
].options
;
352 if (streq_ptr(mount_table
[k
].type
, "tmpfs")) {
353 r
= tmpfs_patch_options(o
, use_userns
, uid_shift
, uid_range
, selinux_apifs_context
, &options
);
360 if (mount(mount_table
[k
].what
,
363 mount_table
[k
].flags
,
366 if (mount_table
[k
].fatal
)
367 return log_error_errno(errno
, "mount(%s) failed: %m", where
);
369 log_warning_errno(errno
, "mount(%s) failed, ignoring: %m", where
);
376 static int parse_mount_bind_options(const char *options
, unsigned long *mount_flags
, char **mount_opts
) {
377 const char *p
= options
;
378 unsigned long flags
= *mount_flags
;
384 _cleanup_free_
char *word
= NULL
;
385 int r
= extract_first_word(&p
, &word
, ",", 0);
387 return log_error_errno(r
, "Failed to extract mount option: %m");
391 if (streq(word
, "rbind"))
393 else if (streq(word
, "norbind"))
396 log_error("Invalid bind mount option: %s", word
);
401 *mount_flags
= flags
;
402 /* in the future mount_opts will hold string options for mount(2) */
408 static int mount_bind(const char *dest
, CustomMount
*m
) {
409 struct stat source_st
, dest_st
;
411 unsigned long mount_flags
= MS_BIND
| MS_REC
;
412 _cleanup_free_
char *mount_opts
= NULL
;
418 r
= parse_mount_bind_options(m
->options
, &mount_flags
, &mount_opts
);
423 if (stat(m
->source
, &source_st
) < 0)
424 return log_error_errno(errno
, "Failed to stat %s: %m", m
->source
);
426 where
= prefix_roota(dest
, m
->destination
);
428 if (stat(where
, &dest_st
) >= 0) {
429 if (S_ISDIR(source_st
.st_mode
) && !S_ISDIR(dest_st
.st_mode
)) {
430 log_error("Cannot bind mount directory %s on file %s.", m
->source
, where
);
434 if (!S_ISDIR(source_st
.st_mode
) && S_ISDIR(dest_st
.st_mode
)) {
435 log_error("Cannot bind mount file %s on directory %s.", m
->source
, where
);
439 } else if (errno
== ENOENT
) {
440 r
= mkdir_parents_label(where
, 0755);
442 return log_error_errno(r
, "Failed to make parents of %s: %m", where
);
444 return log_error_errno(errno
, "Failed to stat %s: %m", where
);
447 /* Create the mount point. Any non-directory file can be
448 * mounted on any non-directory file (regular, fifo, socket,
451 if (S_ISDIR(source_st
.st_mode
))
452 r
= mkdir_label(where
, 0755);
455 if (r
< 0 && r
!= -EEXIST
)
456 return log_error_errno(r
, "Failed to create mount point %s: %m", where
);
458 if (mount(m
->source
, where
, NULL
, mount_flags
, mount_opts
) < 0)
459 return log_error_errno(errno
, "mount(%s) failed: %m", where
);
462 r
= bind_remount_recursive(where
, true);
464 return log_error_errno(r
, "Read-only bind mount failed: %m");
470 static int mount_tmpfs(
473 bool userns
, uid_t uid_shift
, uid_t uid_range
,
474 const char *selinux_apifs_context
) {
476 const char *where
, *options
;
477 _cleanup_free_
char *buf
= NULL
;
483 where
= prefix_roota(dest
, m
->destination
);
485 r
= mkdir_p_label(where
, 0755);
486 if (r
< 0 && r
!= -EEXIST
)
487 return log_error_errno(r
, "Creating mount point for tmpfs %s failed: %m", where
);
489 r
= tmpfs_patch_options(m
->options
, userns
, uid_shift
, uid_range
, selinux_apifs_context
, &buf
);
492 options
= r
> 0 ? buf
: m
->options
;
494 if (mount("tmpfs", where
, "tmpfs", MS_NODEV
|MS_STRICTATIME
, options
) < 0)
495 return log_error_errno(errno
, "tmpfs mount to %s failed: %m", where
);
500 static char *joined_and_escaped_lower_dirs(char * const *lower
) {
501 _cleanup_strv_free_
char **sv
= NULL
;
503 sv
= strv_copy(lower
);
509 if (!strv_shell_escape(sv
, ",:"))
512 return strv_join(sv
, ":");
515 static int mount_overlay(const char *dest
, CustomMount
*m
) {
516 _cleanup_free_
char *lower
= NULL
;
517 const char *where
, *options
;
523 where
= prefix_roota(dest
, m
->destination
);
525 r
= mkdir_label(where
, 0755);
526 if (r
< 0 && r
!= -EEXIST
)
527 return log_error_errno(r
, "Creating mount point for overlay %s failed: %m", where
);
529 (void) mkdir_p_label(m
->source
, 0755);
531 lower
= joined_and_escaped_lower_dirs(m
->lower
);
536 _cleanup_free_
char *escaped_source
= NULL
;
538 escaped_source
= shell_escape(m
->source
, ",:");
542 options
= strjoina("lowerdir=", escaped_source
, ":", lower
);
544 _cleanup_free_
char *escaped_source
= NULL
, *escaped_work_dir
= NULL
;
547 (void) mkdir_label(m
->work_dir
, 0700);
549 escaped_source
= shell_escape(m
->source
, ",:");
552 escaped_work_dir
= shell_escape(m
->work_dir
, ",:");
553 if (!escaped_work_dir
)
556 options
= strjoina("lowerdir=", lower
, ",upperdir=", escaped_source
, ",workdir=", escaped_work_dir
);
559 if (mount("overlay", where
, "overlay", m
->read_only
? MS_RDONLY
: 0, options
) < 0)
560 return log_error_errno(errno
, "overlay mount to %s failed: %m", where
);
567 CustomMount
*mounts
, unsigned n
,
568 bool userns
, uid_t uid_shift
, uid_t uid_range
,
569 const char *selinux_apifs_context
) {
576 for (i
= 0; i
< n
; i
++) {
577 CustomMount
*m
= mounts
+ i
;
581 case CUSTOM_MOUNT_BIND
:
582 r
= mount_bind(dest
, m
);
585 case CUSTOM_MOUNT_TMPFS
:
586 r
= mount_tmpfs(dest
, m
, userns
, uid_shift
, uid_range
, selinux_apifs_context
);
589 case CUSTOM_MOUNT_OVERLAY
:
590 r
= mount_overlay(dest
, m
);
594 assert_not_reached("Unknown custom mount type");
604 static int mount_legacy_cgroup_hierarchy(const char *dest
, const char *controller
, const char *hierarchy
, bool read_only
) {
608 to
= strjoina(strempty(dest
), "/sys/fs/cgroup/", hierarchy
);
610 r
= path_is_mount_point(to
, 0);
611 if (r
< 0 && r
!= -ENOENT
)
612 return log_error_errno(r
, "Failed to determine if %s is mounted already: %m", to
);
618 /* The superblock mount options of the mount point need to be
619 * identical to the hosts', and hence writable... */
620 if (mount("cgroup", to
, "cgroup", MS_NOSUID
|MS_NOEXEC
|MS_NODEV
, controller
) < 0)
621 return log_error_errno(errno
, "Failed to mount to %s: %m", to
);
623 /* ... hence let's only make the bind mount read-only, not the
626 if (mount(NULL
, to
, NULL
, MS_BIND
|MS_REMOUNT
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
|MS_RDONLY
, NULL
) < 0)
627 return log_error_errno(errno
, "Failed to remount %s read-only: %m", to
);
632 static int mount_legacy_cgroups(
634 bool userns
, uid_t uid_shift
, uid_t uid_range
,
635 const char *selinux_apifs_context
) {
637 _cleanup_set_free_free_ Set
*controllers
= NULL
;
638 const char *cgroup_root
;
641 cgroup_root
= prefix_roota(dest
, "/sys/fs/cgroup");
643 (void) mkdir_p(cgroup_root
, 0755);
645 /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */
646 r
= path_is_mount_point(cgroup_root
, AT_SYMLINK_FOLLOW
);
648 return log_error_errno(r
, "Failed to determine if /sys/fs/cgroup is already mounted: %m");
650 _cleanup_free_
char *options
= NULL
;
652 r
= tmpfs_patch_options("mode=755", userns
, uid_shift
, uid_range
, selinux_apifs_context
, &options
);
656 if (mount("tmpfs", cgroup_root
, "tmpfs", MS_NOSUID
|MS_NOEXEC
|MS_NODEV
|MS_STRICTATIME
, options
) < 0)
657 return log_error_errno(errno
, "Failed to mount /sys/fs/cgroup: %m");
660 if (cg_unified() > 0)
661 goto skip_controllers
;
663 controllers
= set_new(&string_hash_ops
);
667 r
= cg_kernel_controllers(controllers
);
669 return log_error_errno(r
, "Failed to determine cgroup controllers: %m");
672 _cleanup_free_
char *controller
= NULL
, *origin
= NULL
, *combined
= NULL
;
674 controller
= set_steal_first(controllers
);
678 origin
= prefix_root("/sys/fs/cgroup/", controller
);
682 r
= readlink_malloc(origin
, &combined
);
684 /* Not a symbolic link, but directly a single cgroup hierarchy */
686 r
= mount_legacy_cgroup_hierarchy(dest
, controller
, controller
, true);
691 return log_error_errno(r
, "Failed to read link %s: %m", origin
);
693 _cleanup_free_
char *target
= NULL
;
695 target
= prefix_root(dest
, origin
);
699 /* A symbolic link, a combination of controllers in one hierarchy */
701 if (!filename_is_valid(combined
)) {
702 log_warning("Ignoring invalid combined hierarchy %s.", combined
);
706 r
= mount_legacy_cgroup_hierarchy(dest
, combined
, combined
, true);
710 r
= symlink_idempotent(combined
, target
);
712 log_error("Invalid existing symlink for combined hierarchy");
716 return log_error_errno(r
, "Failed to create symlink for combined hierarchy: %m");
721 r
= mount_legacy_cgroup_hierarchy(dest
, "none,name=systemd,xattr", "systemd", false);
725 if (mount(NULL
, cgroup_root
, NULL
, MS_REMOUNT
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
|MS_STRICTATIME
|MS_RDONLY
, "mode=755") < 0)
726 return log_error_errno(errno
, "Failed to remount %s read-only: %m", cgroup_root
);
731 static int mount_unified_cgroups(const char *dest
) {
737 p
= prefix_roota(dest
, "/sys/fs/cgroup");
739 (void) mkdir_p(p
, 0755);
741 r
= path_is_mount_point(p
, AT_SYMLINK_FOLLOW
);
743 return log_error_errno(r
, "Failed to determine if %s is mounted already: %m", p
);
745 p
= prefix_roota(dest
, "/sys/fs/cgroup/cgroup.procs");
746 if (access(p
, F_OK
) >= 0)
749 return log_error_errno(errno
, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p
);
751 log_error("%s is already mounted but not a unified cgroup hierarchy. Refusing.", p
);
755 if (mount("cgroup", p
, "cgroup", MS_NOSUID
|MS_NOEXEC
|MS_NODEV
, "__DEVEL__sane_behavior") < 0)
756 return log_error_errno(errno
, "Failed to mount unified cgroup hierarchy to %s: %m", p
);
763 bool unified_requested
,
764 bool userns
, uid_t uid_shift
, uid_t uid_range
,
765 const char *selinux_apifs_context
) {
767 if (unified_requested
)
768 return mount_unified_cgroups(dest
);
770 return mount_legacy_cgroups(dest
, userns
, uid_shift
, uid_range
, selinux_apifs_context
);
773 int mount_systemd_cgroup_writable(
775 bool unified_requested
) {
777 _cleanup_free_
char *own_cgroup_path
= NULL
;
778 const char *systemd_root
, *systemd_own
;
783 r
= cg_pid_get_path(NULL
, 0, &own_cgroup_path
);
785 return log_error_errno(r
, "Failed to determine our own cgroup path: %m");
787 /* If we are living in the top-level, then there's nothing to do... */
788 if (path_equal(own_cgroup_path
, "/"))
791 if (unified_requested
) {
792 systemd_own
= strjoina(dest
, "/sys/fs/cgroup", own_cgroup_path
);
793 systemd_root
= prefix_roota(dest
, "/sys/fs/cgroup");
795 systemd_own
= strjoina(dest
, "/sys/fs/cgroup/systemd", own_cgroup_path
);
796 systemd_root
= prefix_roota(dest
, "/sys/fs/cgroup/systemd");
799 /* Make our own cgroup a (writable) bind mount */
800 if (mount(systemd_own
, systemd_own
, NULL
, MS_BIND
, NULL
) < 0)
801 return log_error_errno(errno
, "Failed to turn %s into a bind mount: %m", own_cgroup_path
);
803 /* And then remount the systemd cgroup root read-only */
804 if (mount(NULL
, systemd_root
, NULL
, MS_BIND
|MS_REMOUNT
|MS_NOSUID
|MS_NOEXEC
|MS_NODEV
|MS_RDONLY
, NULL
) < 0)
805 return log_error_errno(errno
, "Failed to mount cgroup root read-only: %m");
810 int setup_volatile_state(
811 const char *directory
,
813 bool userns
, uid_t uid_shift
, uid_t uid_range
,
814 const char *selinux_apifs_context
) {
816 _cleanup_free_
char *buf
= NULL
;
817 const char *p
, *options
;
822 if (mode
!= VOLATILE_STATE
)
825 /* --volatile=state means we simply overmount /var
826 with a tmpfs, and the rest read-only. */
828 r
= bind_remount_recursive(directory
, true);
830 return log_error_errno(r
, "Failed to remount %s read-only: %m", directory
);
832 p
= prefix_roota(directory
, "/var");
834 if (r
< 0 && errno
!= EEXIST
)
835 return log_error_errno(errno
, "Failed to create %s: %m", directory
);
837 options
= "mode=755";
838 r
= tmpfs_patch_options(options
, userns
, uid_shift
, uid_range
, selinux_apifs_context
, &buf
);
844 if (mount("tmpfs", p
, "tmpfs", MS_STRICTATIME
, options
) < 0)
845 return log_error_errno(errno
, "Failed to mount tmpfs to /var: %m");
851 const char *directory
,
853 bool userns
, uid_t uid_shift
, uid_t uid_range
,
854 const char *selinux_apifs_context
) {
856 bool tmpfs_mounted
= false, bind_mounted
= false;
857 char template[] = "/tmp/nspawn-volatile-XXXXXX";
858 _cleanup_free_
char *buf
= NULL
;
859 const char *f
, *t
, *options
;
864 if (mode
!= VOLATILE_YES
)
867 /* --volatile=yes means we mount a tmpfs to the root dir, and
868 the original /usr to use inside it, and that read-only. */
870 if (!mkdtemp(template))
871 return log_error_errno(errno
, "Failed to create temporary directory: %m");
873 options
= "mode=755";
874 r
= tmpfs_patch_options(options
, userns
, uid_shift
, uid_range
, selinux_apifs_context
, &buf
);
880 if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME
, options
) < 0) {
881 r
= log_error_errno(errno
, "Failed to mount tmpfs for root directory: %m");
885 tmpfs_mounted
= true;
887 f
= prefix_roota(directory
, "/usr");
888 t
= prefix_roota(template, "/usr");
891 if (r
< 0 && errno
!= EEXIST
) {
892 r
= log_error_errno(errno
, "Failed to create %s: %m", t
);
896 if (mount(f
, t
, NULL
, MS_BIND
|MS_REC
, NULL
) < 0) {
897 r
= log_error_errno(errno
, "Failed to create /usr bind mount: %m");
903 r
= bind_remount_recursive(t
, true);
905 log_error_errno(r
, "Failed to remount %s read-only: %m", t
);
909 if (mount(template, directory
, NULL
, MS_MOVE
, NULL
) < 0) {
910 r
= log_error_errno(errno
, "Failed to move root mount: %m");
914 (void) rmdir(template);
923 (void) umount(template);
924 (void) rmdir(template);
928 VolatileMode
volatile_mode_from_string(const char *s
) {
932 return _VOLATILE_MODE_INVALID
;
934 b
= parse_boolean(s
);
940 if (streq(s
, "state"))
941 return VOLATILE_STATE
;
943 return _VOLATILE_MODE_INVALID
;