mirror of
https://github.com/troglobit/finit.git
synced 2026-10-08 16:34:45 +07:00
Add support for starting/stopping services on Netlink events
This commit adds support for event based services to Finit. Along with
the previously added support for multiple instances of the same process,
the feature scope for Finit v2.0 has been reached!
As of now a service can be declared in /etc/finit.conf or /etc/finit.d/
like this:
service :1 [2345] <!IFUP:eth0,GW> /sbin/dropbear -R -F -p 22 -- SSH daemon
Here the first instance `:1` of dropbear is declared to run in runlevels
2-5, but only if eth0 `IFUP:eth0` is up and a gateway `GW` is set. When
the configuration changes, a new gateway is set, or somehow a new `IFUP`
event for eth0 is received, then dropbear is not SIGHUP'ed, but instead
stop-started `<!>`. The latter trick applies to all services, even
those that do not define any events.
Currently inetd services are not supported for event based starting, but
it could easily be added. Likely scenario is to deny incoming requests
on, e.g., eth0 if there is no gateway.
Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
This commit is contained in:
@@ -37,7 +37,7 @@ EXEC = finit initctl reboot
|
||||
HEADERS = finit.h plugin.h svc.h inetd.h helpers.h queue.h
|
||||
DISTFILES = LICENSE README CHANGELOG finit.conf services
|
||||
OBJS = finit.o api.o client.o conf.o exec.o helpers.o pid.o sig.o \
|
||||
svc.o service.o plugin.o tty.o inetd.o
|
||||
svc.o service.o plugin.o tty.o inetd.o event.o
|
||||
|
||||
TOPDIR = $(shell pwd)
|
||||
-include config.mk
|
||||
|
||||
@@ -18,16 +18,10 @@ very low dependency on external packages.
|
||||
General
|
||||
-------
|
||||
|
||||
* Add compile-time support for running `/bin/sh` instead of `getty`
|
||||
* Add `!` or `[wait <pidfile>]` to service directive to let finit pause
|
||||
and wait for a pidfile before starting processes -- both at boot and
|
||||
when starting processes in a runlevel.
|
||||
* Add `<GW,ADDR:iface,IF:iface>` events to service stanza so services
|
||||
can be reloaded/restarted/stopped -- e.g, new GW --> SIGHUP process,
|
||||
or loss of `IF:eth1` --> stop service, start it when eth1 comes up
|
||||
again.
|
||||
* Add support for listening to netlink messages for GW, ADDR, IF UP/DOWN
|
||||
* Add `<!,...>` to let Finit know that a service does not support reload
|
||||
(`SIGHUP`), but must be restarted (stop+start)
|
||||
* Allow custom reload for processes like libreSwan's pluto
|
||||
|
||||
service [2345] <!gw,if:eth0,reload:'starter reload'> /sbin/pluto
|
||||
@@ -38,7 +32,6 @@ General
|
||||
`.monitor=` in `svc_t` ... the script named as the basename of the
|
||||
service it monitors + `.sh`.
|
||||
* Add support for `/etc/sysctl.d`
|
||||
* Add compile-time supoprt for running `/bin/sh` instead of `getty`
|
||||
* Implement `initctl stop|start|restart|reload|status <SVC>` and
|
||||
`service <SVC> stop|start|restart|reload|status` on top
|
||||
* Add PRE and POST hooks for when switching between runlevels
|
||||
|
||||
@@ -113,6 +113,38 @@ int conf_parse_runlevels(char *runlevels)
|
||||
return bitmask;
|
||||
}
|
||||
|
||||
void conf_parse_events(svc_t *svc, char *events)
|
||||
{
|
||||
size_t i = 0;
|
||||
char *ptr = events;
|
||||
|
||||
if (!events)
|
||||
return;
|
||||
|
||||
if (!svc) {
|
||||
_e("Invalid service pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
/* First character must be '!' if SIGHUP is not supported. */
|
||||
svc->sighup = 1;
|
||||
if (ptr[i] == '!') {
|
||||
svc->sighup = 0;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
while (ptr[i] != '>' && ptr[i] != 0)
|
||||
i++;
|
||||
ptr[i] = 0;
|
||||
|
||||
if (i >= sizeof(svc->events)) {
|
||||
FLOG_WARN("Too long event list in declaration of %s: %s", svc->cmd, ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
strlcpy(svc->events, ptr, sizeof(svc->events));
|
||||
}
|
||||
|
||||
static void parse_static(char *line)
|
||||
{
|
||||
char *x;
|
||||
|
||||
@@ -24,8 +24,11 @@
|
||||
#ifndef FINIT_CONF_H_
|
||||
#define FINIT_CONF_H_
|
||||
|
||||
#include "svc.h"
|
||||
|
||||
void conf_parse_cmdline (void);
|
||||
int conf_parse_runlevels (char *runlevels);
|
||||
void conf_parse_events (svc_t *svc, char *events);
|
||||
int conf_parse_config (void);
|
||||
void conf_reload_dynamic (void);
|
||||
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
/* Event aggregator, also serves as event cache, remembering GW and IFUP states
|
||||
*
|
||||
* Copyright (c) 2015 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 "config.h" /* Generated by configure script */
|
||||
#include "libite/lite.h"
|
||||
|
||||
#include "finit.h"
|
||||
#include "service.h"
|
||||
|
||||
struct ifc {
|
||||
LIST_ENTRY(ifc) link;
|
||||
|
||||
int updown;
|
||||
char ifname[IFNAMSIZ];
|
||||
};
|
||||
|
||||
static int gwup = 0;
|
||||
static LIST_HEAD(, ifc) ifcache = LIST_HEAD_INITIALIZER();
|
||||
|
||||
|
||||
static int cache_gw(char *updown)
|
||||
{
|
||||
int oldgw = gwup;
|
||||
|
||||
if (!strncasecmp(updown, "UP", 2))
|
||||
gwup = 1;
|
||||
else
|
||||
gwup = 0;
|
||||
|
||||
return (oldgw == gwup) ? 0 : gwup == 0 ? -1 : 1;
|
||||
}
|
||||
|
||||
static struct ifc *find_if(char *ifname)
|
||||
{
|
||||
struct ifc *entry;
|
||||
|
||||
LIST_FOREACH(entry, &ifcache, link) {
|
||||
if (!strncmp(entry->ifname, ifname, sizeof(entry->ifname)))
|
||||
return entry;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int alloc_if(char *ifname, int updown)
|
||||
{
|
||||
struct ifc *entry = calloc(1, sizeof(struct ifc));
|
||||
|
||||
if (!entry) {
|
||||
_pe("Failed recording interface event");
|
||||
return 0;
|
||||
}
|
||||
|
||||
_d("Creating new node for %s", ifname);
|
||||
entry->updown = updown;
|
||||
strlcpy(entry->ifname, ifname, sizeof(entry->ifname));
|
||||
LIST_INSERT_HEAD(&ifcache, entry, link);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int free_if(char *ifname)
|
||||
{
|
||||
struct ifc *entry = find_if(ifname);
|
||||
|
||||
if (!entry)
|
||||
return 0;
|
||||
|
||||
LIST_REMOVE(entry, link);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int cache_if(char *ifname, int updown)
|
||||
{
|
||||
struct ifc *entry = find_if(ifname);
|
||||
|
||||
if (entry) {
|
||||
int old = entry->updown;
|
||||
|
||||
entry->updown = updown;
|
||||
return old == updown ? 0 : updown ? 1 : -1;
|
||||
}
|
||||
|
||||
return alloc_if(ifname, updown);
|
||||
}
|
||||
|
||||
/*
|
||||
* System events like GW/IF are cached, this function caters to that
|
||||
* Returns: 0 if no change, -1 on condition low, +1 on condition high
|
||||
*/
|
||||
static int event_cache(char *msg)
|
||||
{
|
||||
if (!strncmp(msg, "GW:", 3))
|
||||
return cache_gw(&msg[3]);
|
||||
|
||||
if (!strncmp(msg, "IFUP:", 5))
|
||||
return cache_if(&msg[5], 1);
|
||||
|
||||
if (!strncmp(msg, "IFDN:", 5))
|
||||
return cache_if(&msg[5], 0);
|
||||
|
||||
if (!strncmp(msg, "IFDEL:", 6))
|
||||
return free_if(&msg[6]);
|
||||
|
||||
return 0; /* No chnage, unknown event. */
|
||||
}
|
||||
|
||||
int event_cache_gw(void)
|
||||
{
|
||||
return gwup;
|
||||
}
|
||||
|
||||
int event_cache_if(char *ifname)
|
||||
{
|
||||
struct ifc *entry = find_if(ifname);
|
||||
|
||||
if (!entry)
|
||||
return 0;
|
||||
|
||||
return entry->updown;
|
||||
}
|
||||
|
||||
static int validate_events(char *events)
|
||||
{
|
||||
return !events || !events[0];
|
||||
}
|
||||
|
||||
int event_service_cond(char *events)
|
||||
{
|
||||
int cond = 1;
|
||||
char *msg;
|
||||
char temp[MAX_ARG_LEN];
|
||||
|
||||
/* No required events, condition satisfied */
|
||||
if (validate_events(events))
|
||||
return cond;
|
||||
|
||||
strlcpy(temp, events, sizeof(temp));
|
||||
msg = strtok(temp, ",");
|
||||
while (msg) {
|
||||
_d("Looking for %s (in %s), cond %d", msg, events, cond);
|
||||
|
||||
if (!strncmp(msg, "GW", 2))
|
||||
cond &= event_cache_gw();
|
||||
else if (!strncmp(msg, "IFUP:", 5))
|
||||
cond &= event_cache_if(&msg[5]);
|
||||
else if (!strncmp(msg, "IFDN:", 5))
|
||||
cond &= event_cache_if(&msg[5]);
|
||||
else
|
||||
_e("Unknown event %s, discarding.", msg);
|
||||
|
||||
_d("=> cond %d", cond);
|
||||
msg = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
return cond;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispatch an event
|
||||
*
|
||||
* Called by initctl (via api.c) or plugins/netlink.so
|
||||
* May also cache for future reference.
|
||||
*/
|
||||
void event_dispatch(char *msg)
|
||||
{
|
||||
int change;
|
||||
svc_t *svc;
|
||||
|
||||
if (!msg) {
|
||||
_e("Invalid message received.");
|
||||
return;
|
||||
}
|
||||
|
||||
_d("%s", msg);
|
||||
change = event_cache(msg);
|
||||
if (!change) {
|
||||
_d("Nothing to do");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Iterate over svc_t and call service_restart() for event matches */
|
||||
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
|
||||
if (svc->type != SVC_TYPE_SERVICE || validate_events(svc->events))
|
||||
continue;
|
||||
|
||||
if (change == 1) {
|
||||
if (!service_enabled(svc, 1, NULL))
|
||||
continue;
|
||||
|
||||
_d("Got event %s, (re)starting %s ...", svc->events, svc->cmd);
|
||||
if (!svc->pid) {
|
||||
service_start(svc);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (svc->sighup)
|
||||
service_reload(svc);
|
||||
else
|
||||
service_restart(svc);
|
||||
} else { /* change == -1 */
|
||||
if (svc->pid && !event_service_cond(svc->events))
|
||||
service_stop(svc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* version-control: t
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
@@ -0,0 +1,40 @@
|
||||
/* Event aggregator, also serves as event cache, remembering GW and IFUP states
|
||||
*
|
||||
* Copyright (c) 2015 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_EVENT_H_
|
||||
#define FINIT_EVENT_H_
|
||||
|
||||
int event_cache_gw (void);
|
||||
int event_cache_if (char *ifname);
|
||||
int event_service_cond (char *events);
|
||||
void event_dispatch (char *msg);
|
||||
|
||||
#endif /* FINIT_EVENT_H_ */
|
||||
|
||||
/**
|
||||
* Local Variables:
|
||||
* version-control: t
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "linux"
|
||||
* End:
|
||||
*/
|
||||
+22
-14
@@ -18,7 +18,6 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <net/if.h> /* IFNAMSIZ */
|
||||
//#include <stdint.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/netlink.h>
|
||||
@@ -26,6 +25,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../finit.h"
|
||||
#include "../event.h"
|
||||
#include "../helpers.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
@@ -63,18 +63,18 @@ static void nl_route(struct nlmsghdr *nlmsg)
|
||||
switch (a->rta_type) {
|
||||
case RTA_GATEWAY:
|
||||
gw = *((int *)data);
|
||||
_d("GW: 0x%04x", gw);
|
||||
//_d("GW: 0x%04x", gw);
|
||||
break;
|
||||
|
||||
case RTA_DST:
|
||||
dst = *((int *)data);
|
||||
mask = r->rtm_dst_len;
|
||||
_d("MASK: 0x%04x", mask);
|
||||
//_d("MASK: 0x%04x", mask);
|
||||
break;
|
||||
|
||||
case RTA_OIF:
|
||||
idx = *((int *)data);
|
||||
_d("IDX: 0x%04x", idx);
|
||||
//_d("IDX: 0x%04x", idx);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -82,10 +82,13 @@ static void nl_route(struct nlmsghdr *nlmsg)
|
||||
}
|
||||
|
||||
if ((!dst && !mask) && (gw || idx)) {
|
||||
char msg[MAX_ARG_LEN];
|
||||
|
||||
if (nlmsg->nlmsg_type == RTM_DELROUTE)
|
||||
_d("Default gateway 0x%4x removed", gw);
|
||||
snprintf(msg, sizeof(msg), "GW:DN");
|
||||
else
|
||||
_d("New default gateway: 0x%4x", gw);
|
||||
snprintf(msg, sizeof(msg), "GW:UP");
|
||||
event_dispatch(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,23 +110,28 @@ static void nl_link(struct nlmsghdr *nlmsg)
|
||||
|
||||
while (RTA_OK(a, la)) {
|
||||
if (a->rta_type == IFLA_IFNAME) {
|
||||
char msg[MAX_ARG_LEN];
|
||||
|
||||
strncpy(ifname, RTA_DATA(a), sizeof(ifname));
|
||||
switch (nlmsg->nlmsg_type) {
|
||||
case RTM_NEWLINK:
|
||||
/* New interface has appearad, or interface flags has changed.
|
||||
/*
|
||||
* New interface has appearad, or interface flags has changed.
|
||||
* Check ifi_flags here to see if the interface is UP/DOWN
|
||||
*/
|
||||
_d("New link (%s), UP: %d", ifname, i->ifi_flags & IFF_UP);
|
||||
if (i->ifi_change & IFF_UP) {
|
||||
int msg = (i->ifi_flags & IFF_UP) ? 1 : 0;
|
||||
|
||||
_d("%s: %s", ifname, msg ? "UP" : "DOWN");
|
||||
}
|
||||
if (i->ifi_change & IFF_UP)
|
||||
snprintf(msg, sizeof(msg), "IF%s:%s",
|
||||
(i->ifi_flags & IFF_UP) ? "UP" : "DN",
|
||||
ifname);
|
||||
else
|
||||
snprintf(msg, sizeof(msg), "IFADD:%s", ifname);
|
||||
event_dispatch(msg);
|
||||
break;
|
||||
|
||||
case RTM_DELLINK:
|
||||
/* NOTE: Interface has dissapeared, not link down ... */
|
||||
_d("%s: DOWN", ifname);
|
||||
snprintf(msg, sizeof(msg), "IFDEL:%s", ifname);
|
||||
event_dispatch(msg);
|
||||
break;
|
||||
|
||||
case RTM_NEWADDR:
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "finit.h"
|
||||
#include "conf.h"
|
||||
#include "event.h"
|
||||
#include "helpers.h"
|
||||
#include "private.h"
|
||||
#include "sig.h"
|
||||
@@ -94,6 +95,12 @@ svc_cmd_t service_enabled(svc_t *svc, int event, void *arg)
|
||||
if (!svc_in_runlevel(svc, runlevel))
|
||||
return SVC_STOP;
|
||||
|
||||
/*
|
||||
* Event conditions for services are ignored during bootstrap.
|
||||
*/
|
||||
if (runlevel && !event_service_cond(svc->events))
|
||||
return SVC_STOP;
|
||||
|
||||
/* Is there a service plugin registered? */
|
||||
if (svc->cb) {
|
||||
int status;
|
||||
@@ -373,7 +380,11 @@ void service_stop_dynamic(void)
|
||||
plugin_run_hooks(HOOK_SVC_RECONF); /* Reconfigure HW/VLANs/etc here */
|
||||
}
|
||||
|
||||
/* stop + start */
|
||||
/* stop + start
|
||||
*
|
||||
* XXX: This should be refactored into a background job AND marked such
|
||||
* so that the service monitor doesn't attempt to restart it!
|
||||
*/
|
||||
int service_restart(svc_t *svc)
|
||||
{
|
||||
int result = 0;
|
||||
@@ -537,7 +548,7 @@ void service_runlevel(int newlevel)
|
||||
* The @line can optionally start with a username, denoted by an @
|
||||
* character. Like this:
|
||||
*
|
||||
* service @username [!0-6,S] /path/to/daemon arg -- Description
|
||||
* service @username [!0-6,S] <!EV> /path/to/daemon arg -- Description
|
||||
* task @username [!0-6,S] /path/to/task arg -- Description
|
||||
* run @username [!0-6,S] /path/to/cmd arg -- Description
|
||||
* inetd tcp/ssh nowait [2345] @root:root /sbin/sshd -i -- Description
|
||||
@@ -550,6 +561,18 @@ void service_runlevel(int newlevel)
|
||||
* command is listed in more than the [S] runlevel they will be called
|
||||
* when changing runlevel.
|
||||
*
|
||||
* Services (daemons, not inetd services) also support an optional <!EV>
|
||||
* argument. This is for services that, e.g., require a system gateway
|
||||
* or interface to be up before they are started. Or restarted, or even
|
||||
* SIGHUP'ed, when the gateway changes or interfaces come and go. The
|
||||
* special case when a service is declared with <!> means it does not
|
||||
* support SIGHUP but must be STOP/START'ed at system reconfiguration.
|
||||
*
|
||||
* Supported service events are: GW, IFUP[:ifname], IFDN[:ifname], where
|
||||
* the interface name (:ifname) is optional. Actully, the check with a
|
||||
* service event declaration is string based, so 'IFUP:ppp' will match
|
||||
* any of "IFUP:ppp0" or "IFUP:pppoe1" sent by the netlink.so plugin.
|
||||
*
|
||||
* For multiple instances of the same command, e.g. multiple DHCP
|
||||
* clients, the user must enter an ID, using the :ID syntax.
|
||||
*
|
||||
@@ -570,7 +593,7 @@ int service_register(int type, char *line, time_t mtime, char *username)
|
||||
int forking = 0;
|
||||
#endif
|
||||
char *service = NULL, *proto = NULL, *ifaces = NULL;
|
||||
char *cmd, *desc, *runlevels = NULL;
|
||||
char *cmd, *desc, *runlevels = NULL, *events = NULL;
|
||||
svc_t *svc;
|
||||
plugin_t *plugin = NULL;
|
||||
|
||||
@@ -603,6 +626,8 @@ int service_register(int type, char *line, time_t mtime, char *username)
|
||||
username = &cmd[1];
|
||||
else if (cmd[0] == '[') /* [runlevels] */
|
||||
runlevels = &cmd[0];
|
||||
else if (cmd[0] == '<') /* [!ev] */
|
||||
events = &cmd[1];
|
||||
else if (cmd[0] == ':') /* :ID */
|
||||
id = atoi(&cmd[1]);
|
||||
else
|
||||
@@ -701,6 +726,9 @@ int service_register(int type, char *line, time_t mtime, char *username)
|
||||
svc->runlevels = conf_parse_runlevels(runlevels);
|
||||
_d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels);
|
||||
|
||||
if (type == SVC_TYPE_SERVICE)
|
||||
conf_parse_events(svc, events);
|
||||
|
||||
#ifndef INETD_DISABLED
|
||||
if (svc_is_inetd(svc)) {
|
||||
char *iface, *name = service;
|
||||
|
||||
@@ -68,6 +68,8 @@ typedef struct svc {
|
||||
int dirty; /* Set if old mtime != new mtime => reloaded,
|
||||
* or -1 when marked for removal */
|
||||
int runlevels;
|
||||
int sighup; /* This service supports SIGHUP :) */
|
||||
char events[MAX_ARG_LEN];
|
||||
|
||||
/* Incremented for each restart by service monitor. */
|
||||
unsigned int restart_counter;
|
||||
|
||||
Reference in New Issue
Block a user