mirror of
https://github.com/troglobit/finit.git
synced 2026-10-02 14:02:52 +07:00
The responsibility of the kernel event daemon is to relay kernel events to Finit. At shutdown and reboot it is too late for more events and the daemon should just shut down with other services. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1669 lines
36 KiB
C
1669 lines
36 KiB
C
/* Parser for /etc/finit.conf and /etc/finit.d/<SVC>.conf
|
|
*
|
|
* Copyright (c) 2012-2024 Joachim Wiberg <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>
|
|
#include <string.h>
|
|
#include <sys/inotify.h>
|
|
#include <sys/resource.h>
|
|
#ifdef _LIBITE_LITE
|
|
# include <libite/lite.h>
|
|
# include <libite/queue.h> /* BSD sys/queue.h API */
|
|
#else
|
|
# include <lite/lite.h>
|
|
# include <lite/queue.h> /* BSD sys/queue.h API */
|
|
#endif
|
|
#include <time.h>
|
|
#include <glob.h>
|
|
|
|
#include "finit.h"
|
|
#include "cond.h"
|
|
#include "devmon.h"
|
|
#include "iwatch.h"
|
|
#include "private.h"
|
|
#include "service.h"
|
|
#include "tty.h"
|
|
#include "helpers.h"
|
|
#include "util.h"
|
|
|
|
#define BOOTSTRAP (runlevel == INIT_LEVEL)
|
|
|
|
int logfile_size_max = 200000; /* 200 kB */
|
|
int logfile_count_max = 5;
|
|
|
|
struct env_entry {
|
|
TAILQ_ENTRY(env_entry) link;
|
|
char *name;
|
|
};
|
|
static TAILQ_HEAD(, env_entry) env_list = TAILQ_HEAD_INITIALIZER(env_list);
|
|
|
|
struct rlimit initial_rlimit[RLIMIT_NLIMITS];
|
|
struct rlimit global_rlimit[RLIMIT_NLIMITS];
|
|
|
|
/*
|
|
* --enable-fastboot => fsck_mode: NULL => no fsck by default
|
|
* --enable-fsckfix => fsck_mode: "-f" + fsck_repair: "y"
|
|
*/
|
|
#ifdef FSCK_FIX
|
|
# ifdef FAST_BOOT
|
|
char *fsck_mode = NULL;
|
|
# else
|
|
char *fsck_mode = "-f";
|
|
# endif
|
|
char *fsck_repair = "-y";
|
|
#else
|
|
# ifdef FAST_BOOT
|
|
char *fsck_mode = NULL;
|
|
# else
|
|
char *fsck_mode = "";
|
|
# endif
|
|
char *fsck_repair = "-p";
|
|
#endif
|
|
|
|
char *runparts = NULL;
|
|
int runparts_progress;
|
|
int runparts_sysv;
|
|
|
|
char cgroup_current[16]; /* cgroup.NAME sets current cgroup for a set of services */
|
|
|
|
struct conf_change {
|
|
TAILQ_ENTRY(conf_change) link;
|
|
char *name;
|
|
};
|
|
|
|
static struct iwatch iw_conf;
|
|
static int iwatch_fd;
|
|
static uev_t etcw;
|
|
|
|
static TAILQ_HEAD(, conf_change) conf_change_list = TAILQ_HEAD_INITIALIZER(conf_change_list);
|
|
|
|
static char *path;
|
|
static char *shell;
|
|
|
|
static int parse_conf(char *file, int is_rcsd);
|
|
static void drop_changes(void);
|
|
|
|
static int get_bool(char *arg, int default_value)
|
|
{
|
|
if (!arg)
|
|
goto fallback;
|
|
|
|
if (string_compare(arg, "true") || string_compare(arg, "on") || string_compare(arg, "1"))
|
|
return 1;
|
|
if (string_compare(arg, "false") || string_compare(arg, "off") || string_compare(arg, "0"))
|
|
return 0;
|
|
fallback:
|
|
return default_value;
|
|
}
|
|
|
|
static int validate_arg(char *arg, const char *opt)
|
|
{
|
|
if (!arg) {
|
|
errx(1, "option %s missing argument, skipping.", opt);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* finit.cond = foo (=> <boot/foo>)
|
|
* finit.config = /path/to/etc/alt-finit.conf
|
|
* finit.debug = [on,off]
|
|
* finit.fstab = /path/to/etc/fstab.aternative
|
|
* finit.status = [on,off] (compat finit.show_status)
|
|
* finit.status_style = [old,classic,modern]
|
|
*/
|
|
static void parse_finit_opts(char *opt)
|
|
{
|
|
char *arg;
|
|
|
|
arg = strchr(opt, '=');
|
|
if (arg)
|
|
*arg++ = 0;
|
|
|
|
if (string_compare(opt, "cond")) {
|
|
if (validate_arg(arg, "finit.cond"))
|
|
return;
|
|
cond_boot_parse(arg);
|
|
return;
|
|
}
|
|
|
|
if (string_compare(opt, "config")) {
|
|
if (validate_arg(arg, "finit.config"))
|
|
return;
|
|
if (finit_conf)
|
|
free(finit_conf);
|
|
finit_conf = strdup(arg);
|
|
return;
|
|
}
|
|
|
|
if (string_compare(opt, "debug")) {
|
|
debug = get_bool(arg, 1);
|
|
return;
|
|
}
|
|
|
|
if (string_compare(opt, "fstab")) {
|
|
if (validate_arg(arg, "finit.fstab"))
|
|
return;
|
|
if (fstab)
|
|
free(fstab);
|
|
fstab = strdup(arg);
|
|
return;
|
|
}
|
|
|
|
if (string_compare(opt, "status_style")) {
|
|
if (validate_arg(arg, "finit.status_style"))
|
|
return;
|
|
|
|
if (string_compare(arg, "old") || string_compare(arg, "classic"))
|
|
show_progress(PROGRESS_CLASSIC);
|
|
else
|
|
show_progress(PROGRESS_MODERN);
|
|
return;
|
|
}
|
|
|
|
if (string_compare(opt, "status") || string_compare(opt, "show_status")) {
|
|
show_progress(get_bool(arg, 1) ? PROGRESS_DEFAULT : PROGRESS_SILENT);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void parse_fsck_opts(char *opt)
|
|
{
|
|
char *arg;
|
|
|
|
arg = strchr(opt, '=');
|
|
if (arg)
|
|
*arg++ = 0;
|
|
|
|
if (string_compare(opt, "mode")) {
|
|
if (validate_arg(arg, "fsck.mode"))
|
|
return;
|
|
|
|
if (!strcmp(arg, "skip"))
|
|
fsck_mode = NULL;
|
|
if (!strcmp(arg, "auto"))
|
|
fsck_mode = "";
|
|
if (!strcmp(arg, "force"))
|
|
fsck_mode = "-f";
|
|
|
|
return;
|
|
}
|
|
|
|
if (string_compare(opt, "repair")) {
|
|
if (validate_arg(arg, "fsck.repair"))
|
|
return;
|
|
|
|
if (!strcmp(arg, "no"))
|
|
fsck_repair = "-n";
|
|
if (!strcmp(arg, "preen"))
|
|
fsck_repair = "-p";
|
|
if (!strcmp(arg, "yes"))
|
|
fsck_repair = "-y";
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When runlevel (single integer) is given on the command line,
|
|
* it overrides the runlevel in finit.conf and the built-in
|
|
* default (from configure). It do however have to pass the
|
|
* same sanity checks.
|
|
*/
|
|
static int parse_runlevel(char *arg)
|
|
{
|
|
const char *err = NULL;
|
|
char *ptr = arg;
|
|
long long num;
|
|
|
|
/* Sanity check the token is just digit(s) */
|
|
while (*ptr) {
|
|
if (!isdigit(*ptr++))
|
|
return 0;
|
|
}
|
|
|
|
num = strtonum(arg, 1, 9, &err);
|
|
if (err || num == 6) {
|
|
dbg("Not a valid runlevel (%s), valid levels are [1-9], excluding 6, skipping.", arg);
|
|
return 0;
|
|
}
|
|
|
|
return (int)num;
|
|
}
|
|
|
|
static void parse_arg(char *arg)
|
|
{
|
|
if (!strncmp(arg, "finit.", 6)) {
|
|
parse_finit_opts(&arg[6]);
|
|
return;
|
|
}
|
|
|
|
if (!strncmp(arg, "fsck.", 5)) {
|
|
parse_fsck_opts(&arg[5]);
|
|
return;
|
|
}
|
|
|
|
#ifdef RESCUE_MODE
|
|
if (string_compare(arg, "rescue") || string_compare(arg, "recover")) {
|
|
rescue = 1;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (string_compare(arg, "single") || string_compare(arg, "S")) {
|
|
single = 1;
|
|
return;
|
|
}
|
|
|
|
/* Put any new command line options before this line. */
|
|
|
|
cmdlevel = parse_runlevel(arg);
|
|
}
|
|
|
|
#ifdef KERNEL_CMDLINE
|
|
/*
|
|
* Parse /proc/cmdline to find args for init. Don't use this!
|
|
*
|
|
* Instead, rely on the kernel to give Finit its arguments as
|
|
* regular argc + argv[]. Only use this if the system you run
|
|
* on has a broken initramfs system that cannot forward args
|
|
* to Finit properly.
|
|
*/
|
|
static void parse_kernel_cmdline(void)
|
|
{
|
|
char line[LINE_SIZE], *cmdline, *tok;
|
|
FILE *fp;
|
|
|
|
fp = fopen("/proc/cmdline", "r");
|
|
if (!fp)
|
|
return;
|
|
|
|
if (!fgets(line, sizeof(line), fp)) {
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
cmdline = chomp(line);
|
|
dbg("%s", cmdline);
|
|
|
|
while ((tok = strtok(cmdline, " \t"))) {
|
|
cmdline = NULL;
|
|
parse_arg(tok);
|
|
}
|
|
fclose(fp);
|
|
}
|
|
#else
|
|
#define parse_kernel_cmdline()
|
|
#endif
|
|
|
|
static void parse_kernel_loglevel(void)
|
|
{
|
|
char line[LINE_SIZE], *ptr;
|
|
FILE *fp;
|
|
int val;
|
|
|
|
fp = fopen("/proc/sys/kernel/printk", "r");
|
|
if (!fp)
|
|
return;
|
|
|
|
if (!fgets(line, sizeof(line), fp)) {
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
fclose(fp);
|
|
|
|
ptr = chomp(line);
|
|
dbg("%s", ptr);
|
|
val = atoi(ptr);
|
|
if (val >= 7)
|
|
kerndebug = 1;
|
|
}
|
|
|
|
/*
|
|
* Kernel gives us all non-kernel options on our cmdline
|
|
*/
|
|
void conf_parse_cmdline(int argc, char *argv[])
|
|
{
|
|
/* Set up defaults */
|
|
fstab = strdup(FINIT_FSTAB);
|
|
finit_conf = strdup(FINIT_CONF);
|
|
finit_rcsd = strdup(FINIT_RCSD);
|
|
path = getenv("PATH");
|
|
shell = getenv("SHELL");
|
|
|
|
for (int i = 1; i < argc; i++)
|
|
parse_arg(argv[i]);
|
|
|
|
parse_kernel_cmdline();
|
|
parse_kernel_loglevel();
|
|
}
|
|
|
|
/*
|
|
* Clear all environment variables read in parse_env(), they may be
|
|
* removed now so let the next call to parse_env() restore them.
|
|
*/
|
|
void conf_reset_env(void)
|
|
{
|
|
struct env_entry *node, *tmp;
|
|
|
|
TAILQ_FOREACH_SAFE(node, &env_list, link, tmp) {
|
|
TAILQ_REMOVE(&env_list, node, link);
|
|
if (node->name) {
|
|
unsetenv(node->name);
|
|
free(node->name);
|
|
}
|
|
free(node);
|
|
}
|
|
|
|
if (path)
|
|
setenv("PATH", path, 1);
|
|
else
|
|
setenv("PATH", _PATH_STDPATH, 1);
|
|
if (shell)
|
|
setenv("SHELL", shell, 1);
|
|
else
|
|
setenv("SHELL", _PATH_BSHELL, 1);
|
|
setenv("LOGNAME", "root", 1);
|
|
setenv("USER", "root", 1);
|
|
}
|
|
|
|
/*
|
|
* Sourced mainly by initctl and other Finit helper tools
|
|
*/
|
|
void conf_saverc(void)
|
|
{
|
|
FILE *fp;
|
|
|
|
mkpath(_PATH_VARRUN "finit", 0755);
|
|
fp = fopen(_PATH_VARRUN "finit/.initrc", "w");
|
|
if (!fp) {
|
|
err(1, "failed creating .finitrc");
|
|
return;
|
|
}
|
|
|
|
fprintf(fp, "FINIT_CONF=%s\n", finit_conf);
|
|
fprintf(fp, "FINIT_RCSD=%s\n", finit_rcsd);
|
|
fprintf(fp, "FINIT_CGPATH=%s\n", FINIT_CGPATH);
|
|
fprintf(fp, "INIT_SOCKET=%s\n", INIT_SOCKET);
|
|
fprintf(fp, "INIT_MAGIC=0x%08x\n", INIT_MAGIC);
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
/*
|
|
* Called at bootstrap to log execution order (for debug)
|
|
*/
|
|
void conf_save_exec_order(svc_t *svc, char *cmdline, int result)
|
|
{
|
|
const char *fn = _PATH_VARRUN "finit/exec.order";
|
|
const char *ststr = !result ? "[ OK ]" : "[FAIL]";
|
|
static char *prepared = NULL;
|
|
int first = !fexist(fn);
|
|
FILE *fp;
|
|
|
|
fp = fopen(fn, "a+");
|
|
if (!fp) {
|
|
err(1, "failed writing to %s", fn);
|
|
return;
|
|
}
|
|
|
|
if (first) {
|
|
fprintf(fp, "# Execution order of run/task/services at bootstrap\n");
|
|
fprintf(fp, "# ST TYPE COMMAND LINE (DESC)\n");
|
|
}
|
|
|
|
if (result == -1) {
|
|
int len;
|
|
|
|
len = snprintf(NULL, 0, " %-7s %-64s (%s)", svc_typestr(svc), cmdline, svc->desc);
|
|
if (len > 0) {
|
|
if (prepared)
|
|
free(prepared);
|
|
prepared = malloc(++len);
|
|
if (!prepared)
|
|
goto done;
|
|
snprintf(prepared, len, " %-7s %-64s (%s)", svc_typestr(svc), cmdline, svc->desc);
|
|
}
|
|
} else {
|
|
if (prepared) {
|
|
memcpy(prepared, ststr, strlen(ststr));
|
|
fprintf(fp, "%s\n", prepared);
|
|
free(prepared);
|
|
prepared = NULL;
|
|
} else {
|
|
fprintf(fp, "%s %-7s %-64s (%s)\n", ststr, svc_typestr(svc), cmdline, svc->desc);
|
|
}
|
|
}
|
|
done:
|
|
fclose(fp);
|
|
}
|
|
|
|
/*
|
|
* Called by plugins and similar that dynamically generate system services
|
|
*/
|
|
void conf_save_service(int type, char *cfg, char *file)
|
|
{
|
|
char fn[256];
|
|
svc_t foo;
|
|
FILE *fp;
|
|
|
|
mkpath(FINIT_RUNPATH_, 0755);
|
|
snprintf(fn, sizeof(fn), "%s/%s", FINIT_RUNPATH_, file);
|
|
if (fexist(fn))
|
|
warnx("File %s already exists, overwriting.", fn);
|
|
fp = fopen(fn, "w");
|
|
if (!fp) {
|
|
err(1, "Failed creating %s", fn);
|
|
return;
|
|
}
|
|
|
|
foo.type = type;
|
|
fprintf(fp, "# Generated by finit:%s()\n", __func__);
|
|
fprintf(fp, "%s %s\n", svc_typestr(&foo), cfg);
|
|
fclose(fp);
|
|
}
|
|
|
|
/*
|
|
* Sets, and makes a note of, all KEY=VALUE lines in a given .conf line
|
|
* from finit.conf, or other .conf file. Note, PATH is always reset in
|
|
* the conf_reset_env() function.
|
|
*/
|
|
static void parse_env(char *line)
|
|
{
|
|
struct env_entry *node;
|
|
char *key, *val, *end;
|
|
|
|
/* skip any leading whitespace */
|
|
key = line;
|
|
while (isspace(*key))
|
|
key++;
|
|
|
|
/* find end of line */
|
|
end = key;
|
|
while (*end)
|
|
end++;
|
|
|
|
/* strip trailing whitespace */
|
|
if (end > key) {
|
|
end--;
|
|
while (isspace(*end))
|
|
*end-- = 0;
|
|
}
|
|
|
|
val = strchr(key, '=');
|
|
if (!val)
|
|
return;
|
|
*val++ = 0;
|
|
|
|
/* strip leading whitespace from value */
|
|
while (isspace(*val))
|
|
val++;
|
|
|
|
/* unquote value, if quoted */
|
|
if (val[0] == '"' || val[0] == '\'') {
|
|
char q = val[0];
|
|
|
|
if (*end == q) {
|
|
val = &val[1];
|
|
*end = 0;
|
|
}
|
|
}
|
|
|
|
/* find end of key */
|
|
end = key;
|
|
while (*end)
|
|
end++;
|
|
|
|
/* strip trailing whitespace */
|
|
if (end > key) {
|
|
end--;
|
|
while (isspace(*end))
|
|
*end-- = 0;
|
|
}
|
|
|
|
/* strip any leading 'set ' */
|
|
end = key;
|
|
if (!strncmp(key, "set", 3))
|
|
end += 3;
|
|
|
|
/* check key, no spaces allowed */
|
|
while (*end && isspace(*end))
|
|
end++;
|
|
key = end;
|
|
while (*end && !isspace(*end))
|
|
end++;
|
|
if (*end != 0) {
|
|
warnx("'%s=%s': not a valid identifier", key, val);
|
|
return; /* invalid key */
|
|
}
|
|
|
|
dbg("Global env '%s'='%s'", key, val);
|
|
setenv(key, val, 1);
|
|
|
|
node = malloc(sizeof(*node));
|
|
if (!node) {
|
|
err(1, "Out of memory cannot track env vars");
|
|
return;
|
|
}
|
|
node->name = strdup(key);
|
|
TAILQ_INSERT_HEAD(&env_list, node, link);
|
|
}
|
|
|
|
static int kmod_exists(char *mod)
|
|
{
|
|
char buf[256];
|
|
int found = 0;
|
|
FILE *fp;
|
|
|
|
fp = fopen("/proc/modules", "r");
|
|
if (!fp)
|
|
return 0;
|
|
|
|
while (!found && fgets(buf, sizeof(buf), fp)) {
|
|
char *kmod = strtok(buf, " \t");
|
|
|
|
if (kmod && !strcmp(kmod, mod))
|
|
found = 1;
|
|
}
|
|
fclose(fp);
|
|
|
|
return found;
|
|
}
|
|
|
|
static void kmod_load(char *mod)
|
|
{
|
|
char module[64] = { 0 };
|
|
char cmd[CMD_SIZE];
|
|
|
|
if (runlevel != INIT_LEVEL)
|
|
return;
|
|
|
|
/* Strip args for progress below and kmod_exists() */
|
|
strlcpy(module, mod, sizeof(module));
|
|
if (!strtok(module, " \t"))
|
|
return;
|
|
|
|
if (kmod_exists(module))
|
|
return;
|
|
|
|
strcpy(cmd, "modprobe ");
|
|
strlcat(cmd, mod, sizeof(cmd));
|
|
|
|
run_interactive(cmd, "Loading kernel module %s", module);
|
|
}
|
|
|
|
/* 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 = 0x7FE;
|
|
continue;
|
|
}
|
|
|
|
if ('s' == lvl || 'S' == lvl)
|
|
level = INIT_LEVEL;
|
|
else
|
|
level = lvl - '0';
|
|
|
|
if (level > INIT_LEVEL || 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;
|
|
char *c;
|
|
|
|
if (!svc) {
|
|
errx(1, "Invalid service pointer");
|
|
return;
|
|
}
|
|
|
|
/* By default we assume UNIX daemons support SIGHUP */
|
|
if (svc_is_daemon(svc))
|
|
svc->sighup = 1;
|
|
|
|
if (!cond) {
|
|
memset(svc->cond, 0, sizeof(svc->cond));
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* First character must be '!' if:
|
|
* - service: SIGHUP is not supported
|
|
* - run/task: Do not block bootstrap
|
|
*/
|
|
ptr = cond;
|
|
if (ptr[i] == '!') {
|
|
ptr++;
|
|
|
|
if (svc_is_runtask(svc)) {
|
|
/* see service_runtask_clean() */
|
|
svc->sighup = 1;
|
|
svc->once = 1;
|
|
} else {
|
|
svc->sighup = 0;
|
|
}
|
|
}
|
|
|
|
while (ptr[i] != '>' && ptr[i] != 0)
|
|
i++;
|
|
ptr[i] = 0;
|
|
|
|
if (i >= sizeof(svc->cond)) {
|
|
logit(LOG_WARNING, "%s: too long list of conditions: %s", svc_ident(svc, NULL, 0), ptr);
|
|
return;
|
|
}
|
|
|
|
svc->cond[0] = 0;
|
|
for (i = 0, c = strtok(ptr, ","); c; c = strtok(NULL, ","), i++) {
|
|
devmon_add_cond(c);
|
|
if (i)
|
|
strlcat(svc->cond, ",", sizeof(svc->cond));
|
|
strlcat(svc->cond, c, 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 }
|
|
};
|
|
|
|
int str2rlim(char *str)
|
|
{
|
|
const struct rlimit_name *rn;
|
|
|
|
for (rn = rlimit_names; rn->name; rn++) {
|
|
if (!strcmp(str, rn->name))
|
|
return rn->val;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
char *rlim2str(int rlim)
|
|
{
|
|
const struct rlimit_name *rn;
|
|
|
|
for (rn = rlimit_names; rn->name; rn++) {
|
|
if (rn->val == rlim)
|
|
return rn->name;
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
char *lim2str(struct rlimit *rlim)
|
|
{
|
|
char tmp[25];
|
|
static char buf[42];
|
|
|
|
buf[0] = 0;
|
|
if (RLIM_INFINITY == rlim->rlim_cur)
|
|
snprintf(tmp, sizeof(tmp), "unlimited, ");
|
|
else
|
|
snprintf(tmp, sizeof(tmp), "%llu, ", (unsigned long long)rlim->rlim_cur);
|
|
strlcat(buf, tmp, sizeof(buf));
|
|
|
|
if (RLIM_INFINITY == rlim->rlim_max)
|
|
snprintf(tmp, sizeof(tmp), "unlimited, ");
|
|
else
|
|
snprintf(tmp, sizeof(tmp), "%llu, ", (unsigned long long)rlim->rlim_max);
|
|
strlcat(buf, tmp, sizeof(buf));
|
|
|
|
return buf;
|
|
}
|
|
|
|
/* First form: `rlimit <hard|soft> RESOURCE LIMIT` */
|
|
void conf_parse_rlimit(char *line, struct rlimit arr[])
|
|
{
|
|
char *level, *limit, *val;
|
|
int resource = -1;
|
|
rlim_t cfg;
|
|
|
|
level = strtok(line, " \t");
|
|
if (!level)
|
|
goto error;
|
|
|
|
limit = strtok(NULL, " \t");
|
|
if (!limit)
|
|
goto error;
|
|
|
|
val = strtok(NULL, " \t");
|
|
if (!val) {
|
|
/* Second form: `rlimit RESOURCE LIMIT` */
|
|
val = limit;
|
|
limit = level;
|
|
level = "both";
|
|
}
|
|
|
|
resource = str2rlim(limit);
|
|
if (resource < 0 || resource > RLIMIT_NLIMITS)
|
|
goto error;
|
|
|
|
/* Official keyword from v3.1 is `unlimited`, from prlimit(1) */
|
|
if (!strcmp(val, "unlimited") || !strcmp(val, "infinity")) {
|
|
cfg = RLIM_INFINITY;
|
|
} else {
|
|
const char *err = NULL;
|
|
|
|
cfg = strtonum(val, 0, (long long)2 << 31, &err);
|
|
if (err) {
|
|
logit(LOG_WARNING, "rlimit: invalid %s value: %s",
|
|
rlim2str(resource), val);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(level, "soft"))
|
|
arr[resource].rlim_cur = cfg;
|
|
else if (!strcmp(level, "hard"))
|
|
arr[resource].rlim_max = cfg;
|
|
else if (!strcmp(level, "both"))
|
|
arr[resource].rlim_max = arr[resource].rlim_cur = cfg;
|
|
else
|
|
goto error;
|
|
|
|
return;
|
|
error:
|
|
logit(LOG_WARNING, "rlimit: parse error");
|
|
}
|
|
|
|
/* cgroup NAME ctrl.prop:value,ctrl.prop:value ... */
|
|
static void conf_parse_cgroup(char *line)
|
|
{
|
|
char config[strlen(line) + 1];
|
|
char *ptr, *name;
|
|
|
|
name = strtok(line, " \t");
|
|
if (!name)
|
|
return;
|
|
|
|
if (strstr(name, "..") || strchr(name, '/'))
|
|
return; /* illegal */
|
|
|
|
config[0] = 0;
|
|
while ((ptr = strtok(NULL, " \t"))) {
|
|
if (config[0])
|
|
strlcat(config, ",", sizeof(config));
|
|
strlcat(config, ptr, sizeof(config));
|
|
}
|
|
|
|
cgroup_add(name, config, 0);
|
|
}
|
|
|
|
static int parse_static(char *line, int is_rcsd)
|
|
{
|
|
char cmd[CMD_SIZE];
|
|
char *x;
|
|
|
|
if (BOOTSTRAP && (MATCH_CMD(line, "host ", x) || MATCH_CMD(line, "hostname ", x))) {
|
|
if (hostname) free(hostname);
|
|
hostname = strdup(strip_line(x));
|
|
return 0;
|
|
}
|
|
|
|
if (BOOTSTRAP && MATCH_CMD(line, "mknod ", x)) {
|
|
char *dev = strip_line(x);
|
|
|
|
strcpy(cmd, "mknod ");
|
|
strlcat(cmd, dev, sizeof(cmd));
|
|
run_interactive(cmd, "Creating device node %s", dev);
|
|
return 0;
|
|
}
|
|
|
|
/* Kernel module to load */
|
|
if (BOOTSTRAP && MATCH_CMD(line, "module ", x)) {
|
|
kmod_load(strip_line(x));
|
|
return 0;
|
|
}
|
|
|
|
if (BOOTSTRAP && MATCH_CMD(line, "network ", x)) {
|
|
if (network) free(network);
|
|
network = strdup(strip_line(x));
|
|
return 0;
|
|
}
|
|
|
|
if (BOOTSTRAP && MATCH_CMD(line, "rcsd ", x)) {
|
|
if (finit_rcsd) free(finit_rcsd);
|
|
finit_rcsd = strdup(strip_line(x));
|
|
return 0;
|
|
}
|
|
|
|
if (BOOTSTRAP && MATCH_CMD(line, "runparts ", x)) {
|
|
if (runparts) free(runparts);
|
|
runparts_progress = runparts_sysv = 0;
|
|
while (x) {
|
|
if (MATCH_CMD(x, "progress", x))
|
|
runparts_progress = 1;
|
|
else if (MATCH_CMD(x, "sysv", x))
|
|
runparts_sysv = 1;
|
|
else
|
|
break;
|
|
}
|
|
runparts = strdup(strip_line(x));
|
|
return 0;
|
|
}
|
|
|
|
if (BOOTSTRAP && MATCH_CMD(line, "set ", x)) {
|
|
parse_env(x);
|
|
return 0;
|
|
}
|
|
|
|
if (MATCH_CMD(line, "include ", x)) {
|
|
char *file = strip_line(x);
|
|
|
|
strlcpy(cmd, file, sizeof(cmd));
|
|
if (!fexist(cmd)) {
|
|
errx(1, "Cannot find include file %s, absolute path required!", x);
|
|
return 1;
|
|
}
|
|
|
|
return parse_conf(cmd, is_rcsd);
|
|
}
|
|
|
|
if (MATCH_CMD(line, "log ", x)) {
|
|
char *tok;
|
|
static int size = 200000, count = 5;
|
|
|
|
tok = strtok(x, ":= ");
|
|
while (tok) {
|
|
if (!strncmp(tok, "size", 4))
|
|
size = strtobytes(strtok(NULL, ":= "));
|
|
else if (!strncmp(tok, "count", 5))
|
|
count = strtobytes(strtok(NULL, ":= "));
|
|
|
|
tok = strtok(NULL, ":= ");
|
|
}
|
|
|
|
if (size >= 0)
|
|
logfile_size_max = size;
|
|
if (count >= 0)
|
|
logfile_count_max = count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (MATCH_CMD(line, "shutdown ", x)) {
|
|
if (sdown) free(sdown);
|
|
sdown = strdup(strip_line(x));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 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 and 0 for halt/poweroff.
|
|
*/
|
|
if (BOOTSTRAP && 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 0;
|
|
}
|
|
|
|
/*
|
|
* Before Finit 5 the default readiness notification is PID.
|
|
* This default can be changed by setting 'readiness none' in
|
|
* the file /etc/finit.conf, only read once at bootstrap.
|
|
*/
|
|
if (BOOTSTRAP && MATCH_CMD(line, "readiness ", x)) {
|
|
char *token = strip_line(x);
|
|
|
|
if (!strcmp(token, "none"))
|
|
readiness = SVC_NOTIFY_NONE;
|
|
}
|
|
|
|
if (MATCH_CMD(line, "reboot-delay ", x)) {
|
|
syncsec = strtonum(strip_line(x), 0, 60, NULL);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Periodic check and instability index leveler, seconds
|
|
*/
|
|
if (MATCH_CMD(line, "service-interval ", x)) {
|
|
char *token = strip_line(x);
|
|
const char *err = NULL;
|
|
int val;
|
|
|
|
/* 0 min to 1 day, should check at least daily */
|
|
val = strtonum(token, 0, 1440, &err);
|
|
if (!err) {
|
|
int disabled = !service_interval;
|
|
|
|
service_interval = val * 1000; /* to milliseconds */
|
|
if (disabled)
|
|
service_init(NULL);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int parse_dynamic(char *line, struct rlimit rlimit[], char *file)
|
|
{
|
|
char *x;
|
|
|
|
/* Monitored daemon, will be respawned on exit */
|
|
if (MATCH_CMD(line, "service ", x)) {
|
|
service_register(SVC_TYPE_SERVICE, x, rlimit, file);
|
|
return 0;
|
|
}
|
|
|
|
/* One-shot task, will not be respawned */
|
|
if (MATCH_CMD(line, "task ", x)) {
|
|
service_register(SVC_TYPE_TASK, x, rlimit, file);
|
|
return 0;
|
|
}
|
|
|
|
/* Like task but waits for completion, useful w/ [S] */
|
|
if (MATCH_CMD(line, "run ", x)) {
|
|
service_register(SVC_TYPE_RUN, x, rlimit, file);
|
|
return 0;
|
|
}
|
|
|
|
/* Similar to task but is treated like a SysV init script */
|
|
if (MATCH_CMD(line, "sysv ", x)) {
|
|
service_register(SVC_TYPE_SYSV, x, rlimit, file);
|
|
return 0;
|
|
}
|
|
|
|
/* Read resource limits */
|
|
if (MATCH_CMD(line, "rlimit ", x)) {
|
|
conf_parse_rlimit(x, rlimit);
|
|
return 0;
|
|
}
|
|
|
|
/* Read control group limits */
|
|
if (MATCH_CMD(line, "cgroup ", x)) {
|
|
conf_parse_cgroup(x);
|
|
return 0;
|
|
}
|
|
|
|
/* Set current cgroup for the following services/run/tasks */
|
|
if (MATCH_CMD(line, "cgroup.", x)) {
|
|
strlcpy(cgroup_current, x, sizeof(cgroup_current));
|
|
return 0;
|
|
}
|
|
|
|
/* Regular or serial TTYs to run getty */
|
|
if (MATCH_CMD(line, "tty ", x)) {
|
|
service_register(SVC_TYPE_TTY, strip_line(x), rlimit, file);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Very simple and crude implementation, only supports '%i'
|
|
*/
|
|
static char *instantiate(char *line, char *name)
|
|
{
|
|
char *ptr, *end = strchr(line, 0);
|
|
char *pos = line;
|
|
size_t num = 0;
|
|
|
|
if (!name[0] || !end)
|
|
return line;
|
|
|
|
while ((ptr = strchr(pos, '%'))) {
|
|
num++;
|
|
pos++;
|
|
}
|
|
|
|
ptr = realloc(line, strlen(line) + num * strlen(name) + 1);
|
|
if (!ptr)
|
|
return line;
|
|
|
|
pos = line = ptr;
|
|
while ((ptr = strchr(pos, '%'))) {
|
|
if (!strncmp(ptr, "%i", 2)) {
|
|
char *rest = &ptr[2];
|
|
char *next = ptr + strlen(name);
|
|
|
|
memmove(next, rest, strlen(rest) + 1);
|
|
memcpy(ptr, name, strlen(name));
|
|
|
|
pos += strlen(name);
|
|
} else
|
|
pos++;
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
static int is_template(const char *file, char *name, size_t len)
|
|
{
|
|
char *ptr, *nm;
|
|
size_t i = 0;
|
|
|
|
ptr = strchr(file, '@');
|
|
if (!ptr)
|
|
return 0; /* not a template */
|
|
|
|
nm = ptr + 1;
|
|
ptr = strstr(nm, ".conf");
|
|
if (!strcmp(nm, ".conf") || !ptr)
|
|
return 1; /* template itself or invalid */
|
|
|
|
while (nm < ptr && i < len - 1)
|
|
name[i++] = *nm++;
|
|
name[i] = 0;
|
|
|
|
return 1; /* instantiated template */
|
|
}
|
|
|
|
static int parse_conf(char *file, int is_rcsd)
|
|
{
|
|
struct rlimit rlimit[RLIMIT_NLIMITS];
|
|
char name[65] = { 0 };
|
|
FILE *fp;
|
|
|
|
if (is_template(file, name, sizeof(name))) {
|
|
if (!name[0]) {
|
|
dbg("*** Skipping template file %s", file);
|
|
return 0;
|
|
}
|
|
dbg("*** instantiating %s from %s ...", name, file);
|
|
}
|
|
|
|
fp = fopen(file, "r");
|
|
if (!fp)
|
|
return 1;
|
|
|
|
/* Prepare default limits and group for each service in /etc/finit.d/ */
|
|
if (is_rcsd) {
|
|
memcpy(rlimit, global_rlimit, sizeof(rlimit));
|
|
cgroup_current[0] = 0;
|
|
}
|
|
|
|
dbg("*** Parsing %s", file);
|
|
while (!feof(fp)) {
|
|
char *line;
|
|
|
|
line = fparseln(fp, NULL, NULL, NULL, FPARSELN_UNESCCOMM);
|
|
if (!line)
|
|
continue;
|
|
|
|
tabstospaces(line);
|
|
// dbg("raw: %s", line);
|
|
line = instantiate(line, name);
|
|
// dbg("ins: %s", line);
|
|
|
|
if (!parse_static(line, is_rcsd))
|
|
;
|
|
else if (!parse_dynamic(line, is_rcsd ? rlimit : global_rlimit, file))
|
|
;
|
|
else
|
|
parse_env(line);
|
|
|
|
free(line);
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void glob_append(glob_t *gl, int append, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
size_t len;
|
|
char *path;
|
|
|
|
va_start(ap, fmt);
|
|
len = vsnprintf(NULL, 0, fmt, ap);
|
|
va_end(ap);
|
|
|
|
path = alloca(++len);
|
|
if (!path) {
|
|
warn("failed alloca() in glob_append()");
|
|
return;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(path, len, fmt, ap);
|
|
va_end(ap);
|
|
|
|
dbg("conf_reload(): glob %s ...", path);
|
|
glob(path, append ? GLOB_APPEND : 0, NULL, gl);
|
|
}
|
|
|
|
/*
|
|
* Reload /etc/finit.conf and all *.conf in /etc/finit.d/
|
|
*/
|
|
int conf_reload(void)
|
|
{
|
|
glob_t gl;
|
|
size_t i;
|
|
|
|
/* Set time according to current time zone */
|
|
tzset();
|
|
dbg("Set time daylight: %d timezone: %ld tzname: %s %s",
|
|
daylight, timezone, tzname[0], tzname[1]);
|
|
|
|
/* Mark and sweep */
|
|
cgroup_mark_all();
|
|
svc_mark_dynamic();
|
|
conf_reset_env();
|
|
|
|
/*
|
|
* Reset global rlimit to bootstrap values from conf_init().
|
|
*/
|
|
memcpy(global_rlimit, initial_rlimit, sizeof(global_rlimit));
|
|
|
|
/*
|
|
* When built with --disable-rescue mode many other 'if (rescue)'
|
|
* code paths are #ifdeffed out. This one, and the ones in the
|
|
* plugins, are not because a hook or plugin can still trigger
|
|
* the alternative rescue.conf instead of finit.conf. This for
|
|
* very advanced use-cases where files on /etc are generated at
|
|
* bootstrap, including users and passwords, from other sources.
|
|
*/
|
|
if (rescue) {
|
|
int rc;
|
|
char line[80] = "tty [12345789] rescue";
|
|
|
|
/* If rescue.conf is missing, fall back to a root shell */
|
|
rc = parse_conf(RESCUE_CONF, 0);
|
|
if (rc)
|
|
service_register(SVC_TYPE_TTY, line, global_rlimit, NULL);
|
|
|
|
print(rc, "Entering rescue mode");
|
|
goto done;
|
|
}
|
|
|
|
/* First, read /etc/finit.conf */
|
|
parse_conf(finit_conf, 0);
|
|
|
|
/* Set global limits */
|
|
for (int i = 0; i < RLIMIT_NLIMITS; i++) {
|
|
if (setrlimit(i, &global_rlimit[i]) == -1)
|
|
logit(LOG_WARNING, "rlimit: Failed setting %s: %s",
|
|
rlim2str(i), lim2str(&global_rlimit[i]));
|
|
}
|
|
|
|
/*
|
|
* Next, read all *.conf in /lib/finit/system and /etc/finit.d/
|
|
* The system files were previously created at runtime by plugins
|
|
* but are now regular files that can be overridden by files in
|
|
* /etc/finit.d -- similar to how tmfiles.d(5) work. E.g., add
|
|
* an override .conf, or an ignore by symlinking to /dev/null
|
|
*
|
|
* The .conf files (and run/task/service stanzas) are parsed and
|
|
* started in order. Each directory is sorted alphanumerically
|
|
* and then the result is appended to the overall order:
|
|
*
|
|
* /lib/finit/system/10-hotplug.conf
|
|
* /lib/finit/system/90-testserv.conf
|
|
* /run/finit/system/dbus.conf
|
|
* /run/finit/system/tty.conf
|
|
* /etc/finit.d/10-abc.conf
|
|
* /etc/finit.d/20-abc.conf
|
|
* /etc/finit.d/enabled/1-aaa.conf
|
|
* /etc/finit.d/enabled/1-abc.conf
|
|
* /etc/finit.d/enabled/2-aaa.conf
|
|
*/
|
|
glob_append(&gl, 0, "%s/*.conf", FINIT_SYSPATH_);
|
|
glob_append(&gl, 1, "%s/*.conf", FINIT_RUNPATH_);
|
|
glob_append(&gl, 1, "%s/*.conf", finit_rcsd);
|
|
glob_append(&gl, 1, "%s/enabled/*.conf", finit_rcsd);
|
|
|
|
if (bootstrap) {
|
|
const char *fn = _PATH_VARRUN "finit/conf.order";
|
|
FILE *fp;
|
|
|
|
fp = fopen(fn, "w");
|
|
if (!fp) {
|
|
err(1, "failed creating %s", fn);
|
|
} else {
|
|
fprintf(fp, "# Evaluation & execution order of .conf files\n");
|
|
for (i = 0; i < gl.gl_pathc; i++)
|
|
fprintf(fp, "%s\n", gl.gl_pathv[i]);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < gl.gl_pathc; i++) {
|
|
char *path = gl.gl_pathv[i];
|
|
char *rp = NULL;
|
|
struct stat st;
|
|
size_t j, len;
|
|
|
|
/* check for FINIT_SYSPATH_ or FINIT_RUNPATH_ overrides */
|
|
for (j = i + 1; j < gl.gl_pathc; j++) {
|
|
if (strncmp(path, FINIT_SYSPATH_, strlen(FINIT_SYSPATH_)) &&
|
|
strncmp(path, FINIT_RUNPATH_, strlen(FINIT_RUNPATH_)))
|
|
continue;
|
|
if (strcmp(basenm(path), basenm(gl.gl_pathv[j])))
|
|
continue;
|
|
path = NULL; /* replacement later in list, skip this */
|
|
break;
|
|
}
|
|
|
|
if (!path)
|
|
continue; /* skip, override exists */
|
|
|
|
/* Check that it's an actual file ... beyond any symlinks */
|
|
if (lstat(path, &st)) {
|
|
dbg("Skipping %s, cannot access: %s", path, strerror(errno));
|
|
continue;
|
|
}
|
|
|
|
/* Skip directories */
|
|
if (S_ISDIR(st.st_mode)) {
|
|
dbg("Skipping directory %s", path);
|
|
continue;
|
|
}
|
|
|
|
/* Check for dangling symlinks */
|
|
if (S_ISLNK(st.st_mode)) {
|
|
rp = realpath(path, NULL);
|
|
if (!rp) {
|
|
logit(LOG_WARNING, "Skipping %s, dangling symlink: %s", path, strerror(errno));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Check that file ends with '.conf' */
|
|
len = strlen(path);
|
|
if (len < 6 || strcmp(&path[len - 5], ".conf"))
|
|
dbg("Skipping %s, not a Finit .conf file ... ", path);
|
|
else
|
|
parse_conf(path, 1);
|
|
|
|
if (rp)
|
|
free(rp);
|
|
}
|
|
|
|
globfree(&gl);
|
|
|
|
/* Mark any reverse deps as chenaged. */
|
|
service_update_rdeps();
|
|
|
|
/* Prune according to if:[!]ident or if:<[!]cond> */
|
|
service_mark_unavail();
|
|
|
|
/* Set up top-level cgroups */
|
|
cgroup_config();
|
|
done:
|
|
/* Remove all unused top-level cgroups */
|
|
cgroup_cleanup();
|
|
|
|
/* Drop record of all .conf changes */
|
|
drop_changes();
|
|
|
|
if (bootstrap)
|
|
wdog = svc_find("watchdog", "finit");
|
|
|
|
/* Override configured runlevel, user said 'S' on /proc/cmdline */
|
|
if (BOOTSTRAP && single)
|
|
cfglevel = 1;
|
|
|
|
/*
|
|
* Set host name, from %DEFHOST, *.conf or /etc/hostname. The
|
|
* latter wins, if neither exists we default to "noname"
|
|
*/
|
|
set_hostname(&hostname);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct conf_change *conf_find(char *file)
|
|
{
|
|
struct conf_change *node, *tmp;
|
|
|
|
TAILQ_FOREACH_SAFE(node, &conf_change_list, link, tmp) {
|
|
if (string_compare(node->name, file))
|
|
return node;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void drop_change(struct conf_change *node)
|
|
{
|
|
if (!node)
|
|
return;
|
|
|
|
TAILQ_REMOVE(&conf_change_list, node, link);
|
|
free(node->name);
|
|
free(node);
|
|
}
|
|
|
|
|
|
static void drop_changes(void)
|
|
{
|
|
struct conf_change *node, *tmp;
|
|
|
|
TAILQ_FOREACH_SAFE(node, &conf_change_list, link, tmp)
|
|
drop_change(node);
|
|
}
|
|
|
|
static int conf_change_act(char *dir, char *name, uint32_t mask)
|
|
{
|
|
char fn[strlen(dir) + strlen(name) + 2];
|
|
struct conf_change *node;
|
|
char *rp = NULL;
|
|
|
|
/* Check for actual printable characters, sometimes STX */
|
|
if (name[0] && name[0] >= ' ')
|
|
paste(fn, sizeof(fn), dir, name);
|
|
else
|
|
strlcpy(fn, dir, sizeof(fn));
|
|
dbg("path: %s mask: %08x", fn, mask);
|
|
|
|
/* Handle disabling/removal of service */
|
|
rp = realpath(fn, NULL);
|
|
if (!rp) {
|
|
if (errno != ENOENT)
|
|
goto fail;
|
|
rp = strdup(fn);
|
|
if (!rp)
|
|
goto fail;
|
|
}
|
|
|
|
node = conf_find(rp);
|
|
if (node) {
|
|
dbg("event already registered for %s ...", name);
|
|
free(rp);
|
|
return 0;
|
|
}
|
|
|
|
node = malloc(sizeof(*node));
|
|
if (!node) {
|
|
free(rp);
|
|
goto fail;
|
|
}
|
|
|
|
node->name = rp;
|
|
TAILQ_INSERT_HEAD(&conf_change_list, node, link);
|
|
dbg("event registered for %s, mask 0x%x", rp, mask);
|
|
return 0;
|
|
fail:
|
|
warn("failed registering %s event mask %08x", fn, mask);
|
|
return 1;
|
|
}
|
|
|
|
int conf_any_change(void)
|
|
{
|
|
if (TAILQ_EMPTY(&conf_change_list))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int conf_changed(char *file)
|
|
{
|
|
int rc = 0;
|
|
char *rp;
|
|
|
|
if (!file)
|
|
return 0;
|
|
|
|
rp = realpath(file, NULL);
|
|
if (!rp)
|
|
return 0;
|
|
|
|
if (conf_find(rp))
|
|
rc = 1;
|
|
free(rp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int conf_iwatch_read(int fd)
|
|
{
|
|
static char ev_buf[8 *(sizeof(struct inotify_event) + NAME_MAX + 1) + 1];
|
|
struct inotify_event *ev;
|
|
ssize_t sz;
|
|
size_t off;
|
|
|
|
sz = read(fd, ev_buf, sizeof(ev_buf) - 1);
|
|
if (sz <= 0)
|
|
return -1;
|
|
ev_buf[sz] = 0;
|
|
|
|
for (off = 0; off < (size_t)sz; off += sizeof(*ev) + ev->len) {
|
|
struct iwatch_path *iwp;
|
|
|
|
if (off + sizeof(*ev) > (size_t)sz)
|
|
break;
|
|
|
|
ev = (struct inotify_event *)&ev_buf[off];
|
|
if (off + sizeof(*ev) + ev->len > (size_t)sz)
|
|
break;
|
|
|
|
if (!ev->mask)
|
|
continue;
|
|
|
|
dbg("name %s, event: 0x%08x", ev->name, ev->mask);
|
|
|
|
/* Find base path for this event */
|
|
iwp = iwatch_find_by_wd(&iw_conf, ev->wd);
|
|
if (!iwp)
|
|
continue;
|
|
|
|
if (conf_change_act(iwp->path, ev->name, ev->mask))
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void conf_cb(uev_t *w, void *arg, int events)
|
|
{
|
|
if (conf_iwatch_read(w->fd)) {
|
|
err(1, "invalid inotify event");
|
|
return;
|
|
}
|
|
|
|
|
|
#ifdef AUTO_RELOAD
|
|
if (conf_any_change())
|
|
service_reload_dynamic();
|
|
#endif
|
|
}
|
|
|
|
void conf_flush_events(void)
|
|
{
|
|
while (!conf_iwatch_read(iwatch_fd))
|
|
dbg("emptying inotify queue ...");
|
|
}
|
|
|
|
/*
|
|
* Set up inotify watcher and load all *.conf in /etc/finit.d/
|
|
*/
|
|
int conf_monitor(void)
|
|
{
|
|
char path[strlen(finit_rcsd) + 16];
|
|
int rc = 0;
|
|
|
|
/*
|
|
* If only one watcher fails, that's OK. A user may have only
|
|
* one of /etc/finit.conf or /etc/finit.d in use, and may also
|
|
* have or not have symlinks in place. We need to monitor for
|
|
* changes to either symlink or target.
|
|
*/
|
|
rc += iwatch_add(&iw_conf, finit_rcsd, IN_ONLYDIR);
|
|
snprintf(path, sizeof(path), "%s/available/", finit_rcsd);
|
|
rc += iwatch_add(&iw_conf, path, IN_ONLYDIR | IN_DONT_FOLLOW);
|
|
snprintf(path, sizeof(path), "%s/enabled/", finit_rcsd);
|
|
rc += iwatch_add(&iw_conf, path, IN_ONLYDIR | IN_DONT_FOLLOW);
|
|
rc += iwatch_add(&iw_conf, finit_conf, 0);
|
|
|
|
/*
|
|
* Systems with /etc/default, /etc/conf.d, or similar, can also
|
|
* monitor changes in env files sourced by .conf files (above)
|
|
* define your own with --with-sysconfig=/path/to/envfiles
|
|
*/
|
|
rc += iwatch_add(&iw_conf, "/etc/default/", IN_ONLYDIR);
|
|
rc += iwatch_add(&iw_conf, "/etc/conf.d/", IN_ONLYDIR);
|
|
#ifdef FINIT_SYSCONFIG
|
|
rc += iwatch_add(&iw_conf, FINIT_SYSCONFIG, IN_ONLYDIR);
|
|
#endif
|
|
|
|
return rc + conf_reload();
|
|
}
|
|
|
|
/*
|
|
* Prepare .conf parser and load /etc/finit.conf for global settings
|
|
*/
|
|
int conf_init(uev_ctx_t *ctx)
|
|
{
|
|
/* default hostname */
|
|
hostname = strdup(DEFHOST);
|
|
|
|
/*
|
|
* Get current global limits, which may be overridden from both
|
|
* finit.conf, for Finit and its services like getty+watchdogd,
|
|
* and *.conf in finit.d/, for each service(s) listed there.
|
|
*/
|
|
for (int i = 0; i < RLIMIT_NLIMITS; i++) {
|
|
if (getrlimit(i, &initial_rlimit[i]))
|
|
logit(LOG_WARNING, "rlimit: Failed reading setting %s: %s",
|
|
rlim2str(i), strerror(errno));
|
|
}
|
|
|
|
/* Initialize global rlimits, e.g. for built-in services */
|
|
memcpy(global_rlimit, initial_rlimit, sizeof(global_rlimit));
|
|
|
|
/*
|
|
* Start built-in watchdogd as soon as possible, if enabled
|
|
*/
|
|
#ifdef WDT_DEVNODE
|
|
if (whichp(FINIT_EXECPATH_ "/watchdogd") && fexist(WDT_DEVNODE)) {
|
|
conf_save_service(SVC_TYPE_SERVICE, "[S0123456789] cgroup.init notify:none name:watchdog :finit "
|
|
FINIT_EXECPATH_ "/watchdogd -- Finit watchdog daemon", "watchdogd.conf");
|
|
}
|
|
#endif
|
|
/*
|
|
* Start kernel event daemon as soon as possible, if enabled
|
|
*/
|
|
if (whichp(FINIT_EXECPATH_ "/keventd"))
|
|
conf_save_service(SVC_TYPE_SERVICE, "[S12345789] cgroup.init notify:none "
|
|
FINIT_EXECPATH_ "/keventd -- Finit kernel event daemon", "keventd.conf");
|
|
|
|
dbg("Allow plugins to register early runlevel 1 run/task/services ...");
|
|
plugin_run_hooks(HOOK_SVC_PLUGIN);
|
|
|
|
/* Read global rlimits and global cgroup setup from /etc/finit.conf */
|
|
parse_conf(finit_conf, 0);
|
|
|
|
/* prepare /etc watcher */
|
|
iwatch_fd = iwatch_init(&iw_conf);
|
|
if (iwatch_fd < 0)
|
|
return 1;
|
|
|
|
if (uev_io_init(ctx, &etcw, conf_cb, NULL, iwatch_fd, UEV_READ)) {
|
|
err(1, "Failed setting up I/O callback for /etc watcher");
|
|
close(iwatch_fd);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Background startup scripts in the runparts directory, if any.
|
|
*/
|
|
if (runparts && fisdir(runparts) && !rescue) {
|
|
char conf[sizeof(_PATH_RUNPARTS) + strlen(runparts) + 100];
|
|
char args[10] = { 0 };
|
|
|
|
if (debug)
|
|
strlcat(args, "-d ", sizeof(args));
|
|
if (runparts_progress)
|
|
strlcat(args, "-p ", sizeof(args));
|
|
if (runparts_sysv)
|
|
strlcat(args, "-s ", sizeof(args));
|
|
|
|
snprintf(conf, sizeof(conf), "[S] <int/bootstrap> notify:none log:console %s %s %s"
|
|
" -- Calling runparts %s in the background",
|
|
_PATH_RUNPARTS, args, runparts, runparts);
|
|
conf_save_service(SVC_TYPE_TASK, conf, "runparts.conf");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Local Variables:
|
|
* indent-tabs-mode: t
|
|
* c-file-style: "linux"
|
|
* End:
|
|
*/
|