]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/switch_root.c
switch_root: rewrite to use fstatat() and unlinkat()
[thirdparty/util-linux.git] / sys-utils / switch_root.c
1 /*
2 * switchroot.c - switch to new root directory and start init.
3 *
4 * Copyright 2002-2008 Red Hat, Inc. All rights reserved.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Authors:
20 * Peter Jones <pjones@redhat.com>
21 * Jeremy Katz <katzj@redhat.com>
22 */
23 #include <sys/mount.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <sys/param.h>
27 #include <fcntl.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <errno.h>
33 #include <ctype.h>
34 #include <dirent.h>
35
36 #ifndef MS_MOVE
37 #define MS_MOVE 8192
38 #endif
39
40 enum {
41 ok,
42 err_no_directory,
43 err_usage,
44 };
45
46 /* remove all files/directories below dirName -- don't cross mountpoints */
47 static int recursiveRemove(char *dirName)
48 {
49 struct stat rb;
50 DIR *dir;
51 int rc = -1;
52 int dfd;
53
54 if (!(dir = opendir(dirName))) {
55 printf("error opening %s: %m\n", dirName);
56 goto done;
57 }
58
59 dfd = dirfd(dir);
60
61 if (fstat(dfd, &rb)) {
62 printf("unable to stat %s: %m\n", dirName);
63 goto done;
64 }
65
66 while(1) {
67 struct dirent *d;
68
69 errno = 0;
70 if (!(d = readdir(dir))) {
71 if (errno) {
72 printf("error reading from %s: %m\n", dirName);
73 goto done;
74 }
75 break; /* end of directory */
76 }
77
78 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
79 continue;
80
81 if (d->d_type == DT_DIR) {
82 struct stat sb;
83
84 if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
85 printf("failed to stat %s/%s: %m\n",
86 dirName, d->d_name);
87 continue;
88 }
89
90 /* remove subdirectories if device is same as dir */
91 if (sb.st_dev == rb.st_dev) {
92 char subdir[ strlen(dirName) +
93 strlen(d->d_name) + 2 ];
94
95 sprintf(subdir, "%s/%s", dirName, d->d_name);
96 recursiveRemove(subdir);
97 } else
98 continue;
99 }
100
101 if (unlinkat(dfd, d->d_name,
102 d->d_type == DT_DIR ? AT_REMOVEDIR : 0))
103 printf("failed to unlink %s/%s: %m\n", dirName, d->d_name);
104 }
105
106 rc = 0; /* success */
107
108 done:
109 if (dir)
110 closedir(dir);
111 return rc;
112 }
113
114 static int switchroot(const char *newroot)
115 {
116 /* Don't try to unmount the old "/", there's no way to do it. */
117 const char *umounts[] = { "/dev", "/proc", "/sys", NULL };
118 int errnum;
119 int i;
120
121 for (i = 0; umounts[i] != NULL; i++) {
122 char newmount[PATH_MAX];
123 strcpy(newmount, newroot);
124 strcat(newmount, umounts[i]);
125 if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) {
126 fprintf(stderr, "Error mount moving old %s %s %m\n",
127 umounts[i], newmount);
128 fprintf(stderr, "Forcing unmount of %s\n", umounts[i]);
129 umount2(umounts[i], MNT_FORCE);
130 }
131 }
132
133 if (chdir(newroot) < 0) {
134 errnum=errno;
135 fprintf(stderr, "switchroot: chdir failed: %m\n");
136 errno=errnum;
137 return -1;
138 }
139 recursiveRemove("/");
140 if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) {
141 errnum = errno;
142 fprintf(stderr, "switchroot: mount failed: %m\n");
143 errno = errnum;
144 return -1;
145 }
146
147 if (chroot(".")) {
148 errnum = errno;
149 fprintf(stderr, "switchroot: chroot failed: %m\n");
150 errno = errnum;
151 return -2;
152 }
153 return 1;
154 }
155
156 static void usage(FILE *output)
157 {
158 fprintf(output, "usage: switchroot <newrootdir> <init> <args to init>\n");
159 if (output == stderr)
160 exit(err_usage);
161 exit(ok);
162 }
163
164 int main(int argc, char *argv[])
165 {
166 char *newroot = argv[1];
167 char *init = argv[2];
168 char **initargs = &argv[2];
169
170 if (newroot == NULL || newroot[0] == '\0' ||
171 init == NULL || init[0] == '\0' ) {
172 usage(stderr);
173 }
174
175 if (switchroot(newroot) < 0) {
176 fprintf(stderr, "switchroot has failed. Sorry.\n");
177 return 1;
178 }
179 if (access(initargs[0], X_OK))
180 fprintf(stderr, "WARNING: can't access %s\n", initargs[0]);
181
182 /* get session leader */
183 setsid();
184 /* set controlling terminal */
185 ioctl (0, TIOCSCTTY, 1);
186
187 execv(initargs[0], initargs);
188 }
189