aboutsummaryrefslogtreecommitdiff
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
Initial commit
Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
-rw-r--r--.gitignore26
-rw-r--r--Makefile.am14
-rwxr-xr-xautogen.sh3
-rw-r--r--cmd/Makemodule.am13
-rw-r--r--cmd/shutdown.c139
-rw-r--r--configure.ac26
-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
-rw-r--r--lib/Makemodule.am13
-rw-r--r--lib/include/service.h62
-rw-r--r--lib/include/telinit.h20
-rw-r--r--lib/include/util.h58
-rw-r--r--lib/src/del_srv_list.c18
-rw-r--r--lib/src/delsrv.c25
-rw-r--r--lib/src/enum_by_name.c15
-rw-r--r--lib/src/opensock.c33
-rw-r--r--lib/src/rdline.c54
-rw-r--r--lib/src/rdsrv.c249
-rw-r--r--lib/src/splitkv.c124
-rw-r--r--lib/src/srv_tsort.c75
-rw-r--r--lib/src/srvscan.c93
-rw-r--r--lib/src/strexpand.c55
-rw-r--r--m4/ac_define_dir.m435
-rw-r--r--servicecmd/Makemodule.am12
-rw-r--r--servicecmd/disable.c75
-rw-r--r--servicecmd/enable.c84
-rw-r--r--servicecmd/help.c112
-rw-r--r--servicecmd/list.c71
-rw-r--r--servicecmd/servicecmd.c56
-rw-r--r--servicecmd/servicecmd.h36
-rw-r--r--services/Makemodule.am5
-rw-r--r--services/agetty.in7
-rw-r--r--services/hostname.in7
-rw-r--r--services/hwclock.in6
-rw-r--r--services/loopback.in10
-rwxr-xr-xservices/sysctl.in8
-rw-r--r--services/sysinit4
44 files changed, 2248 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3124259
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+*.in
+*.cache
+.*
+*.o
+*.a
+*~
+Makefile
+aclocal.m4
+compile
+config.*
+configure
+depcomp
+install-sh
+missing
+stamp-h1
+
+init
+service
+reboot
+shutdown
+
+services/agetty
+services/hostname
+services/loopback
+services/sysctl
+services/hwclock
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..58b2599
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,14 @@
+ACLOCAL_AMFLAGS = -I m4
+
+AM_CPPFLAGS = -D_GNU_SOURCE -I$(top_srcdir)/lib/include
+AM_CFLAGS = -std=c99 -pedantic -Wall -Wextra
+
+sbin_PROGRAMS =
+noinst_LIBRARIES =
+EXTRA_DIST =
+
+include lib/Makemodule.am
+include cmd/Makemodule.am
+include initd/Makemodule.am
+include services/Makemodule.am
+include servicecmd/Makemodule.am
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..c08fadf
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+autoreconf --force --install --symlink
diff --git a/cmd/Makemodule.am b/cmd/Makemodule.am
new file mode 100644
index 0000000..d26b93b
--- /dev/null
+++ b/cmd/Makemodule.am
@@ -0,0 +1,13 @@
+shutdown_SOURCES = cmd/shutdown.c
+shutdown_CPPFLAGS = $(AM_CPPFLAGS) -DPROGNAME=shutdown
+shutdown_CFLAGS = $(AM_CFLAGS)
+shutdown_LDFLAGS = $(AM_LDFLAGS)
+shutdown_LDADD = libinit.a
+
+reboot_SOURCES = cmd/shutdown.c
+reboot_CPPFLAGS = $(AM_CPPFLAGS) -DPROGNAME=reboot
+reboot_CFLAGS = $(AM_CFLAGS)
+reboot_LDFLAGS = $(AM_LDFLAGS)
+reboot_LDADD = libinit.a
+
+sbin_PROGRAMS += reboot shutdown
diff --git a/cmd/shutdown.c b/cmd/shutdown.c
new file mode 100644
index 0000000..16e0c8e
--- /dev/null
+++ b/cmd/shutdown.c
@@ -0,0 +1,139 @@
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <sys/reboot.h>
+#include <linux/reboot.h>
+
+#include "telinit.h"
+#include "util.h"
+
+#define STRINIFY(x) #x
+#define STRINIFY_VALUE(x) STRINIFY(x)
+#define PROGRAM_NAME STRINIFY_VALUE(PROGNAME)
+
+#define FL_FORCE 0x01
+#define FL_NOSYNC 0x02
+
+static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "poweroff", no_argument, NULL, 'p' },
+ { "reboot", no_argument, NULL, 'r' },
+ { "force", no_argument, NULL, 'f' },
+ { "no-sync", no_argument, NULL, 'n' },
+ { NULL, 0, NULL, 0 },
+};
+
+static const char *shortopt = "hVprfn";
+
+static const char *defact_str = "power-off";
+static int defact = TI_SHUTDOWN;
+
+static NORETURN void usage(int status)
+{
+ fprintf(status == EXIT_SUCCESS ? stdout : stderr,
+"%s [OPTIONS...]\n\n"
+"Perform a system shutdown or reboot.\n\n"
+" -h, --help Display this help text and exit.\n"
+" -V, --version Display version information and exit.\n"
+" -p, --poweroff Power-off the machine.\n"
+" -r, --reboot Reboot the machine.\n"
+" -f, --force Force immediate power-off or reboot. Do not contact the\n"
+" init system.\n"
+" -n, --no-sync Don't sync storage media before power-off or reboot.\n\n"
+"If no option is specified, the default action is %s.\n",
+ PROGRAM_NAME, defact_str);
+ exit(status);
+}
+
+static NORETURN void version(void)
+{
+ fputs(
+PROGRAM_NAME " (Pygos init) " PACKAGE_VERSION "\n"
+"Copyright (C) 2018 David Oberhollenzer\n"
+"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n"
+"This is free software: you are free to change and redistribute it.\n"
+"There is NO WARRANTY, to the extent permitted by law.\n",
+ stdout);
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ int c, fd, flags = 0;
+ ti_msg_t msg;
+ ssize_t ret;
+
+ if (!strcmp(PROGRAM_NAME, "reboot")) {
+ defact_str = "reboot";
+ defact = TI_REBOOT;
+ }
+
+ while (1) {
+ c = getopt_long(argc, argv, shortopt, options, NULL);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'f':
+ flags |= FL_FORCE;
+ break;
+ case 'n':
+ flags |= FL_NOSYNC;
+ break;
+ case 'p':
+ defact = TI_SHUTDOWN;
+ break;
+ case 'r':
+ defact = TI_REBOOT;
+ break;
+ case 'V':
+ version();
+ case 'h':
+ usage(EXIT_SUCCESS);
+ default:
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (flags & FL_FORCE) {
+ if (!(flags & FL_NOSYNC))
+ sync();
+
+ switch (defact) {
+ case TI_REBOOT:
+ reboot(RB_AUTOBOOT);
+ break;
+ case TI_SHUTDOWN:
+ reboot(RB_POWER_OFF);
+ break;
+ }
+
+ perror("reboot system call");
+ return EXIT_FAILURE;
+ }
+
+ fd = opensock();
+ if (fd < 0)
+ return EXIT_FAILURE;
+
+ msg.type = defact;
+retry:
+ ret = write(fd, &msg, sizeof(msg));
+
+ if (ret < 0) {
+ if (errno == EINTR)
+ goto retry;
+ perror("write on init socket");
+ close(fd);
+ return EXIT_FAILURE;
+ }
+
+ close(fd);
+ return EXIT_SUCCESS;
+}
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..a2af87d
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,26 @@
+AC_PREREQ([2.60])
+
+AC_INIT([init], 0.1, [david.oberhollenzer@tele2.at], init)
+AC_CONFIG_MACRO_DIR([m4])
+AM_INIT_AUTOMAKE([foreign subdir-objects dist-bzip2])
+AM_SILENT_RULES([yes])
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_RANLIB
+
+AC_CONFIG_HEADERS([lib/include/config.h])
+AC_DEFINE_DIR(SVCDIR, sysconfdir/init.d, [Startup service directory])
+AC_DEFINE_DIR(TEMPLATEDIR, datadir/init, [Service template directory])
+AC_DEFINE_DIR(SCRIPTDIR, libexecdir/init, [Helper script directory])
+AC_DEFINE_DIR(SOCKDIR, localstatedir/run, [Directory for initd socket])
+
+AC_DEFINE_DIR(BINPATH, bindir, [Fully evaluated bin directory])
+AC_DEFINE_DIR(SBINPATH, sbindir, [Fully evaluated sbin directory])
+
+AC_CONFIG_FILES([services/agetty])
+AC_CONFIG_FILES([services/hostname])
+AC_CONFIG_FILES([services/loopback])
+AC_CONFIG_FILES([services/sysctl])
+AC_CONFIG_FILES([services/hwclock])
+
+AC_OUTPUT([Makefile])
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;
+}
diff --git a/lib/Makemodule.am b/lib/Makemodule.am
new file mode 100644
index 0000000..bd82e81
--- /dev/null
+++ b/lib/Makemodule.am
@@ -0,0 +1,13 @@
+HEADRS = lib/include/util.h lib/include/service.h lib/include/telinit.h
+
+libinit_a_SOURCES = lib/src/delsrv.c lib/src/rdline.c
+libinit_a_SOURCES += lib/src/splitkv.c lib/src/enum_by_name.c
+libinit_a_SOURCES += lib/src/strexpand.c lib/src/rdsrv.c lib/src/srvscan.c
+libinit_a_SOURCES += lib/src/del_srv_list.c lib/src/srv_tsort.c
+libinit_a_SOURCES += lib/src/opensock.c $(HEADRS)
+libinit_a_CPPFLAGS = $(AM_CPPFLAGS)
+libinit_a_CFLAGS = $(AM_CFLAGS)
+
+EXTRA_DIST += $(HEADRS)
+
+noinst_LIBRARIES += libinit.a
diff --git a/lib/include/service.h b/lib/include/service.h
new file mode 100644
index 0000000..eb92d85
--- /dev/null
+++ b/lib/include/service.h
@@ -0,0 +1,62 @@
+#ifndef SERVICE_H
+#define SERVICE_H
+
+#include <sys/types.h>
+
+enum {
+ SVC_ONCE = 0,
+ SVC_WAIT,
+ SVC_RESPAWN,
+};
+
+enum {
+ TGT_BOOT = 0,
+ TGT_SHUTDOWN,
+ TGT_REBOOT,
+ TGT_CAD,
+
+ TGT_MAX
+};
+
+typedef struct service_t {
+ int type; /* SVC_* service type */
+ int target; /* TGT_* service target */
+ char *name; /* canonical service name */
+ char *desc; /* description string */
+ char **exec; /* command lines to execute */
+ size_t num_exec; /* number of command lines */
+ char *ctty; /* controlling tty or log file */
+
+ char **before;
+ size_t num_before;
+ char **after;
+ size_t num_after;
+
+ pid_t pid;
+ int status; /* process exit status */
+
+ struct service_t *next;
+} service_t;
+
+typedef struct {
+ service_t *targets[TGT_MAX];
+} service_list_t;
+
+/*
+ Read a service from a file.
+*/
+service_t *rdsrv(int dirfd, const char *filename);
+
+void delsrv(service_t *srv);
+
+int srvscan(const char *directory, service_list_t *list);
+
+void del_srv_list(service_list_t *list);
+
+/*
+ Sort a list of services by dependencies.
+*/
+service_t *srv_tsort(service_t *list);
+
+#endif /* SERVICE_H */
+
diff --git a/lib/include/telinit.h b/lib/include/telinit.h
new file mode 100644
index 0000000..72ad871
--- /dev/null
+++ b/lib/include/telinit.h
@@ -0,0 +1,20 @@
+#ifndef TELINIT_H
+#define TELINIT_H
+
+#include "config.h"
+
+#define INITSOCK SOCKDIR "/" "initd.socket"
+
+enum {
+ TI_SHUTDOWN = 1,
+ TI_REBOOT = 2,
+};
+
+typedef struct {
+ int type;
+} ti_msg_t;
+
+int opensock(void);
+
+#endif /* TELINIT_H */
+
diff --git a/lib/include/util.h b/lib/include/util.h
new file mode 100644
index 0000000..bed2ba7
--- /dev/null
+++ b/lib/include/util.h
@@ -0,0 +1,58 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "config.h"
+
+#ifdef __GNUC__
+ #define NORETURN __attribute__((noreturn))
+#endif
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+typedef struct {
+ const char *name;
+ int value;
+} enum_map_t;
+
+
+/*
+ Read from fd until end-of-file or a line feed is encountered.
+
+ Returns NULL with errno set on failure. Returns NULL with errno
+ cleared if end-of-file is reached.
+
+ The line must be deallocated with free().
+*/
+char *rdline(int fd);
+
+/*
+ Split a line of the shape "key = value" into key and value part.
+
+ The key can contain alphanumeric characters and can be padded with
+ spaces or tabs.
+
+ The value can be either a sequence of alphanumeric characters, period
+ or underscore OR a string in quotation marks. For strings, the
+ quotation marks are removed and escape sequences are processed.
+
+ The value may also be padded with spaces or tabs but the line may not
+ contain anything else after the value, except for spaces, tabs or
+ the '#' symbol which is interpreted as start of a comment.
+*/
+int splitkv(char *line, char **key, char **value);
+
+/*
+ Search through an array of enum_map_t entries to resolve a string to
+ a numeric value. The end of the map is indicated by a sentinel entry
+ with the name set to NULL.
+*/
+const enum_map_t *enum_by_name(const enum_map_t *map, const char *name);
+
+char *strexpand(const char *inp, size_t argc, const char *const *argv);
+
+#endif /* UTIL_H */
+
diff --git a/lib/src/del_srv_list.c b/lib/src/del_srv_list.c
new file mode 100644
index 0000000..916fcb2
--- /dev/null
+++ b/lib/src/del_srv_list.c
@@ -0,0 +1,18 @@
+#include <stdlib.h>
+
+#include "service.h"
+
+void del_srv_list(service_list_t *list)
+{
+ service_t *srv;
+ int i;
+
+ for (i = 0; i < TGT_MAX; ++i) {
+ while (list->targets[i] != NULL) {
+ srv = list->targets[i];
+ list->targets[i] = srv->next;
+
+ delsrv(srv);
+ }
+ }
+}
diff --git a/lib/src/delsrv.c b/lib/src/delsrv.c
new file mode 100644
index 0000000..b660558
--- /dev/null
+++ b/lib/src/delsrv.c
@@ -0,0 +1,25 @@
+#include <stdlib.h>
+
+#include "service.h"
+
+void delsrv(service_t *srv)
+{
+ size_t i;
+
+ for (i = 0; i < srv->num_exec; ++i)
+ free(srv->exec[i]);
+
+ for (i = 0; i < srv->num_before; ++i)
+ free(srv->before[i]);
+
+ for (i = 0; i < srv->num_after; ++i)
+ free(srv->after[i]);
+
+ free(srv->before);
+ free(srv->after);
+ free(srv->name);
+ free(srv->desc);
+ free(srv->exec);
+ free(srv->ctty);
+ free(srv);
+}
diff --git a/lib/src/enum_by_name.c b/lib/src/enum_by_name.c
new file mode 100644
index 0000000..1948eb9
--- /dev/null
+++ b/lib/src/enum_by_name.c
@@ -0,0 +1,15 @@
+#include <string.h>
+
+#include "util.h"
+
+const enum_map_t *enum_by_name(const enum_map_t *map, const char *name)
+{
+ size_t i;
+
+ for (i = 0; map[i].name != NULL; ++i) {
+ if (!strcmp(map[i].name, name))
+ return map + i;
+ }
+
+ return NULL;
+}
diff --git a/lib/src/opensock.c b/lib/src/opensock.c
new file mode 100644
index 0000000..06101ef
--- /dev/null
+++ b/lib/src/opensock.c
@@ -0,0 +1,33 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "telinit.h"
+
+int opensock(void)
+{
+ struct sockaddr_un un;
+ int fd;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ perror("socket");
+ return -1;
+ }
+
+ memset(&un, 0, sizeof(un));
+ un.sun_family = AF_UNIX;
+
+ strcpy(un.sun_path, INITSOCK);
+
+ if (connect(fd, (struct sockaddr *)&un, sizeof(un))) {
+ perror("connect: " INITSOCK);
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
diff --git a/lib/src/rdline.c b/lib/src/rdline.c
new file mode 100644
index 0000000..591c713
--- /dev/null
+++ b/lib/src/rdline.c
@@ -0,0 +1,54 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "util.h"
+
+char *rdline(int fd)
+{
+ size_t i = 0, bufsiz = 0, newsz;
+ char c, *new, *buffer = NULL;
+ int ret;
+
+ for (;;) {
+ switch (read(fd, &c, 1)) {
+ case 0:
+ if (i == 0) {
+ errno = 0;
+ return NULL;
+ }
+ c = '\0';
+ break;
+ case 1:
+ if (c == '\n')
+ c = '\0';
+ break;
+ default:
+ if (errno == EINTR)
+ continue;
+ goto fail;
+ }
+
+ if (i == bufsiz) {
+ newsz = bufsiz ? bufsiz * 2 : 16;
+ new = realloc(buffer, newsz);
+
+ if (new == NULL)
+ goto fail;
+
+ buffer = new;
+ bufsiz = newsz;
+ }
+
+ buffer[i++] = c;
+ if (c == '\0')
+ break;
+ }
+ return buffer;
+fail:
+ ret = errno;
+ free(buffer);
+ errno = ret;
+ return NULL;
+}
diff --git a/lib/src/rdsrv.c b/lib/src/rdsrv.c
new file mode 100644
index 0000000..52eb19c
--- /dev/null
+++ b/lib/src/rdsrv.c
@@ -0,0 +1,249 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "service.h"
+#include "util.h"
+
+static const enum_map_t type_map[] = {
+ { "once", SVC_ONCE },
+ { "wait", SVC_WAIT },
+ { "respawn", SVC_RESPAWN },
+ { NULL, 0 },
+};
+
+static const enum_map_t target_map[] = {
+ { "boot", TGT_BOOT },
+ { "shutdown", TGT_SHUTDOWN },
+ { "reboot", TGT_REBOOT },
+ { "ctrlaltdel", TGT_CAD },
+ { NULL, 0 },
+};
+
+static int srv_name(service_t *srv, char *arg,
+ const char *filename, size_t lineno)
+{
+ (void)filename; (void)lineno;
+ srv->name = arg;
+ return 0;
+}
+
+static int srv_desc(service_t *srv, char *arg,
+ const char *filename, size_t lineno)
+{
+ (void)filename; (void)lineno;
+ srv->desc = arg;
+ return 0;
+}
+
+static int srv_tty(service_t *srv, char *arg,
+ const char *filename, size_t lineno)
+{
+ (void)filename; (void)lineno;
+ srv->ctty = arg;
+ return 0;
+}
+
+static int srv_exec(service_t *srv, char *arg,
+ const char *filename, size_t lineno)
+{
+ char **new = realloc(srv->exec, sizeof(char*) * (srv->num_exec + 1));
+
+ if (new == NULL) {
+ fprintf(stderr, "%s: %zu: out of memory\n", filename, lineno);
+ free(arg);
+ return -1;
+ }
+
+ srv->exec = new;
+ srv->exec[srv->num_exec++] = arg;
+ return 0;
+}
+
+static int srv_before(service_t *srv, char *arg,
+ const char *filename, size_t lineno)
+{
+ char **new = realloc(srv->before,
+ sizeof(char*) * (srv->num_before + 1));
+
+ if (new == NULL) {
+ fprintf(stderr, "%s: %zu: out of memory\n", filename, lineno);
+ free(arg);
+ return -1;
+ }
+
+ srv->before = new;
+ srv->before[srv->num_before++] = arg;
+ return 0;
+}
+
+static int srv_after(service_t *srv, char *arg,
+ const char *filename, size_t lineno)
+{
+ char **new = realloc(srv->after, sizeof(char*) * (srv->num_after + 1));
+
+ if (new == NULL) {
+ fprintf(stderr, "%s: %zu: out of memory\n", filename, lineno);
+ free(arg);
+ return -1;
+ }
+
+ srv->after = new;
+ srv->after[srv->num_after++] = arg;
+ return 0;
+}
+
+static int srv_type(service_t *srv, char *arg,
+ const char *filename, size_t lineno)
+{
+ const enum_map_t *ent = enum_by_name(type_map, arg);
+
+ if (ent == NULL) {
+ fprintf(stderr, "%s: %zu: unknown service type '%s'\n",
+ filename, lineno, arg);
+ return -1;
+ }
+
+ srv->type = ent->value;
+ free(arg);
+ return 0;
+}
+
+static int srv_target(service_t *srv, char *arg,
+ const char *filename, size_t lineno)
+{
+ const enum_map_t *ent = enum_by_name(target_map, arg);
+
+ if (ent == NULL) {
+ fprintf(stderr, "%s: %zu: unknown service target '%s'\n",
+ filename, lineno, arg);
+ return -1;
+ }
+
+ srv->target = ent->value;
+ free(arg);
+ return 0;
+}
+
+
+static const struct {
+ const char *key;
+
+ int (*handle)(service_t *srv, char *arg,
+ const char *filename, size_t lineno);
+} srv_params[] = {
+ { "name", srv_name },
+ { "description", srv_desc },
+ { "exec", srv_exec },
+ { "type", srv_type },
+ { "target", srv_target },
+ { "tty", srv_tty },
+ { "before", srv_before },
+ { "after", srv_after },
+};
+
+
+service_t *rdsrv(int dirfd, const char *filename)
+{
+ const char *arg, *args[1];
+ char *line, *key, *value;
+ size_t i, argc, lineno;
+ service_t *srv;
+ int fd;
+
+ fd = openat(dirfd, filename, O_RDONLY);
+ if (fd < 0) {
+ perror(filename);
+ return NULL;
+ }
+
+ arg = strchr(filename, '@');
+ if (arg != NULL) {
+ args[0] = arg + 1;
+ argc = 1;
+ } else {
+ argc = 0;
+ }
+
+ srv = calloc(1, sizeof(*srv));
+ if (srv == NULL) {
+ fputs("out of memory\n", stderr);
+ close(fd);
+ return NULL;
+ }
+
+ for (lineno = 1; ; ++lineno) {
+ errno = 0;
+ line = rdline(fd);
+
+ if (line == NULL) {
+ if (errno != 0) {
+ fprintf(stderr, "read: %s: %zu: %s\n",
+ filename, lineno, strerror(errno));
+ goto fail;
+ }
+ break;
+ }
+
+ if (splitkv(line, &key, &value)) {
+ if (key == NULL) {
+ fprintf(stderr,
+ "%s: %zu: expected <key> = <value>\n",
+ filename, lineno);
+ } else if (value == NULL) {
+ fprintf(stderr,
+ "%s: %zu: expected value after %s\n",
+ filename, lineno, key);
+ } else {
+ fprintf(stderr,
+ "%s: %zu: unexpected arguments "
+ "after key-value pair\n",
+ filename, lineno);
+ }
+ goto fail;
+ }
+
+ if (key == NULL) {
+ free(line);
+ continue;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(srv_params); ++i) {
+ if (!strcmp(srv_params[i].key, key))
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(srv_params)) {
+ fprintf(stderr, "%s: %zu: unknown parameter '%s'\n",
+ filename, lineno, key);
+ goto fail_line;
+ }
+
+ value = strexpand(value, argc, args);
+ if (value == NULL) {
+ fputs("out of memory", stderr);
+ goto fail_line;
+ }
+
+ if (srv_params[i].handle(srv, value, filename, lineno)) {
+ free(value);
+ goto fail_line;
+ }
+
+ free(line);
+ }
+
+ close(fd);
+ return srv;
+fail_line:
+ free(line);
+fail:
+ close(fd);
+ delsrv(srv);
+ return NULL;
+}
diff --git a/lib/src/splitkv.c b/lib/src/splitkv.c
new file mode 100644
index 0000000..6c6fe94
--- /dev/null
+++ b/lib/src/splitkv.c
@@ -0,0 +1,124 @@
+#include <ctype.h>
+
+#include "util.h"
+
+static char *skpspc(char *ptr)
+{
+ while (*ptr == ' ' || *ptr == '\t')
+ ++ptr;
+ return ptr;
+}
+
+static int xdigit(int x)
+{
+ if (isupper(x))
+ return x - 'A' + 0x0A;
+ if (islower(x))
+ return x - 'a' + 0x0A;
+ return x - '0';
+}
+
+static char *parse_str(char *src)
+{
+ char *dst = src;
+ int c;
+
+ for (;;) {
+ c = *(src++);
+
+ switch (c) {
+ case '\\':
+ c = *(src++);
+
+ switch (c) {
+ case 'a': c = '\a'; break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 't': c = '\t'; break;
+ case '\\': break;
+ case '"': break;
+ case 'x':
+ c = 0;
+ if (isxdigit(*src))
+ c = (c << 4) | xdigit(*(src++));
+ if (isxdigit(*src))
+ c = (c << 4) | xdigit(*(src++));
+ break;
+ case '0':
+ c = 0;
+ if (isdigit(*src) && *src < '8')
+ c = (c << 3) | (*(src++) - '0');
+ if (isdigit(*src) && *src < '8')
+ c = (c << 3) | (*(src++) - '0');
+ if (isdigit(*src) && *src < '8')
+ c = (c << 3) | (*(src++) - '0');
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ case '"':
+ *(dst++) = '\0';
+ goto out;
+ }
+
+ *(dst++) = c;
+ }
+out:
+ return src;
+}
+
+int splitkv(char *line, char **key, char **value)
+{
+ *key = NULL;
+ *value = NULL;
+
+ line = skpspc(line);
+
+ if (*line == '#' || *line == '\0')
+ return 0;
+
+ if (!isalpha(*line))
+ return -1;
+
+ *key = line;
+
+ while (isalnum(*line))
+ ++line;
+
+ if (*line == ' ' || *line == '\t') {
+ *(line++) = '\0';
+ line = skpspc(line);
+ }
+
+ if (*line != '=')
+ return -1;
+
+ *(line++) = '\0';
+ line = skpspc(line);
+
+ if (*line == '"') {
+ ++line;
+ *value = line;
+
+ line = parse_str(line);
+ } else if (isalnum(*line)) {
+ *value = line;
+
+ while (isalnum(*line) || *line == '.' || *line == '_')
+ ++line;
+
+ if (*line != '\0')
+ *(line++) = '\0';
+ } else {
+ return -1;
+ }
+
+ line = skpspc(line);
+
+ if (*line != '\0' && *line != '#')
+ return -1;
+
+ return 0;
+}
diff --git a/lib/src/srv_tsort.c b/lib/src/srv_tsort.c
new file mode 100644
index 0000000..e2549b1
--- /dev/null
+++ b/lib/src/srv_tsort.c
@@ -0,0 +1,75 @@
+#include <stdbool.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+
+#include "service.h"
+
+static bool has_dependencies(service_t *list, service_t *svc)
+{
+ size_t i;
+
+ while (list != NULL) {
+ for (i = 0; i < svc->num_after; ++i) {
+ if (!strcmp(svc->after[i], list->name))
+ return true;
+ }
+
+ for (i = 0; i < list->num_before; ++i) {
+ if (!strcmp(list->before[i], svc->name))
+ return true;
+ }
+
+ list = list->next;
+ }
+
+ return false;
+}
+
+service_t *srv_tsort(service_t *list)
+{
+ service_t *nl = NULL, *end = NULL;
+ service_t *svc, *prev;
+
+ while (list != NULL) {
+ /* remove first service without dependencies */
+ prev = NULL;
+ svc = list;
+
+ while (svc != NULL) {
+ if (has_dependencies(list, svc)) {
+ prev = svc;
+ svc = svc->next;
+ } else {
+ if (prev != NULL) {
+ prev->next = svc->next;
+ } else {
+ list = svc->next;
+ }
+ svc->next = NULL;
+ break;
+ }
+ }
+
+ /* cycle! */
+ if (svc == NULL) {
+ if (end == NULL) {
+ nl = list;
+ } else {
+ end->next = list;
+ }
+ errno = ELOOP;
+ break;
+ }
+
+ /* append to new list */
+ if (end == NULL) {
+ nl = end = svc;
+ } else {
+ end->next = svc;
+ end = svc;
+ }
+ }
+
+ return nl;
+}
diff --git a/lib/src/srvscan.c b/lib/src/srvscan.c
new file mode 100644
index 0000000..71ad4f9
--- /dev/null
+++ b/lib/src/srvscan.c
@@ -0,0 +1,93 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stddef.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "service.h"
+
+int srvscan(const char *directory, service_list_t *list)
+{
+ int i, dfd, type, ret = 0;
+ struct dirent *ent;
+ const char *ptr;
+ service_t *srv;
+ struct stat sb;
+ DIR *dir;
+
+ for (i = 0; i < TGT_MAX; ++i)
+ list->targets[i] = NULL;
+
+ dir = opendir(directory);
+ if (dir == NULL) {
+ perror(directory);
+ return -1;
+ }
+
+ dfd = dirfd(dir);
+ if (dfd < 0) {
+ perror(directory);
+ closedir(dir);
+ return -1;
+ }
+
+ for (;;) {
+ errno = 0;
+ ent = readdir(dir);
+
+ if (ent == NULL) {
+ if (errno != 0) {
+ perror(directory);
+ ret = -1;
+ }
+ break;
+ }
+
+ for (ptr = ent->d_name; isalnum(*ptr) || *ptr == '_'; ++ptr)
+ ;
+
+ if (*ptr != '\0' && *ptr != '@')
+ continue;
+
+ if (fstatat(dfd, ent->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
+ fprintf(stderr, "stat %s/%s: %s\n",
+ directory, ent->d_name, strerror(errno));
+ ret = -1;
+ continue;
+ }
+
+ type = (sb.st_mode & S_IFMT);
+
+ if (type != S_IFREG && type != S_IFLNK)
+ continue;
+
+ srv = rdsrv(dfd, ent->d_name);
+ if (srv == NULL) {
+ ret = -1;
+ continue;
+ }
+
+ srv->next = list->targets[srv->target];
+ list->targets[srv->target] = srv;
+ }
+
+ for (i = 0; i < TGT_MAX; ++i) {
+ if (list->targets[i] == NULL)
+ continue;
+
+ errno = 0;
+ list->targets[i] = srv_tsort(list->targets[i]);
+
+ if (errno != 0) {
+ fprintf(stderr, "sorting services read from %s: %s\n",
+ directory, strerror(errno));
+ }
+ }
+
+ closedir(dir);
+ return ret;
+}
diff --git a/lib/src/strexpand.c b/lib/src/strexpand.c
new file mode 100644
index 0000000..7e552eb
--- /dev/null
+++ b/lib/src/strexpand.c
@@ -0,0 +1,55 @@
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "util.h"
+
+char *strexpand(const char *inp, size_t argc, const char *const *argv)
+{
+ char *out, *dst;
+ const char *ptr;
+ size_t i, len;
+
+ ptr = inp;
+ len = 0;
+
+ while (*ptr != '\0') {
+ if (ptr[0] == '%' && isdigit(ptr[1])) {
+ i = ptr[1] - '0';
+ if (i < argc)
+ len += strlen(argv[i]);
+ ptr += 2;
+ } else if (ptr[0] == '%' && ptr[1] == '%') {
+ ptr += 2;
+ len += 1;
+ } else {
+ ++ptr;
+ ++len;
+ }
+ }
+
+ out = calloc(1, len + 1);
+ if (out == NULL)
+ return NULL;
+
+ dst = out;
+
+ while (*inp != '\0') {
+ if (inp[0] == '%' && isdigit(inp[1])) {
+ i = inp[1] - '0';
+ if (i < argc) {
+ len = strlen(argv[i]);
+ memcpy(dst, argv[i], len);
+ dst += len;
+ }
+ inp += 2;
+ } else if (inp[0] == '%' && inp[1] == '%') {
+ *(dst++) = '%';
+ inp += 2;
+ } else {
+ *(dst++) = *(inp++);
+ }
+ }
+
+ return out;
+}
diff --git a/m4/ac_define_dir.m4 b/m4/ac_define_dir.m4
new file mode 100644
index 0000000..3b48c8b
--- /dev/null
+++ b/m4/ac_define_dir.m4
@@ -0,0 +1,35 @@
+dnl @synopsis AC_DEFINE_DIR(VARNAME, DIR [, DESCRIPTION])
+dnl
+dnl This macro sets VARNAME to the expansion of the DIR variable,
+dnl taking care of fixing up ${prefix} and such.
+dnl
+dnl VARNAME is then offered as both an output variable and a C
+dnl preprocessor symbol.
+dnl
+dnl Example:
+dnl
+dnl AC_DEFINE_DIR([DATADIR], [datadir], [Where data are placed to.])
+dnl
+dnl @category Misc
+dnl @author Stepan Kasal <kasal@ucw.cz>
+dnl @author Andreas Schwab <schwab@suse.de>
+dnl @author Guido U. Draheim <guidod@gmx.de>
+dnl @author Alexandre Oliva
+dnl @version 2006-10-13
+dnl @license AllPermissive
+
+AC_DEFUN([AC_DEFINE_DIR], [
+ prefix_NONE=
+ exec_prefix_NONE=
+ test "x$prefix" = xNONE && prefix_NONE=yes && prefix=$ac_default_prefix
+ test "x$exec_prefix" = xNONE && exec_prefix_NONE=yes && exec_prefix=$prefix
+dnl In Autoconf 2.60, ${datadir} refers to ${datarootdir}, which in turn
+dnl refers to ${prefix}. Thus we have to use `eval' twice.
+ eval ac_define_dir="\"[$]$2\""
+ eval ac_define_dir="\"$ac_define_dir\""
+ AC_SUBST($1, "$ac_define_dir")
+ AC_DEFINE_UNQUOTED($1, "$ac_define_dir", [$3])
+ test "$prefix_NONE" && prefix=NONE
+ test "$exec_prefix_NONE" && exec_prefix=NONE
+])
+
diff --git a/servicecmd/Makemodule.am b/servicecmd/Makemodule.am
new file mode 100644
index 0000000..fd671b6
--- /dev/null
+++ b/servicecmd/Makemodule.am
@@ -0,0 +1,12 @@
+SRVHEADERS = servicecmd/servicecmd.h
+
+service_SOURCES = servicecmd/servicecmd.c servicecmd/help.c servicecmd/list.c
+service_SOURCES += servicecmd/enable.c servicecmd/disable.c $(SRVHEADERS)
+service_CPPFLAGS = $(AM_CPPFLAGS)
+service_CFLAGS = $(AM_CFLAGS)
+service_LDFLAGS = $(AM_LDFLAGS)
+service_LDADD = libinit.a
+
+EXTRA_DIST += $(SRVHEADERS)
+
+sbin_PROGRAMS += service
diff --git a/servicecmd/disable.c b/servicecmd/disable.c
new file mode 100644
index 0000000..90d8b80
--- /dev/null
+++ b/servicecmd/disable.c
@@ -0,0 +1,75 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "servicecmd.h"
+
+static int cmd_disable(int argc, char **argv)
+{
+ int ret = EXIT_FAILURE;
+ char *linkname, *ptr;
+ struct stat sb;
+
+ if (argc < 2 || argc > 3) {
+ fputs("Wrong number of arguments for `disable'.\n"
+ "Try `service help disable' for more information.\n",
+ stderr);
+ return EXIT_FAILURE;
+ }
+
+ for (ptr = argv[1]; isalnum(*ptr) || *ptr == '_'; ++ptr)
+ ;
+
+ if (*ptr != '\0') {
+ fprintf(stderr, "Invalid service name '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ if (argc == 3) {
+ ret = asprintf(&linkname, "%s/%s@%s",
+ SVCDIR, argv[1], argv[2]);
+ } else {
+ ret = asprintf(&linkname, "%s/%s", SVCDIR, argv[1]);
+ }
+
+ if (ret < 0) {
+ perror("asprintf");
+ return EXIT_FAILURE;
+ }
+
+ if (lstat(linkname, &sb)) {
+ fprintf(stderr, "lstat %s: %s\n", linkname, strerror(errno));
+ goto out;
+ }
+
+ if ((sb.st_mode & S_IFMT) != S_IFLNK) {
+ fprintf(stderr, "error: '%s' is not a symlink!", linkname);
+ goto out;
+ }
+
+ if (unlink(linkname)) {
+ fprintf(stderr, "removing %s: %s\n",
+ linkname, strerror(errno));
+ goto out;
+ }
+
+ ret = EXIT_SUCCESS;
+out:
+ free(linkname);
+ return ret;
+}
+
+static command_t disable = {
+ .cmd = "disable",
+ .usage = "<name> [argument]",
+ .s_desc = "disable a service",
+ .l_desc = "This disables a service by removing the coresponding "
+ "symlink in " SVCDIR ".",
+ .run_cmd = cmd_disable,
+};
+
+REGISTER_COMMAND(disable)
diff --git a/servicecmd/enable.c b/servicecmd/enable.c
new file mode 100644
index 0000000..8e6200c
--- /dev/null
+++ b/servicecmd/enable.c
@@ -0,0 +1,84 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "servicecmd.h"
+
+static int cmd_enable(int argc, char **argv)
+{
+ char *target, *linkname, *ptr;
+ int ret = EXIT_FAILURE;
+ struct stat sb;
+
+ if (argc < 2 || argc > 3) {
+ fputs("Wrong number of arguments for `enable'.\n"
+ "Try `service help enable' for more information.\n",
+ stderr);
+ return EXIT_FAILURE;
+ }
+
+ for (ptr = argv[1]; isalnum(*ptr) || *ptr == '_'; ++ptr)
+ ;
+
+ if (*ptr != '\0') {
+ fprintf(stderr, "Invalid service name '%s'\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ if (asprintf(&target, "%s/%s", TEMPLATEDIR, argv[1]) < 0) {
+ perror("asprintf");
+ return EXIT_FAILURE;
+ }
+
+ if (stat(target, &sb)) {
+ fprintf(stderr, "%s: %s\n", target, strerror(errno));
+ goto out_tgt;
+ }
+
+ if ((sb.st_mode & S_IFMT) != S_IFREG) {
+ fprintf(stderr, "%s: must be a regular file\n", target);
+ goto out_tgt;
+ }
+
+ if (argc == 3) {
+ ret = asprintf(&linkname, "%s/%s@%s",
+ SVCDIR, argv[1], argv[2]);
+ } else {
+ ret = asprintf(&linkname, "%s/%s", SVCDIR, argv[1]);
+ }
+
+ if (ret < 0) {
+ perror("asprintf");
+ goto out_tgt;
+ }
+
+ if (symlink(target, linkname)) {
+ fprintf(stderr, "creating symlink '%s' -> '%s: %s\n",
+ linkname, target, strerror(errno));
+ goto out;
+ }
+
+ ret = EXIT_SUCCESS;
+out:
+ free(linkname);
+out_tgt:
+ free(target);
+ return ret;
+}
+
+static command_t enable = {
+ .cmd = "enable",
+ .usage = "<name> [argument]",
+ .s_desc = "enable a service",
+ .l_desc = "This marks a service as enabled by creating a symlink in "
+ SVCDIR " pointing to the template file in " TEMPLATEDIR ". "
+ "An optional argument can be supplied to parameterize the "
+ "template.",
+ .run_cmd = cmd_enable,
+};
+
+REGISTER_COMMAND(enable)
diff --git a/servicecmd/help.c b/servicecmd/help.c
new file mode 100644
index 0000000..6cd816e
--- /dev/null
+++ b/servicecmd/help.c
@@ -0,0 +1,112 @@
+#include "servicecmd.h"
+
+extern char *__progname;
+
+static void pretty_print(const char *str, int padd, int len)
+{
+ int i, brk;
+next:
+ while (isspace(*str) && *str != '\n')
+ ++str;
+
+ if (!(*str))
+ return;
+
+ if (*str == '\n') {
+ fputc('\n', stdout);
+ ++str;
+ len = padd;
+ goto next;
+ }
+
+ for (i = 0, brk = 0; str[i]; ++i) {
+ if (str[i] == '<' || str[i] == '[')
+ ++brk;
+ if (str[i] == '>' || str[i] == ']')
+ --brk;
+ if (!brk && isspace(str[i]))
+ break;
+ }
+
+ if ((len + i) < 80) {
+ fwrite(str, 1, i, stdout);
+ str += i;
+ len += i;
+
+ if ((len + 1) < 80) {
+ fputc(' ', stdout);
+ ++len;
+ goto next;
+ }
+ }
+
+ printf("\n%*s", padd, "");
+ len = padd;
+ goto next;
+}
+
+static void print_cmd_usage(const char *cmd, const char *usage)
+{
+ int padd;
+
+ padd = printf("Usage: %s %s ", __progname, cmd);
+
+ if ((strlen(usage) + padd) < 80) {
+ fputs(usage, stdout);
+ return;
+ }
+
+ pretty_print(usage, padd, padd);
+}
+
+static int cmd_help(int argc, char **argv)
+{
+ const char *help;
+ command_t *cmd;
+
+ if (argc < 2)
+ usage(EXIT_SUCCESS);
+
+ if (argc > 2) {
+ fprintf(stderr, "Too many arguments\n\n"
+ "Usage: %s help <command>", __progname);
+ return EXIT_FAILURE;
+ }
+
+ for (cmd = commands; cmd != NULL; cmd = cmd->next) {
+ if (strcmp(cmd->cmd, argv[1]) != 0)
+ continue;
+
+ print_cmd_usage(cmd->cmd, cmd->usage);
+ fputs("\n\n", stdout);
+
+ help = cmd->l_desc ? cmd->l_desc : cmd->s_desc;
+
+ if (islower(*help)) {
+ fputc(toupper(*(help++)), stdout);
+ pretty_print(help, 0, 1);
+ } else {
+ pretty_print(help, 0, 0);
+ }
+
+ fputc('\n', stdout);
+ return EXIT_SUCCESS;
+ }
+
+ fprintf(stderr, "Unknown command '%s'\n\n"
+ "Try `%s help' for a list of available commands\n",
+ argv[1], __progname);
+ return EXIT_FAILURE;
+}
+
+static command_t help = {
+ .cmd = "help",
+ .usage = "<command>",
+ .s_desc = "print a help text for a command",
+ .l_desc = "Print a help text for a specified command. If no command "
+ "is specified, print a generic help text and a list of "
+ "available commands.",
+ .run_cmd = cmd_help,
+};
+
+REGISTER_COMMAND(help)
diff --git a/servicecmd/list.c b/servicecmd/list.c
new file mode 100644
index 0000000..2286197
--- /dev/null
+++ b/servicecmd/list.c
@@ -0,0 +1,71 @@
+#include "servicecmd.h"
+#include "service.h"
+#include "config.h"
+
+static int cmd_list(int argc, char **argv)
+{
+ int i, ret = EXIT_SUCCESS;
+ service_list_t list;
+ service_t *svc;
+
+ (void)argc; (void)argv;
+
+ if (srvscan(SVCDIR, &list)) {
+ fprintf(stderr, "Error while reading services from %s\n",
+ SVCDIR);
+ ret = EXIT_FAILURE;
+ }
+
+ for (i = 0; i < TGT_MAX; ++i) {
+ if (list.targets[i] == NULL)
+ continue;
+
+ fputs("******** target: ", stdout);
+
+ switch (i) {
+ case TGT_BOOT:
+ fputs("boot", stdout);
+ break;
+ case TGT_SHUTDOWN:
+ fputs("shutdown", stdout);
+ break;
+ case TGT_REBOOT:
+ fputs("reboot", stdout);
+ break;
+ case TGT_CAD:
+ fputs("ctrl-alt-delete", stdout);
+ break;
+ }
+
+ fputs(" ********\n", stdout);
+
+ for (svc = list.targets[i]; svc != NULL; svc = svc->next) {
+ fprintf(stdout, "Name: %s\n", svc->name);
+ fprintf(stdout, "Descrption: %s\n", svc->desc);
+
+ fputs("Type: ", stdout);
+ switch (svc->type) {
+ case SVC_ONCE: fputs("once\n", stdout); break;
+ case SVC_WAIT: fputs("wait\n", stdout); break;
+ case SVC_RESPAWN: fputs("respawn\n", stdout); break;
+ }
+
+ fputc('\n', stdout);
+ }
+
+ fputc('\n', stdout);
+ }
+
+ del_srv_list(&list);
+ return ret;
+}
+
+static command_t list = {
+ .cmd = "list",
+ .usage = "",
+ .s_desc = "print a list of currently enabled services",
+ .l_desc = "Print a list of currently enabled services.",
+ .run_cmd = cmd_list,
+};
+
+REGISTER_COMMAND(list)
diff --git a/servicecmd/servicecmd.c b/servicecmd/servicecmd.c
new file mode 100644
index 0000000..278afaf
--- /dev/null
+++ b/servicecmd/servicecmd.c
@@ -0,0 +1,56 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "servicecmd.h"
+#include "service.h"
+#include "config.h"
+#include "util.h"
+
+
+command_t *commands;
+
+extern char *__progname;
+
+
+void usage(int status)
+{
+ FILE *stream = (status == EXIT_SUCCESS ? stdout : stderr);
+ int padd = 0, len;
+ command_t *cmd;
+
+ fprintf(stream, "usage: %s <command> [args...]\n\n"
+ "Available commands:\n\n", __progname);
+
+ for (cmd = commands; cmd != NULL; cmd = cmd->next) {
+ len = strlen(cmd->cmd);
+
+ padd = len > padd ? len : padd;
+ }
+
+ for (cmd = commands; cmd != NULL; cmd = cmd->next) {
+ fprintf(stream, "%*s - %s\n",
+ (int)padd + 1, cmd->cmd, cmd->s_desc);
+ }
+
+ fprintf(stream, "\nTry `%s help <command>' for more information "
+ "on a specific command\n", __progname);
+
+ exit(status);
+}
+
+int main(int argc, char **argv)
+{
+ command_t *cmd;
+
+ if (argc < 2)
+ usage(EXIT_SUCCESS);
+
+ for (cmd = commands; cmd != NULL; cmd = cmd->next) {
+ if (!strcmp(cmd->cmd, argv[1])) {
+ return cmd->run_cmd(argc - 1, argv + 1);
+ }
+ }
+
+ fprintf(stderr, "Unknown command '%s'\n\n", argv[1]);
+ usage(EXIT_FAILURE);
+}
diff --git a/servicecmd/servicecmd.h b/servicecmd/servicecmd.h
new file mode 100644
index 0000000..ce5e58f
--- /dev/null
+++ b/servicecmd/servicecmd.h
@@ -0,0 +1,36 @@
+#ifndef SERVICECMD_H
+#define SERVICECMD_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "util.h"
+
+typedef struct command_t {
+ struct command_t *next;
+
+ const char *cmd;
+ const char *usage;
+ const char *s_desc;
+ const char *l_desc;
+
+ int (*run_cmd)(int argc, char **argv);
+} command_t;
+
+extern command_t *commands;
+
+void usage(int status) NORETURN;
+
+#define REGISTER_COMMAND(cmd) \
+ static void __attribute__((constructor)) register_##cmd(void) \
+ { \
+ command_t *c = (command_t *)&cmd; \
+ \
+ c->next = commands; \
+ commands = c; \
+ }
+
+#endif /* SERVICECMD_H */
+
diff --git a/services/Makemodule.am b/services/Makemodule.am
new file mode 100644
index 0000000..88c34a4
--- /dev/null
+++ b/services/Makemodule.am
@@ -0,0 +1,5 @@
+initdir = @TEMPLATEDIR@
+init_DATA = services/agetty services/hostname services/loopback
+init_DATA += services/sysctl services/hwclock services/sysinit
+
+EXTRA_DIST += services/sysinit
diff --git a/services/agetty.in b/services/agetty.in
new file mode 100644
index 0000000..b4aeebd
--- /dev/null
+++ b/services/agetty.in
@@ -0,0 +1,7 @@
+name = "agetty"
+description = "agetty on %0"
+exec = "@SBINPATH@/agetty %0 linux"
+type = respawn
+target = boot
+after = sysinit
+tty = "/dev/%0"
diff --git a/services/hostname.in b/services/hostname.in
new file mode 100644
index 0000000..4ff5acd
--- /dev/null
+++ b/services/hostname.in
@@ -0,0 +1,7 @@
+name = "hostname"
+description = "reload hostname"
+exec = "@BINPATH@/hostname --file /etc/hostname"
+type = wait
+target = boot
+before = sysinit
+after = hwclock
diff --git a/services/hwclock.in b/services/hwclock.in
new file mode 100644
index 0000000..b558493
--- /dev/null
+++ b/services/hwclock.in
@@ -0,0 +1,6 @@
+name = "hwclock"
+description = "restore time from RTC"
+exec = "@SBINPATH@/hwclock --hctosys --utc"
+type = wait
+target = boot
+before = sysinit
diff --git a/services/loopback.in b/services/loopback.in
new file mode 100644
index 0000000..2720e3c
--- /dev/null
+++ b/services/loopback.in
@@ -0,0 +1,10 @@
+name = "loopback"
+description = "configure network loopback device"
+type = wait
+target = boot
+before = sysinit
+after = hwclock
+after = hostname
+
+exec = "@SBINPATH@/ip addr add 127.0.0.1/8 dev lo brd +"
+exec = "@SBINPATH@/ip link set lo up"
diff --git a/services/sysctl.in b/services/sysctl.in
new file mode 100755
index 0000000..5f76328
--- /dev/null
+++ b/services/sysctl.in
@@ -0,0 +1,8 @@
+name = "sysctl"
+description = "configure kernel paramters"
+exec = "@SBINPATH@/sysctl --system"
+type = wait
+target = boot
+before = sysinit
+after = hwclock
+after = hostname
diff --git a/services/sysinit b/services/sysinit
new file mode 100644
index 0000000..37645a5
--- /dev/null
+++ b/services/sysinit
@@ -0,0 +1,4 @@
+name = "sysinit"
+description = "basic system initialization"
+type = once
+target = boot