Relocate source files to an src/ subdirectory

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
This commit is contained in:
Joachim Nilsson committed 2017-01-16 01:31:02 +01:00
1 parent 044ded3045
commit fff68b7b06
61 files changed
+155 -154

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
AM_CPPFLAGS = -U_FORTIFY_SOURCE
AM_CPPFLAGS += -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_GNU_SOURCE -D_DEFAULT_SOURCE
if STATIC
AM_CPPFLAGS += -DENABLE_STATIC
AM_LDFLAGS = -static-libtool-libs
else
AM_CPPFLAGS += -DPLUGIN_PATH=\"$(pkglibdir)\"
AM_LDFLAGS = -export-dynamic
endif
sbin_PROGRAMS = finit initctl reboot
finit_SOURCES = api.c \
cond.c cond-w.c cond.h \
client.c \
conf.c conf.h \
exec.c finit.c finit.h \
getty.c \
helpers.c helpers.h \
mdadm.c mount.c pid.c \
plugin.c plugin.h private.h \
service.c service.h \
sig.c sig.h \
sm.c sm.h \
svc.c svc.h \
tty.c tty.h \
util.c util.h \
utmp-api.c utmp-api.h
pkginclude_HEADERS = cond.h finit.h helpers.h inetd.h plugin.h svc.h
if INETD
finit_SOURCES += inetd.c inetd.h
endif
if WATCHDOGD
finit_SOURCES += watchdog.c watchdog.h
endif
finit_CFLAGS = -W -Wall -Wextra -std=gnu99 $(lite_CFLAGS) $(uev_CFLAGS)
finit_LDADD = $(lite_LIBS) $(uev_LIBS)
if STATIC
finit_LDADD += plugins/libplug.la
else
finit_LDADD += -ldl
endif
initctl_SOURCES = initctl.c svc.c svc.h helpers.c helpers.h cond.c cond.h \
util.c util.h utmp-api.c utmp-api.h
initctl_CFLAGS = $(lite_CFLAGS)
initctl_LDADD = $(lite_LIBS)
reboot_SOURCES = reboot.c util.c util.h
INIT_LNKS = telinit init
REBOOT_LNKS = shutdown halt poweroff suspend
# Hook in install to add finit and reboot symlink(s)
install-exec-hook:
@for file in $(INIT_LNKS); do \
lnk=$(DESTDIR)$(sbindir)/$$file; \
if [ -e $$lnk ]; then \
echo "Skipping $$lnk exists already."; \
else \
$(LN_S) finit $$lnk; \
fi; \
done
@if [ -L $(DESTDIR)$(sbindir)/reboot ]; then \
echo "Non-finit reboot, skipping install."; \
else \
for file in $(REBOOT_LNKS); do \
link=$(DESTDIR)$(sbindir)/$$file; \
if [ -e $$link ]; then \
echo "$$link exists, skipping.";\
else \
$(LN_S) reboot $$link; \
fi; \
done; \
fi
uninstall-hook:
@for file in $(INIT_LNKS); do \
lnk=`readlink $(DESTDIR)$(sbindir)/$$file`; \
if [ "$$lnk" != "finit" ]; then \
echo "Skipping non-finit $$file."; \
continue; \
fi; \
$(RM) $(DESTDIR)$(sbindir)/$$file; \
done
@if [ -L $(DESTDIR)$(sbindir)/reboot ]; then \
echo "Non-finit reboot, skipping uninstall."; \
else \
for file in $(REBOOT_LNKS); do \
if [ "`readlink $$file`" != "reboot" ]; \
then continue; \
fi; \
$(RM) $(DESTDIR)$(sbindir)/$$file; \
done; \
fi
+348
View File
@@ -0,0 +1,348 @@
/* External client API, using UNIX domain socket.
*
* 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 <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <lite/lite.h>
#include <uev/uev.h>
#include "config.h"
#include "finit.h"
#include "cond.h"
#include "conf.h"
#include "helpers.h"
#include "plugin.h"
#include "sig.h"
#include "service.h"
#include "util.h"
uev_t api_watcher;
static int call(int (*action)(svc_t *), char *buf, size_t len)
{
return svc_parse_jobstr(buf, len, action, NULL);
}
static int service_block(svc_t *svc)
{
if (!svc)
return 1;
svc_block(svc);
service_step(svc);
return 0;
}
static int service_unblock(svc_t *svc)
{
if (!svc)
return 1;
svc_unblock(svc);
service_step(svc);
return 0;
}
static int service_restart(svc_t *svc)
{
if (!svc)
return 1;
svc_mark_dirty(svc);
service_step(svc);
return 0;
}
static int do_start (char *buf, size_t len) { return call(service_unblock, buf, len); }
static int do_pause (char *buf, size_t len) { return call(service_block, buf, len); }
static int do_restart(char *buf, size_t len) { return call(service_restart, buf, len); }
#ifdef INETD_ENABLED
static int do_query_inetd(char *buf, size_t len)
{
int id = 1;
char *ptr, *input = sanitize(buf, len);
svc_t *svc;
if (!input)
return -1;
ptr = strchr(input, ':');
if (ptr) {
*ptr++ = 0;
id = atonum(ptr);
}
svc = svc_find_by_jobid(atonum(input), id);
if (!svc || !svc_is_inetd(svc)) {
_e("Cannot %s svc %s ...", !svc ? "find" : "query, not an inetd", input);
return 1;
}
return inetd_filter_str(&svc->inetd, buf, len);
}
#endif /* INETD_ENABLED */
typedef struct {
char *event;
void (*cb)(void);
} ev_t;
ev_t ev_list[] = {
{ "RELOAD", service_reload_dynamic },
{ NULL, NULL }
};
static int do_handle_event(char *event)
{
int i;
for (i = 0; ev_list[i].event; i++) {
ev_t *e = &ev_list[i];
size_t len = MAX(strlen(e->event), strlen(event));
if (!strncasecmp(e->event, event, len)) {
e->cb();
return 0;
}
}
if (event[0] == '-')
cond_clear(&event[1]);
else if (event[0] == '+')
cond_set(&event[1]);
else
cond_set(event);
return 0;
}
static int do_handle_emit(char *buf, size_t len)
{
int result = 0;
char *input, *event, *pos;
input = sanitize(buf, len);
if (!input)
return -1;
event = strtok_r(input, " ", &pos);
while (event) {
result += do_handle_event(event);
event = strtok_r(NULL, " ", &pos);
}
return result;
}
/*
* In contrast to the SysV compat handling in plugins/initctl.c, when
* `initctl runlevel 0` is issued we default to POWERDOWN the system
* instead of just halting.
*/
static void cb(uev_t *w, void *UNUSED(arg), int UNUSED(events))
{
int sd, lvl;
struct init_request rq;
sd = accept(w->fd, NULL, NULL);
if (sd < 0) {
_pe("Failed serving API request");
return;
}
while (1) {
int result = 0;
ssize_t len;
len = read(sd, &rq, sizeof(rq));
if (len <= 0) {
if (-1 == len) {
if (EINTR == errno)
continue;
if (EAGAIN == errno)
break;
_e("Failed reading initctl request, error %d: %s", errno, strerror(errno));
}
break;
}
if (rq.magic != INIT_MAGIC || len != sizeof(rq)) {
_e("Invalid initctl request");
break;
}
switch (rq.cmd) {
case INIT_CMD_RUNLVL:
switch (rq.runlevel) {
case 's':
case 'S':
rq.runlevel = '1'; /* Single user mode */
/* Fall through to regular processing */
case '0'...'9':
_d("Setting new runlevel %c", rq.runlevel);
lvl = rq.runlevel - '0';
if (lvl == 0)
halt = SHUT_OFF;
if (lvl == 6)
halt = SHUT_REBOOT;
service_runlevel(lvl);
break;
default:
_d("Unsupported runlevel: %d", rq.runlevel);
break;
}
break;
case INIT_CMD_DEBUG:
debug = !debug;
if (debug)
silent = 0;
else
silent = quiet ? 1 : SILENT_MODE;
logit(LOG_INFO, "Debug mode %s", debug ? "enabled" : "disabled");
break;
case INIT_CMD_RELOAD: /* 'init q' and 'initctl reload' */
service_reload_dynamic();
break;
case INIT_CMD_START_SVC:
result = do_start(rq.data, sizeof(rq.data));
break;
case INIT_CMD_STOP_SVC:
result = do_pause(rq.data, sizeof(rq.data));
break;
case INIT_CMD_RESTART_SVC:
result = do_restart(rq.data, sizeof(rq.data));
break;
#ifdef INETD_ENABLED
case INIT_CMD_QUERY_INETD:
result = do_query_inetd(rq.data, sizeof(rq.data));
break;
#endif
case INIT_CMD_EMIT:
result = do_handle_emit(rq.data, sizeof(rq.data));
break;
case INIT_CMD_GET_RUNLEVEL:
rq.runlevel = runlevel;
break;
case INIT_CMD_ACK:
_d("Client failed reading ACK");
goto leave;
case INIT_CMD_WDOG_HELLO:
if (rq.runlevel <= 0) {
result = 1;
break;
}
if (wdogpid > 0 && wdogpid != rq.runlevel) {
kill(wdogpid, SIGTERM);
do_sleep(1);
}
wdogpid = rq.runlevel;
break;
default:
_d("Unsupported cmd: %d", rq.cmd);
break;
}
if (result)
rq.cmd = INIT_CMD_NACK;
else
rq.cmd = INIT_CMD_ACK;
len = write(sd, &rq, sizeof(rq));
if (len != sizeof(rq))
_d("Failed sending ACK/NACK back to client");
}
leave:
close(sd);
}
int api_init(uev_ctx_t *ctx)
{
int sd;
mode_t oldmask;
struct sockaddr_un sun = {
.sun_family = AF_UNIX,
.sun_path = INIT_SOCKET,
};
sd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (-1 == sd) {
_pe("Failed starting external API socket");
return 1;
}
erase(INIT_SOCKET);
oldmask = umask(0077);
if (-1 == bind(sd, (struct sockaddr*)&sun, sizeof(sun)))
goto error;
if (-1 == listen(sd, 10))
goto error;
umask(oldmask);
if (!uev_io_init(ctx, &api_watcher, cb, NULL, sd, UEV_READ))
return 0;
error:
_pe("Failed intializing API socket");
umask(oldmask);
close(sd);
return 1;
}
int api_exit(void)
{
return shutdown(api_watcher.fd, SHUT_RDWR) ||
close(api_watcher.fd);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+160
View File
@@ -0,0 +1,160 @@
/* Simple telinit client
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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 <ctype.h>
#include <getopt.h>
#include <stdio.h>
#include <lite/lite.h>
#include "finit.h"
#include "util.h"
static int do_send(struct init_request *rq)
{
static int fd = -1;
int result = 255;
ssize_t len;
if (fd == -1) {
if (!fexist(FINIT_FIFO)) {
fprintf(stderr, "%s does not support %s!\n", prognm, FINIT_FIFO);
return 1;
}
fd = open(FINIT_FIFO, O_RDWR);
if (-1 == fd) {
perror("Failed opening " FINIT_FIFO);
return 1;
}
}
len = write(fd, rq, sizeof(*rq));
if (sizeof(*rq) == len)
result = 0;
return result;
}
static int do_reload(void)
{
struct init_request rq = {
.magic = INIT_MAGIC,
.cmd = INIT_CMD_RELOAD,
};
return do_send(&rq);
}
static int set_env(int runlevel)
{
struct init_request rq = {
.magic = INIT_MAGIC,
.cmd = INIT_CMD_SETENV,
};
if (runlevel == 0) {
strlcpy(rq.data, "INIT_HALT=POWERDOWN", sizeof(rq.data));
return do_send(&rq);
}
return 0;
}
static int set_runlevel(int runlevel)
{
struct init_request rq = {
.magic = INIT_MAGIC,
.cmd = INIT_CMD_RUNLVL,
.runlevel = runlevel,
};
return set_env(runlevel) || do_send(&rq);
}
/* telinit q | telinit <0-9> */
static int usage(int rc)
{
fprintf(stderr, "Usage: %s [OPTIONS] [q | Q | 0-9]\n\n"
"Options:\n"
" -h, --help This help text\n"
" -V, --version Show Finit version\n\n"
"Commands:\n"
" 0 Power-off the system, same as initctl poweroff\n"
" 6 Reboot the system, same as initctl reboot\n"
" 2, 3, 4, 5 Change runlevel. Starts services in new runlevel, stops any\n"
" services in prev. runlevel that are not allowed in new.\n"
" q, Q Reload *.conf in /etc/finit.d/, same as initctl reload or\n"
" sending SIGHUP to PID 1\n"
" 1, s, S Enter system rescue mode, runlevel 1\n"
"\n", prognm);
return rc;
}
int client(int argc, char *argv[])
{
int c;
struct option long_options[] = {
{"help", 0, NULL, 'h'},
{"version", 0, NULL, 'v'},
{NULL, 0, NULL, 0}
};
silent = 1;
progname(argv[0]);
while ((c = getopt_long(argc, argv, "h?v", long_options, NULL)) != EOF) {
switch(c) {
case 'v':
return puts("v" VERSION) == EOF;
case 'h':
case '?':
return usage(0);
}
}
if (optind < argc) {
int req = (int)argv[optind][0];
if (isdigit(req))
return set_runlevel(req);
if (req == 'q' || req == 'Q')
return do_reload();
if (req == 's' || req == 'S')
return set_runlevel(1);
}
return usage(1);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+161
View File
@@ -0,0 +1,161 @@
/* Condition engine (write)
*
* Copyright (c) 2015-2016 Tobias Waldekranz <tobias@waldekranz.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 <ftw.h>
#include <libgen.h>
#include <lite/lite.h>
#include <stdio.h>
#include <sys/stat.h>
#include "finit.h"
#include "cond.h"
#include "service.h"
int cond_set_path(const char *path, enum cond_state new)
{
char buf[MAX_ARG_LEN], *dir;
enum cond_state old;
old = cond_get_path(path);
switch (new) {
case COND_ON:
strlcpy(buf, path, sizeof(buf));
dir = dirname(buf);
if (!dir) {
_e("Invalid path '%s' for condition", path);
return 0;
}
if (makepath(dir) && errno != EEXIST) {
_pe("Failed creating dir '%s' for condition '%s'", dir, path);
return 0;
}
touch(path);
utimensat(0, path, NULL, 0);
break;
case COND_OFF:
unlink(path);
break;
default:
_e("Invalid condition state");
return 0;
}
return new != old;
}
/* Has condition in configuration and cond is allowed? */
static int svc_has_cond(svc_t *svc)
{
if (!svc->cond[0])
return 0;
switch (svc->type) {
case SVC_TYPE_SERVICE:
case SVC_TYPE_TASK:
case SVC_TYPE_RUN:
return 1;
default:
break;
}
return 0;
}
static void cond_update(const char *name)
{
svc_t *svc;
_d("%s", name);
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (!svc_has_cond(svc) || !cond_affects(name, svc->cond))
continue;
_d("%s: match <%s> %s", name ?: "nil", svc->cond, svc->cmd);
service_step(svc);
}
}
void cond_set(const char *name)
{
_d("%s", name);
if (!cond_set_path(cond_path(name), COND_ON))
return;
cond_update(name);
}
void cond_clear(const char *name)
{
_d("%s", name);
if (!cond_set_path(cond_path(name), COND_OFF))
return;
cond_update(name);
}
void cond_reload(void)
{
_d("");
cond_set_path(COND_RECONF, COND_ON);
cond_update(NULL);
}
static int reassert(const char *fpath, const struct stat *UNUSED(sb), int tflg, struct FTW *ftw)
{
char *nm;
if (ftw->level == 0)
return 1;
if (tflg != FTW_F)
return 0;
nm = (char *)fpath + sizeof(COND_PATH);
_d("Reasserting %s => %s", fpath, nm);
cond_set(nm);
return 0;
}
/*
* Used only by netlink plugin atm.
* type: is a one of svc/, net/, etc.
*/
void cond_reassert(const char *type)
{
_d("%s", type);
nftw(cond_path(type), reassert, 20, FTW_DEPTH);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+117
View File
@@ -0,0 +1,117 @@
/* Condition engine (read)
*
* Copyright (c) 2015-2016 Tobias Waldekranz <tobias@waldekranz.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 <lite/lite.h>
#include <stdio.h>
#include "finit.h"
#include "cond.h"
#include "service.h"
static inline int timespec_newer(const struct timespec *a,
const struct timespec *b)
{
if (a->tv_sec != b->tv_sec)
return a->tv_sec > b->tv_sec;
return a->tv_nsec > b->tv_nsec;
}
const char *condstr(enum cond_state s)
{
static const char *strs[] = {
[COND_OFF] = "off",
[COND_FLUX] = "flux",
[COND_ON] = "on",
};
return strs[s];
}
const char *cond_path(const char *name)
{
static char file[MAX_ARG_LEN];
snprintf(file, sizeof(file), COND_PATH "/%s", name);
return file;
}
enum cond_state cond_get_path(const char *path)
{
struct stat st, st_reconf;
if (stat(path, &st))
return COND_OFF;
if (stat(COND_RECONF, &st_reconf) ||
timespec_newer(&st.st_mtim, &st_reconf.st_mtim))
return COND_ON;
return COND_FLUX;
}
enum cond_state cond_get(const char *name)
{
return cond_get_path(cond_path(name));
}
enum cond_state cond_get_agg(const char *names)
{
char *cond;
enum cond_state s = COND_ON;
static char conds[MAX_ARG_LEN];
if (!names)
return COND_ON;
strlcpy(conds, names, sizeof(conds));
for (cond = strtok(conds, ","); s && cond; cond = strtok(NULL, ","))
s = min(s, cond_get(cond));
return s;
}
int cond_affects(const char *name, const char *names)
{
char *cond;
static char conds[MAX_ARG_LEN];
if (!name || !names)
return 0;
strlcpy(conds, names, sizeof(conds));
for (cond = strtok(conds, ","); cond; cond = strtok(NULL, ",")) {
if (!strcmp(cond, name))
return 1;
}
return 0;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+36
View File
@@ -0,0 +1,36 @@
#ifndef FINIT_COND_H_
#define FINIT_COND_H_
#include <paths.h>
#define COND_PATH _PATH_VARRUN "finit/cond"
#define COND_SVC_PATH COND_PATH "/svc/"
#define COND_RECONF COND_PATH "/reconf"
typedef enum cond_state {
COND_OFF = 0,
COND_FLUX,
COND_ON
} cond_state_t;
const char *condstr (enum cond_state s);
const char *cond_path (const char *name);
enum cond_state cond_get_path(const char *path);
enum cond_state cond_get (const char *name);
enum cond_state cond_get_agg (const char *names);
int cond_affects (const char *name, const char *names);
int cond_set_path(const char *path, enum cond_state new);
void cond_set (const char *name);
void cond_clear (const char *name);
void cond_reload (void);
void cond_reassert(const char *pat);
#endif /* FINIT_COND_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+527
View File
@@ -0,0 +1,527 @@
/* Parser for /etc/finit.conf and /etc/finit.d/<SVC>.conf
*
* Copyright (c) 2012-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 <dirent.h>
#include <string.h>
#include <sys/resource.h>
#include <lite/lite.h>
#include <sys/time.h>
#include "finit.h"
#include "cond.h"
#include "service.h"
#include "tty.h"
#include "helpers.h"
#define MATCH_CMD(l, c, x) \
(!strncasecmp(l, c, strlen(c)) && (x = (l) + strlen(c)))
static int parse_conf(char *file);
void conf_parse_cmdline(void)
{
FILE *fp;
char line[LINE_SIZE];
fp = fopen("/proc/cmdline", "r");
if (fp) {
fgets(line, sizeof(line), fp);
_d("Kernel command line: %s", line);
if (strstr(line, "finit_debug") || strstr(line, "--debug"))
debug = 1;
if (!debug && strstr(line, "quiet"))
quiet = 1;
fclose(fp);
}
}
/* Convert optional "[!123456789S]" string into a bitmask */
int conf_parse_runlevels(char *runlevels)
{
int i, not = 0, bitmask = 0;
if (!runlevels)
runlevels = "[234]";
i = 1;
while (i) {
int level;
char lvl = runlevels[i++];
if (']' == lvl || 0 == lvl)
break;
if ('!' == lvl) {
not = 1;
bitmask = 0x3FE;
continue;
}
if ('s' == lvl || 'S' == lvl)
lvl = '0';
level = lvl - '0';
if (level > 9 || level < 0)
continue;
if (not)
CLRBIT(bitmask, level);
else
SETBIT(bitmask, level);
}
return bitmask;
}
void conf_parse_cond(svc_t *svc, char *cond)
{
size_t i = 0;
char *ptr;
if (!svc) {
_e("Invalid service pointer");
return;
}
/* By default we assume UNIX daemons support SIGHUP */
if (svc_is_daemon(svc))
svc->sighup = 1;
if (!cond)
return;
/* First character must be '!' if SIGHUP is not supported. */
ptr = cond;
if (ptr[i] == '!') {
svc->sighup = 0;
ptr++;
}
while (ptr[i] != '>' && ptr[i] != 0)
i++;
ptr[i] = 0;
if (i >= sizeof(svc->cond)) {
logit(LOG_WARNING, "Too long event list in declaration of %s: %s", svc->cmd, ptr);
return;
}
strlcpy(svc->cond, ptr, sizeof(svc->cond));
}
struct rlimit_name {
char *name;
int val;
};
static const struct rlimit_name rlimit_names[] = {
{ "as", RLIMIT_AS },
{ "core", RLIMIT_CORE },
{ "cpu", RLIMIT_CPU },
{ "data", RLIMIT_DATA },
{ "fsize", RLIMIT_FSIZE },
{ "locks", RLIMIT_LOCKS },
{ "memlock", RLIMIT_MEMLOCK },
{ "msgqueue", RLIMIT_MSGQUEUE },
{ "nice", RLIMIT_NICE },
{ "nofile", RLIMIT_NOFILE },
{ "nproc", RLIMIT_NPROC },
{ "rss", RLIMIT_RSS },
{ "rtprio", RLIMIT_RTPRIO },
#ifdef RLIMIT_RTTIME
{ "rttime", RLIMIT_RTTIME },
#endif
{ "sigpending", RLIMIT_SIGPENDING },
{ "stack", RLIMIT_STACK },
{ NULL, 0 }
};
void conf_parse_rlimit(char *line)
{
struct rlimit rlim;
rlim_t new, *set;
const struct rlimit_name *name;
int resource = -1;
char *tok = strtok(line, " \t");
if (tok && !strcmp(tok, "soft"))
set = &rlim.rlim_cur;
else if (tok && !strcmp(tok, "hard"))
set = &rlim.rlim_max;
else
goto error;
tok = strtok(NULL, " \t");
if (!tok)
goto error;
for (name = rlimit_names; name->name; name++) {
if (!strcmp(tok, name->name))
resource = name->val;
}
if (resource < 0)
goto fail;
tok = strtok(NULL, " \t");
if (!tok)
goto fail;
if (!strcmp(tok, "infinity")) {
new = RLIM_INFINITY;
} else {
const char *err = NULL;
new = strtonum(tok, 0, (long long)2 << 31, &err);
if (err)
goto fail;
}
if (getrlimit(resource, &rlim))
goto fail;
*set = new;
if (setrlimit(resource, &rlim))
goto fail;
return;
fail:
logit(LOG_WARNING, "rlimit: Failed setting rlimit %s", name->name ? : "unknown");
return;
error:
logit(LOG_WARNING, "rlimit: parse error");
}
static void parse_static(char *line)
{
char *x;
char cmd[CMD_SIZE];
/* Do this before mounting / read-write
* XXX: Move to plugin which checks /etc/fstab instead */
if (MATCH_CMD(line, "check ", x)) {
char *dev = strip_line(x);
strcpy(cmd, "/sbin/fsck -C -a ");
strlcat(cmd, dev, sizeof(cmd));
run_interactive(cmd, "Checking file system %s", dev);
return;
}
if (MATCH_CMD(line, "user ", x)) {
if (username) free(username);
username = strdup(strip_line(x));
return;
}
if (MATCH_CMD(line, "host ", x)) {
if (hostname) free(hostname);
hostname = strdup(strip_line(x));
return;
}
if (MATCH_CMD(line, "module ", x)) {
char *mod = strip_line(x);
strcpy(cmd, "/sbin/modprobe ");
strlcat(cmd, mod, sizeof(cmd));
run_interactive(cmd, "Loading kernel module %s", mod);
return;
}
if (MATCH_CMD(line, "mknod ", x)) {
char *dev = strip_line(x);
strcpy(cmd, "/bin/mknod ");
strlcat(cmd, dev, sizeof(cmd));
run_interactive(cmd, "Creating device node %s", dev);
return;
}
if (MATCH_CMD(line, "network ", x)) {
if (network) free(network);
network = strdup(strip_line(x));
return;
}
if (MATCH_CMD(line, "runparts ", x)) {
if (runparts) free(runparts);
runparts = strdup(strip_line(x));
return;
}
if (MATCH_CMD(line, "include ", x)) {
char *file = strip_line(x);
strlcpy(cmd, file, sizeof(cmd));
if (!fexist(cmd)) {
_e("Cannot find include file %s, absolute path required!", x);
return;
}
parse_conf(cmd);
return;
}
if (MATCH_CMD(line, "startx ", x)) {
service_register(SVC_TYPE_SERVICE, strip_line(x), NULL, username);
return;
}
if (MATCH_CMD(line, "shutdown ", x)) {
if (sdown) free(sdown);
sdown = strdup(strip_line(x));
return;
}
/* The desired runlevel to start when leaving bootstrap (S).
* Finit supports 1-9, but most systems only use 1-6, where
* 6 is reserved for reboot */
if (MATCH_CMD(line, "runlevel ", x)) {
char *token = strip_line(x);
const char *err = NULL;
cfglevel = strtonum(token, 1, 9, &err);
if (err)
cfglevel = RUNLEVEL;
if (cfglevel < 1 || cfglevel > 9 || cfglevel == 6)
cfglevel = 2; /* Fallback */
return;
}
/* TODO: Make console & tty dynamically loadable from /etc/finit.d */
if (MATCH_CMD(line, "console ", x)) {
if (console) free(console);
console = strdup(strip_line(x));
return;
}
/* TODO: Make console & tty dynamically loadable from /etc/finit.d */
if (MATCH_CMD(line, "tty ", x)) {
tty_register(strip_line(x));
return;
}
}
static void parse_dynamic(char *line, struct timeval *mtime)
{
char *x;
/* Skip comments, i.e. lines beginning with # */
if (MATCH_CMD(line, "#", x))
return;
/* Monitored daemon, will be respawned on exit */
if (MATCH_CMD(line, "service ", x)) {
service_register(SVC_TYPE_SERVICE, x, mtime, NULL);
return;
}
/* One-shot task, will not be respawned */
if (MATCH_CMD(line, "task ", x)) {
service_register(SVC_TYPE_TASK, x, mtime, NULL);
return;
}
/* Like task but waits for completion, useful w/ [S] */
if (MATCH_CMD(line, "run ", x)) {
service_register(SVC_TYPE_RUN, x, mtime, NULL);
return;
}
/* Classic inetd service */
if (MATCH_CMD(line, "inetd ", x)) {
#ifdef INETD_ENABLED
service_register(SVC_TYPE_INETD, x, mtime, NULL);
#else
_e("Finit built with inetd support disabled, cannot register service inetd %s!", x);
#endif
return;
}
if (MATCH_CMD(line, "rlimit ", x)) {
conf_parse_rlimit(x);
return;
}
}
static void tabstospaces(char *line)
{
if (!line)
return;
for (int i = 0; line[i]; i++) {
if (line[i] == '\t')
line[i] = ' ';
}
}
static int parse_conf_dynamic(char *file, struct timeval *mtime)
{
FILE *fp = fopen(file, "r");
if (!fp) {
_pe("Failed opening %s", file);
return 1;
}
while (!feof(fp)) {
char line[LINE_SIZE] = "";
if (!fgets(line, sizeof(line), fp))
continue;
chomp(line);
tabstospaces(line);
_d("dyn conf: %s", line);
parse_dynamic(line, mtime);
}
fclose(fp);
return 0;
}
static int parse_conf(char *file)
{
FILE *fp;
char line[LINE_SIZE] = "";
char *x;
fp = fopen(file, "r");
if (!fp)
return 1;
/*
* If not standard finit.conf, then we want to show just the base name
* Loading configuration ............. vs
* Loading services configuration ....
*/
if (!string_match (file, FINIT_CONF)) {
/* Remove leading path */
x = strrchr(file, '/');
if (!x) x = file;
else x++;
/* Remove ending .conf */
strlcpy(line, x, sizeof(line));
x = strstr(line, ".conf");
if (x) *x = 0;
/* Add empty space. */
strcat(line, " ");
}
if (!silent)
print(0, "Loading %sconfiguration", line);
while (!feof(fp)) {
if (!fgets(line, sizeof(line), fp))
continue;
chomp(line);
tabstospaces(line);
_d("conf: %s", line);
parse_static(line);
parse_dynamic(line, NULL);
}
fclose(fp);
return 0;
}
/* Reload all *.conf in /etc/finit.d/ */
void conf_reload_dynamic(void)
{
int i, num;
char *dir = rcsd;
struct dirent **e;
/* Mark and sweep */
svc_mark_dynamic();
num = scandir(dir, &e, NULL, alphasort);
if (num < 0) {
_d("No files found in %s, skipping ...", dir);
return;
}
for (i = 0; i < num; i++) {
char *name = e[i]->d_name;
char path[CMD_SIZE];
struct stat st;
struct timeval mtime;
snprintf(path, sizeof(path), "%s/%s", dir, name);
/* Check that it's an actual file ... */
if (stat(path, &st)) {
_d("Cannot even read .conf file %s, skipping ...", path);
continue;
}
if (S_ISEXEC(st.st_mode) || S_ISDIR(st.st_mode))
continue;
/* Check that file ends with '.conf' */
if (strcmp(&path[strlen(path) - 5], ".conf")) {
_d("File %s is not a .conf, skipping ... ", path);
continue;
}
TIMESPEC_TO_TIMEVAL(&mtime, &st.st_mtim);
parse_conf_dynamic(path, &mtime);
}
while (num--)
free(e[num]);
free(e);
set_hostname(&hostname);
}
int conf_parse_config(void)
{
username = strdup(DEFUSER);
hostname = strdup(DEFHOST);
return parse_conf(FINIT_CONF);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+42
View File
@@ -0,0 +1,42 @@
/* Parser for /etc/finit.conf and /etc/finit.d/<SVC>.conf
*
* Copyright (c) 2012-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_CONF_H_
#define FINIT_CONF_H_
#include "svc.h"
void conf_parse_cmdline (void);
int conf_parse_runlevels (char *runlevels);
void conf_parse_cond (svc_t *svc, char *cond);
int conf_parse_config (void);
void conf_reload_dynamic (void);
#endif /* FINIT_CONF_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+307
View File
@@ -0,0 +1,307 @@
/* Functions for exec'ing processes
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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 <ctype.h> /* isdigit() */
#include <dirent.h>
#include <stdarg.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <lite/lite.h>
#include "finit.h"
#include "helpers.h"
#include "sig.h"
#include "utmp-api.h"
#define NUM_ARGS 16
int getty(char *tty, char *baud, char *term, char *user);
/* Wait for process completion, returns status of waitpid(2) syscall */
int complete(char *cmd, int pid)
{
int status = 0;
if (waitpid(pid, &status, 0) == -1) {
if (errno == EINTR)
_e("Caught unblocked signal waiting for %s, aborting", cmd);
else if (errno == ECHILD)
_e("Caught SIGCHLD waiting for %s, aborting", cmd);
else
_e("Failed starting %s, error %d: %s", cmd, errno, strerror (errno));
return -1;
}
return status;
}
int run(char *cmd)
{
int status, result, i = 0;
char *args[NUM_ARGS + 1], *arg, *backup;
pid_t pid;
/* We must create a copy that is possible to modify. */
backup = arg = strdup(cmd);
if (!arg)
return 1; /* Failed allocating a string to be modified. */
/* Split command line into tokens of an argv[] array. */
args[i++] = strsep(&arg, "\t ");
while (arg && i < NUM_ARGS) {
/* Handle run("su -c \"dbus-daemon --system\" messagebus");
* => "su", "-c", "\"dbus-daemon --system\"", "messagebus" */
if (*arg == '\'' || *arg == '"') {
char *p, delim[2] = " ";
delim[0] = arg[0];
args[i++] = arg++;
strsep(&arg, delim);
p = arg - 1;
*p = *delim;
*arg++ = 0;
} else {
args[i++] = strsep(&arg, "\t ");
}
}
args[i] = NULL;
if (i == NUM_ARGS && arg) {
_e("Command too long: %s", cmd);
free(backup);
errno = EOVERFLOW;
return 1;
}
pid = fork();
if (0 == pid) {
FILE *fp;
/* Reset signal handlers that were set by the parent process */
sig_unblock();
setsid();
/* Always redirect stdio for run() */
fp = fopen("/dev/null", "w");
if (fp) {
int fd = fileno(fp);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
}
sig_unblock();
execvp(args[0], args);
_exit(1); /* Only if execv() fails. */
} else if (-1 == pid) {
_pe("%s", args[0]);
free(backup);
return -1;
}
status = complete(args[0], pid);
if (-1 == status) {
free(backup);
return 1;
}
result = WEXITSTATUS(status);
if (WIFEXITED(status)) {
_d("Started %s and ended OK: %d", args[0], result);
} else if (WIFSIGNALED(status)) {
_d("Process %s terminated by signal %d", args[0], WTERMSIG(status));
if (!result)
result = 1; /* Must alert callee that the command did complete successfully.
* This is necessary since not all programs trap signals and
* change their return code accordingly. --Jocke */
}
free(backup);
return result;
}
int run_interactive(char *cmd, char *fmt, ...)
{
int status, oldout = 1, olderr = 2;
char line[LINE_SIZE];
va_list ap;
FILE *fp;
if (!cmd) {
errno = EINVAL;
return 1;
}
if (!silent && fmt) {
va_start(ap, fmt);
vsnprintf(line, sizeof(line), fmt, ap);
va_end(ap);
print_desc("", line);
}
/* Redirect output from cmd to a tempfile */
fp = tempfile();
if (fp && !debug) {
oldout = dup(STDOUT_FILENO);
olderr = dup(STDERR_FILENO);
dup2(fileno(fp), STDOUT_FILENO);
dup2(fileno(fp), STDERR_FILENO);
}
/* Run cmd ... */
status = run(cmd);
/* Restore stderr/stdout */
if (fp && !debug) {
if (oldout >= 0) {
dup2(oldout, STDOUT_FILENO);
close(oldout);
}
if (olderr >= 0) {
dup2(olderr, STDERR_FILENO);
close(olderr);
}
}
if (!silent && fmt)
print_result(status);
/* Dump any results of cmd on stderr after we've printed [ OK ] or [FAIL] */
if (fp && !debug) {
size_t len, written;
rewind(fp);
do {
len = fread(line, 1, sizeof(line), fp);
written = fwrite(line, len, sizeof(char), stderr);
} while (len > 0 && written == len);
}
if (fp)
fclose(fp);
return status;
}
pid_t run_getty(char *tty, char *speed, char *term, int console)
{
pid_t pid;
pid = fork();
if (!pid) {
/* Reset signal handlers that were set by the parent process */
sig_unblock();
setsid();
/* Set INIT_PROCESS UTMP entry */
utmp_set_init(tty, 0);
if (console)
prctl(PR_SET_NAME, "console", 0, 0, 0);
else
prctl(PR_SET_NAME, "finit-getty", 0, 0, 0);
_exit(getty(tty, speed, term, NULL));
}
return pid;
}
int run_parts(char *dir, char *cmd)
{
struct dirent **e;
int i, num;
num = scandir(dir, &e, NULL, alphasort);
if (num < 0) {
_d("No files found in %s, skipping ...", dir);
return -1;
}
for (i = 0; i < num; i++) {
int j = 0;
pid_t pid = 0;
mode_t mode;
char *args[NUM_ARGS];
char *name = e[i]->d_name;
char path[CMD_SIZE];
snprintf(path, sizeof(path), "%s/%s", dir, name);
mode = fmode(path);
if (!S_ISEXEC(mode) || S_ISDIR(mode)) {
_d("Skipping %s ...", path);
continue;
}
/* Fill in args[], starting with full path to executable */
args[j++] = path;
/* If the callee didn't supply a run_parts() argument */
if (!cmd) {
/* Check if S<NUM>service or K<NUM>service notation is used */
_d("Checking if %s is a sysvinit startstop script ...", name);
if (name[0] == 'S' && isdigit(name[1])) {
args[j++] = "start";
} else if (name[0] == 'K' && isdigit(name[1])) {
args[j++] = "stop";
}
} else {
args[j++] = cmd;
}
args[j++] = NULL;
pid = fork();
if (!pid) {
_d("Calling %s ...", path);
sig_unblock();
execv(path, args);
exit(0);
}
complete(path, pid);
}
while (num--)
free(e[num]);
free(e);
return 0;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+432
View File
@@ -0,0 +1,432 @@
/* Finit - Fast /sbin/init replacement w/ I/O, hook & service plugins
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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 <ctype.h>
#include <dirent.h>
#ifdef HAVE_FSTAB_H
#include <fstab.h>
#endif
#include <mntent.h>
#include <sys/mount.h>
#include <sys/stat.h> /* umask(), mkdir() */
#include <sys/wait.h>
#include <lite/lite.h>
#include "finit.h"
#include "conf.h"
#include "helpers.h"
#include "private.h"
#include "plugin.h"
#include "service.h"
#include "sig.h"
#include "sm.h"
#include "tty.h"
#include "utmp-api.h"
#include "watchdog.h"
int debug = 0;
int quiet = QUIET_MODE; /* Delayed disable of silent mode. */
int silent = SILENT_MODE; /* Completely silent, including boot */
int wdogpid = 0; /* No watchdog by default */
int runlevel = 0; /* Bootstrap 'S' */
int cfglevel = RUNLEVEL; /* Fallback if no configured runlevel */
int prevlevel = -1;
char *sdown = NULL;
char *network = NULL;
char *username = NULL;
char *hostname = NULL;
char *rcsd = FINIT_RCSD;
char *runparts = NULL;
char *console = NULL;
uev_ctx_t *ctx = NULL; /* Main loop context */
static int banner(void)
{
char buf[42] = INIT_HEADING;
const char separator[] = "========================================================================";
if (silent)
return 0;
fprintf(stderr, "\e[2K\e[1m%s %.*s\e[0m\n", buf, 66 - (int)strlen(buf), separator);
return 0;
}
/*
* Check all filesystems in /etc/fstab with a fs_passno > 0
*/
static int fsck(int pass)
{
int save;
struct fstab *fs;
if (!setfsent()) {
_pe("Failed opening fstab");
return 1;
}
save = debug;
debug = 0;
while ((fs = getfsent())) {
char cmd[80];
struct stat st;
if (fs->fs_passno != pass)
continue;
errno = 0;
if (stat(fs->fs_spec, &st) || !S_ISBLK(st.st_mode)) {
if (!string_match(fs->fs_spec, "UUID=") && !string_match(fs->fs_spec, "LABEL=")) {
_d("Cannot fsck %s, not a block device: %s", fs->fs_spec, strerror(errno));
continue;
}
}
snprintf(cmd, sizeof(cmd), "/sbin/fsck -a %s", fs->fs_spec);
run_interactive(cmd, "Checking filesystem %.13s", fs->fs_spec);
}
debug = save;
endfsent();
return 0;
}
static void networking(void)
{
FILE *fp;
/* Run user network start script if enabled */
if (network) {
run_interactive(network, "Starting networking: %s", network);
return;
}
/* Debian/Ubuntu/Busybox interfaces file */
fp = fopen("/etc/network/interfaces", "r");
if (fp) {
int i = 0;
char buf[160];
/* Bring up all 'auto' interfaces */
while (fgets(buf, sizeof(buf), fp)) {
char cmd[80];
char *line, *ifname = NULL;
chomp(buf);
line = strip_line(buf);
if (!strncmp(line, "auto", 4))
ifname = &line[5];
if (!strncmp(line, "allow-hotplug", 13))
ifname = &line[14];
if (!ifname)
continue;
snprintf(cmd, 80, "/sbin/ifup %s", ifname);
run_interactive(cmd, "Bringing up interface %s", ifname);
i++;
}
fclose(fp);
if (i)
return;
}
/* Fall back to bring up at least loopback */
ifconfig("lo", "127.0.0.1", "255.0.0.0", 1);
}
/* Requires /proc to be mounted */
static int fismnt(char *dir)
{
FILE *fp;
int found = 0;
struct mntent *mnt;
fp = setmntent("/proc/mounts", "r");
if (!fp)
return 0; /* Dunno, maybe not */
while ((mnt = getmntent(fp))) {
if (!strcmp(mnt->mnt_dir, dir)) {
found = 1;
break;
}
}
endmntent(fp);
return found;
}
/*
* If everything goes south we can use this to give the operator an
* emergency shell to debug the problem -- Finit should not crash!
*/
static void emergency_shell(void)
{
#ifdef EMERGENCY_SHELL
pid_t pid;
pid = fork();
if (pid) {
waitpid(pid, NULL, 0);
fprintf(stderr, "\n=> Embarrassingly, Finit has crashed. Check /dev/kmsg for details.\n");
fprintf(stderr, "=> For more, remove 'quiet' and add '--debug' to the kernel command line.\n\n");
/*
* Become session leader and set controlling TTY
* to enable Ctrl-C and job control in shell.
*/
setsid();
ioctl(STDIN_FILENO, TIOCSCTTY, 1);
execl(_PATH_BSHELL, _PATH_BSHELL, NULL);
}
#endif /* EMERGENCY_SHELL */
}
int main(int argc, char* argv[])
{
char *devfsd;
uev_ctx_t loop;
/*
* finit/init/telinit client tool uses /dev/initctl pipe
* for compatibility but initctl client tool uses socket
*/
if (getpid() != 1)
return client(argc, argv);
/*
* In case of emergency.
*/
emergency_shell();
/*
* Hello world.
*/
banner();
/*
* Initial setup of signals, ignore all until we're up.
*/
sig_init();
/*
* Initalize event context.
*/
uev_init(&loop);
ctx = &loop;
/*
* Check file filesystems in /etc/fstab
*/
for (int pass = 1; pass < 10; pass++) {
if (fsck(pass))
break;
}
/*
* Mount base file system, kernel is assumed to run devtmpfs for /dev
*/
chdir("/");
umask(0);
mount("none", "/proc", "proc", 0, NULL);
mount("none", "/proc/bus/usb", "usbfs", 0, NULL);
mount("none", "/sys", "sysfs", 0, NULL);
/*
* Some non-embedded systems without an initramfs may not have /dev mounted yet
* If they do, check if system has udevadm and perform cleanup from initramfs
*/
if (!fismnt("/dev"))
mount("udev", "/dev", "devtmpfs", MS_RELATIME, "size=10%,nr_inodes=61156,mode=755");
else if (fexist("/sbin/udevadm"))
run_interactive("/sbin/udevadm info --cleanup-db", "Cleaning up udev db");
/* Some systems use /dev/pts */
makedir("/dev/pts", 0755);
mount("devpts", "/dev/pts", "devpts", 0, "gid=5,mode=620");
makedir("/dev/shm", 0755);
if (!fismnt("/dev/shm"))
mount("shm", "/dev/shm", "tmpfs", 0, NULL);
/*
* New tmpfs based /run for volatile runtime data
* For details, see http://lwn.net/Articles/436012/
*/
if (fisdir("/run") && !fismnt("/run"))
mount("tmpfs", "/run", "tmpfs", MS_NODEV, "mode=0755,size=10%");
umask(022);
/*
* Parse kernel parameters
*/
conf_parse_cmdline();
/*
* Populate /dev and prepare for runtime events from kernel.
*/
if (fexist("/sbin/mdev")) {
/* Embedded Linux systems usually have BusyBox mdev */
if (debug)
touch("/dev/mdev.log");
devfsd = "/sbin/mdev -s";
} else {
if (fexist("/sbin/udevd"))
devfsd = "/sbin/udevd --daemon";
else
devfsd = "/lib/systemd/systemd-udevd --daemon";
}
run_interactive(devfsd, "Populating device tree");
if (fexist("/sbin/udevadm")) {
run("/sbin/udevadm trigger --action=add --type=subsystems");
run("/sbin/udevadm trigger --action=add --type=devices");
run("/sbin/udevadm settle --timeout=120");
}
/*
* Load plugins first, finit.conf may contain references to
* features implemented by plugins.
*/
plugin_init(&loop, PLUGIN_PATH);
/*
* Parse /etc/finit.conf, main configuration file
*/
conf_parse_config();
/*
* Start built-in watchdog as soon as possible, if enabled
*/
watchdog(argv[0]);
/* Set hostname as soon as possible, for syslog et al. */
set_hostname(&hostname);
/* Set default PATH, for uid 0 */
setenv("PATH", _PATH_STDPATH, 1);
/*
* Mount filesystems
*/
#ifdef REMOUNT_ROOTFS
run("/bin/mount -n -o remount,rw /");
#endif
#ifdef SYSROOT
mount(SYSROOT, "/", NULL, MS_MOVE, NULL);
#endif
/* Debian has this little script to copy generated rules while the system was read-only */
if (fexist("/lib/udev/udev-finish"))
run_interactive("/lib/udev/udev-finish", "Finalizing udev");
_d("Root FS up, calling hooks ...");
plugin_run_hooks(HOOK_ROOTFS_UP);
umask(0);
if (run_interactive("/bin/mount -na", "Mounting filesystems"))
plugin_run_hooks(HOOK_MOUNT_ERROR);
run("/sbin/swapon -ea");
umask(0022);
/* Base FS up, enable standard SysV init signals */
sig_setup(&loop);
_d("Base FS up, calling hooks ...");
plugin_run_hooks(HOOK_BASEFS_UP);
/*
* Initalize finit state machine and start all bootstrap tasks, no network available!
*/
sm_init(&sm);
sm_step(&sm);
/*
* Network stuff
*/
networking();
umask(022);
/* Hooks that rely on loopback, or basic networking being up. */
plugin_run_hooks(HOOK_NETWORK_UP);
/*
* Start all tasks/services in the configured runlevel
*/
service_runlevel(cfglevel);
_d("Running svc up hooks ...");
plugin_run_hooks(HOOK_SVC_UP);
/*
* Run startup scripts in the runparts directory, if any.
*/
if (runparts && fisdir(runparts)) {
_d("Running startup scripts in %s ...", runparts);
run_parts(runparts, NULL);
service_reload_dynamic();
}
/* Convenient SysV compat for when you just don't care ... */
if (!access(FINIT_RC_LOCAL, X_OK)) {
run_interactive(FINIT_RC_LOCAL, "Calling %s", FINIT_RC_LOCAL);
service_reload_dynamic();
}
/* Hooks that should run at the very end */
plugin_run_hooks(HOOK_SYSTEM_UP);
/* Start TTYs */
tty_runlevel(runlevel);
/* Enable silent mode, if selected */
if (quiet && !debug)
silent = 1;
/* Start new initctl API responder */
api_init(&loop);
/*
* Enter main loop to monior /dev/initctl and services
*/
return uev_run(&loop, 0);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+105
View File
@@ -0,0 +1,105 @@
/* Finit - Fast /sbin/init replacement w/ I/O, hook & service plugins
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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_H_
#define FINIT_H_
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <lite/lite.h>
#include <uev/uev.h>
#define CMD_SIZE 256
#define LINE_SIZE 1024
#define BUF_SIZE 4096
/*
* We extend the INIT_CMD_ range for the new initctl tool, Finit only
* support changing runlevel, reloading the configuration and setenv
* with the old-style /dev/initctl FIFO.
*/
#define INIT_SOCKET _PATH_VARRUN "finit.sock"
#define INIT_MAGIC 0x03091969
#define INIT_CMD_START 0
#define INIT_CMD_RUNLVL 1
#define INIT_CMD_POWERFAIL 2
#define INIT_CMD_POWERFAILNOW 3
#define INIT_CMD_POWEROK 4
#define INIT_CMD_BSD 5
#define INIT_CMD_SETENV 6
#define INIT_CMD_UNSETENV 7
/* Finit extensions over std SysV */
#define INIT_CMD_DEBUG 8 /* Toggle Finit debug */
#define INIT_CMD_RELOAD 9 /* Reload *.conf in /etc/finit.d/ */
#define INIT_CMD_START_SVC 10
#define INIT_CMD_STOP_SVC 11
#define INIT_CMD_RELOAD_SVC 12 /* SIGHUP service */
#define INIT_CMD_RESTART_SVC 13 /* STOP + START service */
#define INIT_CMD_QUERY_INETD 14
#define INIT_CMD_EMIT 15
#define INIT_CMD_GET_RUNLEVEL 16
#define INIT_CMD_WDOG_HELLO 128 /* Watchdog register and hello */
#define INIT_CMD_NACK 254
#define INIT_CMD_ACK 255
struct init_request {
int magic; /* Magic number */
int cmd; /* What kind of request */
int runlevel; /* Runlevel to change to */
int sleeptime; /* Time between TERM and KILL */
char data[368];
};
extern int debug;
extern int quiet;
extern int silent;
extern int wdogpid;
extern int runlevel;
extern int cfglevel;
extern int prevlevel;
extern char *rcsd;
extern char *sdown;
extern char *network;
extern char *hostname;
extern char *username;
extern char *runparts;
extern char *console;
extern uev_ctx_t *ctx;
#endif /* FINIT_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+347
View File
@@ -0,0 +1,347 @@
/* Initialize and serve a login terminal
*
* Copyright (c) 2016 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 <err.h>
#include <errno.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <sys/ttydefaults.h> /* Not included by default in musl libc */
#include <termios.h>
#include "finit.h"
#include "helpers.h"
#include "sig.h"
#include "utmp-api.h"
#ifndef _PATH_LOGIN
#define _PATH_LOGIN "/bin/login"
#endif
#define print(s) (void)write(STDOUT_FILENO, s, strlen(s))
/*
* Read one character from stdin.
*/
static int readch(char *tty)
{
int st;
char ch1;
st = read(STDIN_FILENO, &ch1, 1);
if (st == 0) {
print("\n");
_exit(0);
}
if (st < 0)
errx(1, "getty: %s: read error", tty);
return ch1 & 0xFF;
}
static void stty(int fd, speed_t speed)
{
struct termios term;
tcdrain(fd);
if (tcgetattr(fd, &term))
return;
cfsetispeed(&term, speed);
cfsetospeed(&term, speed);
tcsetattr(fd, TCSAFLUSH, &term);
tcflush(fd, TCIOFLUSH);
/* Disable modem specific flags */
term.c_cflag &= ~(0|CSTOPB|PARENB|PARODD);
term.c_cflag &= ~CRTSCTS;
term.c_cflag |= CLOCAL;
/* Timeouts, minimum chars and default flags */
term.c_cc[VTIME] = 0;
term.c_cc[VMIN] = 1;
term.c_iflag = ICRNL|IXON|IXOFF;
term.c_oflag = OPOST|ONLCR;
term.c_cflag |= CS8|CREAD|HUPCL|CBAUDEX;
term.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOKE;
/* Reset special characters to defaults */
term.c_cc[VINTR] = CTRL('C');
term.c_cc[VQUIT] = CTRL('\\');
term.c_cc[VEOF] = CTRL('D');
term.c_cc[VEOL] = '\n';
term.c_cc[VKILL] = CTRL('U');
term.c_cc[VERASE] = CERASE;
tcsetattr(fd, TCSANOW, &term);
/* Clear screen in silent mode */
if (silent)
print("\033[r\033[H\033[J");
}
/*
* Parse and display a line from /etc/issue
*/
static void do_parse(char *line, struct utsname *uts, char *tty)
{
char *s, *s0;
s0 = line;
for (s = line; *s != 0; s++) {
if (*s == '\\') {
(void)write(1, s0, s - s0);
s0 = s + 2;
switch (*++s) {
case 'l':
print(tty);
break;
case 'm':
print(uts->machine);
break;
case 'n':
print(uts->nodename);
break;
#ifdef _GNU_SOURCE
case 'o':
print(uts->domainname);
break;
#endif
case 'r':
print(uts->release);
break;
case 's':
print(uts->sysname);
break;
case 'v':
print(uts->version);
break;
case 0:
goto leave;
default:
s0 = s - 1;
}
}
}
leave:
(void)write(1, s0, s - s0);
}
/*
* Parse and display /etc/issue
*/
static void do_issue(char *tty)
{
FILE *fp;
char buf[BUFSIZ] = "Welcome to \\s \\v \\n \\l\n\n";
struct utsname uts;
/*
* Get data about this machine.
*/
uname(&uts);
print("\n");
fp = fopen("/etc/issue", "r");
if (fp) {
while (fgets(buf, sizeof(buf), fp))
do_parse(buf, &uts, tty);
fclose(fp);
} else {
do_parse(buf, &uts, tty);
}
do_parse("\\n login: ", &uts, tty);
}
/*
* Handle the process of a GETTY.
*/
static void do_getty(char *tty, char *name, size_t len)
{
int ch;
char *np;
/*
* Clean up tty name.
*/
if (!strncmp(tty, _PATH_DEV, strlen(_PATH_DEV)))
tty += 5;
/*
* Display prompt.
*/
ch = ' ';
*name = '\0';
while (ch != '\n') {
do_issue(tty);
np = name;
while ((ch = readch(tty)) != '\n') {
if (np < name + len)
*np++ = ch;
}
*np = '\0';
if (*name == '\0')
ch = ' '; /* blank line typed! */
}
name[len - 1] = 0;
}
/* Execute the login(1) command with the current
* username as its argument. It will reply to the
* calling user by typing "Password: "...
*/
static int do_login(char *name)
{
struct stat st;
execl(_PATH_LOGIN, _PATH_LOGIN, name, NULL);
/*
* Failed to exec login, should be impossible. Try a starting a
* fallback shell instead.
*
* Note: Add /etc/securetty handling.
*/
warnx("Failed exec %s, attempting fallback to %s ...", _PATH_LOGIN, _PATH_BSHELL);
if (fstat(0, &st) == 0 && S_ISCHR(st.st_mode))
execl(_PATH_BSHELL, _PATH_BSHELL, NULL);
return 1; /* We shouldn't get here ... */
}
static speed_t do_parse_speed(char *baud)
{
char *ptr;
size_t i;
unsigned long val;
struct { unsigned long val; speed_t speed; } v2s[] = {
{ 0, B0 },
{ 50, B50 },
{ 75, B75 },
{ 110, B110 },
{ 134, B134 },
{ 150, B150 },
{ 200, B200 },
{ 300, B300 },
{ 600, B600 },
{ 1200, B1200 },
{ 1800, B1800 },
{ 2400, B2400 },
{ 4800, B4800 },
{ 9600, B9600 },
{ 19200, B19200 },
{ 38400, B38400 },
{ 57600, B57600 },
{ 115200, B115200 },
{ 230400, B230400 },
{ 460800, B460800 },
{ 500000, B500000 },
{ 576000, B576000 },
{ 921600, B921600 },
{ 1000000, B1000000 },
{ 1152000, B1152000 },
{ 1500000, B1500000 },
{ 2000000, B2000000 },
{ 2500000, B2500000 },
{ 3000000, B3000000 },
{ 3500000, B3500000 },
{ 4000000, B4000000 },
};
errno = 0;
val = strtoul(baud, &ptr, 10);
if (errno || ptr == baud)
return B0;
for (i = 0; i < sizeof(v2s) / sizeof(v2s[0]); i++) {
if (v2s[i].val == val)
return v2s[i].speed;
}
return B0;
}
int getty(char *tty, char *baud, char *UNUSED(term), char *user)
{
int fd;
char name[30];
speed_t speed = B38400;
struct sigaction sa;
if (baud) {
speed = do_parse_speed(baud);
if (speed == B0) {
logit(LOG_CRIT, "TTY %s: Invalid speed %s", tty, baud);
return 1;
}
}
/* Detach from initial controlling TTY */
vhangup();
fd = open(tty, O_RDWR);
if (fd < 0)
err(1, "Failed opening %s", tty);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) < 0)
warn("Failed TIOCSCTTY");
/*
* Don't let QUIT dump core.
*/
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = _exit;
sigaction(SIGQUIT, &sa, NULL);
stty(fd, speed);
close(fd);
utmp_set_login(tty, NULL);
if (!user)
do_getty(tty, name, sizeof(name));
else
strlcpy(name, user, sizeof(name));
return do_login(name);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+326
View File
@@ -0,0 +1,326 @@
/* Misc. utility functions for finit and its plugins
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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 <ctype.h> /* isblank() */
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <limits.h>
#include <net/if.h>
#include <netinet/in.h>
#include <stdarg.h>
#include <sys/ioctl.h>
#include <lite/lite.h>
#include <lite/conio.h>
#include "finit.h"
#include "helpers.h"
#include "private.h"
#include "utmp-api.h"
int _slup = 0; /* INTERNAL: Is syslog up yet? */
char *strip_line(char *line)
{
char *ptr;
/* Trim leading whitespace */
while (*line && isblank(*line))
line++;
/* Strip any comment at end of line */
ptr = line;
while (*ptr && *ptr != '#')
ptr++;
*ptr = 0;
return line;
}
void runlevel_set(int pre, int now)
{
utmp_set_runlevel(pre, now);
}
/**
* runlevel_string - Convert a bit encoded runlevel to .conf syntax
* @levels: Bit encoded runlevels
*
* Returns:
* Pointer to string on the form [2345]
*/
char *runlevel_string(int levels)
{
int i, pos = 1;
static char lvl[20];
memset(lvl, 0, sizeof(lvl));
lvl[0] = '[';
for (i = 0; i < 10; i++) {
if (ISSET(levels, i)) {
if (runlevel == i)
pos = strlcat(lvl, "\e[1m", sizeof(lvl));
if (i == 0)
lvl[pos++] = 'S';
else
lvl[pos++] = '0' + i;
if (runlevel == i)
pos = strlcat(lvl, "\e[0m", sizeof(lvl));
}
}
lvl[pos++] = ']';
lvl[pos] = 0;
return lvl;
}
static int print_timestamp(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(int action, const char *fmt, ...)
{
char buf[80];
size_t len;
va_list ap;
const char success[] = " \e[1m[ OK ]\e[0m\n";
const char failure[] = " \e[7m[FAIL]\e[0m\n";
const char warning[] = " \e[7m[WARN]\e[0m\n";
const char pending[] = " \e[1m[ \\/ ]\e[0m\n";
const char dots[] = " .....................................................................";
if (silent)
return;
if (fmt) {
va_start(ap, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
delline();
print_timestamp();
write(STDERR_FILENO, "\r", 1);
write(STDERR_FILENO, buf, len);
write(STDERR_FILENO, dots, 60 - len); /* pad with dots. */
}
switch (action) {
case -1:
break;
case 0:
write(STDERR_FILENO, success, sizeof(success));
break;
case 1:
write(STDERR_FILENO, failure, sizeof(failure));
break;
case 2:
write(STDERR_FILENO, warning, sizeof(warning));
break;
default:
write(STDERR_FILENO, pending, sizeof(pending));
break;
}
}
void print_desc(char *action, char *desc)
{
print(-1, "%s%s", action ?: "", desc ?: "");
}
int print_result(int fail)
{
print(!!fail, NULL);
return fail;
}
/*
* Log to /dev/kmsg until syslogd has started, then openlog()
* and continue logging as a regular daemon.
*/
void logit(int prio, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (!_slup && !fexist("/dev/log")) {
FILE *fp;
fp = fopen("/dev/kmsg", "w");
if (fp) {
if (debug)
prio = LOG_ERR;
fprintf(fp, "<%d>finit[1]:", LOG_DAEMON | prio);
vfprintf(fp, fmt, ap);
fclose(fp);
} else {
fprintf(stderr, "Failed opening /dev/kmsg for appending ...\n");
if (debug || prio <= LOG_ERR)
vfprintf(stderr, fmt, ap);
}
va_end(ap);
return;
}
if (!_slup) {
openlog("finit", LOG_PID, LOG_DAEMON);
_slup = 1;
}
vsyslog(prio, fmt, ap);
va_end(ap);
}
int getuser(char *username, char **home)
{
#ifdef ENABLE_STATIC
*home = "/"; /* XXX: Fixme */
return fgetint("/etc/passwd", "x:\n", username);
#else
struct passwd *usr;
if (!username || (usr = getpwnam(username)) == NULL)
return -1;
*home = usr->pw_dir;
return usr->pw_uid;
#endif
}
int getgroup(char *group)
{
#ifdef ENABLE_STATIC
return fgetint("/etc/group", "x:\n", group);
#else
struct group *grp;
if ((grp = getgrnam(group)) == NULL)
return -1;
return grp->gr_gid;
#endif
}
void set_hostname(char **hostname)
{
FILE *fp;
fp = fopen("/etc/hostname", "r");
if (fp) {
struct stat st;
if (fstat(fileno(fp), &st)) {
fclose(fp);
return;
}
*hostname = realloc(*hostname, st.st_size);
if (!*hostname) {
fclose(fp);
return;
}
fgets(*hostname, st.st_size, fp);
chomp(*hostname);
fclose(fp);
}
if (*hostname)
sethostname(*hostname, strlen(*hostname));
}
#ifndef HAVE_GETFSENT
static lfile_t *fstab = NULL;
int setfsent(void)
{
if (fstab)
lfclose(fstab);
fstab = lfopen("/etc/fstab", " \t\n");
if (!fstab)
return 0;
return 1;
}
struct fstab *getfsent(void)
{
static struct fstab fs;
fs.fs_spec = lftok(fstab);
if (fs.fs_spec == NULL)
return NULL;
fs.fs_file = lftok(fstab);
fs.fs_vfstype = lftok(fstab);
fs.fs_mntops = lftok(fstab);
fs.fs_type = "rw";
fs.fs_freq = atoi(lftok(fstab) ?: "0");
fs.fs_passno = atoi(lftok(fstab) ?: "0");
return &fs;
}
void endfsent(void)
{
if (fstab)
lfclose(fstab);
fstab = NULL;
}
#endif /* HAVE_GETFSENT */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+99
View File
@@ -0,0 +1,99 @@
/* Misc. utility functions and C-library extensions for finit and its plugins
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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_HELPERS_H_
#define FINIT_HELPERS_H_
#include <err.h>
#include <stdio.h>
#include <string.h> /* strerror() */
#include <syslog.h>
#include <sys/types.h>
/*
* Error and debug messages
*
* Use of logit() is preferred, but these are guaranteed to display on
* /dev/console as well. Useful for critical errors and early debug.
*/
#define _l(prio, fmt, args...) do { if (_slup) warnx(fmt, ##args); logit(prio, fmt "\n", ##args); } while (0)
#define _m(prio, fmt, args...) do { _l(prio, "%s() - " fmt, __func__, ##args); } while (0)
#define _d(fmt, args...) do { if (debug) { _m(LOG_DEBUG, fmt, ##args); } } while (0)
#define _w(fmt, args...) do { _m(LOG_WARNING, fmt, ##args); } while (0)
#define _e(fmt, args...) do { _m(LOG_ERR, fmt, ##args); } while (0)
#define _pe(fmt, args...) do { _m(LOG_ERR, fmt ". Error %d: %s", ##args, errno, strerror(errno)); } while (0)
extern int _slup; /* INTERNAL: Is syslog up yet? */
char *strip_line (char *line);
void runlevel_set (int pre, int now);
char *runlevel_string (int levels);
int pid_alive (pid_t pid);
char *pid_get_name (pid_t pid, char *name, size_t len);
void procname_set (char *name, char *args[]);
void print (int action, const char *fmt, ...);
void print_desc (char *action, char *desc);
int print_result (int fail);
void logit (int prio, const char *fmt, ...);
int start_process (char *cmd, char *args[], int console);
int getuser (char *username, char **home);
int getgroup (char *group);
void set_hostname (char **hostname);
int complete (char *cmd, int pid);
int run (char *cmd);
int run_interactive (char *cmd, char *fmt, ...);
pid_t run_getty (char *tty, char *speed, char *term, int console);
int run_parts (char *dir, char *cmd);
#ifndef HAVE_GETFSENT
struct fstab {
char *fs_spec; /* block device name */
char *fs_file; /* mount point */
char *fs_vfstype; /* file-system type */
char *fs_mntops; /* mount options */
const char *fs_type; /* rw/rq/ro/sw/xx option */
int fs_freq; /* dump frequency, in days */
int fs_passno; /* pass number on parallel dump */
};
int setfsent(void);
void endfsent(void);
struct fstab *getfsent(void);
#endif /* HAVE_GETFSENT */
#endif /* FINIT_HELPERS_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+735
View File
@@ -0,0 +1,735 @@
/* Classic inetd services launcher for Finit
*
* 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 <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <uev/uev.h>
#include <lite/lite.h>
#include "finit.h"
#include "inetd.h"
#include "helpers.h"
#include "private.h"
#include "service.h"
#define ENABLE_SOCKOPT(sd, level, opt) \
do { \
int val = 1; \
if (setsockopt(sd, level, opt, &val, sizeof(val)) < 0) \
logit(LOG_CRIT, "Failed enabling %s on %s service", \
#opt, inetd->name); \
} while (0);
/* Peek into SOCK_DGRAM socket to figure out where an inbound packet comes from. */
static int inetd_dgram_peek(int sd, char *ifname)
{
char cmbuf[0x100];
struct msghdr msgh;
struct cmsghdr *cmsg;
memset(&msgh, 0, sizeof(msgh));
msgh.msg_control = cmbuf;
msgh.msg_controllen = sizeof(cmbuf);
if (recvmsg(sd, &msgh, MSG_PEEK) < 0)
return -1;
for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg; cmsg = CMSG_NXTHDR(&msgh,cmsg)) {
struct in_pktinfo *ipi = (struct in_pktinfo *)CMSG_DATA(cmsg);
if (cmsg->cmsg_level != SOL_IP || cmsg->cmsg_type != IP_PKTINFO)
continue;
if_indextoname(ipi->ipi_ifindex, ifname);
return 0;
}
return -1;
}
/* Peek into SOCK_STREAM on accepted client socket to figure out inbound interface */
static int inetd_stream_peek(int sd, char *ifname)
{
struct ifaddrs *ifaddr, *ifa;
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
if (-1 == getsockname(sd, (struct sockaddr *)&sin, &len))
return -1;
if (-1 == getifaddrs(&ifaddr))
return -1;
for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
size_t len = sizeof(struct in_addr);
struct sockaddr_in *iin;
if (!ifa->ifa_addr)
continue;
if (ifa->ifa_addr->sa_family != AF_INET)
continue;
iin = (struct sockaddr_in *)ifa->ifa_addr;
if (!memcmp(&sin.sin_addr, &iin->sin_addr, len)) {
strncpy(ifname, ifa->ifa_name, IF_NAMESIZE);
break;
}
}
freeifaddrs(ifaddr);
return 0;
}
static int get_stdin(svc_t *svc)
{
int stdin = svc->inetd.watcher.fd;
char ifname[IF_NAMESIZE] = "UNKNOWN";
if (svc->inetd.type == SOCK_STREAM) {
/* Open new client socket from server socket */
stdin = accept(stdin, NULL, NULL);
if (stdin < 0) {
logit(LOG_CRIT, "Failed accepting inetd service %d/tcp", svc->inetd.port);
return -1;
}
_d("New client socket %d accepted for inetd service %d/tcp", stdin, svc->inetd.port);
inetd_stream_peek(stdin, ifname);
} else { /* SOCK_DGRAM */
inetd_dgram_peek(stdin, ifname);
}
if (!inetd_is_allowed(&svc->inetd, ifname)) {
logit(LOG_INFO, "Service %s on %s:%d is not allowed", svc->inetd.name, ifname, svc->inetd.port);
if (svc->inetd.type == SOCK_STREAM)
close(stdin);
return -1;
}
return stdin;
}
/* Socket callback, looks up correct svc and starts it as an inetd service */
static void socket_cb(uev_t *UNUSED(w), void *arg, int UNUSED(events))
{
svc_t *svc = (svc_t *)arg, *task;
int stdin;
_d("Got event on %s socket ...", svc->cmd);
stdin = get_stdin(svc);
if (stdin < 0) {
logit(LOG_CRIT, "%s: Unable to accept incoming connection", svc->cmd);
return;
}
/*
* Make sure to disable O_NONBLOCK on the descriptor before
* passing it to the inetd service, that's what is expected.
*/
if (fcntl(stdin, F_SETFL, fcntl(stdin, F_GETFL, 0) & ~O_NONBLOCK) < 0) {
logit(LOG_CRIT, "Failed disabling non-blocking on %s socket", svc->cmd);
return;
}
task = svc_new(svc->cmd, svc->inetd.next_id++, SVC_TYPE_INETD_CONN);
if (!task) {
logit(LOG_CRIT, "%s: Unable to allocate service for inetd client",
svc->cmd);
return;
}
if (!svc->inetd.forking) {
svc_busy(svc);
service_step(svc);
}
/* Copy inherited attributes from inetd service's svc */
task->runlevels = svc->runlevels;
/* Only copy the most relevant parts of inetd, in particular we
* must *not* copy the watcher data to the clone! */
task->inetd.svc = svc;
task->inetd.cmd = svc->inetd.cmd;
task->inetd.type = svc->inetd.type;
memcpy(task->cond, svc->cond, sizeof(task->cond));
memcpy(task->username, svc->username, sizeof(task->username));
memcpy(task->group, svc->group, sizeof(task->group));
memcpy(task->args, svc->args, sizeof(task->args));
snprintf(task->desc, sizeof(task->desc), "%s connection", svc->desc);
task->stdin_fd = stdin;
service_step(task);
}
/*
* Refuse service if the request specifies a reply port corresponding to any internal service.
* This is done as a defense against looping attacks; the remote IP address is logged.
* http://www.freebsd.org/cgi/man.cgi?inetd(8)
* https://svnweb.freebsd.org/base/head/usr.sbin/inetd/inetd.c?revision=298909&view=markup#l2094
*/
int inetd_check_loop(struct sockaddr *sa, socklen_t len, char *name)
{
svc_t *svc;
char pname[NI_MAXHOST];
for (svc = svc_inetd_iterator(1); svc; svc = svc_inetd_iterator(0)) {
inetd_t *i = &svc->inetd;
if (!i->builtin || i->type != SOCK_DGRAM)
continue;
if (((const struct sockaddr_in *)sa)->sin_port == i->port) {
getnameinfo(sa, len, pname, sizeof(pname), NULL, 0, NI_NUMERICHOST);
logit(LOG_WARNING, "%s/%s:%s/%s loop request REFUSED from %s", i->name, "UDP", name, "UDP", pname);
return 1;
}
}
return 0;
}
/* Launch Inet socket for service.
* TODO: Add filtering ALLOW/DENY per interface.
*/
static int spawn_socket(inetd_t *inetd)
{
int sd;
socklen_t len = sizeof(struct sockaddr);
struct sockaddr_in s;
if (!inetd->type) {
logit(LOG_CRIT, "Invalid inetd service %s, skipping ...", inetd->name);
return -EINVAL;
}
_d("Spawning server socket for inetd %s, type %s ...", inetd->name, inetd->type == SOCK_STREAM ? "stream" : "dgram");
sd = socket(AF_INET, inetd->type | SOCK_NONBLOCK | SOCK_CLOEXEC, inetd->proto);
if (-1 == sd) {
logit(LOG_CRIT, "Failed opening inetd socket type %d proto %d", inetd->type, inetd->proto);
return -errno;
}
ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEADDR);
#ifdef SO_REUSEPORT
ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEPORT);
#endif
memset(&s, 0, sizeof(s));
s.sin_family = AF_INET;
s.sin_addr.s_addr = INADDR_ANY;
s.sin_port = htons(inetd->port);
if (bind(sd, (struct sockaddr *)&s, len) < 0) {
logit(LOG_CRIT, "Failed binding to port %d, maybe another %s server is already running?",
inetd->port, inetd->name);
close(sd);
return -errno;
}
if (inetd->port) {
if (inetd->type == SOCK_STREAM) {
if (-1 == listen(sd, 10)) {
logit(LOG_CRIT, "Failed listening to inetd service %s", inetd->name);
close(sd);
return -errno;
}
} else { /* SOCK_DGRAM */
/* Set extra sockopt to get ifindex from inbound packets */
ENABLE_SOCKOPT(sd, SOL_IP, IP_PKTINFO);
}
}
if (uev_io_init(ctx, &inetd->watcher, socket_cb, inetd->svc, sd, UEV_READ)) {
logit(LOG_CRIT, "Failed setting up inetd watcher for %s", inetd->name);
close(sd);
return -errno;
}
return 0;
}
int inetd_start(inetd_t *inetd)
{
int sd;
char buf[BUFSIZ];
ssize_t len;
sd = inetd->watcher.fd;
if (sd == -1)
return spawn_socket(inetd);
/* Read anything lingering, or clean up socket after failure */
len = recv(sd, buf, sizeof(buf), MSG_DONTWAIT);
if (len > 0)
_d("Read %zd lingering bytes from socket before (re)starting %s ...", len, inetd->svc->cmd);
/* Restore O_NONBLOCK for socket */
if (fcntl(sd, F_SETFL, fcntl(sd, F_GETFL, 0) | O_NONBLOCK)) {
logit(LOG_CRIT, "Cannot safely (re)start %s inetd service", inetd->svc->cmd);
return -errno;
}
_d("Re-starting %s socket watcher ...", inetd->svc->cmd);
uev_io_start(&inetd->watcher);
return 0;
}
void inetd_stop(inetd_t *inetd)
{
if (inetd->watcher.fd != -1) {
_d("Stopping %s socket watcher ...", inetd->svc->cmd);
uev_io_stop(&inetd->watcher);
/* For dgram inetd services we block the parent SVC
* and halt the watcher, so don't close the socket! */
if (!svc_is_busy(inetd->svc)) {
_d("Shutting down inet socket %d ...", inetd->watcher.fd);
if (inetd->type == SOCK_STREAM)
shutdown(inetd->watcher.fd, SHUT_RDWR);
close(inetd->watcher.fd);
inetd->watcher.fd = -1;
}
}
}
static struct servent *fallback_service(char *service, char *proto)
{
int service_num;
static struct servent lfs;
service_num = fgetint("/etc/services", " \n\t", service);
if (service_num > 0) {
lfs.s_name = service;
lfs.s_port = htons(service_num);
lfs.s_proto = NULL;
if (!strcmp("tcp", proto) || !strcmp("udp", proto))
lfs.s_proto = proto;
return &lfs;
}
return NULL;
}
static struct servent *getent_service(char *service, char *proto)
{
struct servent *ent;
#ifdef ENABLE_STATIC
ent = fallback_service(service, proto);
#else
ent = getservbyname(service, proto);
if (!ent)
ent = fallback_service(service, proto);
#endif
return ent;
}
static struct protoent *fallback_proto(char *proto)
{
int proto_num;
static struct protoent lfp;
proto_num = fgetint("/etc/protocols", " \n\t", proto);
if (proto_num > 0) {
lfp.p_name = proto;
lfp.p_proto = proto_num;
return &lfp;
}
return NULL;
}
static struct protoent *getent_proto(char *proto)
{
struct protoent *ent;
#ifdef ENABLE_STATIC
ent = fallback_proto(proto);
#else
ent = getprotobyname(proto);
if (!ent)
ent = fallback_proto(proto);
#endif
return ent;
}
static int getent(char *service, char *proto, struct servent **sv, struct protoent **pv)
{
if (!fexist("/etc/services") || !fexist("/etc/protocols")) {
_w("Cannot register inetd %s/%s, system missing /etc/services or /etc/protocols", service, proto);
return errno = ECANCELED;
}
*sv = getent_service(service, proto);
if (!*sv) {
const char *errstr;
static struct servent s;
s.s_name = service;
s.s_port = strtonum(service, 1, UINT16_MAX, &errstr);
s.s_proto = NULL;
if (!strcmp("tcp", proto) || !strcmp("udp", proto))
s.s_proto = proto;
if (errstr || !s.s_proto) {
_e("Invalid/unknown inetd service, cannot create custom entry");
return errno = EINVAL;
}
_d("Creating cutom inetd service %s/%s", service, proto);
s.s_port = htons(s.s_port);
*sv = &s;
}
if (pv && (*sv)->s_proto) {
*pv = getent_proto((*sv)->s_proto);
if (!*pv) {
_e("Cannot find proto %s, skipping ...", (*sv)->s_proto);
return errno = EINVAL;
}
}
return 0;
}
/*
* Find exact match.
*/
static inetd_filter_t *find_filter(inetd_t *inetd, char *ifname)
{
inetd_filter_t *filter;
if (!ifname)
ifname = "*";
TAILQ_FOREACH(filter, &inetd->filters, link) {
_d("Checking filters for %s: '%s' vs '%s' (exact match) ...",
inetd->name, filter->ifname, ifname);
if (!strcmp(filter->ifname, ifname))
return filter;
}
return NULL;
}
/*
* First try exact match, then fall back to any match.
*/
inetd_filter_t *inetd_filter_match(inetd_t *inetd, char *ifname)
{
inetd_filter_t *filter = find_filter(inetd, ifname);
if (filter)
return filter;
TAILQ_FOREACH(filter, &inetd->filters, link) {
_d("Checking filters for %s: '%s' vs '%s' (any match) ...",
inetd->name, filter->ifname, ifname);
if (!strcmp(filter->ifname, "*"))
return filter;
}
return NULL;
}
int inetd_flush(inetd_t *inetd)
{
inetd_filter_t *filter, *next;
TAILQ_FOREACH_SAFE(filter, &inetd->filters, link, next) {
TAILQ_REMOVE(&inetd->filters, filter, link);
free(filter);
}
return 0;
}
/* Poor man's tcpwrappers filtering */
int inetd_allow(inetd_t *inetd, char *ifname)
{
inetd_filter_t *filter;
if (!inetd)
return errno = EINVAL;
if (!ifname)
ifname = "*";
filter = inetd_filter_match(inetd, ifname);
if (filter) {
_d("Filter %s for inetd %s already exists, skipping ...", ifname, inetd->name);
return 0;
}
_d("Allow iface %s for service %s (port %d)", ifname, inetd->name, inetd->port);
filter = calloc(1, sizeof(*filter));
if (!filter) {
_e("Out of memory, cannot add filter to service %s", inetd->name);
return errno = ENOMEM;
}
filter->deny = 0;
strlcpy(filter->ifname, ifname, sizeof(filter->ifname));
TAILQ_INSERT_TAIL(&inetd->filters, filter, link);
return 0;
}
int inetd_deny(inetd_t *inetd, char *ifname)
{
inetd_filter_t *filter;
if (!inetd)
return errno = EINVAL;
if (!ifname)
ifname = "*";
filter = find_filter(inetd, ifname);
if (filter) {
_d("%s filter %s for inetd %s already exists, cannot set deny filter for same, skipping ...",
filter->deny ? "Deny" : "Allow", ifname, inetd->name);
return 1;
}
_d("Deny iface %s for service %s (port %d)", ifname, inetd->name, inetd->port);
filter = calloc(1, sizeof(*filter));
if (!filter) {
_e("Out of memory, cannot add filter to service %s", inetd->name);
return errno = ENOMEM;
}
filter->deny = 1;
strlcpy(filter->ifname, ifname, sizeof(filter->ifname));
TAILQ_INSERT_TAIL(&inetd->filters, filter, link);
return 0;
}
int inetd_is_allowed(inetd_t *inetd, char *ifname)
{
inetd_filter_t *filter;
if (!inetd) {
errno = EINVAL;
return 0;
}
filter = inetd_filter_match(inetd, ifname);
if (filter) {
_d("Found matching filter for %s, deny: %d ... ", inetd->name, filter->deny);
return !filter->deny;
}
_d("No matching filter for %s ... ", inetd->name);
return 0;
}
int inetd_match(inetd_t *inetd, char *service, char *proto)
{
struct servent *sv = NULL;
struct protoent *pv = NULL;
if (!inetd || !service || !proto)
return errno = EINVAL;
if (strncmp(inetd->name, service, sizeof(inetd->name)))
return 0;
if (getent(service, proto, &sv, &pv))
return 0;
if (inetd->proto == ntohs(pv->p_proto) &&
inetd->port == ntohs(sv->s_port))
return 1;
return 0;
}
/* Compose presentable string of inetd filters: !eth0,eth1,eth2,!eth3 */
int inetd_filter_str(inetd_t *inetd, char *str, size_t len)
{
int prev = 0;
char buf[42];
inetd_filter_t *filter;
if (!inetd || !str || len <= 0) {
_e("Dafuq?");
return 1;
}
snprintf(str, len, "%s allow %s ", inetd->name,
inetd->type == SOCK_DGRAM ? "UDP" : "TCP");
TAILQ_FOREACH(filter, &inetd->filters, link) {
char ifname[IFNAMSIZ];
if (filter->deny)
continue;
if (!strlen(filter->ifname))
snprintf(ifname, sizeof(ifname), "*");
else
strlcpy(ifname, filter->ifname, sizeof(ifname));
snprintf(buf, sizeof(buf), "%s%s:%d", prev ? "," : "",
ifname, inetd->port);
prev = 1;
strlcat(str, buf, len);
}
prev = 0;
TAILQ_FOREACH(filter, &inetd->filters, link) {
char ifname[IFNAMSIZ];
if (!filter->deny)
continue;
if (!prev) {
snprintf(buf, sizeof(buf), " deny ");
strlcat(str, buf, len);
}
if (!strlen(filter->ifname))
snprintf(ifname, sizeof(ifname), "*");
else
strlcpy(ifname, filter->ifname, sizeof(ifname));
snprintf(buf, sizeof(buf), "%s%s", prev ? "," : "", ifname);
prev = 1;
strlcat(str, buf, len);
}
return 0;
}
svc_t *inetd_find_svc(char *path, char *service, char *proto)
{
svc_t *svc;
for (svc = svc_inetd_iterator(1); svc; svc = svc_inetd_iterator(0)) {
if (strncmp(path, svc->cmd, strlen(svc->cmd)))
continue;
if (inetd_match(&svc->inetd, service, proto)) {
_d("Found a matching inetd svc for %s %s %s", path, service, proto);
return svc;
}
}
return NULL;
}
/*
* This function is called to add a new, unique, inetd service. When an
* ifname is given as argument this means *only* run service on this
* interface. If a similar service runs on another port, this function
* must add this ifname as "deny" to that other service.
*
* Example:
* inetd ssh@eth0:222/tcp nowait [2345] /usr/sbin/sshd -i
* inetd ssh/tcp nowait [2345] /usr/sbin/sshd -i
*
* In this example eth0:222 is very specific, so when ssh/tcp (default)
* is added we must find the previous 'ssh' service and add its eth0 as
* deny, so we don't accept port 22 session on eth0.
*
* In the reverse case, where the default (ssh/tcp) entry is listed
* before the specific (eth0:222), the new inetd service must find the
* (any!) previous rule and add its ifname to their deny list.
*
* If equivalent service exists already service_register() will instead call
* inetd_allow().
*/
int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forking, svc_t *svc)
{
int result;
struct servent *sv = NULL;
struct protoent *pv = NULL;
if (!inetd || !service || !proto)
return errno = EINVAL;
result = getent(service, proto, &sv, &pv);
if (result)
return result;
/* Setup defaults */
inetd->std = 1;
inetd->port = ntohs(sv->s_port);
inetd->proto = pv->p_proto;
inetd->forking = !!forking;
inetd->next_id = 2;
if (!name)
name = service;
strlcpy(inetd->name, name, sizeof(inetd->name));
TAILQ_INIT(&inetd->filters);
/* Naïve mapping tcp->stream, udp->dgram, other->dgram */
if (!strcasecmp(sv->s_proto, "tcp"))
inetd->type = SOCK_STREAM;
else
inetd->type = SOCK_DGRAM;
if (inetd->type == SOCK_DGRAM && inetd->forking) {
logit(LOG_WARNING, "%s: 'nowait' is not applicable on UDP services, ignoring", svc->cmd);
inetd->forking = 0;
}
/* Reset descriptor, used internally */
inetd->watcher.fd = -1;
/* Setup socket callback argument */
inetd->svc = svc;
_d("New service %s (default port %d proto %s:%d)", name, inetd->port, sv->s_proto, pv->p_proto);
return 0;
}
int inetd_del(inetd_t *inetd)
{
svc_unblock(inetd->svc);
inetd_stop(inetd);
return inetd_flush(inetd);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+82
View File
@@ -0,0 +1,82 @@
/* Classic inetd services launcher for Finit
*
* 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_INETD_H_
#define FINIT_INETD_H_
#include <netdb.h>
#include <net/if.h>
#include <uev/uev.h>
#include <lite/queue.h> /* BSD sys/queue.h API */
typedef struct svc svc_t;
typedef struct inetd_filter {
TAILQ_ENTRY(inetd_filter) link;
int deny; /* 0:allow, 1:deny */
char ifname[IFNAMSIZ]; /* E.g., eth0 */
} inetd_filter_t;
typedef struct {
uev_t watcher;
svc_t *svc; /* svc_t pointer for the socket callback */
int type; /* Socket type: SOCK_STREAM/SOCK_DGRAM */
int std; /* Standard proto/port from /etc/services */
int proto;
int port;
int forking;
int builtin; /* Set by built-in inetd services only */
int next_id; /* Next child job's id */
char name[10];
int (*cmd)(int type); /* internal inetd service, like 'time' */
TAILQ_HEAD(, inetd_filter) filters;
} inetd_t;
int inetd_check_loop(struct sockaddr *sa, socklen_t len, char *name);
int inetd_start (inetd_t *inetd);
void inetd_stop (inetd_t *inetd);
int inetd_new (inetd_t *inetd, char *name, char *service, char *proto, int forking, svc_t *svc);
int inetd_del (inetd_t *inetd);
svc_t *inetd_find_svc (char *path, char *service, char *proto);
int inetd_match (inetd_t *inetd, char *service, char *proto);
int inetd_filter_str(inetd_t *inetd, char *str, size_t len);
int inetd_flush (inetd_t *inetd);
int inetd_allow (inetd_t *inetd, char *ifname);
int inetd_deny (inetd_t *inetd, char *ifname);
int inetd_is_allowed(inetd_t *inetd, char *ifname);
#endif /* FINIT_INETD_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+582
View File
@@ -0,0 +1,582 @@
/* New client tool, complements old /dev/initctl API and telinit tool
*
* Copyright (c) 2015-2016 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 <err.h>
#include <ctype.h>
#include <getopt.h>
#include <glob.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <lite/lite.h>
#include "config.h"
#include "finit.h"
#include "cond.h"
#include "helpers.h"
#include "service.h"
#include "util.h"
#include "utmp-api.h"
#define _PATH_COND _PATH_VARRUN "finit/cond/"
typedef struct {
char *cmd;
int (*cb)(char *arg);
} command_t;
int debug = 0;
int verbose = 0;
int silent = 1; /* For helpers.c */
int runlevel = 0;
static int do_send(struct init_request *rq, ssize_t len)
{
int sd, result = 255;
struct sockaddr_un sun = {
.sun_family = AF_UNIX,
.sun_path = INIT_SOCKET,
};
sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (-1 == sd)
return -1;
if (connect(sd, (struct sockaddr*)&sun, sizeof(sun)) == -1)
goto error;
if (write(sd, rq, len) != len)
goto error;
if (read(sd, rq, len) != len)
goto error;
result = 0;
goto exit;
error:
perror("Failed communicating with finit");
exit:
close(sd);
return result;
}
static int runlevel_get(void)
{
int sd, result = 255;
struct init_request rq;
struct sockaddr_un sun = {
.sun_family = AF_UNIX,
.sun_path = INIT_SOCKET,
};
memset(&rq, 0, sizeof(rq));
rq.cmd = INIT_CMD_GET_RUNLEVEL;
rq.magic = INIT_MAGIC;
sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (-1 == sd)
return -1;
if (connect(sd, (struct sockaddr*)&sun, sizeof(sun)) == -1)
goto error;
if (write(sd, &rq, sizeof(rq)) != sizeof(rq))
goto error;
if (read(sd, &rq, sizeof(rq)) != sizeof(rq))
goto error;
result = rq.runlevel;
goto exit;
error:
perror("Failed communicating with finit");
result = -1;
exit:
close(sd);
return result;
}
static int toggle_debug(char *UNUSED(arg))
{
struct init_request rq = {
.magic = INIT_MAGIC,
.cmd = INIT_CMD_DEBUG,
};
return do_send(&rq, sizeof(rq));
}
static int do_log(char *svc)
{
char cmd[128];
char *logfile = "/var/log/messages";
if (!svc || !svc[0])
svc = "finit";
if (!fexist(logfile))
logfile = "/var/log/syslog";
snprintf(cmd, sizeof(cmd), "cat %s | grep %s | tail -10", logfile, svc);
return system(cmd);
}
static int do_runlevel(char *arg)
{
struct init_request rq = {
.magic = INIT_MAGIC,
.cmd = INIT_CMD_RUNLVL,
.runlevel = (int)arg[0],
};
/* Not compatible with the SysV runlevel(8) command, it prints
* "PREVLEVEL RUNLEVEL", we just print the current runlevel. */
if (!rq.runlevel) {
printf("%d\n", runlevel_get());
return 0;
}
return do_send(&rq, sizeof(rq));
}
static int do_svc(int cmd, char *arg)
{
struct init_request rq = {
.magic = INIT_MAGIC,
.cmd = cmd,
};
strlcpy(rq.data, arg, sizeof(rq.data));
return do_send(&rq, sizeof(rq));
}
static int do_emit (char *arg) { return do_svc(INIT_CMD_EMIT, arg); }
static int do_reload (char *arg) { return do_svc(INIT_CMD_RELOAD, arg); }
static int missing(char *job, int id)
{
char idstr[10] = "";
if (!job)
job = "";
if (id > 1)
snprintf(idstr, sizeof(idstr), ":%d", id);
if (verbose)
fprintf(stderr, "No such service %s%s\n", job, idstr);
return 1;
}
/*
* This is a wrapper for do_svc() that adds a simple sanity check of
* the service(s) provided as argument. If a service does not exist
* we make sure to return an error code.
*/
static int do_startstop(int cmd, char *arg)
{
int ret;
char *svcs;
if (!arg) {
fprintf(stderr, "Usage: initctl %s <JOB|NAME>[:ID]",
cmd == INIT_CMD_START_SVC ? "start" :
(cmd == INIT_CMD_STOP_SVC ? "stop" : "restart"));
return 1;
}
svcs = strdup(arg);
if (!svcs) {
perror("Failed validating service(s) name");
return 1;
}
/* Include \0 in len, needed by parser */
ret = svc_parse_jobstr(svcs, strlen(svcs) + 1, NULL, missing);
free(svcs);
if (ret)
return ret;
return do_svc(cmd, arg);
}
static int do_start (char *arg) { return do_startstop(INIT_CMD_START_SVC, arg); }
static int do_stop (char *arg) { return do_startstop(INIT_CMD_STOP_SVC, arg); }
static int do_restart(char *arg) { return do_startstop(INIT_CMD_RESTART_SVC, arg); }
static void show_cond_one(const char *_conds)
{
static char conds[MAX_ARG_LEN];
char *cond;
strlcpy(conds, _conds, sizeof(conds));
putchar('<');
for (cond = strtok(conds, ","); cond; cond = strtok(NULL, ",")) {
if (cond != conds)
putchar(',');
switch (cond_get(cond)) {
case COND_ON:
printf("+%s", cond);
break;
case COND_FLUX:
printf("\e[1m~%s\e[0m", cond);
break;
case COND_OFF:
printf("\e[1m-%s\e[0m", cond);
break;
}
}
putchar('>');
}
static int do_cond_magic(char op, char *cond)
{
cond[0] = op;
return do_emit(cond);
}
static int do_cond_set (char *cond) { return do_cond_magic('+', cond); }
static int do_cond_clear(char *cond) { return do_cond_magic('-', cond); }
static int do_cond_show(char *UNUSED(arg))
{
svc_t *svc;
enum cond_state cond;
if (verbose) {
glob_t gl;
printf("Asserted conditions (taken from %s)\n", _PATH_COND);
printf("===============================================================================\n");
if (!glob(_PATH_COND "*/*/*", 0, NULL, &gl)) {
size_t i;
for (i = 0; i < gl.gl_pathc; i++) {
char *cond, *name = gl.gl_pathv[i];
struct stat st;
if (stat(name, &st) || S_ISDIR(st.st_mode))
continue;
cond = name + strlen(_PATH_COND);
printf("\t%s\n", cond);
}
globfree(&gl);
}
puts("");
}
printf("PID Service Status Condition (+ on, ~ flux, - off)\n");
printf("===============================================================================\n");
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (!svc->cond[0])
continue;
cond = cond_get_agg(svc->cond);
printf("%-6d %-20.20s ", svc->pid, svc->cmd);
if (cond == COND_ON)
printf("%-6.6s ", condstr(cond));
else
printf("\e[1m%-6.6s\e[0m ", condstr(cond));
show_cond_one(svc->cond);
puts("");
}
return 0;
}
static int do_cond(char *cmd)
{
int c;
char *arg;
command_t command[] = {
{ "set", do_cond_set },
{ "clear", do_cond_clear },
{ "show", do_cond_show },
{ NULL, NULL }
};
arg = strpbrk(cmd, " ");
if (arg) {
*arg = 0;
for (c = 0; command[c].cmd; c++) {
if (!strcmp(command[c].cmd, cmd))
return command[c].cb(arg);
}
}
return do_cond_show(NULL);
}
static int do_signal(int signo, const char *msg)
{
if (kill(1, signo))
err(1, "Failed signalling init to %s", msg);
return 0;
}
static int do_halt (char *UNUSED(arg)) { return do_signal(SIGUSR1, "halt"); }
static int do_poweroff(char *UNUSED(arg)) { return do_signal(SIGUSR2, "power off"); }
static int do_reboot (char *UNUSED(arg)) { return do_signal(SIGTERM, "reboot"); }
static int do_utmp(char *UNUSED(cmd))
{
return utmp_show(_PATH_WTMP) ||
utmp_show(_PATH_UTMP);
}
static int show_version(char *UNUSED(arg))
{
puts("v" PACKAGE_VERSION);
return 0;
}
/* In verbose mode we skip the header and each service description.
* This in favor of having all info on one line so a machine can more
* easily parse it. */
static int show_status(char *arg)
{
svc_t *svc;
/* Fetch UTMP runlevel, needed for svc_status() call below */
runlevel = runlevel_get();
if (arg && arg[0]) {
int id = 1;
char *ptr, *next;
ptr = strchr(arg, ':');
if (ptr) {
*ptr++ = 0;
next = strchr(ptr, ' ');
if (next)
*next = 0;
id = atonum(ptr);
}
if (isdigit(arg[0]))
svc = svc_find_by_jobid(atonum(arg), id);
else
svc = svc_find_by_nameid(arg, id);
if (!svc)
return 1;
printf("%s\n", svc_status(svc));
return 0;
}
if (!verbose) {
printf("# Status PID Runlevels Service Description\n");
printf("===============================================================================\n");
}
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
char jobid[10], args[512] = "", *lvls;
if (svc_is_unique(svc))
snprintf(jobid, sizeof(jobid), "%d", svc->job);
else
snprintf(jobid, sizeof(jobid), "%d:%d", svc->job, svc->id);
printf("%-5s %7s ", jobid, svc_status(svc));
if (svc_is_inetd(svc))
printf("inetd ");
else
printf("%-6d ", svc->pid);
lvls = runlevel_string(svc->runlevels);
if (strchr(lvls, '\e'))
printf("%-18.18s ", lvls);
else
printf("%-10.10s ", lvls);
if (!verbose) {
char *name = strrchr(svc->cmd, '/');
if (!name)
name = svc->cmd;
else
name++;
printf("%-20.20s %-21.21s\n", name, svc->desc);
continue;
}
#ifdef INETD_ENABLED
if (svc_is_inetd(svc)) {
char *info;
struct init_request rq = {
.magic = INIT_MAGIC,
.cmd = INIT_CMD_QUERY_INETD,
};
snprintf(rq.data, sizeof(rq.data), "%s", jobid);
if (do_send(&rq, sizeof(rq))) {
snprintf(args, sizeof(args), "Unknown inetd");
info = args;
} else {
info = rq.data;
if (strcmp("internal", svc->cmd)) {
char *ptr = strchr(info, ' ');
if (ptr)
info = ptr + 1;
}
}
printf("%s %s\n", svc->cmd, info);
}
else
#endif /* INETD_ENABLED */
{
int i;
for (i = 1; i < MAX_NUM_SVC_ARGS; i++) {
strlcat(args, svc->args[i], sizeof(args));
strlcat(args, " ", sizeof(args));
}
printf("%s %s\n", svc->cmd, args);
}
}
return 0;
}
static int usage(int rc)
{
fprintf(stderr, "Usage: %s [OPTIONS] <COMMAND>\n\n"
"Options:\n"
" -d, --debug Debug initctl (client)\n"
" -v, --verbose Verbose output\n"
" -h, --help This help text\n\n"
"Commands:\n"
" debug Toggle Finit (daemon) debug\n"
" help This help text\n"
" log [NAME] Show ten last Finit, or NAME, messages from syslog\n"
" reload Reload *.conf in /etc/finit.d and activate changes\n"
" runlevel [0-9] Show or set runlevel: 0 halt, 6 reboot\n"
" status | show Show status of services\n"
" cond set <COND> Set (assert) condition => +COND\n"
" cond clear <COND> Clear (deassert) condition => -COND\n"
" cond show Show condition status\n"
" start <JOB|NAME>[:ID] Start service by job# or name, with optional ID\n"
" stop <JOB|NAME>[:ID] Stop/Pause a running service by job# or name\n"
" restart <JOB|NAME>[:ID] Restart (stop/start) service by job# or name\n"
" reboot Reboot system\n"
" halt Halt system\n"
" poweroff Halt and power off system\n"
" utmp show Raw dump of UTMP/WTMP db\n"
" version Show Finit version\n\n", prognm);
return rc;
}
int main(int argc, char *argv[])
{
int c;
command_t command[] = {
{ "debug", toggle_debug },
{ "log", do_log },
{ "reload", do_reload },
{ "runlevel", do_runlevel },
{ "status", show_status },
{ "show", show_status }, /* Convenience alias */
{ "cond", do_cond },
{ "start", do_start },
{ "stop", do_stop },
{ "restart", do_restart },
{ "reboot", do_reboot },
{ "halt", do_halt },
{ "poweroff", do_poweroff },
{ "utmp", do_utmp },
{ "version", show_version },
{ NULL, NULL }
};
struct option long_options[] = {
{"help", 0, NULL, 'h'},
{"debug", 0, NULL, 'd'},
{"verbose", 0, NULL, 'v'},
{NULL, 0, NULL, 0}
};
progname(argv[0]);
while ((c = getopt_long(argc, argv, "dh?v", long_options, NULL)) != EOF) {
switch(c) {
case 'h':
case '?':
return usage(0);
case 'd':
debug = 1;
break;
case 'v':
verbose = 1;
break;
}
}
silent = !verbose;
if (optind < argc) {
char *cmd = argv[optind++];
char arg[120] = "";
while (optind < argc) {
strlcat(arg, argv[optind++], sizeof(arg));
strlcat(arg, " ", sizeof(arg));
}
for (c = 0; command[c].cmd; c++) {
if (!strcmp(command[c].cmd, cmd))
return command[c].cb(arg);
}
}
return usage(1);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+72
View File
@@ -0,0 +1,72 @@
/* Handle MD arrays
*
* Copyright (c) 2016 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 <glob.h>
#include <stdio.h>
#include <string.h>
#include "helpers.h"
static glob_t *get_arrays(void)
{
static glob_t arrays;
glob("/sys/block/md*", 0, NULL, &arrays);
if (arrays.gl_pathc > 0)
return &arrays;
return NULL;
}
/*
* If system has an MD raid, we must tell it to stop before continuing
* with the shutdown. Some controller cards, in particular the Intel(R)
* Matrix Storage Manager, must be properly notified.
*/
void mdadm_wait(void)
{
size_t i;
glob_t *gl;
char cmd[160];
/* Setup kernel specific settings, e.g. allow broadcast ping, etc. */
gl = get_arrays();
if (gl) {
for (i = 0; i < gl->gl_pathc; i++) {
char *array;
array = basename(gl->gl_pathv[i]);
snprintf(cmd, sizeof(cmd), "mdadm --wait-clean /dev/%s >/dev/null", array);
run_interactive(cmd, "Marking MD array %s as clean", array);
}
globfree(gl);
}
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+110
View File
@@ -0,0 +1,110 @@
/* Mount and unmount helpers
*
* Copyright (c) 2016 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 <mntent.h>
#include <sys/mount.h>
/*
* SysV init on Debian/Ubuntu skips these protected mount points
*
* It also skips anything below /proc, /sys and /run for good meausre so
* we do the same here. What we want is a list of non-protected tmpfs
* and regular filesystems that can be safely unmounted before we do
* swapoff, and remount / as read-only, respectively.
*/
static int is_protected(char *dir)
{
size_t i;
char *protected[] = {
"/sys", "/proc",
"/.dev", "/dev", "/dev/pts", "/dev/shm", "dev/.static/dev", "/dev/vcs",
"/run", "/var/run",
"/", NULL
};
char *any[] = {
"/proc/", "/sys/", "/run/", NULL
};
for (i = 0; protected[i]; i++) {
if (!strcmp(dir, protected[i]))
return 1;
}
for (i = 0; any[i]; i++) {
if (!strncmp(dir, any[i], strlen(any[i])))
return 1;
}
return 0;
}
static struct mntent *iterator(char *fstab)
{
static FILE *fp = NULL;
static struct mntent *mnt;
if (!fp && fstab) {
fp = setmntent(fstab, "r");
if (!fp)
return NULL;
}
while ((mnt = getmntent(fp))) {
if (is_protected(mnt->mnt_dir))
continue;
return mnt;
}
endmntent(fp);
fp = NULL;
return NULL;
}
void unmount_tmpfs(void)
{
struct mntent *mnt;
while ((mnt = iterator("/proc/mounts"))) {
if (!strcmp("tmpfs", mnt->mnt_fsname))
umount(mnt->mnt_dir);
}
}
void unmount_regular(void)
{
struct mntent *mnt;
while ((mnt = iterator("/proc/mounts"))) {
umount(mnt->mnt_dir);
}
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+98
View File
@@ -0,0 +1,98 @@
/* Misc. utility functions and C-library extensions for finit and its plugins
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <lite/lite.h>
#include "helpers.h"
/**
* pid_alive - Check if a given process ID is running
* @pid: Process ID to check for.
*
* Returns:
* %TRUE(1) if pid is alive (/proc/pid exists), otherwise %FALSE(0)
*/
int pid_alive(pid_t pid)
{
char name[24]; /* Enough for max pid_t */
snprintf(name, sizeof(name), "/proc/%d", pid);
return fexist(name);
}
/**
* pid_get_name - 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 *pid_get_name(pid_t pid, char *name, size_t len)
{
int ret = 1;
FILE *fp;
char *pname, path[32];
static char line[64];
snprintf(path, sizeof(path), "/proc/%d/status", pid);
fp = fopen(path, "r");
if (fp) {
if (fgets(line, sizeof (line), fp)) {
pname = line + 6; /* Skip first part of line --> "Name:\t" */
chomp(pname);
if (name)
strlcpy(name, pname, len);
ret = 0; /* Found it! */
}
fclose(fp);
}
if (ret)
return NULL;
if (name)
return name;
return pname;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+376
View File
@@ -0,0 +1,376 @@
/* Plugin based services architecture for finit
*
* Copyright (c) 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 <dlfcn.h> /* dlopen() et al */
#include <dirent.h> /* readdir() et al */
#include <poll.h>
#include <string.h>
#include <lite/lite.h>
#include <lite/queue.h> /* BSD sys/queue.h API */
#include "config.h"
#include "finit.h"
#include "private.h"
#include "helpers.h"
#include "plugin.h"
#define is_io_plugin(p) ((p)->io.cb && (p)->io.fd > 0)
#define SEARCH_PLUGIN(str) \
PLUGIN_ITERATOR(p, tmp) { \
if (!strcmp(p->name, str)) \
return p; \
}
static char *plugpath = NULL; /* Set by first load. */
static TAILQ_HEAD(plugin_head, plugin) plugins = TAILQ_HEAD_INITIALIZER(plugins);
#ifndef ENABLE_STATIC
static void check_plugin_depends(plugin_t *plugin);
#endif
static char *trim_ext(char *name)
{
char *ptr;
if (name) {
ptr = strstr(name, ".so");
if (!ptr)
ptr = strstr(name, ".c");
if (ptr)
*ptr = 0;
}
return name;
}
int plugin_register(plugin_t *plugin)
{
if (!plugin) {
errno = EINVAL;
return 1;
}
/* Setup default name if none is provided */
if (!plugin->name) {
#ifndef ENABLE_STATIC
Dl_info info;
if (dladdr(plugin, &info) && info.dli_fname)
plugin->name = basename(info.dli_fname);
#endif
if (!plugin->name)
plugin->name = "unknown";
}
plugin->name = trim_ext(strdup(plugin->name));
/* Already registered? */
if (plugin_find(plugin->name)) {
_d("... %s already loaded", plugin->name);
free(plugin->name);
return 0;
}
#ifndef ENABLE_STATIC
/* Resolve plugin dependencies */
check_plugin_depends(plugin);
#endif
TAILQ_INSERT_TAIL(&plugins, plugin, link);
return 0;
}
/* Not called, at the moment plugins cannot be unloaded. */
int plugin_unregister(plugin_t *plugin)
{
#ifndef ENABLE_STATIC
TAILQ_REMOVE(&plugins, plugin, link);
_d("%s exiting ...", plugin->name);
free(plugin->name);
#else
_d("Finit built statically, cannot unload %s ...", plugin->name);
#endif
return 0;
}
/**
* plugin_find - Find a plugin by name
* @name: With or without path, or .so extension
*
* This function uses an opporunistic search for a suitable plugin and
* returns the first match. Albeit with at least some measure of
* heuristics.
*
* First it checks for an exact match. If no match is found and @name
* starts with a slash the search ends. Otherwise a new search with the
* plugin path prepended to @name is made. Also, if @name does not end
* with .so it too is added to @name before searching.
*
* Returns:
* On success the pointer to the matching &plugin_t is returned,
* otherwise %NULL is returned.
*/
plugin_t *plugin_find(char *name)
{
plugin_t *p, *tmp;
if (!name) {
errno = EINVAL;
return NULL;
}
SEARCH_PLUGIN(name);
if (plugpath && name[0] != '/') {
int noext;
char path[CMD_SIZE];
noext = strcmp(name + strlen(name) - 3, ".so");
snprintf(path, sizeof(path), "%s%s%s%s", plugpath,
fisslashdir(plugpath) ? "" : "/",
name, noext ? ".so" : "");
SEARCH_PLUGIN(path);
}
errno = ENOENT;
return NULL;
}
/* Private daemon API *******************************************************/
/* Some hooks are called with a fixed argument, like HOOK_SVC_LOST */
void plugin_run_hook(hook_point_t no, void *arg)
{
plugin_t *p, *tmp;
PLUGIN_ITERATOR(p, tmp) {
if (p->hook[no].cb) {
_d("Calling %s hook n:o %d (arg: %p) ...", basename(p->name), no, arg);
p->hook[no].cb(arg ? arg : p->hook[no].arg);
}
}
}
/* Regular hooks are called with the registered plugin's argument */
void plugin_run_hooks(hook_point_t no)
{
plugin_run_hook(no, NULL);
}
/* Generic libev I/O callback, looks up correct plugin and calls its callback */
static void generic_io_cb(uev_t *w, void *arg, int events)
{
plugin_t *p = (plugin_t *)arg;
if (is_io_plugin(p) && p->io.fd == w->fd) {
/* Stop watcher, callback may close descriptor on us ... */
uev_io_stop(w);
_d("Calling I/O %s from runloop...", basename(p->name));
p->io.cb(p->io.arg, w->fd, events);
/* Update fd, may be changed by plugin callback, e.g., if FIFO */
uev_io_set(w, p->io.fd, p->io.flags);
}
}
int plugin_io_init(plugin_t *p)
{
if (!is_io_plugin(p))
return 0;
_d("Initializing plugin %s for I/O", basename(p->name));
if (uev_io_init(ctx, &p->watcher, generic_io_cb, p, p->io.fd, p->io.flags)) {
_e("Failed setting up I/O plugin %s", basename(p->name));
return 1;
}
return 0;
}
/* Setup any I/O callbacks for plugins that use them */
static int init_plugins(uev_ctx_t *UNUSED(ctx))
{
int fail = 0;
plugin_t *p, *tmp;
PLUGIN_ITERATOR(p, tmp) {
if (plugin_io_init(p))
fail++;
}
return fail;
}
#ifndef ENABLE_STATIC
/**
* load_one - Load one plugin
* @path: Path to finit plugins, usually %PLUGIN_PATH
* @name: Name of plugin, optionally ending in ".so"
*
* Loads a plugin from @path/@name[.so]. Note, if ".so" is missing from
* the plugin @name it is added before attempting to load.
*
* It is up to the plugin itself ot register itself as a "ctor" with the
* %PLUGIN_INIT macro so that plugin_register() is called automatically.
*
* Returns:
* POSIX OK(0) on success, non-zero otherwise.
*/
static int load_one(char *path, char *name)
{
int noext;
char sofile[CMD_SIZE];
void *handle;
plugin_t *plugin;
if (!path || !fisdir(path) || !name) {
errno = EINVAL;
return 1;
}
/* Compose full path, with optional .so extension, to plugin */
noext = strcmp(name + strlen(name) - 3, ".so");
snprintf(sofile, sizeof(sofile), "%s/%s%s", path, name, noext ? ".so" : "");
_d("Loading plugin %s ...", basename(sofile));
handle = dlopen(sofile, RTLD_LAZY | RTLD_LOCAL);
if (!handle) {
_e("Failed loading plugin %s: %s", sofile, dlerror());
return 1;
}
plugin = TAILQ_LAST(&plugins, plugin_head);
if (!plugin) {
_e("Plugin %s failed to register, unloading from memory", sofile);
dlclose(handle);
return 1;
}
/* Remember handle from dlopen() for plugin_unregister() */
plugin->handle = handle;
return 0;
}
/**
* check_plugin_depends - Check and load any plugins this one depends on.
* @plugin: Plugin with possible depends to check
*
* Very simple dependency resolver, should actually load the plugin of
* the correct .name, but currently loads a matching filename.
*
* Works, but only for now. A better way might be to have a try_load()
* that actually loads all plugins and checks their &plugin_t for the
* correct .name.
*/
static void check_plugin_depends(plugin_t *plugin)
{
int i;
for (i = 0; i < PLUGIN_DEP_MAX && plugin->depends[i]; i++) {
// _d("Plugin %s depends on %s ...", plugin->name, plugin->depends[i]);
if (plugin_find(plugin->depends[i])) {
// _d("OK plugin %s was already loaded.", plugin->depends[i]);
continue;
}
load_one(plugpath, plugin->depends[i]);
}
}
static int load_plugins(char *path)
{
int fail = 0;
DIR *dp;
struct dirent *entry;
if (!silent)
print_desc("Loading plugins", NULL);
dp = opendir(path);
if (!dp) {
_e("Failed, cannot open plugin directory %s: %s", path, strerror(errno));
return 1;
}
plugpath = path;
while ((entry = readdir(dp))) {
if (entry->d_name[0] == '.')
continue; /* Skip . and .. directories */
if (load_one(path, entry->d_name))
fail++;
}
closedir(dp);
return fail;
}
#else
static int load_plugins(char *UNUSED(path))
{
if (!silent)
print_desc("Initializing plugins", NULL);
return 0;
}
#endif /* ENABLE_STATIC */
int plugin_init(uev_ctx_t *ctx, char *path)
{
int fail = 1;
if (!load_plugins(path))
fail = init_plugins(ctx);
if (!silent)
print_result(fail);
return fail;
}
void plugin_exit(void)
{
#ifndef ENABLE_STATIC
plugin_t *p, *tmp;
PLUGIN_ITERATOR(p, tmp) {
if (dlclose(p->handle))
_e("Failed: unloading plugin %s: %s", p->name, dlerror());
}
#endif
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+145
View File
@@ -0,0 +1,145 @@
/* Plugin based services architecture for finit
*
* Copyright (c) 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_PLUGIN_H_
#define FINIT_PLUGIN_H_
#include <uev/uev.h>
#include <lite/queue.h> /* BSD sys/queue.h API */
#include "svc.h"
#ifndef PLUGIN_PATH /* Fallback for --enable-static builds */
#define PLUGIN_PATH "/dev/null"
#endif
#define PLUGIN_DEP_MAX 10
#define PLUGIN_IO_READ UEV_READ
#define PLUGIN_IO_WRITE UEV_WRITE
#define PLUGIN_INIT(x) static void __attribute__ ((constructor)) x(void)
#define PLUGIN_EXIT(x) static void __attribute__ ((destructor)) x(void)
#define PLUGIN_ITERATOR(x, tmp) TAILQ_FOREACH_SAFE(x, &plugins, link, tmp)
/*
* Predefined hook points for easier plugin debugging
*/
typedef enum {
/* Bootstrap hooks */
HOOK_ROOTFS_UP = 0,
HOOK_MOUNT_ERROR,
HOOK_BASEFS_UP,
HOOK_NETWORK_UP,
HOOK_SVC_UP,
HOOK_SYSTEM_UP,
/* Runtime hooks */
HOOK_SVC_RECONF,
HOOK_SVC_LOST,
HOOK_SVC_START,
HOOK_RUNLEVEL_CHANGE,
/* Shutdown hooks */
HOOK_SHUTDOWN,
HOOK_MAX_NUM
} hook_point_t;
/**
* plugin_t - Finit &plugin_t object
* @link: BSD sys/queue.h linked list node
* @name: Plugin name, or identifier to match against a &svc_t object
* @svc: Service callback for a loaded &svc_t object
* @hook: Hook callback definitions
* @io: I/O hook callback
*
* To setup an &svc_t object callback for a service monitor the @name
* must match the @svc_t @cmd exactly for them to "pair".
*
* A &plugin_t object may contain a service callback, several hooks
* and/or an I/O callback as well. This way all critical extensions
* to finit can fit in one single plugin, if needed.
*
* The "dynamic events" discussed in the svc callback is for external
* service plugins to implement. However, it can be anything that
* can cause a service to need to SIGHUP at runtime. E.g., acquiring
* a DHCP lease, or an interface going UP/DOWN. The event itself can
* be passed as an integer @event and any optional argument @event_arg
*
* It is up to the external service plugin to track these events and
* relay them to each @dynamic service plugins' callback. I.e., to
* all those with the dynamic flag set.
*/
typedef struct plugin {
/* BSD sys/queue.h linked list node. */
TAILQ_ENTRY(plugin) link;
/* Handle to be sent to dlclose() when plugins is unloaded. */
void *handle;
/* Event loop handler, used internally by Finit */
uev_t watcher;
/* Plugin name, defaults to basename of plugin path if unset.
* NOTE: Must match cmd for services or inetd plugins! */
char *name;
/* List of hook callbacks. */
struct {
void *arg; /* Optional argument to callback func. */
void (*cb)(void *arg);
} hook[HOOK_MAX_NUM];
/* I/O Plugin */
struct {
int fd, flags; /* 1:READ, 2:WRITE */
void *arg;
void (*cb)(void *arg, int fd, int events);
} io;
/* Inetd Plugin, stdio used as client socket.
* @type argument will be either SOCK_DGRAM or SOCK_STREAM */
struct {
int (*cmd)(int type);
} inetd;
char *depends[PLUGIN_DEP_MAX]; /* List of other .name's this depends on. */
} plugin_t;
/* Public plugin API */
int plugin_io_init (plugin_t *plugin);
int plugin_register (plugin_t *plugin);
int plugin_unregister (plugin_t *plugin);
/* Helper API */
plugin_t *plugin_find (char *name);
#endif /* FINIT_PLUGIN_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+56
View File
@@ -0,0 +1,56 @@
/* Private header file for main finit daemon, not for plugins
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-2013 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_PRIVATE_H_
#define FINIT_PRIVATE_H_
#include "svc.h"
#include "plugin.h"
/*
* Main loop context
*/
uev_ctx_t *ctx;
int api_init (uev_ctx_t *ctx);
int api_exit (void);
int client (int argc, char *argv[]);
void service_monitor (pid_t lost);
void plugin_run_hook (hook_point_t no, void *arg);
void plugin_run_hooks (hook_point_t no);
int plugin_init (uev_ctx_t *ctx, char *path);
void plugin_exit (void);
#endif /* FINIT_PRIVATE_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+220
View File
@@ -0,0 +1,220 @@
/* reboot, halt, poweroff, shutdown, suspend for finit
*
* Copyright (c) 2009-2016 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 <err.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/reboot.h>
#include "util.h"
#ifndef RB_SW_SUSPEND /* Since Linux 2.5.18, but not in GLIBC until 2.19 */
#define RB_SW_SUSPEND 0xd000fce2
#endif
typedef enum {
CMD_UNKNOWN = -1,
CMD_REBOOT = 0,
CMD_HALT,
CMD_POWEROFF,
CMD_SUSPEND,
} cmd_t;
static cmd_t cmd = CMD_UNKNOWN;
static char *msg = NULL;
static void transform(char *nm)
{
if (!nm)
nm = prognm;
if (cmd == CMD_UNKNOWN) {
if (!strcmp(nm, "halt") || !strcmp(nm, "shutdown"))
cmd = CMD_HALT;
else if (!strcmp(nm, "poweroff"))
cmd = CMD_POWEROFF;
else if (!strcmp(nm, "suspend"))
cmd = CMD_SUSPEND;
else
cmd = CMD_REBOOT;
}
switch (cmd) {
case CMD_REBOOT:
msg = "reboot";
break;
case CMD_HALT:
msg = "halt";
break;
case CMD_POWEROFF:
msg = "power-off";
break;
case CMD_SUSPEND:
msg = "suspend";
break;
case CMD_UNKNOWN:
cmd = CMD_REBOOT;
msg = "reboot";
break;
}
}
static void sig(int signo)
{
(void)signo; /* NOP */
}
static int usage(int rc)
{
fprintf(stderr, "Usage: %s [OPTIONS]\n\n"
"Options:\n"
" -h, --help This help text\n"
" -f, --force Force unsafe %s now, do not contact the init system.\n"
" --halt Halt system, regardless of how the command is called.\n"
" -p, --poweroff Power-off system, regardless of how the command is called.\n"
" --reboot Reboot system, regardless of how the command is called.\n"
"\n", prognm, msg);
return rc;
}
int main(int argc, char *argv[])
{
int c, force = 0;
struct option long_options[] = {
{"help", 0, NULL, 'h'},
{"force", 0, NULL, 'f'},
{"halt", 0, NULL, 'H'},
{"poweroff", 0, NULL, 'p'},
{"reboot", 0, NULL, 'r'},
{NULL, 0, NULL, 0}
};
/* Initial command taken from program name */
transform(progname(argv[0]));
while ((c = getopt_long(argc, argv, "h?fHpr", long_options, NULL)) != EOF) {
switch(c) {
case 'h':
case '?':
return usage(0);
case 'f':
force = 1;
break;
case 'H':
cmd = CMD_HALT;
break;
case 'p':
cmd = CMD_POWEROFF;
break;
case 'r':
cmd = CMD_REBOOT;
break;
default:
return usage(1);
}
}
/* Check for any overrides */
transform(NULL);
/* Catch and ignore SIGTERM on halt/poweroff */
signal(SIGTERM, sig);
if (force) {
switch (cmd) {
case CMD_REBOOT:
c = reboot(RB_AUTOBOOT);
break;
case CMD_HALT:
c = reboot(RB_HALT_SYSTEM);
break;
case CMD_POWEROFF:
c = reboot(RB_POWER_OFF);
break;
case CMD_SUSPEND:
c = reboot(RB_SW_SUSPEND);
break;
case CMD_UNKNOWN:
errx(1, "Invalid command");
break;
}
if (c)
warn("Failed forced %s", msg);
else
return 0;
}
switch (cmd) {
case CMD_REBOOT:
c = kill(1, SIGTERM);
break;
case CMD_HALT:
c = kill(1, SIGUSR1);
break;
case CMD_POWEROFF:
c = kill(1, SIGUSR2);
break;
case CMD_SUSPEND:
/*
* Only initctl supports suspend, we avoid adding
* another signal to finit for compat reasons.
*/
case CMD_UNKNOWN:
errx(1, "Invalid command");
break;
}
if (c)
err(1, "Failed signalling init to %s", msg);
do_sleep(2);
return 0;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+1007
View File
File diff suppressed because it is too large. Load diff
+47
View File
@@ -0,0 +1,47 @@
/* Finit service monitor, task starter and generic API for managing svc_t
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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_SERVICE_H_
#define FINIT_SERVICE_H_
#include "svc.h"
void service_runlevel (int newlevel);
int service_register (int type, char *line, struct timeval *mtime, char *username);
void service_unregister (svc_t *svc);
int service_enabled (svc_t *svc);
void service_reload_dynamic (void);
void service_step (svc_t *svc);
void service_step_all (int types);
#endif /* FINIT_SERVICE_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+404
View File
@@ -0,0 +1,404 @@
/* Signal management - Be conservative with what finit responds to!
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-2014 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.
*
* Finit is often used on embedded and small Linux systems with BusyBox.
* For compatibility with its existing toolset (halt, poweroff, reboot),
* the following signals have been adopted:
*
* SIGHUP
* Same effect as init/telinit q, reloads *.conf in /etc/finit.d/
*
* SIGUSR1
* Calls shutdown hooks, including HOOK_SHUTDOWN, stops all running
* processes, and unmounts all file systems. Then tells kernel to
* halt. Most people these days want SIGUSR2 though.
*
* (SysV init and systemd use this to re-open their FIFO/D-Bus.)
*
* SIGUSR2
* Like SIGUSR1, but tell kernel to power-off the system, if ACPI
* or similar exists to actually do this. If the kernel fails
* power-off, Finit falls back to halt.
*
* (SysV init N/A, systemd dumps its internal state to log.)
*
* SIGTERM
* Like SIGUSR1, but tell kernel to reboot the system when done.
*
* (SysV init N/A, systemd rexecutes itself.)
*
* SIGINT
* Sent from kernel when the CTRL-ALT-DEL key combo is pressed.
* SysV init and systemd default to reboot with `shutdown -r`.
* Finit currently forwards this to SIGTERM.
*
* SIGWINCH
* Sent from kernel when the KeyboardSignal key is pressed. Some
* users bind keys, see loadkeys(1), to create consoles on demand.
* Finit currently ignores this signal.
*
* SIGPWR
* Sent from a power daemon, like powstatd(8), on changes to the
* UPS status. Traditionally SysV init read /etc/powerstatus and
* acted on "OK", "FAIL", or "LOW" and then removed the file.
* Finit currently forwards this to SIGUSR2.
*/
#include <dirent.h>
#include <string.h> /* strerror() */
#include <sys/reboot.h>
#include <sys/wait.h>
#include <lite/lite.h>
#include "finit.h"
#include "conf.h"
#include "config.h"
#include "helpers.h"
#include "plugin.h"
#include "private.h"
#include "sig.h"
#include "service.h"
#include "util.h"
#include "utmp-api.h"
/*
* Old-style SysV shutdown sends a setenv cmd INIT_HALT with "=HALT",
* "=POWERDOWN", or "" to cancel shutdown, before requesting change to
* runlevel 6 over the /dev/initctl FIFO. See plugins/initctl.c
*/
shutop_t halt = SHUT_DEFAULT;
static int stopped = 0;
static uev_t sigterm_watcher, sigusr1_watcher, sigusr2_watcher;
static uev_t sighup_watcher, sigint_watcher, sigpwr_watcher;
static uev_t sigchld_watcher;
static uev_t sigstop_watcher, sigtstp_watcher, sigcont_watcher;
void mdadm_wait(void);
void unmount_tmpfs(void);
void unmount_regular(void);
/*
* Kernel threads have no cmdline so fgets() returns NULL for them. We
* also skip "special" processes, e.g. mdadm/mdmon or watchdogd that must
* not be stopped here, for various reasons.
*
* https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
*/
void do_kill(int signo)
{
DIR *dirp;
dirp = opendir("/proc");
if (dirp) {
struct dirent *d;
while ((d = readdir(dirp))) {
int pid;
FILE *fp;
char file[80] = "";
if (d->d_type != DT_DIR)
continue;
pid = atoi(d->d_name);
if (!pid)
continue;
snprintf(file, sizeof(file), "/proc/%s/cmdline", d->d_name);
fp = fopen(file, "r");
if (!fp)
continue;
if (fgets(file, sizeof(file), fp)) {
if (file[0] != '@')
kill(pid, signo);
}
fclose(fp);
}
closedir(dirp);
}
}
void do_shutdown(shutop_t op)
{
touch(SYNC_SHUTDOWN);
if (sdown)
run_interactive(sdown, "Calling shutdown hook: %s", sdown);
/* Update UTMP db */
utmp_set_halt();
/*
* Tell all remaining non-monitored processes to exit, give them
* some time to exit gracefully, 2 sec is customary.
*/
do_kill(SIGTERM);
do_sleep(2);
do_kill(SIGKILL);
/* Exit plugins and API gracefully */
plugin_exit();
api_exit();
/* Reap 'em */
while (waitpid(-1, NULL, WNOHANG) > 0)
;
/* Close all local non-console descriptors */
for (int fd = 3; fd < 128; fd++)
close(fd);
if (vfork()) {
/*
* Put PID 1 aside and let child perform reboot/halt
* kernel may exit child and we don't want to exit PID 1
* ... causing "aiii killing init" during reboot ...
*/
return;
}
/* Unmount any tmpfs before unmounting swap ... */
unmount_tmpfs();
run("/sbin/swapoff -e -a");
/* ... unmount remaining regular file systems. */
unmount_regular();
/* We sit on / so we must remount it ro, try all the things! */
sync();
run("/bin/mount -n -o remount,ro -t dummytype dummydev /");
run("/bin/mount -n -o remount,ro dummydev /");
run("/bin/mount -n -o remount,ro /");
/* Call mdadm to mark any RAID array(s) as clean before halting. */
mdadm_wait();
/* Reboot via watchdog or kernel, or shutdown? */
if (op == SHUT_REBOOT) {
if (wdogpid) {
int timeout = 10;
/* Wait here until the WDT reboots, or timeout with fallback */
print(kill(wdogpid, SIGPWR) == 1, "Pending watchdog reboot");
while (timeout--)
do_sleep(1);
}
_d("Rebooting ...");
reboot(RB_AUTOBOOT);
} else if (op == SHUT_OFF) {
_d("Powering down ...");
reboot(RB_POWER_OFF);
}
/* Also fallback if any of the other two fails */
_d("Halting ...");
reboot(RB_HALT_SYSTEM);
}
/*
* Reload .conf files in /etc/finit.d/
*/
static void sighup_cb(uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events))
{
_d("...");
/* INIT_CMD_RELOAD: 'init q', 'initctl reload', and SIGHUP */
service_reload_dynamic();
}
/*
* SIGINT: Should generate <sys/key/ctrlaltdel> condition, for now reboot
*/
static void sigint_cb(uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events))
{
_d("...");
halt = SHUT_REBOOT;
service_runlevel(6);
}
/*
* SIGUSR1: BusyBox style halt
*/
static void sigusr1_cb(uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events))
{
_d("...");
halt = SHUT_HALT;
service_runlevel(0);
}
/*
* SIGUSR2: BusyBox style poweroff
*/
static void sigusr2_cb(uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events))
{
_d("...");
halt = SHUT_OFF;
service_runlevel(0);
}
/*
* SIGTERM: BusyBox style reboot
*/
static void sigterm_cb(uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events))
{
_d("...");
halt = SHUT_REBOOT;
service_runlevel(6);
}
/*
* SIGCHLD: one of our children has died
*/
static void sigchld_cb(uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events))
{
pid_t pid;
/* Reap all the children! */
do {
pid = waitpid(-1, NULL, WNOHANG);
if (pid > 0) {
_d("Collected child %d", pid);
service_monitor(pid);
}
} while (pid > 0);
}
/*
* SIGSTOP/SIGTSTP: Paused by user or netflash
*/
static void sigstop_cb(uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events))
{
touch(SYNC_STOPPED);
stopped++;
}
/*
* SIGCONT: Restart service monitor
*/
static void sigcont_cb(uev_t *UNUSED(w), void *UNUSED(arg), int UNUSED(events))
{
stopped = 0;
erase(SYNC_STOPPED);
}
/*
* Is SIGSTOP asserted?
*/
int sig_stopped(void)
{
return stopped;
}
/*
* Inital signal setup - ignore everything but SIGCHLD until we're capable of responding
*/
static void chld_handler(int UNUSED(sig), siginfo_t *UNUSED(info), void *UNUSED(ctx))
{
/* NOP */
}
void sig_init(void)
{
int i;
struct sigaction sa;
for (i = 1; i < NSIG; i++)
IGNSIG(sa, i, SA_RESTART);
SETSIG(sa, SIGCHLD, chld_handler, SA_RESTART);
}
/*
* Unblock all signals blocked by finit when starting children
*/
void sig_unblock(void)
{
int i;
sigset_t nmask;
struct sigaction sa;
sigemptyset(&nmask);
sigaddset(&nmask, SIGHUP);
sigaddset(&nmask, SIGCHLD);
sigaddset(&nmask, SIGINT);
sigaddset(&nmask, SIGPWR);
sigaddset(&nmask, SIGSTOP);
sigaddset(&nmask, SIGTSTP);
sigaddset(&nmask, SIGCONT);
sigaddset(&nmask, SIGTERM);
sigaddset(&nmask, SIGUSR1);
sigaddset(&nmask, SIGUSR2);
sigprocmask(SIG_UNBLOCK, &nmask, NULL);
/* Reset signal handlers that were set by the parent process */
for (i = 1; i < NSIG; i++)
DFLSIG(sa, i, 0);
}
/*
* Setup limited set of SysV compatible signals to respond to
*/
void sig_setup(uev_ctx_t *ctx)
{
struct sigaction sa;
_d("Setup signals");
/* Cleanup any stale finit control files */
erase(SYNC_SHUTDOWN);
erase(SYNC_STOPPED);
/* Standard SysV init calls ctrl-alt-delete handler */
uev_signal_init(ctx, &sigint_watcher, sigint_cb, NULL, SIGINT);
uev_signal_init(ctx, &sigpwr_watcher, sigint_cb, NULL, SIGPWR);
/* BusyBox init style signals for halt, power-off and reboot. */
uev_signal_init(ctx, &sigusr1_watcher, sigusr1_cb, NULL, SIGUSR1);
uev_signal_init(ctx, &sigusr2_watcher, sigusr2_cb, NULL, SIGUSR2);
uev_signal_init(ctx, &sigterm_watcher, sigterm_cb, NULL, SIGTERM);
/* Some C APIs may need SIGALRM for implementing timers. */
IGNSIG(sa, SIGALRM, 0);
/* /etc/inittab not supported yet, instead /etc/finit.d/ is scanned for *.conf */
uev_signal_init(ctx, &sighup_watcher, sighup_cb, NULL, SIGHUP);
/* After initial bootstrap of Finit we call the service monitor to reap children */
uev_signal_init(ctx, &sigchld_watcher, sigchld_cb, NULL, SIGCHLD);
/* Stopping init is a bit tricky. */
uev_signal_init(ctx, &sigstop_watcher, sigstop_cb, NULL, SIGSTOP);
uev_signal_init(ctx, &sigtstp_watcher, sigstop_cb, NULL, SIGTSTP);
uev_signal_init(ctx, &sigcont_watcher, sigcont_cb, NULL, SIGCONT);
setsid();
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+84
View File
@@ -0,0 +1,84 @@
/* Signal management - Be conservative with what finit responds to!
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-2014 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_SIG_H_
#define FINIT_SIG_H_
#include <signal.h>
#include <uev/uev.h>
#define SYNC_SHUTDOWN "/var/lock/finit.shutdown"
#define SYNC_STOPPED "/var/lock/finit.stopped"
#define SETSIG(sa, sig, fun, flags) \
do { \
sa.sa_sigaction = fun; \
sa.sa_flags = SA_SIGINFO | flags; \
sigemptyset(&sa.sa_mask); \
sigaction(sig, &sa, NULL); \
} while (0)
#define IGNSIG(sa, sig, flags) \
do { \
sa.sa_handler = SIG_IGN; \
sa.sa_flags = flags; \
sigemptyset(&sa.sa_mask); \
sigaction(sig, &sa, NULL); \
} while (0)
#define DFLSIG(sa, sig, flags) \
do { \
sa.sa_handler = SIG_DFL; \
sa.sa_flags = flags; \
sigemptyset(&sa.sa_mask); \
sigaction(sig, &sa, NULL); \
} while (0)
/*
* For old-style /dev/initctl shutdown we default to halt
*/
#define SHUT_DEFAULT SHUT_HALT
typedef enum {
SHUT_OFF,
SHUT_HALT,
SHUT_REBOOT
} shutop_t;
extern shutop_t halt;
void do_shutdown (shutop_t op);
int sig_stopped (void);
void sig_init (void);
void sig_unblock (void);
void sig_setup (uev_ctx_t *ctx);
#endif /* FINIT_SIG_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+229
View File
@@ -0,0 +1,229 @@
/* Finit state machine
*
* Copyright (c) 2016 Jonas Johansson <jonas.johansson@westermo.se>
*
* 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 <sys/types.h>
#include "finit.h"
#include "cond.h"
#include "conf.h"
#include "helpers.h"
#include "private.h"
#include "service.h"
#include "sig.h"
#include "tty.h"
#include "sm.h"
sm_t sm;
void sm_init(sm_t *sm)
{
sm->state = SM_BOOTSTRAP_STATE;
sm->newlevel = -1;
sm->reload = 0;
sm->in_teardown = 0;
}
static char *sm_status(sm_t *sm)
{
switch (sm->state) {
case SM_BOOTSTRAP_STATE:
return "bootstrap";
case SM_RUNNING_STATE:
return "running";
case SM_RUNLEVEL_CHANGE_STATE:
return "runlevel/change";
case SM_RUNLEVEL_WAIT_STATE:
return "runlevel/wait";
case SM_RELOAD_CHANGE_STATE:
return "reload/change";
case SM_RELOAD_WAIT_STATE:
return "reload/wait";
default:
return "unknown";
}
}
void sm_set_runlevel(sm_t *sm, int newlevel)
{
sm->newlevel = newlevel;
}
void sm_set_reload(sm_t *sm)
{
sm->reload = 1;
}
int sm_is_in_teardown(sm_t *sm)
{
return sm->in_teardown;
}
void sm_step(sm_t *sm)
{
sm_state_t old_state;
restart:
old_state = sm->state;
_d("state: %s", sm_status(sm));
switch (sm->state) {
case SM_BOOTSTRAP_STATE:
_d("Bootstrapping all services in runlevel S from %s", FINIT_CONF);
service_step_all(SVC_TYPE_RUN | SVC_TYPE_TASK | SVC_TYPE_SERVICE);
sm->state = SM_RUNNING_STATE;
break;
case SM_RUNNING_STATE:
/* runlevel changed? */
if (sm->newlevel >= 0 && sm->newlevel <= 9) {
if (runlevel == sm->newlevel) {
sm->newlevel = -1;
break;
}
sm->state = SM_RUNLEVEL_CHANGE_STATE;
break;
}
/* reload ? */
if (sm->reload) {
sm->reload = 0;
sm->state = SM_RELOAD_CHANGE_STATE;
}
break;
case SM_RUNLEVEL_CHANGE_STATE:
prevlevel = runlevel;
runlevel = sm->newlevel;
sm->newlevel = -1;
_d("Setting new runlevel --> %d <-- previous %d", runlevel, prevlevel);
runlevel_set(prevlevel, runlevel);
/* Disable login in single-user mode as well as shutdown/reboot */
if (runlevel == 1 || runlevel == 0 || runlevel == 6)
touch("/etc/nologin");
else
erase("/etc/nologin");
/* Make sure to (re)load all *.conf in /etc/finit.d/ */
conf_reload_dynamic();
_d("Stopping services services not allowed in new runlevel ...");
sm->in_teardown = 1;
service_step_all(SVC_TYPE_ANY);
sm->state = SM_RUNLEVEL_WAIT_STATE;
break;
case SM_RUNLEVEL_WAIT_STATE:
/* Need to wait for any services to stop? If so, exit early
* and perform second stage from service_monitor later. */
if (!svc_stop_completed())
break;
/* Prev runlevel services stopped, call hooks before starting new runlevel ... */
_d("All services have been stoppped, calling runlevel change hooks ...");
plugin_run_hooks(HOOK_RUNLEVEL_CHANGE); /* Reconfigure HW/VLANs/etc here */
_d("Starting services services new to this runlevel ...");
sm->in_teardown = 0;
service_step_all(SVC_TYPE_ANY);
/* Cleanup stale services */
svc_clean_dynamic(service_unregister);
/*
* "I've seen things you people wouldn't believe. Attack ships on fire off
* the shoulder of Orion. I watched C-beams glitter in the dark near the
* Tannhäuser Gate. All those .. moments .. will be lost in time, like
* tears ... in ... rain."
*/
if (runlevel == 0 || runlevel == 6) {
do_shutdown(halt);
sm->state = SM_RUNNING_STATE;
break;
}
/* No TTYs run at bootstrap, they have a delayed start. */
if (prevlevel > 0)
tty_runlevel(runlevel);
sm->state = SM_RUNNING_STATE;
break;
case SM_RELOAD_CHANGE_STATE:
/* First reload all *.conf in /etc/finit.d/ */
conf_reload_dynamic();
/* Then, mark all affected service conditions as in-flux and
* let all affected services move to WAITING/HALTED */
_d("Stopping services services not allowed after reconf ...");
sm->in_teardown = 1;
cond_reload();
service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD);
sm->state = SM_RELOAD_WAIT_STATE;
break;
case SM_RELOAD_WAIT_STATE:
/* Need to wait for any services to stop? If so, exit early
* and perform second stage from service_monitor later. */
if (!svc_stop_completed())
break;
sm->in_teardown = 0;
/* Cleanup stale services */
svc_clean_dynamic(service_unregister);
_d("Starting services after reconf ...");
service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD);
_d("Calling reconf hooks ...");
plugin_run_hooks(HOOK_SVC_RECONF);
service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD);
_d("Reconfiguration done");
sm->state = SM_RUNNING_STATE;
break;
}
if (sm->state != old_state) {
goto restart;
}
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+58
View File
@@ -0,0 +1,58 @@
/* Finit state machine
*
* Copyright (c) 2016 Jonas Johansson <jonas.johansson@westermo.se>
*
* 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_SM_H_
#define FINIT_SM_H_
typedef enum {
SM_BOOTSTRAP_STATE = 0, /* Init state, bootstrap services */
SM_RUNNING_STATE, /* Normal state, services running */
SM_RUNLEVEL_CHANGE_STATE, /* A runlevel change has occured */
SM_RUNLEVEL_WAIT_STATE, /* Waiting for all stopped runlevel processes to be halted */
SM_RELOAD_CHANGE_STATE, /* A reload event has occured */
SM_RELOAD_WAIT_STATE, /* Waiting for all stopped reload processes to be halted */
} sm_state_t;
typedef struct sm {
sm_state_t state; /* Running, Changed, Waiting, ... */
int newlevel; /* Set on runlevel change to new runlevel, -1 if not change */
int reload; /* Set on reload event, else 0 */
int in_teardown; /* Set when waiting for all processes to be halted */
} sm_t;
extern sm_t sm;
void sm_init(sm_t *sm);
void sm_step(sm_t *sm);
void sm_set_runlevel(sm_t *sm, int newlevel);
void sm_set_reload(sm_t *sm);
int sm_is_in_teardown(sm_t *sm);
#endif /* FINIT_SM_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+646
View File
@@ -0,0 +1,646 @@
/* Low-level service primitives and generic API for managing svc_t structures
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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 <ctype.h> /* isdigit() */
#include <stdlib.h>
#include <strings.h>
#include <sys/time.h>
#include "finit.h"
#include "svc.h"
#include "helpers.h"
#include "util.h"
/* Each svc_t needs a unique job# */
static int jobcounter = 1;
static svc_t *__connect_shm(void)
{
svc_t *list = finit_svc_connect();
if (!list) {
/* This should never happen, but if it does we're probably
* knee-deep in more serious problems already... */
_e("Failed connecting to shared memory, error %d: %s", errno, strerror(errno));
abort();
}
return list;
}
/**
* svc_new - Create a new service
* @cmd: External program to call, or 'internal' for internal inetd services
* @id: Instance id
* @type: Service type, one of service, task, run or inetd
*
* Returns:
* A pointer to a new &svc_t object, or %NULL if out of empty slots.
*/
svc_t *svc_new(char *cmd, int id, int type)
{
int i, job = -1;
svc_t *svc, *list = __connect_shm();
/* Find first job n:o if registering multiple instances */
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (!strcmp(svc->cmd, cmd)) {
job = svc->job;
break;
}
}
if (job == -1)
job = jobcounter++;
for (i = 0; i < MAX_NUM_SVC; i++) {
svc_t *svc = &list[i];
if (svc->type == SVC_TYPE_FREE) {
char *desc;
memset(svc, 0, sizeof(*svc));
svc->type = type;
svc->job = job;
svc->id = id;
strlcpy(svc->cmd, cmd, sizeof(svc->cmd));
/* Default description, if missing */
desc = rindex(cmd, '/');
if (desc)
desc++;
else
desc = cmd;
strlcpy(svc->desc, desc, sizeof(svc->desc));
return svc;
}
}
errno = ENOMEM;
return NULL;
}
/**
* svc_del - Delete a service object
* @svc: Pointer to an &svc_t object
*
* Returns:
* Always succeeds, returning POSIX OK(0).
*/
int svc_del(svc_t *svc)
{
svc->type = SVC_TYPE_FREE;
return 0;
}
/**
* svc_iterator - Naive iterator over all registered services.
* @first: Get first &svc_t object, or next until end.
*
* Returns:
* The first &svc_t when @first is set, otherwise the next &svc_t until
* the end when %NULL is returned.
*/
svc_t *svc_iterator(int first)
{
static int i = 0;
svc_t *list = __connect_shm();
if (first)
i = 0;
while (i < MAX_NUM_SVC) {
svc_t *svc = &list[i++];
if (svc->type != SVC_TYPE_FREE)
return svc;
}
return NULL;
}
/**
* svc_inetd_iterator - Naive iterator over all registered inetd services.
* @first: Get first &svc_t object, or next until end.
*
* Returns:
* The first inetd &svc_t when @first is set, otherwise the next
* inetd &svc_t until the end when %NULL is returned.
*/
svc_t *svc_inetd_iterator(int first)
{
svc_t *svc;
for (svc = svc_iterator(first); svc; svc = svc_iterator(0)) {
if (svc_is_inetd(svc))
return svc;
}
return NULL;
}
/**
* svc_dynamic_iterator - Naive iterator over all registered dynamic services.
* @first: Get first &svc_t object, or next until end.
*
* Returns:
* The first dynamically loaded &svc_t when @first is set, otherwise the
* next dynamically loaded &svc_t until the end when %NULL is returned.
*/
svc_t *svc_dynamic_iterator(int first)
{
svc_t *svc;
for (svc = svc_iterator(first); svc; svc = svc_iterator(0)) {
if (svc->mtime.tv_sec)
return svc;
}
return NULL;
}
/**
* svc_named_iterator - Iterates over all instances of a service.
* @first: Get first &svc_t object, or next until end.
* @cmd: Service name to look for.
*
* Returns:
* The first matching &svc_t when @first is set, otherwise the next
* &svc_t instance with the same @cmd until the end when %NULL is
* returned.
*/
svc_t *svc_named_iterator(int first, char *cmd)
{
svc_t *svc;
for (svc = svc_iterator(first); svc; svc = svc_iterator(0)) {
char *name = basename(svc->cmd);
if (!strncmp(name, cmd, strlen(name)))
return svc;
}
return NULL;
}
/**
* svc_job_iterator - Iterates over all instances of a service.
* @first: Get first &svc_t object, or next until end.
* @job: Job to look for.
*
* Returns:
* The first matching &svc_t when @first is set, otherwise the next
* &svc_t instance with the same @job until the end when %NULL is
* returned.
*/
svc_t *svc_job_iterator(int first, int job)
{
svc_t *svc;
for (svc = svc_iterator(first); svc; svc = svc_iterator(0)) {
if (svc->job == job)
return svc;
}
return NULL;
}
/**
* svc_foreach - Run a callback for each registered service
* @cb: Callback to run for each service
*/
void svc_foreach(void (*cb)(svc_t *))
{
svc_t *svc;
if (!cb)
return;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0))
cb(svc);
}
/**
* svc_foreach_dynamic - Run a callback for each registered dynamic service
* @cb: Callback to run for each dynamic service
*/
void svc_foreach_dynamic(void (*cb)(svc_t *))
{
svc_t *svc;
if (!cb)
return;
for (svc = svc_dynamic_iterator(1); svc; svc = svc_dynamic_iterator(0))
cb(svc);
}
/**
* svc_foreach_type - Run a callback for each matching type
* @types: Mask of service types
* @cb: Callback to run for each matching type
*/
void svc_foreach_type(int types, void (*cb)(svc_t *))
{
svc_t *svc;
if (!cb)
return;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (!(svc->type & types))
continue;
cb(svc);
}
}
/**
* svc_stop_completed - Have all stopped services been collected?
*
* Returns:
* 1, if all stopped services have been collected. 0 otherwise.
*/
int svc_stop_completed(void)
{
svc_t *svc;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0))
if (svc->state == SVC_STOPPING_STATE)
return 0;
return 1;
}
/**
* svc_find - Find a service object by its full path name
* @cmd: Full path name, e.g., /sbin/syslogd
*
* Returns:
* A pointer to an &svc_t object, or %NULL if not found.
*/
svc_t *svc_find(char *cmd, int id)
{
svc_t *svc;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (svc->id == id && !strncmp(svc->cmd, cmd, strlen(svc->cmd))) {
_d("Found a matching svc for %s", cmd);
return svc;
}
}
return NULL;
}
/**
* svc_find_by_pid - Find an service object by its PID
* @pid: Process ID to match
*
* Returns:
* A pointer to an &svc_t object, or %NULL if not found.
*/
svc_t *svc_find_by_pid(pid_t pid)
{
svc_t *svc;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (svc->pid == pid)
return svc;
}
return NULL;
}
/**
* svc_find_by_jobid - Find an service object by its JOB:ID
* @job: Job n:o
* @id: Instance id
*
* Returns:
* A pointer to an &svc_t object, or %NULL if not found.
*/
svc_t *svc_find_by_jobid(int job, int id)
{
svc_t *svc;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (svc->job == job && svc->id == id)
return svc;
}
return NULL;
}
/**
* svc_find_by_nameid - Find an service object by its basename:ID
* @name: Process name to match
* @id: Instance id
*
* Returns:
* A pointer to an &svc_t object, or %NULL if not found.
*/
svc_t *svc_find_by_nameid(char *name, int id)
{
char *ptr;
svc_t *svc;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
ptr = strrchr(svc->cmd, '/');
if (ptr)
ptr++;
else
ptr = svc->cmd;
if (svc->id == id && !strcmp(name, ptr))
return svc;
}
return NULL;
}
/**
* svc_mark_dynamic - Mark dynamically loaded services for deletion.
*
* This function traverses the list of known services, marking all that
* have been loaded from /etc/finit.d/ for deletion. If a .conf file
* has been removed svc_cleanup_dynamic() will stop and delete the
* service.
*
* This function is called from sighup_cb().
*/
void svc_mark_dynamic(void)
{
svc_t *svc = svc_dynamic_iterator(1);
while (svc) {
*((int *)&svc->dirty) = -1;
svc = svc_dynamic_iterator(0);
}
}
void svc_mark_dirty(svc_t *svc)
{
*((int *)&svc->dirty) = 1;
}
void svc_mark_clean(svc_t *svc)
{
*((int *)&svc->dirty) = 0;
}
void svc_check_dirty(svc_t *svc, struct timeval *mtime)
{
if (mtime && timercmp(&svc->mtime, mtime, !=))
svc_mark_dirty(svc);
else
svc_mark_clean(svc);
svc->mtime.tv_sec = mtime ? mtime->tv_sec : 0;
svc->mtime.tv_usec = mtime ? mtime->tv_usec : 0;
}
/**
* svc_clean_dynamic - Stop and cleanup stale services removed from /etc/finit.d
* @cb: Callback to run for each stale service
*
* This function traverses the list of known services to stop and clean
* up all non-existing services previously marked by svc_mark_dynamic().
*/
void svc_clean_dynamic(void (*cb)(svc_t *))
{
svc_t *svc = svc_dynamic_iterator(1);
while (svc) {
if (svc->dirty == -1 && cb)
cb(svc);
svc = svc_dynamic_iterator(0);
}
}
/**
* svc_clean_bootstrap - Remove bootstrap-only services after boot
* @svc: Pointer to &svc_t object
*
* Returns:
* %TRUE(1) if object was bootstrap-only, otherwise %FALSE(0)
*/
int svc_clean_bootstrap(svc_t *svc)
{
if (!ISOTHER(svc->runlevels, 0)) {
svc->pid = 0;
svc_del(svc);
return 1;
}
return 0;
}
char *svc_status(svc_t *svc)
{
switch (svc->state) {
case SVC_HALTED_STATE:
switch (svc->block) {
case SVC_BLOCK_NONE:
return "halted";
case SVC_BLOCK_MISSING:
return "missing";
case SVC_BLOCK_CRASHING:
return "crashed";
case SVC_BLOCK_USER:
return "blocked";
case SVC_BLOCK_BUSY:
return "busy";
case SVC_BLOCK_RESTARTING:
return "restart";
}
case SVC_DONE_STATE:
return "done";
case SVC_STOPPING_STATE:
switch (svc->type) {
case SVC_TYPE_INETD_CONN:
case SVC_TYPE_RUN:
case SVC_TYPE_TASK:
return "active";
default:
return "stopping";
}
case SVC_WAITING_STATE:
return "waiting";
case SVC_READY_STATE:
return "ready";
case SVC_RUNNING_STATE:
return "running";
default:
return "UNKNOWN";
}
}
const char *svc_dirtystr(svc_t *svc)
{
if (svc_is_removed(svc))
return "removed";
if (svc_is_updated(svc))
return "updated";
if (svc_is_changed(svc))
return "UNKNOWN";
return "clean";
}
/* Same base service, return unique ID */
int svc_next_id(char *cmd)
{
int id = 0;
svc_t *svc;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (!strcmp(svc->cmd, cmd) && id < svc->id)
id = svc->id;
}
return id + 1;
}
int svc_is_unique(svc_t *svc)
{
svc_t *list = __connect_shm();
int i, unique = 1;
for (i = 0; i < MAX_NUM_SVC; i++) {
svc_t *s = &list[i];
if (svc->type == SVC_TYPE_FREE)
continue;
if (s == svc)
continue;
if (s->job == svc->job) {
unique = 0;
break;
}
}
return unique;
}
/*
* Used by api.c (to start/stop/restart) and initctl.c (for input validation)
*/
int svc_parse_jobstr(char *str, size_t len, int (*found)(svc_t *), int (not_found)(char *, int))
{
int result = 0;
char *input, *token, *pos;
input = sanitize(str, len);
if (!input)
return -1;
token = strtok_r(input, " ", &pos);
while (token) {
int id = 1;
svc_t *svc;
char *ptr = strchr(token, ':');
if (isdigit(token[0])) {
int job = atonum(token);
if (!ptr) {
svc = svc_job_iterator(1, job);
if (!svc && not_found)
result += not_found(NULL, job);
while (svc) {
if (found)
result += found(svc);
svc = svc_job_iterator(0, job);
}
} else {
*ptr++ = 0;
id = atonum(ptr);
job = atonum(token);
svc = svc_find_by_jobid(job, id);
if (!svc && not_found)
result += not_found(token, id);
else if (found)
result += found(svc);
}
} else {
if (!ptr) {
svc = svc_named_iterator(1, token);
if (!svc && not_found)
result += not_found(token, id);
while (svc) {
if (found)
result += found(svc);
svc = svc_named_iterator(0, token);
}
} else {
*ptr++ = 0;
id = atonum(ptr);
svc = svc_find_by_nameid(token, id);
if (!svc && not_found)
result += not_found(token, id);
else if (found)
result += found(svc);
}
}
token = strtok_r(NULL, " ", &pos);
}
return result;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+208
View File
@@ -0,0 +1,208 @@
/* Low-level service primitives and generic API for managing svc_t structures
*
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
* Copyright (c) 2008-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_SVC_H_
#define FINIT_SVC_H_
#include <sys/ipc.h> /* IPC_CREAT */
#include <sys/shm.h> /* shmat() */
#include <sys/types.h> /* pid_t */
#include <lite/lite.h>
#include "inetd.h"
#include "helpers.h"
typedef int svc_cmd_t;
typedef enum {
SVC_TYPE_FREE = 0, /* Free to allocate */
SVC_TYPE_SERVICE = 1, /* Monitored, will be respawned */
SVC_TYPE_TASK = 2, /* One-shot, runs in parallell */
SVC_TYPE_RUN = 4, /* Like task, but wait for completion */
SVC_TYPE_INETD = 8, /* Classic inetd service */
SVC_TYPE_INETD_CONN = 16, /* Single inetd connection */
} svc_type_t;
#define SVC_TYPE_ANY (-1)
typedef enum {
SVC_HALTED_STATE = 0, /* Not allowed in runlevel, or not enabled. */
SVC_DONE_STATE, /* Task/Run job has been run */
SVC_STOPPING_STATE, /* Waiting to collect the child process */
SVC_WAITING_STATE, /* Condition is in flux, process SIGSTOPed */
SVC_READY_STATE, /* Enabled but condition not satisfied */
SVC_RUNNING_STATE, /* Process running */
} svc_state_t;
typedef enum {
SVC_BLOCK_NONE = 0,
SVC_BLOCK_MISSING,
SVC_BLOCK_CRASHING,
SVC_BLOCK_USER,
SVC_BLOCK_BUSY,
SVC_BLOCK_RESTARTING,
} svc_block_t;
#define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */
#define MAX_ARG_LEN 64
#define MAX_STR_LEN 64
#define MAX_USER_LEN 16
#define MAX_NUM_FDS 64 /* Max number of I/O plugins */
#define MAX_NUM_SVC 64 /* Enough? */
#define MAX_NUM_SVC_ARGS 32
/*
* Default enable for all services, can be stopped by means
* of issuing an initctl call. E.g.
*
* initctl <stop|start|restart> service
*/
typedef struct svc {
/* Instance specifics */
int job, id; /* JOB:ID */
/* Service details */
pid_t pid;
const svc_state_t state; /* Paused, Reloading, Restart, Running, ... */
svc_type_t type;
struct timeval mtime; /* Modification time for .conf from /etc/finit.d/ */
const int dirty; /* Set if old mtime != new mtime => reloaded,
* or -1 when marked for removal */
int starting; /* ... waiting for pidfile to be re-asserted */
int runlevels;
int sighup; /* This service supports SIGHUP :) */
svc_block_t block; /* Reason that this service is currently blocked */
char cond[MAX_ARG_LEN];
/* Incremented for each restart by service monitor. */
const unsigned int restart_counter;
/* For inetd services */
inetd_t inetd;
int stdin_fd;
/* Set for services we need to redirect stdout/stderr to syslog */
int log;
/* Identity */
char username[MAX_USER_LEN];
char group[MAX_USER_LEN];
/* Command, arguments and service description */
char cmd[MAX_ARG_LEN];
char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN];
char desc[MAX_STR_LEN];
/*
* Used to forcefully kill services that won't shutdown on
* termination and to delay restarts of crashing services.
*/
uev_t timer;
void (*timer_cb)(struct svc *svc);
} svc_t;
typedef struct svc_map svc_map_t;
/* Put services array in shm */
static inline svc_t *finit_svc_connect(void)
{
static void *ptr = (void *)-1;
if ((void *)-1 == ptr) {
ptr = shmat(shmget(FINIT_SHM_ID, sizeof(svc_t) * MAX_NUM_SVC,
0600 | IPC_CREAT), NULL, 0);
if ((void *)-1 == ptr)
return NULL;
}
return (svc_t *)ptr;
}
svc_t *svc_new (char *cmd, int id, int type);
int svc_del (svc_t *svc);
svc_t *svc_find (char *cmd, int id);
svc_t *svc_find_by_pid (pid_t pid);
svc_t *svc_find_by_jobid (int job, int id);
svc_t *svc_find_by_nameid (char *name, int id);
svc_t *svc_iterator (int first);
svc_t *svc_inetd_iterator (int first);
svc_t *svc_dynamic_iterator (int first);
svc_t *svc_named_iterator (int first, char *cmd);
svc_t *svc_job_iterator (int first, int job);
void svc_foreach (void (*cb)(svc_t *));
void svc_foreach_dynamic (void (*cb)(svc_t *));
void svc_foreach_type (int types, void (*cb)(svc_t *));
int svc_stop_completed (void);
void svc_mark_dynamic (void);
void svc_check_dirty (svc_t *svc, struct timeval *mtime);
void svc_mark_dirty (svc_t *svc);
void svc_mark_clean (svc_t *svc);
void svc_clean_dynamic (void (*cb)(svc_t *));
int svc_clean_bootstrap (svc_t *svc);
char *svc_status (svc_t *svc);
const char *svc_dirtystr (svc_t *svc);
int svc_next_id (char *cmd);
int svc_is_unique (svc_t *svc);
int svc_parse_jobstr (char *str, size_t len, int (*found)(svc_t *), int (not_found)(char *, int));
static inline int svc_in_runlevel (svc_t *svc, int runlevel) { return svc && ISSET(svc->runlevels, runlevel); }
static inline int svc_has_sighup (svc_t *svc) { return svc && 0 != svc->sighup; }
static inline void svc_starting (svc_t *svc) { svc->starting = 1; }
static inline void svc_started (svc_t *svc) { svc->starting = 0; }
static inline int svc_is_starting (svc_t *svc) { return 0 != svc->starting; }
static inline int svc_is_dynamic (svc_t *svc) { return svc && 0 != svc->mtime.tv_sec; }
static inline int svc_is_removed (svc_t *svc) { return svc && -1 == svc->dirty; }
static inline int svc_is_changed (svc_t *svc) { return svc && 0 != svc->dirty; }
static inline int svc_is_updated (svc_t *svc) { return svc && 1 == svc->dirty; }
static inline int svc_is_inetd (svc_t *svc) { return svc && SVC_TYPE_INETD == svc->type; }
static inline int svc_is_inetd_conn(svc_t *svc) { return svc && SVC_TYPE_INETD_CONN == svc->type; }
static inline int svc_is_daemon (svc_t *svc) { return svc && SVC_TYPE_SERVICE == svc->type; }
static inline int svc_is_blocked (svc_t *svc) { return svc->block != SVC_BLOCK_NONE; }
static inline int svc_is_busy (svc_t *svc) { return svc->block == SVC_BLOCK_BUSY; }
static inline void svc_unblock (svc_t *svc) { svc->block = SVC_BLOCK_NONE; }
static inline void svc_block (svc_t *svc) { svc->block = SVC_BLOCK_USER; }
static inline void svc_busy (svc_t *svc) { svc->block = SVC_BLOCK_BUSY; }
static inline void svc_missing (svc_t *svc) { svc->block = SVC_BLOCK_MISSING; }
static inline void svc_restarting (svc_t *svc) { svc->block = SVC_BLOCK_RESTARTING; }
static inline void svc_crashing (svc_t *svc) { svc->block = SVC_BLOCK_CRASHING; }
#endif /* FINIT_SVC_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+335
View File
@@ -0,0 +1,335 @@
/* Finit TTY handling
*
* Copyright (c) 2013 Mattias Walström <lazzer@gmail.com>
* Copyright (c) 2013 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 <signal.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <lite/lite.h>
#include "config.h" /* Generated by configure script */
#include "finit.h"
#include "conf.h"
#include "helpers.h"
#include "tty.h"
#include "util.h"
#include "utmp-api.h"
#ifdef FALLBACK_SHELL
static pid_t fallback = 0;
#endif
static LIST_HEAD(, tty_node) tty_list = LIST_HEAD_INITIALIZER();
/* tty [!1-9,S] <DEV> [BAUD[,BAUD,...]] [TERM] */
int tty_register(char *line)
{
tty_node_t *entry;
int insert = 0;
char *cmd, *dev, *baud = NULL;
char *runlevels = NULL, *term = NULL;
if (!line) {
_e("Invalid input argument");
return errno = EINVAL;
}
cmd = strtok(line, " ");
if (!cmd) {
incomplete:
_e("Incomplete tty, cannot register");
return errno = EINVAL;
}
if (cmd[0] == '[') { /* [runlevels] */
runlevels = &cmd[0];
dev = strtok(NULL, " ");
if (!dev)
goto incomplete;
} else {
dev = cmd;
}
cmd = strtok(NULL, " ");
if (cmd) {
baud = strdup(cmd);
term = strtok(NULL, " ");
}
entry = tty_find(dev);
if (!entry) {
insert = 1;
entry = calloc(1, sizeof(*entry));
if (!entry) {
if (baud)
free(baud);
return errno = ENOMEM;
}
}
entry->data.name = strdup(dev);
entry->data.baud = baud;
entry->data.term = term ? strdup(term) : NULL;
entry->data.runlevels = conf_parse_runlevels(runlevels);
_d("Registering tty %s at %s baud with term=%s on runlevels %s", dev, baud ?: "NULL", term ?: "N/A", runlevels);
if (insert)
LIST_INSERT_HEAD(&tty_list, entry, link);
return 0;
}
tty_node_t *tty_find(char *dev)
{
tty_node_t *entry;
LIST_FOREACH(entry, &tty_list, link) {
if (!strcmp(dev, entry->data.name))
return entry;
}
return NULL;
}
size_t tty_num(void)
{
size_t num = 0;
tty_node_t *entry;
LIST_FOREACH(entry, &tty_list, link)
num++;
return num;
}
size_t tty_num_active(void)
{
size_t num = 0;
tty_node_t *entry;
LIST_FOREACH(entry, &tty_list, link) {
if (entry->data.pid)
num++;
}
return num;
}
tty_node_t *tty_find_by_pid(pid_t pid)
{
tty_node_t *entry;
LIST_FOREACH(entry, &tty_list, link) {
if (entry->data.pid == pid)
return entry;
}
return NULL;
}
static char *canonicalize(char *tty)
{
struct stat st;
static char path[80];
if (!tty)
return NULL;
strlcpy(path, tty, sizeof(path));
if (stat(path, &st)) {
snprintf(path, sizeof(path), "%s%s", _PATH_DEV, tty);
if (stat(path, &st))
return NULL;
}
if (!S_ISCHR(st.st_mode))
return NULL;
return path;
}
int tty_check(char *dev)
{
int fd;
struct termios c;
if (access(dev, F_OK))
return 1;
fd = open(dev, O_RDONLY);
if (-1 == fd)
return 1;
/* XXX: Add check for errno == EIO? */
if (tcgetattr(fd, &c)) {
close(fd);
return 1;
}
close(fd);
return 0;
}
void tty_start(finit_tty_t *fitty)
{
int is_console = 0;
char *tty;
if (fitty->pid) {
_d("%s: TTY already active", fitty->name);
return;
}
tty = canonicalize(fitty->name);
if (!tty) {
_d("%s: Cannot find TTY device: %s", fitty->name, strerror(errno));
return;
}
if (console && !strcmp(tty, console))
is_console = 1;
if (tty_check(tty)) {
_d("%s: Not a valid TTY: %s", tty, strerror(errno));
return;
}
fitty->pid = run_getty(tty, fitty->baud, fitty->term, is_console);
}
void tty_stop(finit_tty_t *tty)
{
if (!tty->pid)
return;
_d("Stopping TTY %s", tty->name);
kill(tty->pid, SIGTERM);
do_sleep(2);
kill(tty->pid, SIGKILL);
waitpid(tty->pid, NULL, 0);
tty->pid = 0;
}
int tty_enabled(finit_tty_t *tty, int runlevel)
{
if (!tty)
return 0;
if (!ISSET(tty->runlevels, runlevel))
return 0;
if (fexist(tty->name))
return 1;
return 0;
}
/*
* Fallback shell if no TTYs are active
*/
int tty_fallback(pid_t lost)
{
#ifdef FALLBACK_SHELL
if (lost == 1) {
if (fallback) {
_d("Stopping fallback shell");
kill(fallback, SIGKILL);
fallback = 0;
}
return 0;
}
if (fallback != lost || tty_num_active()) {
_d("Not starting fallback (%d) shell, lost pid %d", fallback, lost);
return 0;
}
_d("Starting fallback shell");
fallback = fork();
if (fallback)
return 1;
/*
* Become session leader and set controlling TTY
* to enable Ctrl-C and job control in shell.
*/
setsid();
ioctl(STDIN_FILENO, TIOCSCTTY, 1);
_exit(execl(_PATH_BSHELL, _PATH_BSHELL, NULL));
#else
(void)lost;
#endif /* FALLBACK_SHELL */
return 0;
}
/*
* TTY monitor, called by service_monitor()
*/
int tty_respawn(pid_t pid)
{
tty_node_t *entry = tty_find_by_pid(pid);
if (!entry)
return tty_fallback(pid);
/* Set DEAD_PROCESS UTMP entry */
utmp_set_dead(pid);
/* Clear PID to be able to respawn it. */
entry->data.pid = 0;
if (!tty_enabled(&entry->data, runlevel))
tty_stop(&entry->data);
else
tty_start(&entry->data);
return 1;
}
/* Start all TTYs that exist in the system and are allowed at this runlevel */
void tty_runlevel(int runlevel)
{
tty_node_t *entry;
LIST_FOREACH(entry, &tty_list, link) {
if (!tty_enabled(&entry->data, runlevel))
tty_stop(&entry->data);
else
tty_start(&entry->data);
}
/* Start fallback shell if enabled && no TTYs */
tty_fallback(tty_num_active() > 0 ? 1 : 0);
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+67
View File
@@ -0,0 +1,67 @@
/* Finit TTY handling
*
* Copyright (c) 2013 Mattias Walström <lazzer@gmail.com>
* Copyright (c) 2013 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_TTY_H_
#define FINIT_TTY_H_
#include <limits.h>
#include <lite/queue.h> /* BSD sys/queue.h API */
#define EVENT_SIZE ((sizeof(struct inotify_event) + NAME_MAX + 1))
typedef struct {
char *name;
char *baud;
char *term;
int runlevels;
int pid;
} finit_tty_t;
typedef struct tty_node {
LIST_ENTRY(tty_node) link;
finit_tty_t data;
} tty_node_t;
//extern LIST_HEAD(, tty_node) tty_list;
int tty_register (char *line);
tty_node_t *tty_find (char *dev);
size_t tty_num (void);
size_t tty_num_active (void);
tty_node_t *tty_find_by_pid (pid_t pid);
void tty_start (finit_tty_t *tty);
void tty_stop (finit_tty_t *tty);
int tty_enabled (finit_tty_t *tty, int runlevel);
int tty_respawn (pid_t pid);
void tty_runlevel (int runlevel);
#endif /* FINIT_TTY_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+77
View File
@@ -0,0 +1,77 @@
/* Misc. shared utility functions for initctl, reboot and finit
*
* Copyright (c) 2016 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 <ctype.h> /* isprint() */
#include <string.h>
#include <unistd.h>
char *prognm = NULL;
char *progname(char *arg0)
{
prognm = strrchr(arg0, '/');
if (prognm)
prognm++;
else
prognm = arg0;
return prognm;
}
void do_sleep(unsigned int sec)
{
while ((sec = sleep(sec)))
;
}
/* Allowed characters in job/id/name */
static int isallowed(int ch)
{
return isprint(ch);
}
/* Sanitize user input, make sure to NUL terminate. */
char *sanitize(char *arg, size_t len)
{
size_t i = 0;
while (isallowed(arg[i]) && i < len)
i++;
if (i + 1 < len) {
arg[i + 1] = 0;
return arg;
}
if (i > 1 && arg[i] == 0)
return arg;
return NULL;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+40
View File
@@ -0,0 +1,40 @@
/* Misc. shared utility functions for initctl, reboot and finit
*
* Copyright (c) 2016 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_UTIL_H_
#define FINIT_UTIL_H_
extern char *prognm;
char *progname(char *arg0);
void do_sleep(unsigned int sec);
char *sanitize(char *arg, size_t len);
#endif /* FINIT_UTIL_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+184
View File
@@ -0,0 +1,184 @@
/* UTMP API
*
* Copyright (c) 2016 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 <stdio.h>
#include <time.h>
#include <utmp.h>
#include <arpa/inet.h>
#include <sys/utsname.h>
#include <lite/lite.h>
static void utmp_strncpy(char *dst, const char *src, size_t dlen)
{
size_t i;
for (i = 0; i < dlen && src[i]; i++)
dst[i] = src[i];
if (i < dlen)
dst[i] = 0;
}
int utmp_set(int type, int pid, char *line, char *id, char *user)
{
int result = 0;
struct utmp ut;
struct utsname uts;
switch (type) {
case RUN_LVL:
case BOOT_TIME:
line = "~";
id = "~~";
break;
default:
break;
}
memset(&ut, 0, sizeof(ut));
ut.ut_type = type;
ut.ut_pid = pid;
if (user)
utmp_strncpy(ut.ut_user, user, sizeof(ut.ut_user));
if (line)
utmp_strncpy(ut.ut_line, line, sizeof(ut.ut_line));
if (id)
utmp_strncpy(ut.ut_id, id, sizeof(ut.ut_id));
if (!uname(&uts))
utmp_strncpy(ut.ut_host, uts.release, sizeof(ut.ut_host));
ut.ut_tv.tv_sec = time(NULL);
if (type != DEAD_PROCESS) {
setutent();
result += pututline(&ut) ? 0 : 1;
endutent();
}
updwtmp(_PATH_WTMP, &ut);
return result;
}
int utmp_set_boot(void)
{
return utmp_set(BOOT_TIME, 0, NULL, NULL, "reboot");
}
int utmp_set_halt(void)
{
return utmp_set(RUN_LVL, 0, NULL, NULL, "shutdown");
}
static int set_getty(int type, char *tty, char *id, char *user)
{
char *line;
if (!tty)
line = "";
else if (!strncmp(tty, "/dev/", 5))
line = tty + 5;
else
line = tty;
if (!id) {
id = line;
if (!strncmp(id, "pts/", 4))
id += 4;
}
return utmp_set(type, getpid(), line, id, user);
}
int utmp_set_init(char *tty, char *id)
{
return set_getty(INIT_PROCESS, tty, id, NULL);
}
int utmp_set_login(char *tty, char *id)
{
return set_getty(LOGIN_PROCESS, tty, id, "LOGIN");
}
int utmp_set_dead(int pid)
{
return utmp_set(DEAD_PROCESS, pid, NULL, NULL, NULL);
}
static int encode(int lvl)
{
if (!lvl) return 0;
return lvl + '0';
}
int utmp_set_runlevel(int pre, int now)
{
return utmp_set(RUN_LVL, (encode(pre) << 8) | (encode(now) & 0xFF), NULL, NULL, "runlevel");
}
int utmp_show(char *file)
{
time_t sec;
struct utmp *ut;
struct tm *sectm;
int pid;
char id[sizeof(ut->ut_id) + 1], user[sizeof(ut->ut_user) + 1], when[80];
printf("%s =============================================================================================\n", file);
utmpname(file);
setutent();
while ((ut = getutent())) {
char addr[64];
memset(id, 0, sizeof(id));
strlcpy(id, ut->ut_id, sizeof(id));
memset(user, 0, sizeof(user));
strlcpy(user, ut->ut_user, sizeof(user));
sec = ut->ut_tv.tv_sec;
sectm = localtime(&sec);
strftime(when, sizeof(when), "%F %T", sectm);
pid = ut->ut_pid;
if (ut->ut_addr_v6[1] || ut->ut_addr_v6[2] || ut->ut_addr_v6[3])
inet_ntop(AF_INET6, ut->ut_addr_v6, addr, sizeof(addr));
else
inet_ntop(AF_INET, &ut->ut_addr, addr, sizeof(addr));
printf("[%d] [%05d] [%-4.4s] [%-8.8s] [%-12.12s] [%-20.20s] [%-15.15s] [%-19.19s]\n",
ut->ut_type, pid, id, user, ut->ut_line, ut->ut_host, addr, when);
}
endutent();
return 0;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+43
View File
@@ -0,0 +1,43 @@
/* UTMP API
*
* Copyright (c) 2016 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_UTMP_API_H_
#define FINIT_UTMP_API_H_
int utmp_set (int type, int pid, char *line, char *id, char *user);
int utmp_set_boot (void);
int utmp_set_halt (void);
int utmp_set_init (char *tty, char *id);
int utmp_set_login (char *tty, char *id);
int utmp_set_dead (int pid);
int utmp_set_runlevel(int pre, int now);
int utmp_show (char *file);
#endif /* FINIT_UTMP_API_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+137
View File
@@ -0,0 +1,137 @@
/* Built-in watchdog daemon
*
* Copyright (c) 2016 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 <paths.h>
#include <signal.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/watchdog.h>
#include "finit.h"
#include "helpers.h"
#include "util.h"
#include "watchdog.h"
int running = 1;
int handover = 0;
static int notify(void)
{
int sd, result = 255;
ssize_t len;
struct sockaddr_un sun = {
.sun_family = AF_UNIX,
.sun_path = INIT_SOCKET,
};
struct init_request rq;
sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (-1 == sd)
return -1;
if (connect(sd, (struct sockaddr*)&sun, sizeof(sun)) == -1)
goto exit;
rq.cmd = INIT_CMD_WDOG_HELLO;
rq.magic = INIT_MAGIC;
rq.runlevel = getpid();
len = sizeof(rq);
if (write(sd, &rq, len) != len)
goto exit;
if (read(sd, &rq, len) != len)
goto exit;
result = 0;
exit:
close(sd);
return result;
}
static void sighandler(int signo)
{
if (signo == SIGTERM)
handover = 1;
running = 0;
}
static int init(char *progname, char *devnode, int timeout)
{
int fd;
sprintf(progname, "@finit-watchdog");
signal(SIGTERM, sighandler);
signal(SIGPWR, sighandler);
fd = open(devnode, O_WRONLY);
if (fd == -1)
return -1;
ioctl(fd, WDIOC_SETTIMEOUT, &timeout);
return fd;
}
static int loop(int fd)
{
int done = 0;
while (running) {
do_sleep(1);
ioctl(fd, WDIOC_KEEPALIVE, &done);
if (!done && !notify())
done = 1;
}
if (handover)
return !write(fd, "V", 1);
return 0;
}
void watchdog(char *progname)
{
if (fork() == 0) {
int fd, ret;
fd = init(progname, WDT_DEVNODE, WDT_TIMEOUT);
if (fd == -1) {
_pe("Failed connecting to watchdog %s", WDT_DEVNODE);
_exit(1);
}
ret = loop(fd);
close(fd);
_exit(ret);
}
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+40
View File
@@ -0,0 +1,40 @@
/* Built-in watchdog daemon
*
* Copyright (c) 2016 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 */
#define WDT_DEVNODE "/dev/watchdog"
#define WDT_TIMEOUT 3
#ifdef BUILTIN_WATCHDOG
void watchdog(char *progname);
#else
#define watchdog(progname)
#endif /* BUILTIN_WATCHDOG */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/