diff options
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 |