Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 968 lines (774 sloc) 23.294 kb
e5a8d5b @falconindy standardize file headers and add MIT licensee
authored
1 /*
2 * init.c
3 *
4 * This file is part of geninit.
5 *
6 * PID 1 for early userspace.
7 *
8 */
9
75052c2 @falconindy initial commit
authored
10 #define _GNU_SOURCE
11 #include <ctype.h>
12 #include <dirent.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <limits.h>
16 #include <linux/magic.h>
17 #include <signal.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/mount.h>
22 #include <sys/stat.h>
23 #include <sys/time.h>
24 #include <sys/types.h>
25 #include <sys/vfs.h>
26 #include <sys/wait.h>
27 #include <unistd.h>
28
29 /* util-linux */
1d8224a @falconindy provide the proper path for blkid.h
authored
30 #include <blkid/blkid.h>
75052c2 @falconindy initial commit
authored
31
21346da @falconindy remove useless curly braces
authored
32 #define msg(...) if (!quiet) fprintf(stderr, ":: " __VA_ARGS__)
33 #define err(...) fprintf(stderr, "error: " __VA_ARGS__)
34 #define warn(...) fprintf(stderr, "warning: " __VA_ARGS__)
35 #define die(...) err(__VA_ARGS__); _exit(1)
75052c2 @falconindy initial commit
authored
36
9e335d4 @falconindy STRING => QUOTE
authored
37 #define QUOTE(x) #x
482d4d2 @falconindy fix CHILD_WRITE_FD declaration
authored
38 #define TOSTRING(x) QUOTE(x)
a001934 @falconindy fully abstract write pipe from child to parent
authored
39
165842a @falconindy rename CHILD_READ_FD as CHILD_WRITE_FD, since this is way more accurate
authored
40 #define CMDLINE_SIZE 257 /* 256 max cmdline len + NULL */
75052c2 @falconindy initial commit
authored
41
165842a @falconindy rename CHILD_READ_FD as CHILD_WRITE_FD, since this is way more accurate
authored
42 #define CHILD_WRITE_FD 6
354f0b0 @falconindy use FD 6 instead of 3. It's less likely to conflict.
authored
43
165842a @falconindy rename CHILD_READ_FD as CHILD_WRITE_FD, since this is way more accurate
authored
44 #define NEWROOT "/new_root"
45 #define BUSYBOX "/bin/busybox"
46 #define UDEVD "/sbin/udevd"
47 #define UDEVADM "/sbin/udevadm"
48 #define MODPROBE "/sbin/modprobe"
75052c2 @falconindy initial commit
authored
49
50 int rootflags = 0;
51 int quiet = 0;
be50613 @falconindy Install busybox symlinks prior to running hooks, if needed.
authored
52 int bbox_installed = 0;
3b17a57 @falconindy refactor handling of the udev daemon
authored
53 pid_t udevpid = 0;
75052c2 @falconindy initial commit
authored
54
55 /* utility */
56 static dev_t hex2dev(char *hexstring) { /* {{{ */
57 char *endptr;
58 char hexmajor[3], hexminor[3];
59 long major, minor;
60 size_t len;
61
62 len = strlen(hexstring);
63 if (len > 4) {
64 return 1;
65 }
66
67 /* 2 less than the length, plus a NULL */
68 snprintf(hexmajor, len - 2 + 1, "%s", hexstring);
69
70 /* leave off after the major, 2 chars plus a NULL */
71 snprintf(hexminor, 3, "%s", hexstring + len - 2);
72
73 major = strtol(hexmajor, &endptr, 16);
74 if (!endptr) {
75 return makedev(0, 0);
76 }
77
78 minor = strtol(hexminor, &endptr, 16);
79 if (!endptr) {
80 return makedev(0, 0);
81 }
82
83 return makedev(major, minor);
84 } /* }}} */
85
86 static int forkexecwait(char **argv) { /* {{{ */
87 pid_t pid;
88 int statloc;
89
90 pid = vfork();
91 if (pid == -1) {
92 perror("fork");
93 return errno;
94 }
95
96 if (pid == 0) {
97 execv(argv[0], argv);
98 fprintf(stderr, "exec: %s: %s\n", argv[0], strerror(errno));
99 _exit(errno); /* avoid flushing streams */
100 }
101
102 /* block for process exit */
103 waitpid(pid, &statloc, 0);
104
105 if (WIFEXITED(statloc) > 0) {
106 return WEXITSTATUS(statloc);
b3832b5 @falconindy improve return values of forkexecwait
authored
107 } else if (WIFSIGNALED(statloc)) {
108 return WTERMSIG(statloc) + 128;
75052c2 @falconindy initial commit
authored
109 }
110
b3832b5 @falconindy improve return values of forkexecwait
authored
111 /* we really shouldn't get here */
112 return 255;
75052c2 @falconindy initial commit
authored
113 } /* }}} */
114
115 static char *sanitize_var(char *var) { /* {{{ */
116 char *p;
117
118 /* special attention to first letter */
119 p = var;
120 if (!(isalpha(*p) || *p == '_')) {
121 /* invalid var name, can't use this */
122 return NULL;
123 }
124
125 while (*++p) {
126 if (isalnum((unsigned char)*p) || *p == '_') {
127 /* valid character */
128 continue;
129 }
130
131 if (*p == '=') {
132 /* stop here, don't mangle the values */
133 return var;
134 }
135
136 if (*p == '.' || *p == '-') {
137 /* sanitizable */
138 *p = '_';
139 } else {
140 /* gfy */
141 return NULL;
142 }
143 }
144
145 return var;
146 } /* }}} */
147
821dd7e @falconindy replace busybox's rm -rf implementation with util-linux's
authored
148 static int delete_contents(char *dirname) { /* {{{ */
149 struct stat rb; /* rootdir buffer */
150 int dfd, rc = -1;
151 DIR *dp;
75052c2 @falconindy initial commit
authored
152
821dd7e @falconindy replace busybox's rm -rf implementation with util-linux's
authored
153 if (!(dp = opendir(dirname))) {
154 warn("failed to open %s", dirname);
155 goto done;
75052c2 @falconindy initial commit
authored
156 }
157
821dd7e @falconindy replace busybox's rm -rf implementation with util-linux's
authored
158 dfd = dirfd(dp);
159
160 if (fstat(dfd, &rb)) {
161 warn("failed to stat %s", dirname);
162 goto done;
163 }
164
165 while(1) {
166 struct dirent *d;
167
168 errno = 0;
169 if (!(d = readdir(dp))) {
170 if (errno) {
171 warn("failed to read %s", dirname);
172 goto done;
75052c2 @falconindy initial commit
authored
173 }
821dd7e @falconindy replace busybox's rm -rf implementation with util-linux's
authored
174 break; /* end of directory */
175 }
75052c2 @falconindy initial commit
authored
176
821dd7e @falconindy replace busybox's rm -rf implementation with util-linux's
authored
177 if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
178 continue;
75052c2 @falconindy initial commit
authored
179 }
821dd7e @falconindy replace busybox's rm -rf implementation with util-linux's
authored
180
181 if (d->d_type == DT_DIR) {
182 struct stat sb; /* subdir buffer */
183
184 if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
185 warn("failed to stat %s/%s", dirname, d->d_name);
186 continue;
187 }
188
189 /* don't descend into other filesystems */
190 if (sb.st_dev == rb.st_dev) {
191 char subdir[strlen(dirname) + strlen(d->d_name) + 2];
192
193 sprintf(subdir, "%s/%s", dirname, d->d_name);
194 delete_contents(subdir);
195 } else {
196 continue;
197 }
198 }
199
200 if (unlinkat(dfd, d->d_name, d->d_type == DT_DIR ? AT_REMOVEDIR : 0)) {
201 warn("failed to unlink %s/%s", dirname, d->d_name);
202 }
203 }
204
205 rc = 0; /* success */
206
207 done:
208 if (dp) {
209 closedir(dp);
75052c2 @falconindy initial commit
authored
210 }
821dd7e @falconindy replace busybox's rm -rf implementation with util-linux's
authored
211 return rc;
75052c2 @falconindy initial commit
authored
212 } /* }}} */
213
fae5479 @falconindy expose FDINIT for rescue shells
authored
214 static ssize_t read_child_response(char **argv, char *buffer) { /* {{{ */
215 int statloc, pfds[2];
af47f6e @falconindy initial support for custom mount handlers
authored
216 ssize_t len, total = 0;
aa2c1ae @falconindy loop to read from child processes
authored
217 char readbuf[BUFSIZ];
fae5479 @falconindy expose FDINIT for rescue shells
authored
218 pid_t pid;
75052c2 @falconindy initial commit
authored
219
fae5479 @falconindy expose FDINIT for rescue shells
authored
220 if (pipe(pfds) != 0) {
221 perror("pipe");
283da7e @falconindy return negative errno to avoid returning a valid len
authored
222 return -errno;
75052c2 @falconindy initial commit
authored
223 }
224
fae5479 @falconindy expose FDINIT for rescue shells
authored
225 pid = fork();
226 if (pid < 0) {
227 perror("fork");
283da7e @falconindy return negative errno to avoid returning a valid len
authored
228 return -errno;
be50613 @falconindy Install busybox symlinks prior to running hooks, if needed.
authored
229 }
75052c2 @falconindy initial commit
authored
230
fae5479 @falconindy expose FDINIT for rescue shells
authored
231 if (pid == 0) {
232 close(pfds[0]); /* unused by child */
75052c2 @falconindy initial commit
authored
233
fae5479 @falconindy expose FDINIT for rescue shells
authored
234 /* child writes on CHILD_WRITE_FD will be received by the parent */
235 if (dup2(pfds[1], CHILD_WRITE_FD) == -1) {
236 perror("dup2");
237 _exit(errno);
238 }
75052c2 @falconindy initial commit
authored
239
fae5479 @falconindy expose FDINIT for rescue shells
authored
240 /* now redundant */
241 close(pfds[1]);
75052c2 @falconindy initial commit
authored
242
fae5479 @falconindy expose FDINIT for rescue shells
authored
243 execv(argv[0], argv);
af47f6e @falconindy initial support for custom mount handlers
authored
244 fprintf(stderr, "exec: %s: %s\n", argv[0], strerror(errno));
fae5479 @falconindy expose FDINIT for rescue shells
authored
245 _exit(errno);
75052c2 @falconindy initial commit
authored
246 }
247
fae5479 @falconindy expose FDINIT for rescue shells
authored
248 close(pfds[1]); /* unused by parent */
75052c2 @falconindy initial commit
authored
249
aa2c1ae @falconindy loop to read from child processes
authored
250 memset(buffer, 0, BUFSIZ);
251 while (1) {
252 len = read(pfds[0], readbuf, BUFSIZ);
253 if (len <= 0 && errno != EINTR) {
254 break;
255 }
256
257 if (total + len > BUFSIZ) { /* overflow! */
258 /* this is a ridiculous condition. the user just tried to write an absurd
259 * amount of data to init. if this wasn't an accident, i either messed up,
260 * or i hate the user. */
261 err("buffer overflow detected while writing to init! input may be truncated!");
262
263 /* write what we can to the buffer and get out */
264 memcpy(&buffer[total], readbuf, BUFSIZ - total - 1);
265 break;
266 }
267
268 memcpy(&buffer[total], readbuf, len);
269 total += len;
270 }
af47f6e @falconindy initial support for custom mount handlers
authored
271
fae5479 @falconindy expose FDINIT for rescue shells
authored
272 close(pfds[0]);
75052c2 @falconindy initial commit
authored
273
fae5479 @falconindy expose FDINIT for rescue shells
authored
274 waitpid(pid, &statloc, 0);
275 if (WIFEXITED(statloc) && WEXITSTATUS(statloc) != 0) {
af47f6e @falconindy initial support for custom mount handlers
authored
276 err("`%s' exited with status %d\n", argv[0], WEXITSTATUS(statloc));
283da7e @falconindy return negative errno to avoid returning a valid len
authored
277 return -(WEXITSTATUS(statloc));
75052c2 @falconindy initial commit
authored
278 }
fae5479 @falconindy expose FDINIT for rescue shells
authored
279
aa2c1ae @falconindy loop to read from child processes
authored
280 return total;
75052c2 @falconindy initial commit
authored
281 } /* }}} */
282
71fb815 @falconindy move environment string parsing to separate function
authored
283 static void parse_envstring(char *envstring) { /* {{{ */
75052c2 @falconindy initial commit
authored
284 char *c, *tp;
71fb815 @falconindy move environment string parsing to separate function
authored
285 char token[CMDLINE_SIZE];
286 char quoted = '\0'; /* flag for inside/outside quoted region */
75052c2 @falconindy initial commit
authored
287 int isvar = 0;
71fb815 @falconindy move environment string parsing to separate function
authored
288
289 if (!envstring) {
290 return;
291 }
75052c2 @falconindy initial commit
authored
292
293 /* a bit of pointer/var hell going on...
71fb815 @falconindy move environment string parsing to separate function
authored
294 * c = pointer along contents of envstring
75052c2 @falconindy initial commit
authored
295 * token = container for current token being parsed
296 * tp = pointer along contents of token
297 */
298
299 tp = token;
71fb815 @falconindy move environment string parsing to separate function
authored
300 for (c = envstring; *c; c++) {
75052c2 @falconindy initial commit
authored
301 if (*c == '#') { /* full stop! */
302 break;
303 }
304
305 if (isspace((unsigned char)*c)) {
306 /* don't break inside a quoted region */
307 if (!quoted && tp != token) {
308 *tp = '\0';
309 if (sanitize_var(token)) {
310 if (isvar) {
311 putenv(strdup(token));
312 } else {
313 setenv(strdup(token), "y", 1);
314 }
315 if (strcmp(token, "ro") == 0) {
316 rootflags |= MS_RDONLY;
317 } else if (strcmp(token, "quiet") == 0) {
318 quiet = 1;
319 }
320 }
321 isvar = 0;
322 tp = token;
323 }
324 continue;
325 } else if (*c == '\'' || *c == '"') {
326 if (quoted) {
327 if (quoted == *c) {
328 quoted = '\0';
329 continue;
330 }
331 } else {
332 quoted = *c;
333 continue;
334 }
335 }
336
337 if (*c == '=') {
338 isvar = 1;
339 }
340
341 *tp++ = *c;
342 }
71fb815 @falconindy move environment string parsing to separate function
authored
343
344 } /* }}} */
345
fae5479 @falconindy expose FDINIT for rescue shells
authored
346 static void start_rescue_shell(void) { /* {{{ */
347 char *bboxinstall[] = { BUSYBOX, "--install", NULL };
348 char *bboxlaunch[] = { BUSYBOX, "ash", NULL };
aa2c1ae @falconindy loop to read from child processes
authored
349 char buffer[BUFSIZ];
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
350
fae5479 @falconindy expose FDINIT for rescue shells
authored
351 if (access(BUSYBOX, X_OK) != 0) {
352 return;
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
353 }
354
fae5479 @falconindy expose FDINIT for rescue shells
authored
355 if (!bbox_installed) {
356 forkexecwait(bboxinstall);
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
357 }
358
fae5479 @falconindy expose FDINIT for rescue shells
authored
359 /* set a prompt */
360 putenv("PS1=[ramfs \\W]\\$ ");
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
361
fae5479 @falconindy expose FDINIT for rescue shells
authored
362 /* start the shell, allow writes on FDINIT */
363 if (read_child_response(bboxlaunch, buffer) > 0) {
364 parse_envstring(buffer);
365 }
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
366
fae5479 @falconindy expose FDINIT for rescue shells
authored
367 } /* }}} */
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
368
fae5479 @falconindy expose FDINIT for rescue shells
authored
369 static char *probe_fstype(const char *devname) { /* {{{ */
370 int ret;
91f7aa0 @falconindy always iterate over the returned values from blkid
authored
371 char *fstype = NULL;
fae5479 @falconindy expose FDINIT for rescue shells
authored
372 blkid_probe pr;
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
373
fae5479 @falconindy expose FDINIT for rescue shells
authored
374 pr = blkid_new_probe_from_filename(devname);
375 if (!pr) {
376 err("%s: failed to create a new libblkid probe\n", devname);
377 return NULL;
378 }
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
379
fae5479 @falconindy expose FDINIT for rescue shells
authored
380 blkid_probe_enable_superblocks(pr, 1);
381 blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE);
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
382
fae5479 @falconindy expose FDINIT for rescue shells
authored
383 ret = blkid_do_safeprobe(pr);
384 if (ret == -1) {
385 return NULL;
386 } else if (ret == 1) {
387 err("failed to probe device %s\n", devname);
388 return NULL;
389 } else {
390 const char *name, *data;
91f7aa0 @falconindy always iterate over the returned values from blkid
authored
391 int i, nvals = blkid_probe_numof_values(pr);
392
393 /* btrfs (maybe others) returns more than just its fstype here so we're
394 * forced to iterate over the data to find the one true 'TYPE' */
395 for (i = 0; i < nvals; i++) {
396 blkid_probe_get_value(pr, i, &name, &data, NULL);
397 if (strcmp(name, "TYPE") == 0) {
398 fstype = strdup(data);
399 break;
400 }
401 }
4d09a5d @falconindy throw an error when a hook exits with non-zero
authored
402 }
403
fae5479 @falconindy expose FDINIT for rescue shells
authored
404 blkid_free_probe(pr);
405
406 return fstype;
407 } /* }}} */
408
409 static void movemount(const char *src, const char *dest) { /* {{{ */
410 /* move the mount if it exists on the real root, otherwise get rid of it */
411 if (access(dest, F_OK) == 0) {
412 mount(src, dest, NULL, MS_MOVE, NULL);
413 } else {
414 umount2(src, MNT_DETACH);
415 }
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
416 } /* }}} */
417
137997d @falconindy implement udevadm as a helper function
authored
418 static int udevadm(char *action, char *arg1, char *arg2) { /* {{{ */
419 char *argv[] = { UDEVADM, action, arg1, arg2, NULL };
420 return forkexecwait(argv);
421 } /* }}} */
422
71fb815 @falconindy move environment string parsing to separate function
authored
423 /* meat */
424 static void mount_setup(void) { /* {{{ */
425 int ret;
426
427 /* setup basic filesystems */
e08daf9 @falconindy allow exec on /run
authored
428 mount("proc", "/proc", "proc", MS_NOEXEC|MS_NODEV|MS_NOSUID, NULL);
429 mount("sys", "/sys", "sysfs", MS_NOEXEC|MS_NODEV|MS_NOSUID, NULL);
430 mount("run", "/run", "tmpfs", MS_NODEV|MS_NOSUID, "mode=0755,size=10M");
71fb815 @falconindy move environment string parsing to separate function
authored
431
432 /* ENODEV returned on non-existant FS */
cad5781 @falconindy init: limit size on devtmpfs mount
authored
433 ret = mount("udev", "/dev", "devtmpfs", MS_NOSUID, "mode=0755,size=1024k");
71fb815 @falconindy move environment string parsing to separate function
authored
434 if (ret == -1 && errno == ENODEV) {
435 /* devtmpfs not available, use standard tmpfs */
df5ae8d @falconindy init: limit /dev to 1024k
authored
436 mount("udev", "/dev", "tmpfs", MS_NOSUID, "mode=0755,size=1024k");
71fb815 @falconindy move environment string parsing to separate function
authored
437
2c0b939 @falconindy init: shorten comment about nodes
authored
438 /* create necessary nodes */
71fb815 @falconindy move environment string parsing to separate function
authored
439 mknod("/dev/console", S_IFCHR|0600, makedev(5, 1));
440 mknod("/dev/null", S_IFCHR|0666, makedev(1, 3));
441 mknod("/dev/zero", S_IFCHR|0666, makedev(1, 5));
442 mknod("/dev/mem", S_IFCHR|0640, makedev(1, 1));
443 }
444 } /* }}} */
445
446 static void put_cmdline(void) { /* {{{ */
447 char cmdline[CMDLINE_SIZE];
448 FILE *fp;
449
450 fp = fopen("/proc/cmdline", "r");
451 if (!fp) {
452 return;
453 }
454
455 if (fgets(cmdline, CMDLINE_SIZE, fp) != NULL) {
456 parse_envstring(cmdline);
457 }
458
459 fclose(fp);
75052c2 @falconindy initial commit
authored
460 } /* }}} */
461
3b17a57 @falconindy refactor handling of the udev daemon
authored
462 static void launch_udev(void) { /* {{{ */
be50613 @falconindy Install busybox symlinks prior to running hooks, if needed.
authored
463 char *argv[] = { UDEVD, "--resolve-names=never", NULL };
75052c2 @falconindy initial commit
authored
464
465 if (access(UDEVD, X_OK) != 0) {
3b17a57 @falconindy refactor handling of the udev daemon
authored
466 return;
75052c2 @falconindy initial commit
authored
467 }
468
469 msg("Starting udev...\n");
470
3b17a57 @falconindy refactor handling of the udev daemon
authored
471 udevpid = vfork();
472 if (udevpid == -1) {
75052c2 @falconindy initial commit
authored
473 perror("fork");
3b17a57 @falconindy refactor handling of the udev daemon
authored
474 return;
75052c2 @falconindy initial commit
authored
475 }
476
3b17a57 @falconindy refactor handling of the udev daemon
authored
477 if (udevpid == 0) {
75052c2 @falconindy initial commit
authored
478 execv(argv[0], argv);
479 perror("exec: " UDEVD);
480 _exit(errno);
481 }
482
3b17a57 @falconindy refactor handling of the udev daemon
authored
483 /* we assume here that udevd started correctly, but we won't trust this. */
75052c2 @falconindy initial commit
authored
484 } /* }}} */
485
486 static void load_extra_modules(void) { /* {{{ */
487 FILE *fp;
488 char *tok, *var;
489 char **argv;
490 char line[PATH_MAX];
8af7bcc @falconindy cleanup parsing earlymodules. fix a memory leak and wrong allocation
authored
491 int modcount = 3;
75052c2 @falconindy initial commit
authored
492
493 /* load early modules */
494 if (getenv("earlymodules") != NULL) {
e605090 @falconindy tighten up modprobe execution
authored
495 argv = calloc(3, sizeof(argv));
8af7bcc @falconindy cleanup parsing earlymodules. fix a memory leak and wrong allocation
authored
496 argv[0] = MODPROBE;
497 argv[1] = "-qab";
498 argv[2] = "--";
75052c2 @falconindy initial commit
authored
499
500 var = strdup(getenv("earlymodules"));
501 for (tok = strtok(var, ","); tok; tok = strtok(NULL, ",")) {
502 argv = realloc(argv, sizeof(argv) * ++modcount);
8af7bcc @falconindy cleanup parsing earlymodules. fix a memory leak and wrong allocation
authored
503 argv[modcount - 1] = tok;
75052c2 @falconindy initial commit
authored
504 }
505
e605090 @falconindy tighten up modprobe execution
authored
506 if (modcount > 3) {
75052c2 @falconindy initial commit
authored
507 argv = realloc(argv, sizeof(argv) * ++modcount);
508 *(argv + (modcount - 1)) = NULL;
8d72afc @falconindy Revert "run modprobe in load_extra_modules asynchronously"
authored
509 forkexecwait(argv);
75052c2 @falconindy initial commit
authored
510 }
511 free(argv);
8af7bcc @falconindy cleanup parsing earlymodules. fix a memory leak and wrong allocation
authored
512 free(var);
75052c2 @falconindy initial commit
authored
513 }
514
515 /* load modules from /config */
516 fp = fopen("/config", "r");
7c8ab96 @falconindy fix possible null dereferences
authored
517 if (!fp) {
518 return;
519 }
520
29fdef9 @falconindy fixup init for new config style
authored
521 while ((fgets(line, 1024, fp))) {
522 if (strncmp(line, "%MODULES%", 9) == 0) {
523 strtok(line, " \n"); /* ditch the fieldname */
524 tok = strtok(NULL, " \n");
75052c2 @falconindy initial commit
authored
525
29fdef9 @falconindy fixup init for new config style
authored
526 modcount = atoi(tok);
527 if (!modcount) {
8af7bcc @falconindy cleanup parsing earlymodules. fix a memory leak and wrong allocation
authored
528 break;
29fdef9 @falconindy fixup init for new config style
authored
529 }
530
531 /* commands + number of modules + NULL */
8af7bcc @falconindy cleanup parsing earlymodules. fix a memory leak and wrong allocation
authored
532 argv = calloc(3 + modcount + 1, sizeof argv);
e605090 @falconindy tighten up modprobe execution
authored
533 *argv++ = MODPROBE;
534 *argv++ = "-qab";
535 *argv++ = "--";
8af7bcc @falconindy cleanup parsing earlymodules. fix a memory leak and wrong allocation
authored
536
29fdef9 @falconindy fixup init for new config style
authored
537 while ((tok = strtok(NULL, " \n"))) {
538 *argv++ = tok;
75052c2 @falconindy initial commit
authored
539 }
29fdef9 @falconindy fixup init for new config style
authored
540
541 /* rewind */
e605090 @falconindy tighten up modprobe execution
authored
542 argv -= (modcount + 3);
29fdef9 @falconindy fixup init for new config style
authored
543
544 /* run modprobe */
8d72afc @falconindy Revert "run modprobe in load_extra_modules asynchronously"
authored
545 forkexecwait(argv);
29fdef9 @falconindy fixup init for new config style
authored
546
547 free(argv);
548 break;
75052c2 @falconindy initial commit
authored
549 }
550 }
7c8ab96 @falconindy fix possible null dereferences
authored
551
552 fclose(fp);
75052c2 @falconindy initial commit
authored
553 } /* }}} */
554
555 static void trigger_udev_events(void) { /* {{{ */
2427be9 @falconindy export UDEVPID for children, so they know if udev is running
authored
556 char buffer[8];
75052c2 @falconindy initial commit
authored
557 struct timeval tv[2];
558 long time_ms = 0; /* processing time in ms */
559
239441c @falconindy if running, export udev's pid to the environment
authored
560 /* is udev alive? */
561 if (udevpid <= 0 || kill(udevpid, 0) != 0) {
75052c2 @falconindy initial commit
authored
562 return;
563 }
564
2427be9 @falconindy export UDEVPID for children, so they know if udev is running
authored
565 /* drop udev's pid into the environment for children to use */
566 snprintf(buffer, 8, "%d", udevpid);
567 setenv("UDEVPID", buffer, 1);
568
a3534b6 @falconindy trigger udev events in two stages
authored
569 msg("triggering uevents...\n");
570
137997d @falconindy implement udevadm as a helper function
authored
571 gettimeofday(&tv[0], NULL);
572 udevadm("trigger", "--action=add", "--type=subsystems");
573 udevadm("trigger", "--action=add", "--type=devices");
574 udevadm("settle", "--timeout=30", NULL);
75052c2 @falconindy initial commit
authored
575 gettimeofday(&tv[1], NULL);
576
577 time_ms += (tv[1].tv_sec - tv[0].tv_sec) * 1000; /* s => ms */
578 time_ms += (tv[1].tv_usec - tv[0].tv_usec) / 1000; /* us => ms */
579
580 msg("finished udev processing in %ldms\n", time_ms);
581 } /* }}} */
582
583 static void disable_hooks(void) { /* {{{ */
584 char *hook, *list, *disable;
585
586 disable = getenv("disablehooks");
587 if (!disable) {
588 return;
589 }
590
591 list = strdup(disable);
592 for (hook = strtok(list, ", "); hook; hook = strtok(NULL, ", ")) {
593 char path[PATH_MAX];
594 snprintf(path, PATH_MAX, "/hooks/%s", hook);
595
596 /* mark as non-executable so run_hooks skips over it */
597 chmod(path, 0644);
598 }
599
600 free(list);
601 } /* }}} */
602
603 static void run_hooks(void) { /* {{{ */
be50613 @falconindy Install busybox symlinks prior to running hooks, if needed.
authored
604 char *bboxinstall[] = { BUSYBOX, "--install", NULL };
75052c2 @falconindy initial commit
authored
605 char line[PATH_MAX];
606 char *hook;
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
607 FILE *fp;
75052c2 @falconindy initial commit
authored
608
22b0f4c @falconindy init: export a default PATH
authored
609 putenv("PATH=/usr/sbin:/usr/bin:/sbin:/bin");
fae5479 @falconindy expose FDINIT for rescue shells
authored
610 setenv("FDINIT", TOSTRING(CHILD_WRITE_FD), 1);
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
611 line[0] = '\0';
fae5479 @falconindy expose FDINIT for rescue shells
authored
612
90dfe5f @falconindy dmesg: remove builder/hook. add support for loglevel=N.
authored
613 fp = fopen("/config", "r");
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
614 if (fp) {
615 while (fgets(line, PATH_MAX, fp) != NULL) {
616 if (strncmp(line, "%HOOKS%", 7) == 0) {
617 break;
618 }
619 line[0] = '\0';
620 }
621 fclose(fp);
75052c2 @falconindy initial commit
authored
622 }
623
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
624 if (!line[0]) { /* never found a %HOOKS% line */
625 return;
626 }
75052c2 @falconindy initial commit
authored
627
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
628 strtok(line, " \n"); /* ditch the fieldname */
629 while ((hook = strtok(NULL, " \n"))) {
630 char *argv[] = { NULL, NULL };
631 char response[BUFSIZ], path[PATH_MAX];
75052c2 @falconindy initial commit
authored
632
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
633 snprintf(path, PATH_MAX, "/hooks/%s", hook);
634 if (access(path, X_OK) != 0) {
635 continue;
636 }
be50613 @falconindy Install busybox symlinks prior to running hooks, if needed.
authored
637
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
638 /* lazily install symlinks */
639 if (!bbox_installed) {
640 forkexecwait(bboxinstall);
641 bbox_installed = 1;
642 }
e51793f @falconindy allow hooks to write to FD3 to communicate back to init
authored
643
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
644 argv[0] = path;
75052c2 @falconindy initial commit
authored
645
b15ea26 @falconindy avoid keeping the FD for /config open through hooks
authored
646 /* writes to CHILD_WRITE_FD will read back and parsed as environment
647 * variables. the return value is that of read(3). */
648 if (read_child_response(argv, response) > 0) {
649 parse_envstring(response);
75052c2 @falconindy initial commit
authored
650 }
651 }
652
653 } /* }}} */
654
655 static int wait_for_root(void) { /* {{{ */
656 char *rootdelay, *root;
657 int delay = 0;
658
659 root = getenv("root");
660 rootdelay = getenv("rootdelay");
661
aedbf72 @falconindy add extra checking for wait_for_root slightly
authored
662 if (access(root, F_OK) == 0) {
663 return 0; /* already exists */
664 }
665
75052c2 @falconindy initial commit
authored
666 if (strncmp(root, "/dev/", 5) != 0) {
667 return 1; /* not a path, so it won't be found */
668 }
669
670 if (rootdelay) {
671 /* atoi is "safe" here because delay<=0 is invalid */
672 delay = atoi(rootdelay);
673 }
674
675 if (delay <= 0) {
676 delay = 10;
677 }
678
679 msg("waiting up to %d seconds for %s ...\n", delay, root);
680
681 delay *= 10; /* we sleep in centiseconds */
682 while (delay--) {
683 if (access(root, F_OK) == 0) {
684 struct stat st;
aedbf72 @falconindy add extra checking for wait_for_root slightly
authored
685 if (stat(root, &st) == 0) {
686 if (S_ISBLK(st.st_mode)) {
687 return 0; /* found */
688 } else {
689 warn("%s showed up, but it isn't a block device!\n", root);
690 return 1; /* not found? */
691 }
75052c2 @falconindy initial commit
authored
692 }
693 }
694 usleep(100000); /* .1 seconds */
695 }
696
697 return 1; /* not found */
698 } /* }}} */
699
700 static void try_create_root(void) { /* {{{ */
701 dev_t rootdev;
72272da @falconindy Revert "init: add support for PARTUUID="
authored
702 char *root;
75052c2 @falconindy initial commit
authored
703
704 root = getenv("root");
705
2c40440 @falconindy allow specifying UUID= or LABEL= syntax for root
authored
706 if (strncmp(root, "UUID=", 5) == 0 ||
707 strncmp(root, "LABEL=", 6) == 0) {
72272da @falconindy Revert "init: add support for PARTUUID="
authored
708 /* resolve UUID= or LABEL= syntax */
709 char *key, *val, *res;
2c40440 @falconindy allow specifying UUID= or LABEL= syntax for root
authored
710
72272da @falconindy Revert "init: add support for PARTUUID="
authored
711 key = val = root;
712 strsep(&val, "=");
2c40440 @falconindy allow specifying UUID= or LABEL= syntax for root
authored
713
72272da @falconindy Revert "init: add support for PARTUUID="
authored
714 res = blkid_evaluate_tag(key, val, NULL);
2c40440 @falconindy allow specifying UUID= or LABEL= syntax for root
authored
715 if (!res) {
716 err("failed to resolve %s to a root device", root);
717 return;
718 }
719 root = res;
720
721 /* it may already exist */
722 if (access(root, F_OK) == 0) {
723 setenv("root", root, 1);
724 return;
725 }
726 }
727
728 /* intentional fallthrough from above */
75052c2 @falconindy initial commit
authored
729 if (strncmp(root, "/dev/", 5) == 0) {
2c40440 @falconindy allow specifying UUID= or LABEL= syntax for root
authored
730 /* regular block device */
75052c2 @falconindy initial commit
authored
731 FILE *fp;
732 char path[PATH_MAX], majmin[8];
733
734 snprintf(path, PATH_MAX, "/sys/class/block/%s", root + 6);
735 if (*path && access(path, R_OK) == 0) {
736 fp = fopen(path, "r"); /* this will not fail */
737 fgets(majmin, 8, fp);
738 fclose(fp);
739 setenv("root", majmin, 1);
740 }
741 }
742
743 /* intentional fallthrough from above */
744 if (strchr(root, ':')) {
745 /* major/minor encoding */
746 char *major, *minor;
747
748 major = minor = root;
749 strsep(&minor, ":");
750 rootdev = makedev(atoi(major), atoi(minor));
751 } else if (strtol(root, NULL, 16) > 0) {
752 rootdev = hex2dev(root);
753 } else {
754 /* uhhhhhhhhhhhhh .... ?? */
755 err("unknown device: '%s'. You can try to create "
756 "/dev/root yourself!\n", root);
757 start_rescue_shell();
758 printf("continuing... chance of failure = high\n");
759 return;
760 }
761
aa35bf9 @falconindy catch invalid root device return from hex2dev
authored
762 if (!major(rootdev) && !minor(rootdev)) {
763 err("invalid root specifier: %s\n", root);
764 return;
765 }
766
75052c2 @falconindy initial commit
authored
767 if (mknod("/dev/root", 0660|S_IFBLK, rootdev) != 0) {
768 perror("failed to create root device");
769 return;
770 }
771
772 /* only set this now that /dev/root was created successfully */
773 setenv("root", "/dev/root", 1);
774
775 } /* }}} */
776
777 static int mount_root(void) { /* {{{ */
af47f6e @falconindy initial support for custom mount handlers
authored
778 char *mount_handler, *root, *fstype, *data;
75052c2 @falconindy initial commit
authored
779 int ret = 1;
780
af47f6e @falconindy initial support for custom mount handlers
authored
781 mount_handler = getenv("mount_handler");
782 if (mount_handler != NULL) {
f4109bb @falconindy init: draw mount handlers from /mount. avoid statvfs.
authored
783 struct stat rootdev, newrootdev;
784 char response[BUFSIZ], handlerpath[PATH_MAX];
785 char *argv[] = { handlerpath, NULL };
786
787 snprintf(handlerpath, PATH_MAX, "/mount/%s", mount_handler);
af47f6e @falconindy initial support for custom mount handlers
authored
788
789 if (!bbox_installed) { /* unlikely */
790 char *bboxinstall[] = { BUSYBOX, "--install", NULL };
791 forkexecwait(bboxinstall);
792 bbox_installed = 1;
793 }
794
795 if (read_child_response(argv, response) > 0) {
796 parse_envstring(response);
797 }
798
f4109bb @falconindy init: draw mount handlers from /mount. avoid statvfs.
authored
799 stat("/", &rootdev);
800 stat(NEWROOT, &newrootdev);
af47f6e @falconindy initial support for custom mount handlers
authored
801
f4109bb @falconindy init: draw mount handlers from /mount. avoid statvfs.
authored
802 return !(rootdev.st_dev = newrootdev.st_dev);
af47f6e @falconindy initial support for custom mount handlers
authored
803 }
804
75052c2 @falconindy initial commit
authored
805 root = getenv("root");
2a82ab5 @falconindy init: add support for rootflags var
authored
806 data = getenv("rootflags");
75052c2 @falconindy initial commit
authored
807 fstype = getenv("rootfstype");
2a82ab5 @falconindy init: add support for rootflags var
authored
808
75052c2 @falconindy initial commit
authored
809 if (fstype) {
2a82ab5 @falconindy init: add support for rootflags var
authored
810 return mount(root, NEWROOT, fstype, rootflags, data);
75052c2 @falconindy initial commit
authored
811 }
812
813 fstype = probe_fstype(root);
814 if (!fstype) { /* still no fstype, we're out of ideas */
815 /* should hopefully never reach this */
816 err("the filesystem of the root device could not be determined!\n");
817 fprintf(stderr, "Try adding the rootfstype= parameter to the"
818 "kernel command line\n");
819 return ret;
820 }
821
2a82ab5 @falconindy init: add support for rootflags var
authored
822 ret = mount(root, NEWROOT, fstype, rootflags, data);
75052c2 @falconindy initial commit
authored
823 free(fstype);
824
825 return ret;
826 } /* }}} */
827
828 static int set_init(void) { /* {{{ */
829 char path[PATH_MAX];
830
831 /* don't overwrite, but make sure something is set */
832 setenv("init", "/sbin/init", 0);
833
834 /* existance check */
835 snprintf(path, PATH_MAX, NEWROOT "%s", getenv("init"));
836 return access(path, F_OK);
837 } /* }}} */
838
3b17a57 @falconindy refactor handling of the udev daemon
authored
839 static void kill_udev(void) { /* {{{ */
840 /* pid = 0 : we never attempted to start udev
841 * pid = -1 : udev fork failed
fc034a4 @falconindy comment the 'edge case' where udevd is alive
authored
842 * pid = 1 : udev died at some point
843 * pid > 1 : udevd is alive! */
3b17a57 @falconindy refactor handling of the udev daemon
authored
844
239441c @falconindy if running, export udev's pid to the environment
authored
845 if (getenv("UDEVPID") == NULL) {
75052c2 @falconindy initial commit
authored
846 return;
847 }
848
137997d @falconindy implement udevadm as a helper function
authored
849 /* As per upstream, this is the proper way to shut down udev>=168:
850 *
851 * udevadm control --exit
852 * udevadm info --cleanup-db
853 *
854 * What happens on the initramfs is not supposed to make it into later
855 * userspace. These are completely separate environments with different
856 * rules both due to the nature of initramfs as well as the fact that we're
857 * running with a non-standard udev ruleset. The only exception here is
858 * dm/lvm, which requires their udev rules to have OPTIONS+=db_persist added
859 * in order to keep a persistent state through to later userspace. Ideally,
860 * this will someday change and state will be kept in /run/device-mapper
861 * instead. */
862
3b17a57 @falconindy refactor handling of the udev daemon
authored
863 udevadm("control", "--exit", NULL); /* waits up to 60 seconds */
137997d @falconindy implement udevadm as a helper function
authored
864 udevadm("info", "--cleanup-db", NULL);
75052c2 @falconindy initial commit
authored
865 } /*}}}*/
866
867 static int switch_root(char *argv[]) { /* {{{ */
868 struct stat st;
869 struct statfs stfs;
870
871 /* this is mostly taken from busybox's util_linux/switch_root.c */
872
873 chdir(NEWROOT);
874 stat("/", &st);
875
876 /* sanity checks: we're about to rm -rf / ! */
877 if (stat("/init", &st) != 0 || !S_ISREG(st.st_mode)) {
878 die("/init not found or not a regular file\n");
879 }
880
881 statfs("/", &stfs); /* this never fails */
882 if ((unsigned)stfs.f_type != RAMFS_MAGIC &&
883 (unsigned)stfs.f_type != TMPFS_MAGIC) {
884 die("root filesystem is not ramfs/tmpfs!\n");
885 }
886
821dd7e @falconindy replace busybox's rm -rf implementation with util-linux's
authored
887 /* zap everything out of root */
888 delete_contents("/");
75052c2 @falconindy initial commit
authored
889
890 /* mount $PWD over / and chroot into it */
891 if (mount(".", "/", NULL, MS_MOVE, NULL) != 0) {
892 /* fails when newroot is not a mountpoint */
893 die("error moving root\n");
894 }
895 chroot(".");
896
897 /* The chdir is needed to recalculate "." and ".." links */
898 chdir("/");
899
900 /* redirect stdin/stdout/stderr to new console */
e605090 @falconindy tighten up modprobe execution
authored
901 close(STDIN_FILENO);
75052c2 @falconindy initial commit
authored
902 open("/dev/console", O_RDWR);
e605090 @falconindy tighten up modprobe execution
authored
903 dup2(STDIN_FILENO, STDOUT_FILENO);
904 dup2(STDIN_FILENO, STDERR_FILENO);
75052c2 @falconindy initial commit
authored
905
906 /* exec real pid shady */
907 execv(argv[0], argv);
908 err("failed to execute '%s'\n", argv[0]);
909 fprintf(stderr, ":: This is the end. Something has gone terribly wrong.\n"
910 ":: Please file a detailed bug report.\n");
911 exit(EXIT_FAILURE);
912 } /* }}} */
913
914 int main(int argc, char *argv[]) {
94c3712 @falconindy cleanup the environment before calling switch_root
authored
915 char *term;
916
75052c2 @falconindy initial commit
authored
917 (void)argc; /* poor unloved argc */
918
919 mount_setup(); /* create early tmpfs mountpoints */
920 put_cmdline(); /* parse cmdline and set environment */
3b17a57 @falconindy refactor handling of the udev daemon
authored
921 launch_udev(); /* try to launch udev */
75052c2 @falconindy initial commit
authored
922 load_extra_modules(); /* load modules passed in on cmdline */
923 trigger_udev_events(); /* read and process uevent queue */
924 disable_hooks(); /* delete hooks specified on cmdline */
925 run_hooks(); /* run remaining hooks */
c75b2b1 @falconindy init: inline check_for_break
authored
926
927 if (getenv("break") != NULL) {
928 msg("break requested. type 'exit' or 'logout' to resume\n");
929 start_rescue_shell();
930 }
75052c2 @falconindy initial commit
authored
931
932 if (wait_for_root() != 0) {
933 try_create_root(); /* ensure that root shows up */
934 }
935
936 if (mount_root() != 0) { /* this is what we're here for */
937 err("failed to mount the root device: %s\n", strerror(errno));
938 start_rescue_shell();
939 }
940
941 if (set_init() != 0) { /* mounted something, now find init */
942 err("root device was mounted, but %s does not exist!\n", getenv("init"));
943 start_rescue_shell();
944 }
945
3b17a57 @falconindy refactor handling of the udev daemon
authored
946 kill_udev(); /* shutdown udev in prep switch_root */
75052c2 @falconindy initial commit
authored
947
948 /* migrate to the new root */
949 movemount("/proc", NEWROOT "/proc");
950 movemount("/sys", NEWROOT "/sys");
951 movemount("/run", NEWROOT "/run");
70a60ad @falconindy move /dev to /new_root/dev
authored
952 movemount("/dev", NEWROOT "/dev");
75052c2 @falconindy initial commit
authored
953
94c3712 @falconindy cleanup the environment before calling switch_root
authored
954 /* save these... */
75052c2 @falconindy initial commit
authored
955 argv[0] = getenv("init");
94c3712 @falconindy cleanup the environment before calling switch_root
authored
956 term = getenv("TERM");
957
958 /* purge the environment */
959 clearenv();
960 setenv("TERM", term, 1);
961
75052c2 @falconindy initial commit
authored
962 switch_root(argv);
963 /* unreached */
964 return 0;
965 }
966
abdcbb6 @falconindy init: fix broken vim modeline
authored
967 /* vim: set et ts=2 sw=2: */
Something went wrong with that request. Please try again.