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 <troglobit@gmail.com>
This commit is contained in:
Joachim Nilsson committed 2012-09-20 01:16:00 +02:00
1 parent c0356b5c7a
commit d21dc2b477
14 files changed
+1352 -28

No files matched your search

+2 -1
View File
@@ -28,7 +28,8 @@ ROOTDIR ?= $(shell pwd)
VERSION = 1.0-pre VERSION = 1.0-pre
PKG = finit-$(VERSION) PKG = finit-$(VERSION)
EXEC = finit 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) SRCS = $(OBJS:.o=.c)
DEPS = $(addprefix .,$(SRCS:.c=.d)) DEPS = $(addprefix .,$(SRCS:.c=.d))
CFLAGS += -W -Wall -Werror -Os CFLAGS += -W -Wall -Werror -Os
+9 -2
View File
@@ -1,3 +1,10 @@
# finit.conf # Simple Debian finit.conf
check /dev/vda1 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
+23 -17
View File
@@ -31,6 +31,7 @@
#include "finit.h" #include "finit.h"
#include "helpers.h" #include "helpers.h"
#include "service.h"
int debug = 0; int debug = 0;
char *sdown = NULL; char *sdown = NULL;
@@ -49,7 +50,7 @@ static char *build_cmd(char *cmd, char *line, int len)
if (!cmd) { if (!cmd) {
cmd = malloc (strlen(line) + 1); cmd = malloc (strlen(line) + 1);
if (!cmd) { if (!cmd) {
fprintf (stderr, "finit: No memory left for '%s'", line); _e("No memory left for '%s'", line);
return NULL; return NULL;
} }
*cmd = 0; *cmd = 0;
@@ -130,7 +131,7 @@ int main(int UNUSED(args), char *argv[])
} }
cls(); cls();
_d("finit " VERSION " (built " __DATE__ " " __TIME__ " by " WHOAMI ")"); echo("finit " VERSION " (built " __DATE__ " " __TIME__ " by " WHOAMI ")");
/* /*
* Parse configuration file * Parse configuration file
@@ -194,6 +195,11 @@ int main(int UNUSED(args), char *argv[])
startx = build_cmd(NULL, x, CMD_SIZE); startx = build_cmd(NULL, x, CMD_SIZE);
continue; continue;
} }
if (MATCH_CMD(line, "service ", x)) {
if (service_register (x))
_e("Failed, too many services to monitor.\n");
continue;
}
} }
fclose(fp); fclose(fp);
} }
@@ -283,6 +289,8 @@ int main(int UNUSED(args), char *argv[])
#ifdef TOUCH_ETC_NETWORK_RUN_IFSTATE #ifdef TOUCH_ETC_NETWORK_RUN_IFSTATE
touch("/etc/network/run/ifstate"); touch("/etc/network/run/ifstate");
#else
remove("/etc/network/run/ifstate");
#endif #endif
/* Set initial hostname. */ /* Set initial hostname. */
@@ -325,8 +333,20 @@ int main(int UNUSED(args), char *argv[])
*/ */
mkdir("/tmp/.X11-unix", 01777); mkdir("/tmp/.X11-unix", 01777);
mkdir("/tmp/.ICE-unix", 01777); mkdir("/tmp/.ICE-unix", 01777);
mkdir("/var/run/sshd", 01755);
umask(022); 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 #ifdef LISTEN_INITCTL
listen_initctl(); listen_initctl();
#endif #endif
@@ -366,14 +386,6 @@ int main(int UNUSED(args), char *argv[])
/* ConsoleKit needs this */ /* ConsoleKit needs this */
setenv("DISPLAY", ":0", 1); 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 startup scripts in /etc/finit.d/, if any. */
run_parts(FINIT_RCSD); run_parts(FINIT_RCSD);
@@ -403,13 +415,7 @@ int main(int UNUSED(args), char *argv[])
exit(0); exit(0);
} }
/* We're the grim reaper of innocent orphaned children ... */ service_monitor();
while (1) {
sigset_t nmask;
sigemptyset(&nmask);
pselect(0, NULL, NULL, NULL, NULL, &nmask);
}
return 0; return 0;
} }
+8 -1
View File
@@ -131,19 +131,26 @@
#define MATCH_CMD(l, c, x) \ #define MATCH_CMD(l, c, x) \
(!strncmp(l, c, strlen(c)) && (x = (l) + strlen(c))) (!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 int debug;
extern char *sdown; extern char *sdown;
/* signal.c */ /* signal.c */
void do_shutdown (int sig); void do_shutdown (int sig);
int sig_stopped (void);
void sig_init (void); void sig_init (void);
void sig_setup (void); void sig_setup (void);
/* initctl.c */ /* initctl.c */
void listen_initctl (void); void listen_initctl (void);
/* strlcpy.c */
size_t strlcpy(char *dst, const char *src, size_t siz);
#endif /* FINIT_H_ */ #endif /* FINIT_H_ */
/** /**
+191 -2
View File
@@ -22,6 +22,7 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#include <ctype.h> /* isdigit() */
#include <string.h> #include <string.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
@@ -78,7 +79,7 @@ void ifconfig(char *name, char *inet, char *mask, int up)
return; return;
memset(&ifr, 0, sizeof (ifr)); memset(&ifr, 0, sizeof (ifr));
strncpy(ifr.ifr_name, name, IFNAMSIZ); strlcpy(ifr.ifr_name, name, IFNAMSIZ);
a->sin_family = AF_INET; a->sin_family = AF_INET;
if (up) { 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 #ifdef BUILTIN_RUNPARTS
#define NUM_SCRIPTS 128 /* ought to be enough for anyone */ #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); prctl(PR_SET_NAME, name, 0, 0, 0);
memset(args[0], 0, len); 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) void set_hostname(char *hostname)
+5
View File
@@ -28,11 +28,16 @@
int makepath (char *); int makepath (char *);
void ifconfig (char *, char *, char *, int); void ifconfig (char *, char *, char *, int);
void copyfile (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 cls (void);
void chomp (char *); void chomp (char *);
int getuser (char *); int getuser (char *);
int getgroup (char *); int getgroup (char *);
void set_procname (char *args[], char *name); 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); void set_hostname (char *hostname);
#ifdef BUILTIN_RUNPARTS #ifdef BUILTIN_RUNPARTS
+275
View File
@@ -0,0 +1,275 @@
/* Generic IPC class based on TIPC as message bus
*
* Copyright (c) 2008-2012 Joachim Nilsson <troglobit@gmail.com>
*
* 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 <errno.h>
#include <poll.h>
#include <string.h>
//#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <linux/tipc.h>
#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:
*/
+60
View File
@@ -0,0 +1,60 @@
/* Generic IPC class based on TIPC as message bus
*
* Copyright (c) 2008-2012 Joachim Nilsson <troglobit@gmail.com>
*
* 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:
*/
+459
View File
@@ -0,0 +1,459 @@
/* Service starter and supervisor, inspired by daemontools
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-2012 Joachim Nilsson <troglobit@gmail.com>
*
* 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 <features.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/stat.h> /* mknod() */
#include <sys/wait.h> /* 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:
*/
+92
View File
@@ -0,0 +1,92 @@
/* Service starter and supervisor, inspired by daemontools
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-2012 Joachim Nilsson <troglobit@gmail.com>
*
* 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 <errno.h>
#include <sys/shm.h> /* shmat() */
#include <sys/types.h> /* 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 <stop|start|restart> */
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:
*/
+16 -5
View File
@@ -98,7 +98,8 @@ void do_shutdown (int sig)
*/ */
static void shutdown_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) 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); do_shutdown(sig);
} }
@@ -110,8 +111,8 @@ static void chld_handler(int sig, siginfo_t *info, void *UNUSED(ctx))
{ {
int status; int status;
/* XXX: Add (procname) to debug output, add get_procname(pid) to helpers.c */ _d("Child %s died, harvesting. Signal %d from pid %d code %d",
_d("Child died, harvesting. Signal %d from pid %d code %d", sig, info->si_pid, info->si_code); get_pidname(info->si_pid, NULL, 0), sig, info->si_pid, info->si_code);
while (waitpid(-1, &status, WNOHANG) != 0) { while (waitpid(-1, &status, WNOHANG) != 0) {
if (errno == ECHILD) if (errno == ECHILD)
break; 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)) 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); touch(SYNC_STOPPED);
stopped ++; stopped ++;
} }
static void sigcont_handler(int sig, siginfo_t *info, void *UNUSED(ctx)) 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; stopped = 0;
remove(SYNC_STOPPED); remove(SYNC_STOPPED);
} }
/*
* Is SIGSTOP asserted?
*/
int sig_stopped(void)
{
return stopped;
}
/* /*
* Inital signal setup - ignore everything until we're capable of responding * Inital signal setup - ignore everything until we're capable of responding
*/ */
+58
View File
@@ -0,0 +1,58 @@
/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* 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 <sys/types.h>
#include <string.h>
/*
* 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:
*/
+111
View File
@@ -0,0 +1,111 @@
/* Generic API for managing svc_t structures
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-2012 Joachim Nilsson <troglobit@gmail.com>
*
* 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 <string.h>
#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:
*/
+43
View File
@@ -0,0 +1,43 @@
/* Generic API for managing svc_t structures
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-2012 Joachim Nilsson <troglobit@gmail.com>
*
* 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:
*/