From d21dc2b477b283eb32984acbbcd1838e32ddba5f Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Wed, 19 Sep 2012 20:53:51 +0200 Subject: [PATCH] Westermo service starter and supervisor, inspired by daemontools finit.c: o New service and shell_service stanzas for finit.conf o Run service_startup() before /etc/finit.d/ hooks o Run service_monitor() at end of startup helpers.c: o Several new functions: - print_desc() displays a service starting up - print_result() shows if service started OK or not - start_process() is used by the services framework svc.c: Generic API for managing svc_t structures ipc.o: TIPC based framework for communicating with the service monitor. E.g., to relay (re)configuration signals from a Web/CLI front-end via finit to one or many setup daemons, and back. services.c: Service monitor framework strlcpy.c: Secure strncpy() replacement from OpenBSD under the free ISC License. Signed-off-by: Joachim Nilsson --- Makefile | 3 +- contrib/finit.conf | 11 +- finit.c | 40 ++-- finit.h | 9 +- helpers.c | 193 ++++++++++++++++++- helpers.h | 5 + ipc.c | 275 +++++++++++++++++++++++++++ ipc.h | 60 ++++++ service.c | 459 +++++++++++++++++++++++++++++++++++++++++++++ service.h | 92 +++++++++ signal.c | 21 ++- strlcpy.c | 58 ++++++ svc.c | 111 +++++++++++ svc.h | 43 +++++ 14 files changed, 1352 insertions(+), 28 deletions(-) create mode 100644 ipc.c create mode 100644 ipc.h create mode 100644 service.c create mode 100644 service.h create mode 100644 strlcpy.c create mode 100644 svc.c create mode 100644 svc.h diff --git a/Makefile b/Makefile index 0f2de3b7..51af863d 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,8 @@ ROOTDIR ?= $(shell pwd) VERSION = 1.0-pre PKG = finit-$(VERSION) EXEC = finit -OBJS = finit.o helpers.o initctl.o signal.o +OBJS = finit.o helpers.o initctl.o signal.o svc.o ipc.o service.o \ + strlcpy.o SRCS = $(OBJS:.o=.c) DEPS = $(addprefix .,$(SRCS:.c=.d)) CFLAGS += -W -Wall -Werror -Os diff --git a/contrib/finit.conf b/contrib/finit.conf index 0dbf32d8..03787cbf 100644 --- a/contrib/finit.conf +++ b/contrib/finit.conf @@ -1,3 +1,10 @@ -# finit.conf +# Simple Debian finit.conf check /dev/vda1 - +module tipc +module evdev +module loop +module psmouse +user admin +host testbed +network service networking start +service /usr/sbin/sshd -D -- OpenSSH Server diff --git a/finit.c b/finit.c index f8c7c037..49b6a2e3 100644 --- a/finit.c +++ b/finit.c @@ -31,6 +31,7 @@ #include "finit.h" #include "helpers.h" +#include "service.h" int debug = 0; char *sdown = NULL; @@ -49,7 +50,7 @@ static char *build_cmd(char *cmd, char *line, int len) if (!cmd) { cmd = malloc (strlen(line) + 1); if (!cmd) { - fprintf (stderr, "finit: No memory left for '%s'", line); + _e("No memory left for '%s'", line); return NULL; } *cmd = 0; @@ -130,7 +131,7 @@ int main(int UNUSED(args), char *argv[]) } cls(); - _d("finit " VERSION " (built " __DATE__ " " __TIME__ " by " WHOAMI ")"); + echo("finit " VERSION " (built " __DATE__ " " __TIME__ " by " WHOAMI ")"); /* * Parse configuration file @@ -194,6 +195,11 @@ int main(int UNUSED(args), char *argv[]) startx = build_cmd(NULL, x, CMD_SIZE); continue; } + if (MATCH_CMD(line, "service ", x)) { + if (service_register (x)) + _e("Failed, too many services to monitor.\n"); + continue; + } } fclose(fp); } @@ -283,6 +289,8 @@ int main(int UNUSED(args), char *argv[]) #ifdef TOUCH_ETC_NETWORK_RUN_IFSTATE touch("/etc/network/run/ifstate"); +#else + remove("/etc/network/run/ifstate"); #endif /* Set initial hostname. */ @@ -325,8 +333,20 @@ int main(int UNUSED(args), char *argv[]) */ mkdir("/tmp/.X11-unix", 01777); mkdir("/tmp/.ICE-unix", 01777); + mkdir("/var/run/sshd", 01755); umask(022); +#ifdef USE_MESSAGE_BUS + _d("Starting D-Bus ..."); + mkdir("/var/run/dbus", 0755); + mkdir("/var/lock/subsys/messagebus", 0755); + system("dbus-uuidgen --ensure"); + system("su -c \"dbus-daemon --system\" messagebus"); +#endif + + _d("Starting services from %s", FINIT_CONF); + service_startup(); + #ifdef LISTEN_INITCTL listen_initctl(); #endif @@ -366,14 +386,6 @@ int main(int UNUSED(args), char *argv[]) /* ConsoleKit needs this */ setenv("DISPLAY", ":0", 1); -#ifdef USE_MESSAGE_BUS - _d("Starting D-Bus ..."); - mkdir("/var/run/dbus", 0755); - mkdir("/var/lock/subsys/messagebus", 0755); - system("dbus-uuidgen --ensure"); - system("su -c \"dbus-daemon --system\" messagebus"); -#endif - /* Run startup scripts in /etc/finit.d/, if any. */ run_parts(FINIT_RCSD); @@ -403,13 +415,7 @@ int main(int UNUSED(args), char *argv[]) exit(0); } - /* We're the grim reaper of innocent orphaned children ... */ - while (1) { - sigset_t nmask; - - sigemptyset(&nmask); - pselect(0, NULL, NULL, NULL, NULL, &nmask); - } + service_monitor(); return 0; } diff --git a/finit.h b/finit.h index 7bcc61f8..200ddf27 100644 --- a/finit.h +++ b/finit.h @@ -131,19 +131,26 @@ #define MATCH_CMD(l, c, x) \ (!strncmp(l, c, strlen(c)) && (x = (l) + strlen(c))) -#define _d(fmt, args...) do { if (debug) { fprintf(stderr, "finit:%s() - " fmt "\n", __func__, ##args); } } while (0) +#define echo(fmt, args...) do { fprintf(stderr, fmt "\n", ##args); } while (0) +#define _d(fmt, args...) do { if (debug) { fprintf(stderr, "finit:%s() - " fmt "\n", __func__, ##args); } } while (0) +#define _e(fmt, args...) do { fprintf(stderr, "finit:%s() - " fmt "\n", __func__, ##args); } while (0) +#define _pe(fmt, args...) do { fprintf(stderr, "finit:%s() - " fmt ". Error %d: %s\n", __func__, ##args, errno, strerror(errno)); } while (0) extern int debug; extern char *sdown; /* signal.c */ void do_shutdown (int sig); +int sig_stopped (void); void sig_init (void); void sig_setup (void); /* initctl.c */ void listen_initctl (void); +/* strlcpy.c */ +size_t strlcpy(char *dst, const char *src, size_t siz); + #endif /* FINIT_H_ */ /** diff --git a/helpers.c b/helpers.c index dcd3e464..6ea3df00 100644 --- a/helpers.c +++ b/helpers.c @@ -22,6 +22,7 @@ * THE SOFTWARE. */ +#include /* isdigit() */ #include #include #include @@ -78,7 +79,7 @@ void ifconfig(char *name, char *inet, char *mask, int up) return; memset(&ifr, 0, sizeof (ifr)); - strncpy(ifr.ifr_name, name, IFNAMSIZ); + strlcpy(ifr.ifr_name, name, IFNAMSIZ); a->sin_family = AF_INET; if (up) { @@ -126,6 +127,95 @@ void copyfile(char *src, char *dst, int size) } } +static int print_uptime(void) +{ +#if defined(CONFIG_PRINTK_TIME) + FILE * uptimefile; + float num1, num2; + char uptime_str[30]; + + if((uptimefile = fopen("/proc/uptime", "r")) == NULL) + return 1; + + fgets(uptime_str, 20, uptimefile); + fclose(uptimefile); + + sscanf(uptime_str, "%f %f", &num1, &num2); + sprintf(uptime_str, "[ %.6f ]", num1); + + write(STDERR_FILENO, uptime_str, strlen(uptime_str)); +#endif + return 0; +} + +void print_descr(char *action, char *descr) +{ + const char home[] = "\r\e[K"; + const char dots[] = " ....................................................................."; + + write(STDERR_FILENO, home, strlen(home)); + + print_uptime(); + + write(STDERR_FILENO, action, strlen(action)); + write(STDERR_FILENO, descr, strlen(descr)); + write(STDERR_FILENO, dots, 60 - strlen(descr) - strlen(action)); /* pad with dots. */ +} + +int print_result(int fail) +{ + if (fail) + fprintf(stderr, " \e[7m[FAIL]\e[0m\n"); + else + fprintf(stderr, " \e[1m[ OK ]\e[0m\n"); + + return fail; +} + +int start_process(char *cmd, char *args[], int console) +{ + pid_t pid; + sigset_t nmask, omask; + + if (sig_stopped()) + return 0; + + /* Block SIGCHLD while forking. */ + sigemptyset(&nmask); + sigaddset(&nmask, SIGCHLD); + sigprocmask(SIG_BLOCK, &nmask, &omask); + pid = fork(); + sigprocmask(SIG_SETMASK, &omask, NULL); + if (!pid) { + int i; + struct sigaction act; + + /* Reset signal handlers that were set by the parent process */ + sigemptyset(&act.sa_mask); + act.sa_handler = SIG_DFL; + + sigemptyset(&nmask); + sigaddset(&nmask, SIGCHLD); + sigprocmask(SIG_UNBLOCK, &nmask, NULL); + + for (i = 1; i < NSIG; i++) + sigaction(i, &act, NULL); + + if (console) { + int fd = open (CONSOLE, O_WRONLY | O_APPEND); + if (-1 != fd) { + dup2(STDOUT_FILENO, fd); + dup2(STDERR_FILENO, fd); + } + } + + execv(cmd, args); + exit(!console ? print_result(0) : 0); + } + + return pid; +} + #ifdef BUILTIN_RUNPARTS #define NUM_SCRIPTS 128 /* ought to be enough for anyone */ @@ -251,7 +341,106 @@ void set_procname(char *args[], char *name) prctl(PR_SET_NAME, name, 0, 0, 0); memset(args[0], 0, len); - strncpy(args[0], name, len); + strlcpy(args[0], name, len); +} + +/** + * get_pidname - Find name of a process + * @pid: PID of process to find. + * @name: Pointer to buffer where to return process name, may be %NULL + * @len: Length in bytes of @name buffer. + * + * If @name is %NULL, or @len is zero the function will return + * a static string. This may be useful to one-off calls. + * + * Returns: + * %NULL on error, otherwise a va + */ +char *get_pidname(pid_t pid, char *name, size_t len) +{ + int ret = 1; + FILE *fp; + char path[32]; + static char line[64]; + + if (!name || len == 0) + name = line; + + snprintf(path, sizeof(path), "/proc/%d/status", pid); + if ((fp = fopen(path, "r")) != NULL) { + if (fgets(line, sizeof (line), fp)) { + char *pname = line + 6; /* Skip first part of line --> "Name:\t" */ + + chomp(pname); + strlcpy(name, pname, len); + ret = 0; /* Found it! */ + } + + fclose(fp); + } + + if (ret) + return NULL; + + return name; +} + +/** + * kill_procname - Send a signal to a process group by name. + * @name: Name of process to send signal to. + * @signo: Signal to send. + * + * Send a signal to a running process (group). This function searches + * for a given process @name and sends a signal, @signo, to each + * process ID matching that @name. + * + * Returns: + * Number of signals sent, i.e. number of processes who have received the signal. + */ +int kill_procname (const char *name, int signo) +{ + int result = 0; + char path[32], line[64]; + FILE *fp; + DIR *dir; + struct dirent *entry; + + dir = opendir("/proc"); + if (!dir || !name) { + errno = EINVAL; + return 0; + } + + while ((entry = readdir (dir)) != NULL) { + /* Skip non-process entries in /proc */ + if (!isdigit (*entry->d_name)) + continue; + + snprintf (path, sizeof(path), "/proc/%s/status", entry->d_name); + /* Skip non-readable files (protected?) */ + if ((fp = fopen (path, "r")) == NULL) + continue; + + if (fgets (line, sizeof (line), fp)) { + char *pname = line + 6; /* Skip first part of line --> "Name:\t" */ + + if (strncmp(pname, name, strlen(pname) - 1) == 0) { + int error = errno; + + if (kill(atoi(entry->d_name), signo)) { + ERROR("Failed signalling(%d) %s: %s!", signo, name, strerror(error)); + } else { + result++; /* Track number of processes we deliver the signal to. */ + } + } + } + + fclose(fp); + } + + closedir(dir); + + return result; } void set_hostname(char *hostname) diff --git a/helpers.h b/helpers.h index 466ff250..07341818 100644 --- a/helpers.h +++ b/helpers.h @@ -28,11 +28,16 @@ int makepath (char *); void ifconfig (char *, char *, char *, int); void copyfile (char *, char *, int); +void print_descr (char *action, char *descr); +int print_result (int fail); +int start_process (char *cmd, char *args[], int console); void cls (void); void chomp (char *); int getuser (char *); int getgroup (char *); void set_procname (char *args[], char *name); +char *get_pidname (pid_t pid, char *name, size_t len); +int kill_procname (const char *name, int signo); void set_hostname (char *hostname); #ifdef BUILTIN_RUNPARTS diff --git a/ipc.c b/ipc.c new file mode 100644 index 00000000..13c58f55 --- /dev/null +++ b/ipc.c @@ -0,0 +1,275 @@ +/* Generic IPC class based on TIPC as message bus + * + * Copyright (c) 2008-2012 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +//#include +#include +#include +#include +#include + +#include "finit.h" +#include "ipc.h" + +#define IPC_MAP_SOCKADDR(a,t,p) \ + { \ + (a).family = AF_TIPC; \ + (a).addrtype = TIPC_ADDR_MCAST; \ + (a).scope = TIPC_CLUSTER_SCOPE; \ + (a).addr.nameseq.type = (t); \ + (a).addr.nameseq.lower = (p); \ + (a).addr.nameseq.upper = (p); \ + } + +/** + * ipc_init - Create a TIPC socket for receiving or sending events. + * @type: The msg bus. + * @port: The msg port. + * @conn_type: SERVER or CLIENT connection. + * + * Returns: + * The resulting socket on success, -1 on error. + */ +int ipc_init(int type, int port, int conn_type) +{ + int sd = -1; + int restart_count = 0; + + restart: + while (-1 == sd) { + struct sockaddr_tipc tipc_addr; + + if (restart_count++ > 5) { + _e("Failed too many times, aborting."); + restart_count = 0; + + return -1; + } + + sd = socket(AF_TIPC, SOCK_RDM, 0); + if (-1 == sd) { + _pe("Failed creating TIPC socket"); + sleep(1); + goto restart; + } + + /* In case the user wants a server socket. */ + IPC_MAP_SOCKADDR(tipc_addr, type, port); + /* bind to the socket if it will work as a server part */ + if (conn_type) { + if (bind + (sd, (struct sockaddr *)&tipc_addr, + sizeof(tipc_addr))) { + _pe("TIPC Port {%u,%u,%u} could not be created", + tipc_addr.addr.nameseq.type, + tipc_addr.addr.nameseq.lower, + tipc_addr.addr.nameseq.upper); + close(sd); + return -1; + } + } + + /* Clear counter */ + restart_count = 0; + } + + /* OK, give caller bound socket */ + return sd; +} + +/** + * ipc_send_event - Send a TIPC event. + * @sd: TIPC descriptor + * @event: Data buffer to send. + * @sz: Length of @event data. + * @bus: ID of tipc bus + * + * Returns: + * Posix OK(0) on OK or 1 on error. + */ +int ipc_send_event(int sd, void *event, size_t sz, int bus) +{ + int retries = 0; + struct sockaddr_tipc tipc_addr; + + if (!event) { + errno = EINVAL; + return 1; + } + + memset(&tipc_addr, 0, sizeof(tipc_addr)); + tipc_addr.family = AF_TIPC; + tipc_addr.addrtype = TIPC_ADDR_MCAST; + tipc_addr.addr.nameseq.type = SINGLE_TIPC_NODE; + tipc_addr.addr.nameseq.lower = bus; + tipc_addr.addr.nameseq.upper = bus; + + while (-1 == sendto(sd, (char *)event, sz, 0, (struct sockaddr *)&tipc_addr, (socklen_t) sizeof(tipc_addr))) { + switch (errno) { + case EINTR: + /* Signal received, resume syscall. */ + continue; + + case ENOTSOCK: + case EBADF: + if (retries++ > 1) { + _e("Too many errors while trying to send."); + return 1; + } + continue; + + default: + return 1; + } + } + + return 0; +} + +/** + * ipc_receive_event - Poll or wait for a TIPC event. + * @sd: TIPC descriptor. + * @event: Return buffer for the returned link event. + * @sz: Size of return buffer. + * @timeout: Set to 0 to block, or >0 to have a timeout in milliseconds. + * + * Returns: + * Zero (0) on OK or 1 on error. + */ +int ipc_receive_event(int sd, void *event, size_t sz, int timeout) +{ + struct sockaddr_tipc tipc_addr; + socklen_t alen = sizeof(struct sockaddr_tipc); + + if (!event) { + errno = EINVAL; + return 1; + } + + /* The poll() syscall handles timeout==0 differently */ + while (timeout > 0) { + int ret = 0; + struct pollfd fd = { sd, POLLIN | POLLPRI, 0 }; + + ret = poll(&fd, 1, timeout); + if (0 == ret) { + /* Timeout */ + errno = ETIMEDOUT; + + return 1; + } + if (-1 == ret) { + if (EINTR == errno) { + /* Unlike the below call to recvfrom() we return here, + * because a signal received here could be intended. This + * only affects those tasks calling us with a timeout > 0 + * and they usually know about this, and exploit it. + * E.g. by sending kill() to the pthread in charge of link + * propagation within a process. One such user is + * RSTP. --Jocke 2007-10-15 */ + _d("poll() received signal..."); + errno = EINTR; + return 1; + } + + _pe("poll() failed."); + return 1; + } + + /* Check returned events on our TIPC socket. */ + if (fd.revents & (POLLIN | POLLPRI)) { + /* OK reply, activity on sd */ + break; + } + + /* No input, only error. */ + return 1; + } + + while (-1 == recvfrom(sd, event, sz, 0, (struct sockaddr *)&tipc_addr, &alen)) { + switch (errno) { + case EINTR: + /* Signal received, resume syscall. */ + continue; + + default: + _pe("Failed reading TIPC event"); + return 1; + } + } + + return 0; +} + +/** + * ipc_wait_event - "Robust" IPC receive, waits with timeout and signal resistance before giving up. + * @sd: TIPC descriptor. + * @event: Event type to wait for. + * @timeout: Set to 0 to block, or >0 to have a timeout in seconds. + * + * Returns: + * Zero (0) on OK or 1 on timeout. + */ +int ipc_wait_event(int sd, int event, int timeout) +{ + generic_event_t ev; + struct timeval start, now, diff; + + /* Poll for activation acknowledgement */ + gettimeofday(&start, NULL); + do { + if (ipc_receive_event(sd, &ev, sizeof(ev), timeout * 1000)) { + /* All syscalls may be interrupted by signal, in which case + * errno is set to EINTR */ + if (errno == EINTR) { + _d("GOT SIGNAL"); + continue; + } + if (errno == ETIMEDOUT) { + _d("timeout"); + return 1; + } + + _d("Failed with error %d", errno); + } + + _d("Received event 0x%lx, expected 0x%x", ev.mtype, event); + + gettimeofday(&now, NULL); + timersub(&now, &start, &diff); + if (diff.tv_sec >= timeout) + return 1; + } while (ev.mtype != event); + + return 0; +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/ipc.h b/ipc.h new file mode 100644 index 00000000..a4e192d6 --- /dev/null +++ b/ipc.h @@ -0,0 +1,60 @@ +/* Generic IPC class based on TIPC as message bus + * + * Copyright (c) 2008-2012 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_IPC_H_ +#define FINIT_IPC_H_ + +#define SINGLE_TIPC_NODE 64 +#define PRIMARY_MESSAGE_BUS 100 +#define HWSETUP_MESSAGE_BUS 101 +#define FINIT_MESSAGE_BUS 102 + +#define SERVER_CONNECTION 1 +#define CLIENT_CONNECTION 0 + +/** + * generic_event_t - Generic message type. + * @mtype: Message type. + * + * This is the base message type that is used on the system primary message bus. All + * messages must start with a long @mtype that describes the type, any extended + * messages must add its extra fields after @mtype. + */ +typedef struct { + long mtype; + long arg; /* Optional argument, free use. */ +} generic_event_t; + +int ipc_init (int type, int port, int conn_type); +int ipc_receive_event (int sd, void *event, size_t sz, int timeout); +int ipc_wait_event (int sd, int event, int timeout); + +#endif /* FINIT_IPC_H_ */ + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/service.c b/service.c new file mode 100644 index 00000000..f93664cc --- /dev/null +++ b/service.c @@ -0,0 +1,459 @@ +/* Service starter and supervisor, inspired by daemontools + * + * Copyright (c) 2008-2010 Claudio Matsuoka + * Copyright (c) 2008-2012 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* mknod() */ +#include /* wait() */ + +#include "finit.h" +#include "ipc.h" +#include "svc.h" +#include "service.h" +#include "helpers.h" + +static int dynamic_event_arg = 0; /* XXX: Fragile construct, refactor! --Jocke */ +static int sd = -1; + +/* Returns the event type unless it's a reconfiguration event. */ +static int is_dynamic_event(generic_event_t *event) +{ + if (!event) { + _e("Invalid event pointer received."); + return 0; + } + + /* XXX: Hook point for plugins. */ + + /* XXX: Fragile construct, refactor! --Jocke */ + dynamic_event_arg = (int)event->arg; + + return (int)event->mtype; +} + +/* Return value: + * 0 - Disabled + * 1 - Enabled + * 2 - Enabled, needs restart + */ +int service_enabled(svc_t *svc, int dynamic) +{ + int run; + + if (!svc->reg.cb) { + /* Unknown service, default to enabled. */ + return 1; + } + + run = svc->reg.cb(svc, dynamic); + /* Sanity check callback return value */ + if (run < 0 || run > 2) { + _d("Callback for %s returned invalid value %d, disabling svc.", svc->reg.path, run); + run = 0; + } + + return run; +} + + +/* This is a map of all known services that we care for. All others, found in + * the system finit.conf file, use the default handler. */ +static svc_map_t known_services[] = { + { NULL, NULL, 0, 0, 0}, +}; + +/* Used to cleanup any lingering or semi-restarting tasks before respawning. */ +static int kill_service(svc_t *svc, int signo) +{ + 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; +} + +/** + * service_register - Register a non-backgrounding service to be monitored + * @line: A complete command line with -- separated description text + * + * Returns: + * POSIX OK(0) on success, or non-zero errno exit status on failure. + */ +int service_register(char *line) +{ + int i = 0; + char *cmd, *descr; + svc_t *svc = svc_new_service(); + + if (!svc || !line) { + return -EINVAL; + } + + descr = strstr(line, "-- "); + if (descr) { + *descr = 0; + strlcpy(svc->descr, descr + 3, sizeof(svc->descr)); + } + + cmd = strtok(line, " "); + strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); + strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); + + while ((cmd = strtok(NULL, " "))) + strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); + + svc->args[i][0] = 0; + + /* If this is a known service, then register callbacks and other data. */ + for (i = 0; known_services[i].path; i++) { + svc_map_t *curr = &known_services[i]; + + if (!strncmp(curr->path, svc->cmd, strlen(svc->cmd))) { + memcpy(&svc->reg, curr, sizeof(svc->reg)); + } + } + + return 0; +} + +#if 0 /* May be usefull someday. */ +static int poll_for_pidfile(char *cmd, const char *path) +{ + int pid = 0; + int tries = 0; + + /* Timeout = 100 * 50ms = 5s */ + while (-1 == access(path, F_OK) && tries++ < 100) { + /* Wait 50ms between retries */ + usleep(50000); + } + + if (-1 == access(path, F_OK)) { + _e("Timeout! No PID found for %s, pidfile %s does not exist?", cmd, path); + pid = 0; + } else { + pid = pidfile_read_pid(path); + } + + return pid; +} +#endif +/* See the README in this package for details on how services are started, stopped + * and how the whole process ties together with the rest of the system. + * --Jocke 2009-03-18 */ +/* Remember: service_enabled() must be called before calling service_start() */ +int service_start(svc_t *svc) +{ + char *args[MAX_NUM_SVC_ARGS]; + int i = 0, restart = 0; + + /* Ignore if finit is SIGSTOP'ed */ + if (sig_stopped()) + return 0; + + restart = svc->pid != 0; + if (!restart) + print_descr("Starting ", svc->descr); + while (svc->args[i][0] != 0 && i < MAX_NUM_SVC_ARGS) { + args[i] = svc->args[i]; + i++; + } + args[i] = NULL; + + 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); + + if (!restart) + print_result(0); + + return 0; +} + +/* Tries to find the matching service and start it. */ +int service_start_by_name(char *name) +{ + svc_t *svc = svc_find_by_name (name); + + if (svc && service_enabled(svc, 0)) { + return service_start(svc); + } + + return 1; +} + +/* See the README in this package for details on how services are started, reloaded + * (config), stopped and how the whole process ties together with the rest of the + * system. --Jocke 2009-03-18 */ +static int stop_service(svc_t *svc) +{ + int res = 1; + + if (!svc) { + _e("Failed, no svc pointer."); + return 1; + } + + if (svc->pid <= 1) { + _d("bad pid:%d for %s, SIGTERM", svc->pid, svc->descr); + svc->pid = 0; + return 1; + } + + print_descr("Stopping ", svc->descr); + _d("Sending SIGTERM to pid:%d name:'%s'", svc->pid, get_pidname(svc->pid, NULL, 0)); + res = kill(svc->pid, SIGTERM); + print_result(res); + svc->pid = 0; + + return res; +} + +/* See the README in this package for details on how services are started, reloaded + * (config), stopped and how the whole process ties together with the rest of the + * system. --Jocke 2009-03-18 */ +static int reload_service(svc_t *svc) +{ + int res; + + if (!svc) { + _e("Failed, no svc pointer."); + return 1; + } + + if (svc->pid <= 1) { + _d("Bad PID %d for %s", svc->pid, svc->cmd); + svc->pid = 0; + return 1; + } + + _d("Sending SIGHUP to PID %d", svc->pid); + res = kill(svc->pid, SIGHUP); + + return res; +} + +static int wait_for_event(generic_event_t *event, int timeout) +{ + if (-1 == sd) { + sd = ipc_init(SINGLE_TIPC_NODE, FINIT_MESSAGE_BUS, SERVER_CONNECTION); + if (-1 == sd) { + return 1; + } + } + + /* ipc_receive_event() might be interrupted by signal, errno is then set to EINTR */ + return ipc_receive_event(sd, event, sizeof(*event), timeout); +} + +/* Returns 1 if pid is alive (/proc/pid exists) else 0 */ +static int pid_alive(unsigned int pid) +{ + char name[24]; /* Enough for max pid_t */ + + snprintf(name, sizeof(name), "/proc/%d", pid); + return !access(name, F_OK); +} + +void service_startup(void) +{ + int ret; + svc_t *svc; + + /* Start finit listen socket before start any services, this to + * be sure we don't miss any messages from for example linkd */ + if (-1 == sd) { + sd = ipc_init(SINGLE_TIPC_NODE, FINIT_MESSAGE_BUS, SERVER_CONNECTION); + if (-1 == sd) { + return; + } + } + + + /* Start 'em */ + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + /* Start, when pid==0 we start regardless of ret. */ + ret = service_enabled(svc, 0); + if (ret == 1 || (ret && svc->pid == 0)) + service_start(svc); + else if (ret == 2) + reload_service(svc); + } + + /* XXX: Hook point for plugins. */ +} + +static int is_norespawn(void) +{ + return sig_stopped() || + fexist("/mnt/norespawn") || + fexist("/tmp/norespawn"); +} + +/* See the README in this package for details on how services are started, stopped + * and how the whole process ties together with the rest of the system. + * --Jocke 2009-03-18 */ +void reiterate(generic_event_t *event) +{ + int ret; + int any_stopped = 0; + svc_t *svc; + + _d("event 0x%x", (unsigned int)event->mtype); + /* Reset state variables before each new run. */ + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + svc->reload = 0; + svc->private = 0; + } + + /* Stop services */ + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + /* Only do configuration changes here, e.g. stopping disabled services, etc. */ + if (is_dynamic_event(event) && !svc->reg.dyn_workaround) // XXX: Ugly workaround + continue; + + svc->reload = 0; + if (svc->pid) { + ret = service_enabled(svc, svc->reg.dyn_workaround ? is_dynamic_event(event) : 0); + if (2 == ret) { + _d("Scheduling %s[%d] for delayed reload.", svc->cmd, svc->pid); + svc->reload = 1; + } else if (0 == ret) { + _d("Attempting to stop %s[%d]", svc->cmd, svc->pid); + if (svc->pid && !stop_service(svc)) { + any_stopped++; + } + } + } + } + + /* Wait for any stopped processes before notifying hwsetup. */ + if (any_stopped) + sleep(1); + + /* XXX: Hook point for plugins. */ + + /* (Re)Start services */ + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + /* Filter out services not interested in dynamic changes. */ + if (is_dynamic_event(event) && !(svc->reg.dyn_reload || svc->reg.vlans_changed_reload || svc->reg.dyn_workaround)) + continue; + + /* Check for stale PID! Happens if, e.g., SIGCHLD signal is lost */ + if (svc->pid && !pid_alive(svc->pid)) { + _d("PID:%d (%s) does not seem to be running, scheduling restart.", svc->pid, svc->cmd); + svc->pid = 0; + svc->reload = 0; + goto restart; + } + + if (svc->reload) { + svc->reload = 0; + reload_service(svc); + } else { + int run; + restart: + run = service_enabled(svc, is_dynamic_event(event)); + if (run) { + if (!svc->pid && !is_norespawn()) { + service_start(svc); + } else if (2 == run) { + reload_service(svc); + } + } + } + } + + /* Notify configuration frontends that we're done. */ + if (!is_dynamic_event(event)) { + /* XXX: Hook point for plugins. */ + } +} + +void service_monitor(void) +{ + int status; + pid_t lost; + svc_t *svc; + generic_event_t event; + + while (1) { + /* Poll, timeout 1000 ms, for message from the system, or SIGCHLD + * when children dies. */ + if (!wait_for_event(&event, 1000)) { + _d("Received event 0x%lx", event.mtype); + /* XXX: Hook point for plugins. */ + } + + lost = waitpid(-1, &status, WNOHANG); + if (fexist(SYNC_SHUTDOWN) || lost <= 1) + continue; + + /* Power user at the console, don't respawn tasks. */ + if (is_norespawn()) + return; + + /* Monitor 'em */ + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (lost != svc->pid) + continue; + + 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); + + /* Restarting lost service. */ + if (service_enabled(svc, 0)) + service_start(svc); + } + } +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/service.h b/service.h new file mode 100644 index 00000000..76a9c980 --- /dev/null +++ b/service.h @@ -0,0 +1,92 @@ +/* Service starter and supervisor, inspired by daemontools + * + * Copyright (c) 2008-2010 Claudio Matsuoka + * Copyright (c) 2008-2012 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_SERVICES_H_ +#define FINIT_SERVICES_H_ + +#include +#include /* shmat() */ +#include /* pid_t */ + +#include "finit.h" + +#define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ +#define MAX_NUM_SVC 64 /* Enough? */ +#define MAX_NUM_SVC_ARGS 16 + +/* Default enable for all services, can be stopped by means + * of issuing an initctl call. E.g. + * initctl service */ +typedef struct svc { + char cmd[64]; + char args[MAX_NUM_SVC_ARGS][64]; + char descr[64]; + pid_t pid; + int reload; + int private; /* For svc callbacks to use freely, possibly to store "states". */ + /* Set for known services, otherwise "empty". */ + struct svc_map { + char *path; /* E.g., "/sbin/snmpd" */ + int (*cb)(struct svc *, int); /* Service callback, e.g. is_snmpd_enabled() */ + int dyn_reload; /* Reload on new dynamic event. */ + int dyn_workaround; /* Ugly/Temporary workaround for dyn-stop/start of serialoverip. */ + int vlans_changed_reload; /* Reload when VLANs change. */ + } reg; +} svc_t; + +typedef struct svc_map svc_map_t; + +/* Put services array in shm */ +static inline svc_t *finit_connect_shm(void) +{ + const long ID = FINIT_SHM_ID; + static void *ptr = (void *)-1; + + if ((void *)-1 == ptr) { + ptr = shmat (shmget (ID, sizeof(svc_t) * MAX_NUM_SVC, 0600 | IPC_CREAT), NULL, 0); + if ((void *)-1 == ptr) { + _d("Failed allocating shared memory, error %d: %s", errno, strerror (errno)); + return NULL; + } + } + + return (svc_t *)ptr; +} + +int service_register (char *line); +int service_start (svc_t *svc); +int service_start_by_name (char *name); +int service_enabled (svc_t *svc, int dynamic); +void service_startup (void); +void service_monitor (void); + +#endif /* FINIT_SERVICES_H_ */ + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/signal.c b/signal.c index c12ada7d..721dbcb8 100644 --- a/signal.c +++ b/signal.c @@ -98,7 +98,8 @@ void do_shutdown (int sig) */ static void shutdown_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) { - _d("Rebooting on signal %d from pid %d with code %d", sig, info->si_pid, info->si_code); + _d("Rebooting on signal %d from %s (PID %d) with code %d", + sig, get_pidname(info->si_pid, NULL, 0), info->si_pid, info->si_code); do_shutdown(sig); } @@ -110,8 +111,8 @@ static void chld_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) { int status; - /* XXX: Add (procname) to debug output, add get_procname(pid) to helpers.c */ - _d("Child died, harvesting. Signal %d from pid %d code %d", sig, info->si_pid, info->si_code); + _d("Child %s died, harvesting. Signal %d from pid %d code %d", + get_pidname(info->si_pid, NULL, 0), sig, info->si_pid, info->si_code); while (waitpid(-1, &status, WNOHANG) != 0) { if (errno == ECHILD) break; @@ -123,17 +124,27 @@ static void chld_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) */ static void sigstop_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) { - _d("Received SIGSTOP(%d) from pid %d code %d", sig, info->si_pid, info->si_code); + _d("Received SIGSTOP(%d) from %s (PID %d) code %d", + sig, get_pidname(info->si_pid, NULL, 0), info->si_pid, info->si_code); touch(SYNC_STOPPED); stopped ++; } static void sigcont_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) { - _d("Received SIGCONT(%d) from pid %d code %d", sig, info->si_pid, info->si_code); + _d("Received SIGCONT(%d) from %s (PID %d) code %d", + sig, get_pidname(info->si_pid, NULL, 0), info->si_pid, info->si_code); stopped = 0; remove(SYNC_STOPPED); } +/* + * Is SIGSTOP asserted? + */ +int sig_stopped(void) +{ + return stopped; +} + /* * Inital signal setup - ignore everything until we're capable of responding */ diff --git a/strlcpy.c b/strlcpy.c new file mode 100644 index 00000000..dc36ad73 --- /dev/null +++ b/strlcpy.c @@ -0,0 +1,58 @@ +/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +/** + * Local Variables: + * version-control: t + * c-file-style: "bsd" + * End: + */ diff --git a/svc.c b/svc.c new file mode 100644 index 00000000..85ed183e --- /dev/null +++ b/svc.c @@ -0,0 +1,111 @@ +/* Generic API for managing svc_t structures + * + * Copyright (c) 2008-2010 Claudio Matsuoka + * Copyright (c) 2008-2012 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "finit.h" +#include "svc.h" + +/* The registered services are kept in shared memory for easy read-only access + * by 3rd party APIs. Mostly because of the ability to, from the outside, query + * 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. */ + +/* Connect to/create shared memory area of registered servies */ +static void __connect_shm(void) +{ + if (!services) { + services = finit_connect_shm(); + if (!services) { + /* This should never happen, but if it does we're probably + * knee-deep in more serious problems already... */ + _e("Failed setting up shared memory for service monitor.\n"); + abort(); + } + } +} + +svc_t *svc_new_service (void) +{ + svc_t *svc = NULL; + + __connect_shm(); + if (svc_counter < MAX_NUM_SVC) { + svc = &services[svc_counter++]; + memset(svc, 0, sizeof(*svc)); + } + + return svc; +} + +svc_t *svc_find_by_id (int id) +{ + __connect_shm(); + if (id >= svc_counter) + return NULL; + + return &services[id]; +} + +svc_t *svc_find_by_name (char *name) +{ + int i; + + __connect_shm(); + for (i = 0; i < svc_counter; i++) { + svc_t *svc = &services[i]; + + if (!strncmp(name, svc->cmd, strlen(svc->cmd))) { + _d("Found a matching svc for %s", name); + return svc; + } + } + + return NULL; +} + +/* Iterates over all registered services. Get first svc_t by setting @restart */ +svc_t *svc_iterator (int restart) +{ + static int id = 0; + + __connect_shm(); + if (restart) + id = 0; + + if (id >= svc_counter) + return NULL; + + return &services[id++]; +} + +/** + * Local Variables: + * compile-command: "gcc -g -DUNITTEST -o unittest svc.c" + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/svc.h b/svc.h new file mode 100644 index 00000000..ff1599d3 --- /dev/null +++ b/svc.h @@ -0,0 +1,43 @@ +/* Generic API for managing svc_t structures + * + * Copyright (c) 2008-2010 Claudio Matsuoka + * Copyright (c) 2008-2012 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_SVC_H__ +#define FINIT_SVC_H__ + +#include "service.h" + +svc_t *svc_new_service (void); +svc_t *svc_find_by_id (int id); +svc_t *svc_find_by_name (char *name); +svc_t *svc_iterator (int restart); + +#endif /* FINIT_SVC_H__ */ + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */