1/* See LICENSE file for copyright and license details. */
2
3
4#include <sys/mount.h>
5#include <sys/stat.h>
6#include <sys/vfs.h>
7
8#include <dirent.h>
9#include <fcntl.h>
10#include <limits.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <unistd.h>
15
16#include "util.h"
17
18#define RAMFS_MAGIC 0x858458f6 /* some random number */
19#define TMPFS_MAGIC 0x01021994
20
21static void
22delete_content(const char *dir, dev_t curdevice)
23{
24 char path[PATH_MAX];
25 DIR *d;
26 struct stat st;
27 struct dirent *dent;
28
29 /* don't dive into other filesystems */
30 if (lstat(dir, &st) < 0 || st.st_dev != curdevice)
31 return;
32 if (!(d = opendir(dir)))
33 return;
34 while ((dent = readdir(d))) {
35 if (strcmp(dent->d_name, ".") == 0 ||
36 strcmp(dent->d_name, "..") == 0)
37 continue;
38
39 /* build path and dive deeper */
40 if (strlcpy(path, dir, sizeof(path)) >= sizeof(path))
41 eprintf("path too long\n");
42 if (path[strlen(path) - 1] != '/')
43 if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
44 eprintf("path too long\n");
45 if (strlcat(path, dent->d_name, sizeof(path)) >= sizeof(path))
46 eprintf("path too long\n");
47
48 if (lstat(path, &st) < 0)
49 weprintf("lstat %s:", path);
50
51 if (S_ISDIR(st.st_mode)) {
52 delete_content(path, curdevice);
53 if (rmdir(path) < 0)
54 weprintf("rmdir %s:", path);
55 } else {
56 if (unlink(path) < 0)
57 weprintf("unlink %s:", path);
58 }
59 }
60 closedir(d);
61}
62
63static void
64usage(void)
65{
66 eprintf("usage: %s [-c console] [newroot] [init] (PID 1)\n", argv0);
67}
68
69// ?man switch_root: switch to another root filesystem
70// ?man arguments: newroot] [init] (PID 1)
71// ?man switch to another filesystem as the root of the mount tree
72int
73main(int argc, char *argv[])
74{
75 char *console = NULL;
76 dev_t curdev;
77 struct stat st;
78 struct statfs stfs;
79
80 ARGBEGIN {
81 // ?man -c:str: print count or perform stdout action
82 case 'c':
83 console = EARGF(usage());
84 break;
85 default:
86 usage();
87 } ARGEND;
88
89 /* check number of args and if we are PID 1 */
90 if (argc != 2 || getpid() != 1)
91 usage();
92
93 /* chdir to newroot and make sure it's a different fs */
94 if (chdir(argv[0]))
95 eprintf("chdir %s:", argv[0]);
96
97 if (stat("/", &st))
98 eprintf("stat %s:", "/");
99
100 curdev = st.st_dev;
101 if (stat(".", &st))
102 eprintf("stat %s:", ".");
103 if (st.st_dev == curdev)
104 usage();
105
106 /* avoids trouble with real filesystems */
107 if (stat("/init", &st) || !S_ISREG(st.st_mode))
108 eprintf("/init is not a regular file\n");
109
110 statfs("/", &stfs);
111 if ((unsigned)stfs.f_type != RAMFS_MAGIC && (unsigned)stfs.f_type != TMPFS_MAGIC)
112 eprintf("current filesystem is not a RAMFS or TMPFS\n");
113
114 /* wipe / */
115 delete_content("/", curdev);
116
117 /* overmount / with newroot and chroot into it */
118 if (mount(".", "/", NULL, MS_MOVE, NULL))
119 eprintf("mount %s:", ".");
120
121 if (chroot("."))
122 eprintf("chroot failed\n");
123
124 /* if -c is set, redirect stdin/stdout/stderr to console */
125 if (console) {
126 close(0);
127 if (open(console, O_RDWR) == -1)
128 eprintf("open %s:", console);
129 dup2(0, 1);
130 dup2(0, 2);
131 }
132
133 /* execute init */
134 execv(argv[1], argv);
135 eprintf("can't execute '%s':", argv[1]);
136 return 1;
137}