chiark / gitweb /
use the switch_root function in shutdown
[elogind.git] / src / shared / switch-root.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Harald Hoyer, Lennart Poettering
7
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.
12
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.
17
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/>.
20 ***/
21
22 #include <sys/stat.h>
23 #include <stdbool.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <sys/mount.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29
30 #include "util.h"
31 #include "path-util.h"
32 #include "switch-root.h"
33 #include "mkdir.h"
34 #include "base-filesystem.h"
35 #include "missing.h"
36
37 int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot,  unsigned long mountflags) {
38
39         /*  Don't try to unmount/move the old "/", there's no way to do it. */
40         static const char move_mounts[] =
41                 "/dev\0"
42                 "/proc\0"
43                 "/sys\0"
44                 "/run\0";
45
46         _cleanup_close_ int old_root_fd = -1;
47         struct stat new_root_stat;
48         bool old_root_remove;
49         const char *i, *temporary_old_root;
50         int r;
51
52         if (path_equal(new_root, "/"))
53                 return 0;
54
55         temporary_old_root = strappenda(new_root, oldroot);
56         mkdir_p_label(temporary_old_root, 0755);
57
58         old_root_remove = in_initrd();
59
60         if (stat(new_root, &new_root_stat) < 0) {
61                 log_error("Failed to stat directory %s: %m", new_root);
62                 return -errno;
63         }
64
65         /* Work-around for a kernel bug: for some reason the kernel
66          * refuses switching root if any file systems are mounted
67          * MS_SHARED. Hence remount them MS_PRIVATE here as a
68          * work-around.
69          *
70          * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
71         if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
72                 log_warning("Failed to make \"/\" private mount: %m");
73
74         NULSTR_FOREACH(i, move_mounts) {
75                 char new_mount[PATH_MAX];
76                 struct stat sb;
77
78                 snprintf(new_mount, sizeof(new_mount), "%s%s", new_root, i);
79                 char_array_0(new_mount);
80
81                 mkdir_p_label(new_mount, 0755);
82
83                 if ((stat(new_mount, &sb) < 0) ||
84                     sb.st_dev != new_root_stat.st_dev) {
85
86                         /* Mount point seems to be mounted already or
87                          * stat failed. Unmount the old mount
88                          * point. */
89                         if (umount2(i, MNT_DETACH) < 0)
90                                 log_warning("Failed to unmount %s: %m", i);
91                         continue;
92                 }
93
94                 if (mount(i, new_mount, NULL, mountflags, NULL) < 0) {
95                         if (mountflags & MS_MOVE) {
96                                 log_error("Failed to move mount %s to %s, forcing unmount: %m", i, new_mount);
97
98                                 if (umount2(i, MNT_FORCE) < 0)
99                                         log_warning("Failed to unmount %s: %m", i);
100                         }
101                         if (mountflags & MS_BIND)
102                                 log_error("Failed to bind mount %s to %s: %m", i, new_mount);
103
104                 }
105         }
106
107         r = base_filesystem_create(new_root);
108         if (r < 0) {
109                 log_error("Failed to create the base filesystem: %s", strerror(-r));
110                 return r;
111         }
112
113         if (chdir(new_root) < 0) {
114                 log_error("Failed to change directory to %s: %m", new_root);
115                 return -errno;
116         }
117
118         if (old_root_remove) {
119                 old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
120                 if (old_root_fd < 0)
121                         log_warning("Failed to open root directory: %m");
122         }
123
124         /* We first try a pivot_root() so that we can umount the old
125          * root dir. In many cases (i.e. where rootfs is /), that's
126          * not possible however, and hence we simply overmount root */
127         if (pivot_root(new_root, temporary_old_root) >= 0) {
128
129                 /* Immediately get rid of the old root, if detach_oldroot is set.
130                  * Since we are running off it we need to do this lazily. */
131                 if (detach_oldroot && umount2(oldroot, MNT_DETACH) < 0) {
132                         log_error("Failed to umount old root dir %s: %m", oldroot);
133                         return -errno;
134                 }
135
136         } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0) {
137                 log_error("Failed to mount moving %s to /: %m", new_root);
138                 return -errno;
139         }
140
141         if (chroot(".") < 0) {
142                 log_error("Failed to change root: %m");
143                 return -errno;
144         }
145
146         if (chdir("/") < 0) {
147                 log_error("Failed to change directory: %m");
148                 return -errno;
149         }
150
151         if (old_root_fd >= 0) {
152                 struct stat rb;
153
154                 if (fstat(old_root_fd, &rb) < 0)
155                         log_warning("Failed to stat old root directory, leaving: %m");
156                 else {
157                         rm_rf_children(old_root_fd, false, false, &rb);
158                         old_root_fd = -1;
159                 }
160         }
161
162         return 0;
163 }