├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── init.c /.gitignore: -------------------------------------------------------------------------------- 1 | init 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2020, Adrien Gallouët 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = cc 2 | CFLAGS = -Wall -O2 3 | 4 | init: 5 | $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) init.c -o init 6 | 7 | install: init 8 | mkdir -p $(DESTDIR) 9 | mv -f init $(DESTDIR)/ 10 | 11 | clean: 12 | rm -f init 13 | 14 | .PHONY: init install clean 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `/init` 2 | Minimal PID 1 for initramfs. 3 | 4 | This is used for diskless servers with only two stages: `/etc/boot` and `/etc/reboot`. 5 | 6 | For all spawned processes: 7 | - inputs from `stdin` are dropped. 8 | - outputs from `stdout` are redirected to `/dev/console`. 9 | - logs from `stderr` are redirected to `/dev/kmsg`. 10 | 11 | ## Build & Install 12 | 13 | The binary must be installed as `/init` and nothing else. 14 | 15 | $ make install DESTDIR= 16 | 17 | ## Update & reboot 18 | 19 | When your system is running with `/init` you don't need any extra tool to do an update. 20 | Just put the new kernel at `/kernel` and `/init` will automatically kexec it after the `/etc/reboot` stage. 21 | 22 | If you want to update your system in the usual way (pxe, efi,...) you need a cold reboot: 23 | 24 | $ touch /reboot 25 | $ reboot 26 | 27 | With `reboot` a script that stops your supervisor. For example with `runit`: 28 | 29 | $ cat /bin/reboot 30 | #!/bin/sh 31 | 32 | exec pkill -HUP runsvdir 33 | 34 | ## Configuration 35 | 36 | Only two files are needed, for example: 37 | 38 | $ cat /etc/boot 39 | #!/bin/sh 40 | 41 | # your personal stuff.. 42 | 43 | exec runsvdir -P /run/service 44 | 45 | and 46 | 47 | $ cat /etc/reboot 48 | #!/bin/sh 49 | 50 | # your personal stuff.. 51 | 52 | # you can still use kexec-tools for old kernels or unsupported arch. 53 | kexec -l /kernel --reuse-cmdline && kexec -e 54 | 55 | ## File system 56 | 57 | `/init` will create a minimal file system that should look like this if all goes well: 58 | 59 | # cat /proc/mounts 60 | none / rootfs rw,nosuid 0 0 61 | none /proc proc rw,nosuid,nodev,noexec,relatime 0 0 62 | none /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 63 | none /sys/fs/cgroup cgroup2 rw,nosuid,nodev,noexec,relatime 0 0 64 | none /dev devtmpfs rw,nosuid,noexec 0 0 65 | devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0 66 | 67 | Note that `/init` mounts `cgroup2` as `/sys/fs/cgroup` and not the old `cgroup`. 68 | 69 | ## Kernel configuration 70 | 71 | A minimal kernel configuration to enable all features of `/init` would look like this: 72 | 73 | CONFIG_BLK_DEV_INITRD=y 74 | CONFIG_INITRAMFS_SOURCE= # root directory with /init 75 | CONFIG_BINFMT_ELF=y 76 | CONFIG_BINFMT_SCRIPT=y 77 | CONFIG_DEVTMPFS=y 78 | CONFIG_PROC_FS=y 79 | CONFIG_SYSFS=y 80 | 81 | Since all logs go into the kernel, it's a good idea to increase the size of the log buffer: 82 | 83 | CONFIG_LOG_BUF_SHIFT=24 84 | 85 | Required for updates: 86 | 87 | CONFIG_RELOCATABLE=y 88 | CONFIG_CRYPTO=y 89 | CONFIG_CRYPTO_SHA256=y 90 | CONFIG_KEXEC_FILE=y 91 | 92 | Not mandatory but useful to see what's going on :) 93 | 94 | CONFIG_PRINTK=y 95 | CONFIG_TTY=y 96 | CONFIG_SERIAL_8250=y 97 | CONFIG_SERIAL_8250_CONSOLE=y 98 | 99 | --- 100 | For feature requests and bug reports, 101 | please create an [issue](https://github.com/angt/slashinit/issues). 102 | -------------------------------------------------------------------------------- /init.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define SI_BASE (MS_NOSUID | MS_NOEXEC) 21 | #define SI_COUNT(x) (sizeof(x) / sizeof(x[0])) 22 | 23 | char **__environ; 24 | 25 | enum si_lvl { 26 | si_critical = 2, 27 | si_error = 3, 28 | si_warning = 4, 29 | si_info = 6, 30 | }; 31 | 32 | static void 33 | si_log(enum si_lvl lvl, const char *fmt, ...) 34 | { 35 | va_list ap; 36 | char tmp[256] = "<*>/init: "; 37 | char *buf = &tmp[10]; 38 | size_t size = sizeof(tmp) - 10; 39 | 40 | va_start(ap, fmt); 41 | int ret = vsnprintf(buf, size, fmt, ap); 42 | va_end(ap); 43 | 44 | if (ret <= 0) 45 | return; 46 | 47 | if (size > (size_t)ret) 48 | size = (size_t)ret; 49 | 50 | tmp[1] = '0' + (lvl & 7); 51 | (void)!write(2, tmp, size + 10); 52 | } 53 | 54 | static void 55 | si_spawn(char *argv[], sigset_t *sig) 56 | { 57 | si_log(si_info, "Running %s", argv[0]); 58 | 59 | posix_spawnattr_t attr; 60 | posix_spawnattr_init(&attr); 61 | posix_spawnattr_setsigmask(&attr, sig); 62 | posix_spawnattr_setflags(&attr, 63 | POSIX_SPAWN_SETSID | POSIX_SPAWN_SETSIGMASK); 64 | 65 | pid_t pid; 66 | int err = posix_spawn(&pid, argv[0], 0, &attr, argv, __environ); 67 | posix_spawnattr_destroy(&attr); 68 | 69 | if (err) { 70 | si_log(si_error, "Failed to run %s", argv[0]); 71 | return; 72 | } 73 | while (1) { 74 | int status; 75 | pid_t ret = waitpid(-1, &status, 0); 76 | 77 | if (ret == (pid_t)-1) { 78 | if (errno == EINTR) 79 | continue; 80 | si_log(si_error, "waitpid: %m"); 81 | return; 82 | } 83 | if (ret == pid) { 84 | if (WIFEXITED(status) || WIFSIGNALED(status)) 85 | return; 86 | } 87 | } 88 | } 89 | 90 | static int 91 | si_mount(const char *src, const char *dst, const char *type, 92 | unsigned long flags, const void *data) 93 | { 94 | mkdir(dst, 0755); 95 | 96 | if (mount(src, dst, type, flags, data)) 97 | return errno; 98 | 99 | return 0; 100 | } 101 | 102 | static int 103 | si_path_to_fd(const char *path, int newfd) 104 | { 105 | int fd = open(path, O_RDWR | O_NONBLOCK | O_NOCTTY); 106 | 107 | if (fd < 0) 108 | return errno; 109 | 110 | if (fd == newfd) 111 | return 0; 112 | 113 | dup2(fd, newfd); 114 | close(fd); 115 | return 0; 116 | } 117 | 118 | static void 119 | si_clear_str(char *str) 120 | { 121 | if (!str) 122 | return; 123 | 124 | while (*str) 125 | *str++ = 0; 126 | } 127 | 128 | static ssize_t 129 | si_read_file(const char *file, char *buf, size_t size) 130 | { 131 | int fd = open(file, O_RDONLY); 132 | 133 | if (fd < 0) 134 | return 0; 135 | 136 | ssize_t ret = read(fd, buf, size); 137 | int tmp_errno = errno; 138 | close(fd); 139 | errno = tmp_errno; 140 | 141 | if (ret < 0) { 142 | si_log(si_error, "read(%s): %m", file); 143 | return -1; 144 | } 145 | return ret; 146 | } 147 | 148 | static void 149 | si_reboot(int cmd) 150 | { 151 | si_log(si_info, "Syncing..."); 152 | sync(); 153 | 154 | si_log(si_info, "Killing all processes..."); 155 | kill(-1, SIGKILL); 156 | 157 | si_log(si_info, "Rebooting..."); 158 | 159 | if (cmd && reboot(cmd)) 160 | si_log(si_error, "reboot: %m"); 161 | } 162 | 163 | static void 164 | si_update(const char *kernel) 165 | { 166 | char cmd[1024] = {0}; 167 | 168 | ssize_t len = si_read_file("/kernel.cmdline", cmd, sizeof(cmd) - 1); 169 | 170 | if (len < 0) 171 | return; 172 | 173 | int fd = open(kernel, O_RDONLY); 174 | 175 | if (fd < 0) { 176 | if (errno != ENOENT) 177 | si_log(si_error, "open(%s): %m", kernel); 178 | return; 179 | } 180 | #ifdef SYS_kexec_file_load 181 | si_log(si_info, "Found %s, loading...", kernel); 182 | 183 | if (syscall(SYS_kexec_file_load, fd, -1, (unsigned long)len + 1, 184 | &cmd[0], /* KEXEC_FILE_NO_INITRAMFS */ 0x4)) { 185 | si_log(si_error, "kexec: %m"); 186 | close(fd); 187 | return; 188 | } 189 | close(fd); 190 | si_reboot(RB_KEXEC); 191 | #else 192 | close(fd); 193 | si_log(si_error, "Found %s, but kexec is not supported...", kernel); 194 | #endif 195 | } 196 | 197 | int 198 | main(int argc, char *argv[]) 199 | { 200 | if (getpid() != 1) 201 | return 1; 202 | 203 | (void)!chdir("/"); 204 | 205 | if (!getenv("TERM")) 206 | putenv("TERM=linux"); 207 | 208 | for (int i = 1; i < argc; i++) 209 | si_clear_str(argv[i]); 210 | 211 | struct { 212 | const char *src; 213 | const char *dst; 214 | const char *type; 215 | unsigned long flags; 216 | const char *data; 217 | int err; 218 | } m[] = { 219 | {NULL, "/", NULL, MS_NOSUID | MS_REMOUNT, }, 220 | {"none", "/proc", "proc", SI_BASE | MS_NODEV, }, 221 | {"none", "/dev", "devtmpfs", SI_BASE | MS_STRICTATIME}, 222 | {"none", "/sys", "sysfs", SI_BASE | MS_NODEV, }, 223 | {"none", "/sys/fs/cgroup", "cgroup2", SI_BASE | MS_NODEV, }, 224 | {"devpts", "/dev/pts", "devpts", SI_BASE, "gid=5,mode=0620"}, 225 | }; 226 | for (int i = 0; i < SI_COUNT(m); i++) 227 | m[i].err = si_mount(m[i].src, m[i].dst, m[i].type, m[i].flags, m[i].data); 228 | 229 | struct { 230 | const char *path; 231 | int newfd; 232 | int err; 233 | } p[] = { 234 | {"/dev/null", 0}, 235 | {"/dev/console", 1}, 236 | {"/dev/kmsg", 2}, 237 | }; 238 | for (int i = 0; i < SI_COUNT(p); i++) 239 | p[i].err = si_path_to_fd(p[i].path, p[i].newfd); 240 | 241 | (void)!symlink("/proc/mounts", "/etc/mtab"); 242 | (void)!symlink("/proc/self/fd", "/dev/fd"); 243 | (void)!symlink("/proc/self/fd/0", "/dev/stdin"); 244 | (void)!symlink("/proc/self/fd/1", "/dev/stdout"); 245 | (void)!symlink("/proc/self/fd/2", "/dev/stderr"); 246 | 247 | mkdir("/dev/shm", 01777); 248 | mkdir("/tmp", 01777); 249 | unlink("/init"); 250 | 251 | // All the hard work is done, we can now try to log 252 | 253 | for (int i = 0; i < SI_COUNT(m); i++) if (m[i].err) { 254 | errno = m[i].err; 255 | si_log(si_warning, "mount(%s): %m", m[i].dst); 256 | } 257 | for (int i = 0; i < SI_COUNT(p); i++) if (p[i].err) { 258 | errno = p[i].err; 259 | si_log(si_warning, "open(%s): %m", p[i].path); 260 | } 261 | sigset_t set, old; 262 | sigfillset(&set); 263 | sigprocmask(SIG_BLOCK, &set, &old); 264 | 265 | while (1) { 266 | si_spawn((char*[]){"/etc/boot",0}, &old); 267 | si_spawn((char*[]){"/etc/reboot",0}, &old); 268 | si_update("/kernel"); 269 | si_reboot(access("/reboot", F_OK) ? 0 : RB_AUTOBOOT); 270 | sleep(1); 271 | } 272 | } 273 | --------------------------------------------------------------------------------