1/* See LICENSE file for copyright and license details. */
2
3#include <sys/mount.h>
4#include <sys/stat.h>
5#include <sys/vfs.h>
6
7#include <dirent.h>
8#include <fcntl.h>
9#include <limits.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <unistd.h>
14
15#include "util.h"
16
17#define RAMFS_MAGIC 0x858458f6 /* some random number */
18#define TMPFS_MAGIC 0x01021994
19
20static void
21delete_content(const char *dir, dev_t curdevice)
22{
23 char path[PATH_MAX];
24 DIR *d;
25 struct stat st;
26 struct dirent *dent;
27
28 /* don't dive into other filesystems */
29 if (lstat(dir, &st) < 0 || st.st_dev != curdevice)
30 return;
31 if (!(d = opendir(dir)))
32 return;
33 while ((dent = readdir(d))) {
34 if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
35 continue;
36
37 /* build path and dive deeper */
38 if (strlcpy(path, dir, sizeof(path)) >= sizeof(path))
39 eprintf("path too long\n");
40 if (path[strlen(path) - 1] != '/')
41 if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
42 eprintf("path too long\n");
43 if (strlcat(path, dent->d_name, sizeof(path)) >= sizeof(path))
44 eprintf("path too long\n");
45
46 if (lstat(path, &st) < 0)
47 weprintf("lstat %s:", path);
48
49 if (S_ISDIR(st.st_mode)) {
50 delete_content(path, curdevice);
51 if (rmdir(path) < 0)
52 weprintf("rmdir %s:", path);
53 } else {
54 if (unlink(path) < 0)
55 weprintf("unlink %s:", path);
56 }
57 }
58 closedir(d);
59}
60
61static void
62usage(void)
63{
64 eprintf("usage: %s [-c console] [newroot] [init] (PID 1)\n", argv0);
65}
66
67// ?man switch_root: switch to another root filesystem
68// ?man arguments: newroot [init] (PID 1)
69// ?man switch to another filesystem as the root of the mount tree
70int
71main(int argc, char *argv[])
72{
73 char *console = NULL;
74 dev_t curdev;
75 struct stat st;
76 struct statfs stfs;
77
78 ARGBEGIN
79 {
80 // ?man -c:str: print count or perform stdout action
81 case 'c':
82 console = EARGF(usage());
83 break;
84 default:
85 usage();
86 }
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}