diff options
author | David Oberhollenzer <david.oberhollenzer@tele2.at> | 2018-02-25 14:33:19 +0100 |
---|---|---|
committer | David Oberhollenzer <david.oberhollenzer@tele2.at> | 2018-03-24 17:04:20 +0100 |
commit | 9a88f7da453eadc72d8f333700dbd80777feecd1 (patch) | |
tree | 8a096e37123ece1d20bcb4d0ae8e064bdd39747a /initd |
Initial commit
Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
Diffstat (limited to 'initd')
-rw-r--r-- | initd/Makemodule.am | 8 | ||||
-rw-r--r-- | initd/init.h | 36 | ||||
-rw-r--r-- | initd/main.c | 238 | ||||
-rw-r--r-- | initd/mksock.c | 65 | ||||
-rw-r--r-- | initd/runlst.c | 128 | ||||
-rw-r--r-- | initd/setup_tty.c | 23 | ||||
-rw-r--r-- | initd/shutdown.c | 37 | ||||
-rw-r--r-- | initd/status.c | 27 | ||||
-rw-r--r-- | initd/svclist.c | 43 |
9 files changed, 605 insertions, 0 deletions
diff --git a/initd/Makemodule.am b/initd/Makemodule.am new file mode 100644 index 0000000..423c8eb --- /dev/null +++ b/initd/Makemodule.am @@ -0,0 +1,8 @@ +init_SOURCES = initd/main.c initd/runlst.c initd/init.h initd/setup_tty.c +init_SOURCES += initd/status.c initd/mksock.c initd/shutdown.c initd/svclist.c +init_CPPFLAGS = $(AM_CPPFLAGS) +init_CFLAGS = $(AM_CFLAGS) +init_LDFLAGS = $(AM_LDFLAGS) +init_LDADD = libinit.a + +sbin_PROGRAMS += init diff --git a/initd/init.h b/initd/init.h new file mode 100644 index 0000000..0bfed6c --- /dev/null +++ b/initd/init.h @@ -0,0 +1,36 @@ +#ifndef INIT_H +#define INIT_H + +#include <linux/reboot.h> +#include <sys/reboot.h> + +#include "service.h" +#include "telinit.h" +#include "util.h" + +enum { + STATUS_OK = 0, + STATUS_FAIL, + STATUS_WAIT, +}; + +int runlst_wait(char **exec, size_t num, const char *ctty); + +pid_t runlst(char **exec, size_t num, const char *ctty); + +int setup_tty(void); + +void print_status(const char *msg, int type, bool update); + +int mksock(void); + +NORETURN void do_shutdown(int type); + +bool svclist_have_singleshot(void); + +void svclist_add(service_t *svc); + +service_t *svclist_remove(pid_t pid); + +#endif /* INIT_H */ + diff --git a/initd/main.c b/initd/main.c new file mode 100644 index 0000000..211188e --- /dev/null +++ b/initd/main.c @@ -0,0 +1,238 @@ +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <poll.h> + +#include <sys/signalfd.h> + +#include "init.h" + +static service_list_t cfg; + +static int target = TGT_BOOT; /* runlevel we are targetting */ +static int runlevel = -1; /* runlevel we are currently on */ + +static void handle_exited(service_t *svc) +{ + switch (svc->type) { + case SVC_RESPAWN: + if (target == TGT_REBOOT || target == TGT_SHUTDOWN) { + delsrv(svc); + break; + } + + svc->pid = runlst(svc->exec, svc->num_exec, svc->ctty); + if (svc->pid == -1) { + print_status(svc->desc, STATUS_FAIL, false); + delsrv(svc); + } + + svclist_add(svc); + break; + case SVC_ONCE: + print_status(svc->desc, + svc->status == EXIT_SUCCESS ? + STATUS_OK : STATUS_FAIL, false); + /* fall-through */ + default: + delsrv(svc); + break; + } +} + +static void handle_signal(int sigfd) +{ + struct signalfd_siginfo info; + service_t *svc; + int status; + pid_t pid; + + if (read(sigfd, &info, sizeof(info)) != sizeof(info)) { + perror("read on signal fd"); + return; + } + + switch (info.ssi_signo) { + case SIGCHLD: + for (;;) { + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + break; + + status = WIFEXITED(status) ? WEXITSTATUS(status) : + EXIT_FAILURE; + + svc = svclist_remove(pid); + + if (svc != NULL) + handle_exited(svc); + } + break; + case SIGINT: + /* TODO: ctrl-alt-del */ + break; + } +} + +static void start_runlevel(int level) +{ + service_t *svc; + int status; + + while (cfg.targets[level] != NULL) { + svc = cfg.targets[level]; + cfg.targets[level] = svc->next; + + if (!svc->num_exec) { + print_status(svc->desc, STATUS_OK, false); + delsrv(svc); + continue; + } + + if (svc->type == SVC_WAIT) { + print_status(svc->desc, STATUS_WAIT, false); + + status = runlst_wait(svc->exec, svc->num_exec, + svc->ctty); + + print_status(svc->desc, + status == EXIT_SUCCESS ? + STATUS_OK : STATUS_FAIL, + true); + delsrv(svc); + } else { + svc->pid = runlst(svc->exec, svc->num_exec, svc->ctty); + if (svc->pid == -1) { + print_status(svc->desc, STATUS_FAIL, false); + delsrv(svc); + continue; + } + + svclist_add(svc); + } + } +} + +static int read_msg(int fd, ti_msg_t *msg) +{ + ssize_t ret; +retry: + ret = read(fd, msg, sizeof(*msg)); + + if (ret < 0) { + if (errno == EINTR) + goto retry; + perror("read on telinit socket"); + return -1; + } + + if ((size_t)ret < sizeof(*msg)) { + fputs("short read on telinit socket", stderr); + return -1; + } + + return 0; +} + +static void handle_tellinit(int ti_sock) +{ + ti_msg_t msg; + int fd; + + fd = accept(ti_sock, NULL, NULL); + if (fd == -1) + return; + + if (read_msg(fd, &msg)) { + close(fd); + return; + } + + switch (msg.type) { + case TI_SHUTDOWN: + target = TGT_SHUTDOWN; + break; + case TI_REBOOT: + target = TGT_REBOOT; + break; + } + + close(fd); +} + +int main(void) +{ + int ti_sock, sfd, ret; + struct pollfd pfd[2]; + sigset_t mask; + + if (getpid() != 1) { + fputs("init does not have pid 1, terminating!\n", stderr); + return EXIT_FAILURE; + } + + if (reboot(LINUX_REBOOT_CMD_CAD_OFF)) + perror("cannot disable CTRL+ALT+DEL"); + + if (srvscan(SVCDIR, &cfg)) { + fputs("Error reading service list from " SVCDIR "\n" + "Trying to continue anyway\n", stderr); + } + + sigfillset(&mask); + if (sigprocmask(SIG_SETMASK, &mask, NULL) == -1) { + perror("sigprocmask"); + return EXIT_FAILURE; + } + + sfd = signalfd(-1, &mask, SFD_CLOEXEC); + if (sfd == -1) { + perror("signalfd"); + return EXIT_FAILURE; + } + + ti_sock = mksock(); + if (ti_sock == -1) + return EXIT_FAILURE; + + if (setup_tty()) + return EXIT_FAILURE; + + memset(pfd, 0, sizeof(pfd)); + pfd[0].fd = sfd; + pfd[1].fd = ti_sock; + pfd[0].events = pfd[1].events = POLLIN; + + for (;;) { + if (!svclist_have_singleshot()) { + if (target != runlevel) { + start_runlevel(target); + runlevel = target; + continue; + } + + if (runlevel == TGT_SHUTDOWN) + do_shutdown(RB_POWER_OFF); + + if (runlevel == TGT_REBOOT) + do_shutdown(RB_AUTOBOOT); + } + + ret = poll(pfd, sizeof(pfd) / sizeof(pfd[0]), -1); + + if (ret > 0) { + if (pfd[0].revents & POLLIN) + handle_signal(sfd); + + if (pfd[1].revents & POLLIN) + handle_tellinit(ti_sock); + } + } + + return EXIT_SUCCESS; +} diff --git a/initd/mksock.c b/initd/mksock.c new file mode 100644 index 0000000..ab26e51 --- /dev/null +++ b/initd/mksock.c @@ -0,0 +1,65 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <stdio.h> + +#include "telinit.h" +#include "init.h" + +int mksock(void) +{ + struct sockaddr_un un; + int fd, flags; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return -1; + } + + flags = fcntl(fd, F_GETFD); + if (flags == -1) { + perror("socket F_GETFD"); + goto fail; + } + + if (fcntl(fd, F_SETFD, flags | O_CLOEXEC)) { + perror("socket F_SETFD"); + goto fail; + } + + memset(&un, 0, sizeof(un)); + un.sun_family = AF_UNIX; + + strcpy(un.sun_path, INITSOCK); + + if (bind(fd, (struct sockaddr *)&un, sizeof(un))) { + perror("bind: " INITSOCK); + goto fail; + } + + if (chown(INITSOCK, 0, 0)) { + perror("chown: " INITSOCK); + goto fail; + } + + if (chmod(INITSOCK, 0770)) { + perror("chmod: " INITSOCK); + goto fail; + } + + if (listen(fd, 10)) { + perror("listen"); + goto fail; + } + + return fd; +fail: + close(fd); + unlink(INITSOCK); + return -1; +} diff --git a/initd/runlst.c b/initd/runlst.c new file mode 100644 index 0000000..cf75126 --- /dev/null +++ b/initd/runlst.c @@ -0,0 +1,128 @@ +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> + +#include "init.h" + +extern char **environ; + +static NORETURN void split_and_exec(char *cmd) +{ + char *argv[128]; + size_t i = 0; + + while (*cmd != '\0') { + argv[i++] = cmd; /* FIXME: buffer overflow!! */ + + while (*cmd != '\0' && !isspace(*cmd)) + ++cmd; + + if (isspace(*cmd)) { + *(cmd++) = '\0'; + + while (isspace(*cmd)) + ++cmd; + } + } + + argv[i] = NULL; + + execve(argv[0], argv, environ); + perror(argv[0]); + exit(EXIT_FAILURE); +} + +static int child_setup(const char *ctty) +{ + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, NULL); + + if (ctty != NULL) { + fd = open(ctty, O_RDWR); + if (fd < 0) { + perror(ctty); + return -1; + } + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + setsid(); + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + + return 0; +} + +int runlst_wait(char **exec, size_t num, const char *ctty) +{ + pid_t ret, pid; + int status; + size_t i; + + for (i = 0; i < num; ++i) { + pid = fork(); + + if (pid == 0) { + if (child_setup(ctty)) + exit(EXIT_FAILURE); + split_and_exec(exec[i]); + } + + if (pid == -1) { + perror("fork"); + return EXIT_FAILURE; + } + + do { + ret = waitpid(pid, &status, 0); + } while (ret != pid); + + if (!WIFEXITED(status)) + return EXIT_FAILURE; + + if (WEXITSTATUS(status) != EXIT_SUCCESS) + return WEXITSTATUS(status); + } + + return EXIT_SUCCESS; +} + +pid_t runlst(char **exec, size_t num, const char *ctty) +{ + int status; + pid_t pid; + + pid = fork(); + + if (pid == 0) { + if (child_setup(ctty)) + exit(EXIT_FAILURE); + + if (num > 1) { + status = runlst_wait(exec, num, NULL); + exit(status); + } else { + split_and_exec(exec[0]); + } + } + + if (pid == -1) + perror("fork"); + + return pid; +} diff --git a/initd/setup_tty.c b/initd/setup_tty.c new file mode 100644 index 0000000..cec663b --- /dev/null +++ b/initd/setup_tty.c @@ -0,0 +1,23 @@ +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> + +#include "init.h" + +int setup_tty(void) +{ + int fd; + + fd = open("/dev/console", O_WRONLY | O_NOCTTY); + if (fd < 0) { + perror("/dev/console"); + return -1; + } + + close(STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + + close(fd); + return 0; +} diff --git a/initd/shutdown.c b/initd/shutdown.c new file mode 100644 index 0000000..45af1c2 --- /dev/null +++ b/initd/shutdown.c @@ -0,0 +1,37 @@ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <stdio.h> +#include <errno.h> +#include <time.h> + +#include "init.h" + +void do_shutdown(int type) +{ + struct timespec req, rem; + + print_status("sending SIGTERM to all processes", STATUS_WAIT, false); + kill(-1, SIGTERM); + + memset(&req, 0, sizeof(req)); + memset(&rem, 0, sizeof(rem)); + req.tv_sec = 5; /* TODO: make configurable? */ + + while (nanosleep(&req, &rem) != 0 && errno == EINTR) + req = rem; + + print_status("sending SIGTERM to all processes", STATUS_OK, true); + kill(-1, SIGKILL); + print_status("sending SIGKILL to remaining processes", + STATUS_OK, false); + + print_status("sync", STATUS_WAIT, false); + sync(); + print_status("sync", STATUS_OK, true); + + reboot(type); + perror("reboot system call"); + exit(EXIT_FAILURE); +} diff --git a/initd/status.c b/initd/status.c new file mode 100644 index 0000000..280670b --- /dev/null +++ b/initd/status.c @@ -0,0 +1,27 @@ +#include <stdio.h> + +#include "init.h" + +void print_status(const char *msg, int type, bool update) +{ + const char *str; + + switch (type) { + case STATUS_FAIL: + str = "\033[22;31mFAIL\033[0m"; + break; + case STATUS_WAIT: + str = "\033[22;33m .. \033[0m"; + break; + default: + str = "\033[22;32m OK \033[0m"; + break; + } + + if (update) + fputc('\r', stdout); + printf("[%s] %s", str, msg); + if (type != STATUS_WAIT) + fputc('\n', stdout); + fflush(stdout); +} diff --git a/initd/svclist.c b/initd/svclist.c new file mode 100644 index 0000000..590091e --- /dev/null +++ b/initd/svclist.c @@ -0,0 +1,43 @@ +#include "init.h" + +static service_t *running = NULL; /* currently supervised services */ +static int singleshot = 0; /* active singleshot services */ + +bool svclist_have_singleshot(void) +{ + return singleshot > 0; +} + +void svclist_add(service_t *svc) +{ + svc->next = running; + running = svc; + + if (svc->type == SVC_ONCE) + singleshot += 1; +} + +service_t *svclist_remove(pid_t pid) +{ + service_t *prev = NULL, *svc = running; + + while (svc != NULL) { + if (svc->pid == pid) { + if (prev != NULL) { + prev->next = svc->next; + } else { + running = svc->next; + } + svc->next = NULL; + + if (svc->type == SVC_ONCE) + singleshot -= 1; + break; + } + + prev = svc; + svc = svc->next; + } + + return svc; +} |