summaryrefslogtreecommitdiff
path: root/initd
diff options
context:
space:
mode:
authorDavid Oberhollenzer <david.oberhollenzer@tele2.at>2018-02-25 14:33:19 +0100
committerDavid Oberhollenzer <david.oberhollenzer@tele2.at>2018-03-24 17:04:20 +0100
commit9a88f7da453eadc72d8f333700dbd80777feecd1 (patch)
tree8a096e37123ece1d20bcb4d0ae8e064bdd39747a /initd
Initial commit
Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
Diffstat (limited to 'initd')
-rw-r--r--initd/Makemodule.am8
-rw-r--r--initd/init.h36
-rw-r--r--initd/main.c238
-rw-r--r--initd/mksock.c65
-rw-r--r--initd/runlst.c128
-rw-r--r--initd/setup_tty.c23
-rw-r--r--initd/shutdown.c37
-rw-r--r--initd/status.c27
-rw-r--r--initd/svclist.c43
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;
+}