blob: f2611b48c966bcf120db8a8921b5eb1883f27038 [file] [log] [blame]
Serge Bazanskieac8f732021-10-05 23:30:37 +02001// minit is a barebones Linux-compatible init (PID 1) process.
2//
3// Its goal is to run the Metropolis core executable and reap any children that
4// it stumbles upon. It does not support running under a TTY and is not
5// configurable in any way.
6//
7// The only reason this exists is because Go's child process reaping (when
8// using os/exec.Command) races any PID 1 process reaping, thereby preventing
9// running a complex Go binary as PID 1. In the future this might be rewritten
10// in a memory-safe language like Zig or Rust, but this implementation will do
11// for now, as long as it keeps having basically zero attack surface.
12//
13// This code has been vaguely inspired by github.com/Yelp/dumb-init and
14// github.com/krallin/tini, two already existing minimal init implementations.
15// These, however, attempt to handle being run in a TTY and some
16// configurability, as they're meant to be run in containers. We don't need any
17// of that, and we'd rather have as little C as possible.
18
19#include <errno.h>
20#include <linux/reboot.h>
21#include <signal.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/reboot.h>
26#include <sys/wait.h>
27#include <unistd.h>
28
29void handle_signal(pid_t child_pid, int signum);
30
31int main() {
32 // Block all signals. We'll unblock them in the child.
33 sigset_t all_signals;
34 sigfillset(&all_signals);
35 sigprocmask(SIG_BLOCK, &all_signals, NULL);
36
37 // Say hello.
38 fprintf(stderr,
39 "\n"
40 " Metropolis Cluster Operating System\n"
41 " Copyright 2020-2021 The Monogon Project Authors\n"
42 "\n"
43 );
44
45
46 pid_t pid = fork();
47 if (pid < 0) {
48 fprintf(stderr, "fork(): %s\n", strerror(errno));
49 return 1;
50 }
51
52 if (pid == 0) {
53 // In the child. Unblock all signals.
54 sigprocmask(SIG_UNBLOCK, &all_signals, NULL);
55 if (setsid() == -1) {
56 fprintf(stderr, "setsid: %s\n", strerror(errno));
57 return 1;
58 }
59
60 // Then, start the core executable.
61 char *argv[] = {
62 "/core",
63 NULL,
64 };
65 execvp(argv[0], argv);
66 fprintf(stderr, "execvpe(/core) failed: %s\n", strerror(errno));
67 return 1;
68 }
69
70 // In the parent. Wait for any signal, then handle it and any other pending
71 // ones.
72 for (;;) {
73 int signum;
74 sigwait(&all_signals, &signum);
75 handle_signal(pid, signum);
76 }
77}
78
79// handle_signal is called by the main reap loop for every signal received. It
80// reaps children if SIGCHLD is received, and otherwise dispatches the signal to
81// its direct child.
82void handle_signal(pid_t child_pid, int signum) {
83 // Anything other than SIGCHLD should just be forwarded to the child.
84 if (signum != SIGCHLD) {
85 kill(-child_pid, signum);
86 return;
87 }
88
89 // A SIGCHLD was received. Go through all children and reap them, checking
90 // if any of them is our direct child.
91
92 // exit_status will be set if the direct child process exited.
93 int exit_status = -1;
94
95 pid_t killed_pid;
96 int status;
97 while ((killed_pid = waitpid(-1, &status, WNOHANG)) > 0) {
98 if (killed_pid != child_pid) {
99 // Something else than our direct child died, just reap it.
100 continue;
101 }
102
103 // Our direct child exited. Translate its status into an exit code.
104 if (WIFEXITED(status)) {
105 // For processes which exited, just use the exit code directly.
106 exit_status = WEXITSTATUS(status);
107 } else if (WIFSIGNALED(status)) {
108 // Otherwise, emulate what sh/bash do and return 128 + the signal
109 // number that the child received.
110 exit_status = 128 + WTERMSIG(status);
111 } else {
112 // Something unexpected happened. Attempt to handle this gracefully,
113 // but complain.
114 fprintf(stderr, "child status not EXITED nor SIGNALED: %d\n", status);
115 exit_status = 1;
116 }
117 }
118
119 // Direct child exited, let's also exit.
120 if (exit_status >= 0) {
121 fprintf(stderr, "\n Metropolis core exited with status: %d\n", exit_status);
122 sync();
123 if (exit_status != 0) {
124 fprintf(stderr, " Disks synced, rebooting in 30 seconds...\n", exit_status);
125 sleep(30);
126 fprintf(stderr, " Rebooting...\n\n", exit_status);
127 } else {
128 fprintf(stderr, " Disks synced, rebooting...\n\n");
129 }
130 reboot(LINUX_REBOOT_CMD_RESTART);
131 }
132}