diff --git a/Makefile b/Makefile index 2af49ae3..1313cf9e 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ CPPFLAGS += -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_GNU_SOURCE CPPFLAGS += -DVERSION=\"$(VERSION)\" -DWHOAMI=\"`whoami`@`hostname`\" CPPFLAGS += -DPLUGIN_PATH=\"$(PLUGIN_DIR)\" LDFLAGS += -rdynamic -LDLIBS += -ldl -lev +LDLIBS += -ldl prefix ?= /usr/local sysconfdir ?= /etc diff --git a/conf.c b/conf.c index 00358e6c..8c3f3df8 100644 --- a/conf.c +++ b/conf.c @@ -122,9 +122,12 @@ void parse_finit_conf(char *file) network = build_cmd(NULL, x, CMD_SIZE); continue; } + if (MATCH_CMD(line, "startx ", x)) { + svc_register(x, username); + continue; + } if (MATCH_CMD(line, "service ", x)) { - if (svc_register(x)) - _e("Failed, too many services to monitor.\n"); + svc_register(x, NULL); continue; } } diff --git a/examples/finit.conf b/examples/finit.conf index 8bcb74cb..bf5bb28b 100644 --- a/examples/finit.conf +++ b/examples/finit.conf @@ -1,6 +1,5 @@ # Simple Debian finit.conf check /dev/vda1 -module tipc module evdev module loop module psmouse @@ -10,4 +9,4 @@ network service networking start service /sbin/klogd -n -- Kernel logging server service /sbin/syslogd -n -- Syslog server service /usr/sbin/sshd -D -- OpenSSH server -service /usr/bin/startx -- X Window System +service /usr/sbin/gdm -- GNOME Display Manager diff --git a/finit.c b/finit.c index a7af778b..52034e86 100644 --- a/finit.c +++ b/finit.c @@ -22,10 +22,11 @@ * THE SOFTWARE. */ -#include #include +#include #include #include +#include /* umask(), mkdir() */ #include "finit.h" #include "helpers.h" @@ -60,10 +61,18 @@ static void parse_kernel_cmdline(void) } } +static int run_loop(void) +{ + while (1) { + svc_monitor(); + io_monitor(); + } + + return 0; +} + int main(int UNUSED(args), char *argv[]) { - struct ev_loop *loop = ev_default_loop(0); - /* * Initial setup of signals, ignore all until we're up. */ @@ -133,7 +142,7 @@ int main(int UNUSED(args), char *argv[]) * Load plugins and run first level hooks. */ _d("Loading plugins ..."); - load_plugins(loop, PLUGIN_PATH); + load_plugins(PLUGIN_PATH); _d("Running first level hooks ..."); run_hooks(HOOK_BASEFS_UP); @@ -183,9 +192,7 @@ int main(int UNUSED(args), char *argv[]) * Enter main loop to monior /dev/initctl and services */ _d("Entering main loop ..."); - ev_run(loop, 0); - - return 0; + return run_loop(); } /** diff --git a/finit.h b/finit.h index a61f60c8..5367d485 100644 --- a/finit.h +++ b/finit.h @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include diff --git a/helpers.c b/helpers.c index 79a76da0..0e72bf42 100644 --- a/helpers.c +++ b/helpers.c @@ -174,37 +174,6 @@ int print_result(int fail) return fail; } -int start_process(char *cmd, char *args[], int console) -{ - pid_t pid; - - if (sig_stopped()) - return 0; - - pid = fork(); - if (!pid) { - int i; - struct sigaction sa; - - /* Reset signal handlers that were set by the parent process */ - for (i = 1; i < NSIG; i++) - DFLSIG(sa, i, 0); - - if (console) { - int fd = open (CONSOLE, O_WRONLY | O_APPEND); - if (-1 != fd) { - dup2(STDOUT_FILENO, fd); - dup2(STDERR_FILENO, fd); - } - } - - execvp(cmd, args); - exit(!console ? print_result(0) : 0); - } - - return pid; -} - int run(char *cmd) { int status, result, i = 0; @@ -353,12 +322,14 @@ pid_t run_getty(char *cmd, char *argv[]) sigset_t nmask; struct sigaction sa; + /* Detach from initial controlling TTY */ vhangup(); close(2); close(1); close(0); + /* Attach TTY to console */ if (open(CONSOLE, O_RDWR) != 0) exit(1); @@ -479,7 +450,7 @@ int getuser(char *s) { struct passwd *usr; - if ((usr = getpwnam(s)) == NULL) + if (!s || (usr = getpwnam(s)) == NULL) return -1; return usr->pw_uid; diff --git a/plugin.c b/plugin.c index 9e130799..c7ff5166 100644 --- a/plugin.c +++ b/plugin.c @@ -24,6 +24,7 @@ #include #include /* dlopen() et al */ #include /* readdir() et al */ +#include #include #include /* BSD sys/queue.h API */ @@ -33,10 +34,16 @@ #include "plugin.h" #include "svc.h" +#define is_io_plugin(p) ((p)->io.cb && (p)->io.fd >= 0) + +static size_t num_fds = 0; +static struct pollfd fds[MAX_NUM_FDS]; static LIST_HEAD(, plugin) plugins = LIST_HEAD_INITIALIZER(); int plugin_register(plugin_t *plugin) { + int inuse = 0; + if (!plugin) { errno = EINVAL; return 1; @@ -51,15 +58,29 @@ int plugin_register(plugin_t *plugin) plugin->name = (char *)info.dli_fname; } + if (is_io_plugin(plugin)) { + if (num_fds + 1 >= MAX_NUM_FDS) { + num_fds = MAX_NUM_SVC; + errno = ENOMEM; + return 1; + } + num_fds++; + inuse++; + } + if (plugin->svc.cb) { svc_t *svc = svc_find_by_name(plugin->name); - if (!svc) { - _e("No service \"%s\" loaded, skipping plugin.", basename(plugin->name)); - return 1; + if (svc) { + plugin->svc.id = svc_id(svc); + svc->plugin = &plugin->svc; + inuse++; } - plugin->svc.id = svc_id(svc); - svc->plugin = &plugin->svc; + } + + if (!inuse) { + _e("No service \"%s\" loaded, skipping plugin.", basename(plugin->name)); + return 1; } LIST_INSERT_HEAD(&plugins, plugin, link); @@ -71,6 +92,8 @@ int plugin_unregister(plugin_t *plugin) { LIST_REMOVE(plugin, link); + /* XXX: Unfinished, add cleanup code here! */ + return 0; } @@ -100,44 +123,62 @@ void run_services(void) } /* Generic libev I/O callback, looks up correct plugin and calls its callback */ -static void generic_io_cb(struct ev_loop *loop, ev_io *w, int revents) +static void generic_io_cb(int fd, int events) { plugin_t *p; - ev_io_stop(loop, w); - /* Find matching plugin, pick first matching fd */ PLUGIN_ITERATOR(p) { - if (p->io.cb && p->io.fd == w->fd) { - p->io.cb(p->io.arg, w->fd, revents); + if (is_io_plugin(p) && p->io.fd == fd) { + _d("Calling I/O %s from runloop...", basename(p->name)); + p->io.cb(p->io.arg, fd, events); break; } } +} - ev_io_start(loop, w); +void io_monitor(void) +{ + int ret; + size_t i; + + while ((ret = poll(fds, num_fds, 500))) { + if (-1 == ret) { + if (EINTR == errno) + continue; + + _e("Failed polling I/O plugin descriptors, error %d: %s", + errno, strerror(errno)); + break; + } + + /* Traverse all I/O fds and run callbacks */ + for (i = 0; i < num_fds; i++) { + if (fds[i].revents) + generic_io_cb(fds[i].fd, fds[i].revents); + } + + break; + } } /* Setup any I/O callbacks for plugins that use them */ -static void init_plugins(struct ev_loop *loop) +static void init_plugins(void) { + int i = 0; plugin_t *p; PLUGIN_ITERATOR(p) { - if (p->io.cb && p->io.fd >= 0) { - ev_io *w = calloc(1, sizeof(ev_io)); - + if (is_io_plugin(p)) { _d("Initializing plugin %s for I/O", basename(p->name)); - if (!w) { - _e("Failed setting up plugin %s: Out of memory", basename(p->name)); - continue; - } - ev_io_init(w, generic_io_cb, p->io.fd, p->io.flags); - ev_io_start(loop, w); + fds[i].revents = 0; + fds[i].events = p->io.flags; + fds[num_fds++].fd = p->io.fd; } } } -int load_plugins(struct ev_loop *loop, char *path) +int load_plugins(char *path) { DIR *dp = opendir(path); struct dirent *entry; @@ -167,7 +208,7 @@ int load_plugins(struct ev_loop *loop, char *path) } closedir(dp); - init_plugins(loop); + init_plugins(); return 0; } diff --git a/plugin.h b/plugin.h index 7cb0534c..90a92431 100644 --- a/plugin.h +++ b/plugin.h @@ -24,11 +24,11 @@ #ifndef FINIT_PLUGIN_H_ #define FINIT_PLUGIN_H_ -#include +#include #include /* BSD sys/queue.h API */ -#define PLUGIN_IO_READ EV_READ -#define PLUGIN_IO_WRITE EV_READ +#define PLUGIN_IO_READ POLLIN +#define PLUGIN_IO_WRITE POLLOUT #define PLUGIN_INIT(x) static void __attribute__ ((constructor)) x(void) #define PLUGIN_EXIT(x) static void __attribute__ ((destructor)) x(void) diff --git a/plugins/initctl.c b/plugins/initctl.c index 62db6189..cd4d3efa 100644 --- a/plugins/initctl.c +++ b/plugins/initctl.c @@ -77,6 +77,8 @@ static void read_initctl(void *UNUSED(arg), int fd, int UNUSED(events)) _d("Unsupported runlevel: %d", request.runlevel); break; } + } else { + _d("Unsupported cmd: %d", request.cmd); } } } diff --git a/private.h b/private.h index 740a3034..83978711 100644 --- a/private.h +++ b/private.h @@ -30,7 +30,8 @@ /* plugin.c */ void run_hooks (hook_point_t no); void run_services (void); -int load_plugins (struct ev_loop *loop, char *path); +int load_plugins (char *path); +void io_monitor (void); #endif /* FINIT_PRIVATE_H_ */ diff --git a/signal.c b/signal.c index 12eeb781..102ada36 100644 --- a/signal.c +++ b/signal.c @@ -98,13 +98,7 @@ static void shutdown_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) */ static void chld_handler(int UNUSED(sig), siginfo_t *UNUSED(info), void *UNUSED(ctx)) { - int status; - -// _d("Child %d died due to signal %d, harvesting.", info->si_pid, sig); - while (waitpid(-1, &status, WNOHANG) != 0) { - if (errno == ECHILD) - break; - } + /* Do nothing, the svc_monitor() is the designated child reaper. */ } /* diff --git a/svc.c b/svc.c index 924345a8..a72a4f85 100644 --- a/svc.c +++ b/svc.c @@ -23,6 +23,7 @@ */ #include +#include #include "finit.h" #include "helpers.h" @@ -35,7 +36,7 @@ * liveness state of all daemons that should run. */ static int svc_counter = 0; /* Number of registered services to monitor. */ static svc_t *services = NULL; /* List of registered services, in shared memory. */ -static ev_child watchers[MAX_NUM_SVC]; + /* Connect to/create shared memory area of registered servies */ static void __connect_shm(void) @@ -138,28 +139,61 @@ svc_t *svc_iterator(int restart) /** * svc_register - Register a non-backgrounding service to be monitored - * @line: A complete command line with -- separated description text + * @line: A complete command line with -- separated description text + * @username: Optional username to run service as, or %NULL to run as root + * + * Actually, the @line can optionally start with a username, denoted by + * an @ character. Like this: + * + * "service [@username] /path/to/daemon -- Description text" + * + * The [] brackets means the argument is optional. If the username is + * left out the daemon is started as root. * * Returns: * POSIX OK(0) on success, or non-zero errno exit status on failure. */ -int svc_register(char *line) +int svc_register(char *line, char *username) { int i = 0; char *cmd, *descr; - svc_t *svc = svc_new(); + svc_t *svc; - if (!svc || !line) { - return -EINVAL; + if (!line) { + _e("Invalid input argument."); + return errno = EINVAL; } descr = strstr(line, "-- "); - if (descr) { + if (descr) *descr = 0; - strlcpy(svc->descr, descr + 3, sizeof(svc->descr)); - } cmd = strtok(line, " "); + if (!cmd) { + incomplete: + _e("Incomplete service, cannot register."); + return errno = ENOENT; + } + + if (cmd[0] == '@') { /* @username */ + username = &cmd[1]; + + /* Check if valid command follows... */ + cmd = strtok(NULL, " "); + if (!cmd) + goto incomplete; + } + + svc = svc_new(); + if (!svc) { + _e("Out of memorym, cannot register service %s", cmd); + return errno = ENOMEM; + } + + if (descr) + strlcpy(svc->descr, descr + 3, sizeof(svc->descr)); + if (username) + strlcpy(svc->username, username, sizeof(svc->username)); strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); @@ -206,27 +240,14 @@ static int is_norespawn(void) fexist("/tmp/norespawn"); } -/* Used to cleanup any lingering or semi-restarting tasks before respawning. */ -static int kill_service(svc_t *svc, int signo) +void svc_monitor(void) { - char *name = basename(svc->cmd); - - _d("Sending signal %d to all processes named %s", signo, name); - kill_procname(name, signo); - - /* Don't restart immediately, some throttling necessary. - * Also it's good form to let the application terminate properly. */ - sleep(1); - - return 0; -} - -static void svc_monitor(EV_P_ ev_child *w, int UNUSED(revents)) -{ - pid_t lost = w->rpid; + pid_t lost; svc_t *svc; - ev_child_stop(EV_A_ w); + lost = waitpid(-1, NULL, WNOHANG); + if (lost < 1) + return; if (fexist(SYNC_SHUTDOWN) || lost <= 1) return; @@ -236,16 +257,20 @@ static void svc_monitor(EV_P_ ev_child *w, int UNUSED(revents)) return; for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + char *name = basename(svc->cmd); + if (lost != svc->pid) continue; + _d("Ouch, lost pid %d - %s(%d)", lost, name, svc->pid); if (sig_stopped()) { _e("Stopped, not respawning killed processes."); break; } - _d("Ouch, lost pid %d - %s(%d)", lost, svc->cmd, svc->pid); - kill_service(svc, SIGTERM); + /* Cleanup any lingering or semi-restarting tasks before respawning */ + _d("Sending SIGTERM to service group %s", name); + kill_procname(name, SIGTERM); /* Restarting lost service. */ if (svc_enabled(svc, 0)) @@ -257,36 +282,56 @@ static void svc_monitor(EV_P_ ev_child *w, int UNUSED(revents)) int svc_start(svc_t *svc) { + int respawn = svc->pid != 0; + pid_t pid; char *args[MAX_NUM_SVC_ARGS]; - int i = 0, restart = 0; - ev_child *cw = &watchers[svc->id]; /* Ignore if finit is SIGSTOP'ed */ if (is_norespawn()) return 0; - restart = svc->pid != 0; - if (!restart) print_descr("Starting ", svc->descr); + if (!respawn) + print_descr("Starting ", svc->descr); - /* Serve copy of args to process in case it modifies them. */ - while (svc->args[i][0] != 0 && i < MAX_NUM_SVC_ARGS) { - args[i] = svc->args[i]; - i++; + pid = fork(); + if (pid == 0) { + int i = 0; + int uid = getuser(svc->username); + struct sigaction sa; + + /* Reset signal handlers that were set by the parent process */ + for (i = 1; i < NSIG; i++) + DFLSIG(sa, i, 0); + + /* Set desired user */ + if (uid >= 0) + setuid(uid); + + /* Serve copy of args to process in case it modifies them. */ + for (i = 0; svc->args[i][0] != 0 && i < MAX_NUM_SVC_ARGS; i++) + args[i] = svc->args[i]; + args[i] = NULL; + + if (debug) { + int fd = open (CONSOLE, O_WRONLY | O_APPEND); + if (-1 != fd) { + dup2(STDOUT_FILENO, fd); + dup2(STDERR_FILENO, fd); + } + + _e("%starting %s ", respawn ? "Res" : "S", svc->cmd); + for (i = 0; args[i] && i < MAX_NUM_SVC_ARGS; i++) + _e("%s ", args[i]); + _e(""); + } + + execvp(svc->cmd, args); + exit(0); } - args[i] = NULL; + svc->pid = pid; - if (debug) { - _e("(re)starting %s ", svc->cmd); - for (i = 0; args[i] && i < MAX_NUM_SVC_ARGS; i++) - _e("%s ", args[i]); - _e(""); - } - - svc->pid = start_process(svc->cmd, args, debug); - ev_child_init (cw, svc_monitor, svc->pid, 0); - ev_child_start (EV_DEFAULT_ cw); - - if (!restart) print_result(0); + if (!respawn) + print_result(svc->pid > 1 ? 0 : 1); return 0; } @@ -304,7 +349,6 @@ int svc_start_by_name(char *name) int svc_stop(svc_t *svc) { int res = 1; - ev_child *cw = &watchers[svc->id]; if (!svc) { _e("Failed, no svc pointer."); @@ -319,7 +363,6 @@ int svc_stop(svc_t *svc) print_descr("Stopping ", svc->descr); _d("Sending SIGTERM to pid:%d name:'%s'", svc->pid, get_pidname(svc->pid, NULL, 0)); - ev_child_stop(ev_default_loop(0), cw); res = kill(svc->pid, SIGTERM); print_result(res); svc->pid = 0; diff --git a/svc.h b/svc.h index 940885ce..64fa5691 100644 --- a/svc.h +++ b/svc.h @@ -42,6 +42,8 @@ typedef enum { #define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ #define MAX_ARG_LEN 64 #define MAX_STR_LEN 64 +#define MAX_USER_LEN 16 +#define MAX_NUM_FDS 64 /* Max number of I/O plugins */ #define MAX_NUM_SVC 64 /* Enough? */ #define MAX_NUM_SVC_ARGS 16 @@ -55,6 +57,7 @@ typedef struct svc { char cmd[MAX_ARG_LEN]; char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN]; char descr[MAX_STR_LEN]; + char username[MAX_USER_LEN]; /* Public */ int delayed_reload; /* For external service plugin. */ plugin_svc_t *plugin; @@ -94,7 +97,7 @@ svc_t *svc_find_by_id (int id); svc_t *svc_find_by_name (char *name); svc_t *svc_iterator (int restart); -int svc_register (char *line); +int svc_register (char *line, char *username); int svc_id_by_name (char *name); svc_cmd_t svc_enabled (svc_t *svc, int dynamic); int svc_start (svc_t *svc); @@ -102,6 +105,7 @@ int svc_start_by_name (char *name); int svc_stop (svc_t *svc); int svc_reload (svc_t *svc); void svc_start_all (void); +void svc_monitor (void); #endif /* FINIT_SVC_H_ */