conf: cleanup and relocation of common parsing functions and helpers

- legacy.[ch]: strictly legacy .conf parser boilerplate only
 - conf.[ch]: .conf parser and generic configuration functionality

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-07-30 15:21:26 +02:00
1 parent c20d64587c
commit 47a18140c1
9 files changed
+831 -826

No files matched your search

+3
View File
@@ -26,6 +26,9 @@
#include <uev/uev.h>
#define CGROUP_NAME_SIZE 16
#define CGROUP_SETTINGS_SIZE 128
/* Forward declaration */
typedef struct svc svc_t;
+653 -8
View File
@@ -58,7 +58,9 @@
#include <glob.h>
#include "finit.h"
#include "cond.h"
#include "conf.h"
#include "devmon.h"
#include "iwatch.h"
#include "legacy.h"
#include "private.h"
@@ -66,8 +68,6 @@
#include "helpers.h"
#include "util.h"
#define BOOTSTRAP (runlevel == INIT_LEVEL)
int runlevel = INIT_LEVEL; /* Bootstrap 'S' */
int cfglevel = RUNLEVEL; /* Fallback if no configured runlevel */
int cmdlevel = 0; /* runlevel override from cmdline */
@@ -104,14 +104,19 @@ struct conf_change {
char *name;
};
struct env_entry {
TAILQ_ENTRY(env_entry) link;
char *name;
};
static char cfg_errmsg[256];
static struct iwatch iw_conf;
static int iwatch_fd;
static uev_t etcw;
static TAILQ_HEAD(, env_entry) env_list = TAILQ_HEAD_INITIALIZER(env_list);
static TAILQ_HEAD(, conf_change) conf_change_list = TAILQ_HEAD_INITIALIZER(conf_change_list);
static void drop_changes(void);
/*
* libconfuse schema -- the new block format
*
@@ -288,14 +293,650 @@ static cfg_opt_t conf_opts[] = {
CFG_END()
};
static void drop_changes(void);
static int validate_arg(const 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 = istrue(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(istrue(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[])
{
const char *ptr;
/* Set up defaults */
if ((ptr = FINIT_FSTAB))
fstab = strdup(ptr);
finit_conf = strdup(FINIT_CONF);
finit_rcsd = strdup(FINIT_RCSD);
for (int i = 1; i < argc; i++)
parse_arg(argv[i]);
parse_kernel_cmdline();
parse_kernel_loglevel();
}
/*
* 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);
}
/*
* 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 (!getenv("PATH"))
setenv("PATH", _PATH_STDPATH, 1);
if (!getenv("SHELL"))
setenv("SHELL", _PATH_BSHELL, 1);
setenv("LOGNAME", "root", 1);
setenv("USER", "root", 1);
}
/*
* 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.
*/
void conf_set_env(char *line)
{
struct env_entry *node;
char *key, *val;
key = conf_parse_env(line, &val);
if (!key)
return;
dbg("Global env '%s'='%s'", key, val);
setenv(key, val, 1);
node = malloc(sizeof(*node));
if (!node) {
nomem:
err(1, "Out of memory cannot track env vars");
return;
}
node->name = strdup(key);
if (!node->name) {
free(node);
goto nomem;
}
TAILQ_INSERT_HEAD(&env_list, node, link);
}
/**
* conf_parse_env - Parse a key=value line
* @line: Line buffer without newline
* @value: Whitespace trimmed value
*
* Used by service.c:source_env() and
*
* Returns:
* %NULL on error, otherwise a whitespace trimmed key.
*/
char *conf_parse_env(char *line, char **value)
{
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 NULL;
*val++ = 0;
/* strip leading whitespace from value */
while (isspace(*val))
val++;
/* unquote value, if quoted */
unquote(&val, end);
*value = val;
/* 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 NULL; /* invalid key */
}
return key;
}
/* 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");
}
/* Convert optional "[!123456789S]" string into a bitmask */
int conf_parse_runlevels(const 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;
}
/*
* The '~' prefix means a reload of the upstream service is
* propagated to this service -- it will be reloaded (SIGHUP)
* or restarted (noreload), not just paused and resumed.
* Syntax: <!~pid/foo,~pid/bar> or <~pid/foo>
*
* Strip '~' from each condition and set the service-level
* flag. This allows '~' on any condition in the list.
*/
svc->cond[0] = 0;
for (i = 0, c = strtok(ptr, ","); c; c = strtok(NULL, ","), i++) {
if (c[0] == '~') {
c++;
svc->flux_reload = 1;
}
devmon_add_cond(c);
if (i)
strlcat(svc->cond, ",", sizeof(svc->cond));
strlcat(svc->cond, c, sizeof(svc->cond));
}
}
/*
* Try-parse error capture. Diagnostics are buffered, not printed:
* on fallback to the legacy parser they are only debug logged. The
* last message wins -- the parser stops at the first fatal error, so
* the last callback before failure is the fatal one.
*/
static char cfg_errmsg[256];
static void cfg_error_cb(cfg_t *cfg, const char *fmt, va_list ap)
{
char msg[128];
@@ -669,7 +1310,7 @@ static void env_translate(cfg_t *cfg, const char *section)
snprintf(buf, sizeof(buf), "%s=%s", opt->name,
cfg_opt_getnstr(opt, 0));
legacy_parse_env(buf);
conf_set_env(buf);
}
}
@@ -780,9 +1421,11 @@ static void conf_parse_statics(cfg_t *cfg)
if (cfg_size(cfg, "service-interval")) {
long val = cfg_getint(cfg, "service-interval");
/* 0 min to 1 day, should check at least daily */
if (val >= 0 && val <= 1440) {
int disabled = !service_interval;
/* milliseconds for libuEv timer */
service_interval = (int)val * 1000;
if (disabled)
service_init(NULL);
@@ -913,7 +1556,9 @@ static int is_new_format(char *file)
}
/*
* Parse one Finit .conf file, in either format, see top of file.
* Parse one Finit .conf file, in either format. The legacy .conf
* include directive routes back through this so included files are
* format-detected too.
*/
int conf_parse_file(char *file, int is_rcsd)
{
+30 -22
View File
@@ -27,6 +27,10 @@
#include "cgroup.h"
#include "svc.h"
#include <sys/resource.h>
#define BOOTSTRAP (runlevel == INIT_LEVEL)
extern int runlevel;
extern int cfglevel;
extern int cmdlevel;
@@ -46,34 +50,38 @@ extern char *hostname;
extern char *runparts;
extern char *osheading;
extern int logfile_size_max;
extern int logfile_count_max;
extern int runparts_progress;
extern int runparts_sysv;
#define CGROUP_NAME_SIZE 16
#define CGROUP_SETTINGS_SIZE 128
extern int logfile_size_max;
extern int logfile_count_max;
extern char cgroup_current[CGROUP_NAME_SIZE];
extern char cgroup_settings_current[CGROUP_SETTINGS_SIZE];
extern int cgroup_delegate_current;
extern struct rlimit initial_rlimit[RLIMIT_NLIMITS];
extern struct rlimit global_rlimit[RLIMIT_NLIMITS];
extern char cgroup_current[CGROUP_NAME_SIZE];
extern char cgroup_settings_current[CGROUP_SETTINGS_SIZE];
extern int cgroup_delegate_current;
int str2rlim(char *str);
char *rlim2str(int rlim);
int conf_init (uev_ctx_t *ctx);
int conf_reload (void);
int conf_any_change (void);
int conf_changed (char *file);
int conf_monitor (void);
int conf_init (uev_ctx_t *ctx);
int conf_reload (void);
int conf_any_change (void);
int conf_changed (char *file);
int conf_monitor (void);
void conf_saverc (void);
void conf_save_exec_order (svc_t *svc, char *cmdline, int result);
void conf_save_service (int type, char *cfg, char *file);
void conf_parse_cmdline (int argc, char *argv[]);
void conf_reset_env (void);
void conf_saverc (void);
void conf_save_exec_order (svc_t *svc, char *cmdline, int result);
void conf_save_service (int type, char *cfg, char *file);
void conf_parse_cmdline (int argc, char *argv[]);
char *conf_parse_env (char *line, char **val);
int conf_parse_runlevels (char *runlevels);
void conf_parse_cond (svc_t *svc, char *cond);
void conf_reset_env (void);
void conf_set_env (char *line);
char *conf_parse_env (char *line, char **val);
void conf_parse_rlimit (char *line, struct rlimit arr[]);
int conf_parse_runlevels (const char *runlevels);
void conf_parse_cond (svc_t *svc, char *cond);
int conf_parse_file (char *file, int is_rcsd);
#endif /* FINIT_CONF_H_ */
+46
View File
@@ -470,6 +470,52 @@ done:
}
}
static int kmod_exists(const 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)) {
const char *kmod = strtok(buf, " \t");
if (kmod && !strcmp(kmod, mod))
found = 1;
}
fclose(fp);
return found;
}
/*
* Load a kernel module during bootstrap
*/
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);
}
/*
* Bring up networking, but only if not single-user or rescue mode
*/
+14
View File
@@ -71,6 +71,7 @@ int print_result (int fail);
void print_exit (void);
void set_hostname (char **hostname);
void kmod_load (char *mod);
void networking (int updown);
int in_container (void);
void setprocnm (const char *name);
@@ -113,6 +114,19 @@ static inline int dprint(int fd, const char *s, size_t len)
return rc;
}
static inline int istrue(char *arg, int defval)
{
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 defval;
}
static inline char *fgetval(const char *line, const char *key, char *sep)
{
char *ptr, *str, *copy;
+3 -774
View File
@@ -40,9 +40,7 @@
# include <lite/queue.h> /* BSD sys/queue.h API */
#endif
#include "finit.h"
#include "cond.h"
#include "conf.h"
#include "devmon.h"
#include "legacy.h"
#include "private.h"
#include "service.h"
@@ -50,775 +48,6 @@
#include "helpers.h"
#include "util.h"
#define BOOTSTRAP (runlevel == INIT_LEVEL)
struct env_entry {
TAILQ_ENTRY(env_entry) link;
char *name;
};
static TAILQ_HEAD(, env_entry) env_list = TAILQ_HEAD_INITIALIZER(env_list);
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[])
{
const char *ptr;
/* Set up defaults */
if ((ptr = FINIT_FSTAB))
fstab = strdup(ptr);
finit_conf = strdup(FINIT_CONF);
finit_rcsd = strdup(FINIT_RCSD);
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 (!getenv("PATH"))
setenv("PATH", _PATH_STDPATH, 1);
if (!getenv("SHELL"))
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);
}
/**
* conf_parse_env - Parse a key=value line
* @line: Line buffer without newline
* @value: Whitespace trimmed value
*
* Returns:
* %NULL on error, otherwise a whitespace trimmed key.
*/
char *conf_parse_env(char *line, char **value)
{
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 NULL;
*val++ = 0;
/* strip leading whitespace from value */
while (isspace(*val))
val++;
/* unquote value, if quoted */
unquote(&val, end);
*value = val;
/* 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 NULL; /* invalid key */
}
return key;
}
/*
* 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.
*/
void legacy_parse_env(char *line)
{
struct env_entry *node;
char *key, *val;
key = conf_parse_env(line, &val);
if (!key)
return;
dbg("Global env '%s'='%s'", key, val);
setenv(key, val, 1);
node = malloc(sizeof(*node));
if (!node) {
nomem:
err(1, "Out of memory cannot track env vars");
return;
}
node->name = strdup(key);
if (!node->name) {
free(node);
goto nomem;
}
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;
}
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;
}
/*
* The '~' prefix means a reload of the upstream service is
* propagated to this service -- it will be reloaded (SIGHUP)
* or restarted (noreload), not just paused and resumed.
* Syntax: <!~pid/foo,~pid/bar> or <~pid/foo>
*
* Strip '~' from each condition and set the service-level
* flag. This allows '~' on any condition in the list.
*/
svc->cond[0] = 0;
for (i = 0, c = strtok(ptr, ","); c; c = strtok(NULL, ","), i++) {
if (c[0] == '~') {
c++;
svc->flux_reload = 1;
}
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)
@@ -897,7 +126,7 @@ static int parse_static(char *line, int is_rcsd)
}
if (BOOTSTRAP && MATCH_CMD(line, "set ", x)) {
legacy_parse_env(x);
conf_set_env(x);
return 0;
}
@@ -976,7 +205,7 @@ static int parse_static(char *line, int is_rcsd)
}
if (MATCH_CMD(line, "reboot-watchdog ", x)) {
wdtreboot = get_bool(strip_line(x), 0);
wdtreboot = istrue(strip_line(x), 0);
return 0;
}
@@ -1190,7 +419,7 @@ int legacy_parse_conf(char *file, int is_rcsd)
else if (!parse_dynamic(line, is_rcsd ? rlimit : global_rlimit, file))
;
else
legacy_parse_env(line);
conf_set_env(line);
free(line);
}
+2 -21
View File
@@ -24,27 +24,8 @@
#ifndef FINIT_LEGACY_H_
#define FINIT_LEGACY_H_
#include <sys/resource.h>
extern struct rlimit initial_rlimit[RLIMIT_NLIMITS];
extern int runparts_progress;
extern int runparts_sysv;
/*
* legacy.c -- the frozen legacy one-liner parser. No new features
* land here, only in the libconfuse format frontend in conf.c
*/
int legacy_parse_conf (char *file, int is_rcsd);
void legacy_parse_env (char *line);
void kmod_load (char *mod);
void conf_parse_rlimit (char *line, struct rlimit arr[]);
char *lim2str (struct rlimit *rlim);
/*
* conf.c -- format-detecting frontend. The legacy include directive
* routes back through this so included files are format-detected too.
*/
int conf_parse_file (char *file, int is_rcsd);
int legacy_parse_conf (char *file, int is_rcsd);
void legacy_parse_env (char *line);
#endif /* FINIT_LEGACY_H_ */
+74 -1
View File
@@ -43,8 +43,9 @@
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#include <sys/resource.h>
#include <sys/sysinfo.h> /* sysinfo() */
#include <sys/vfs.h> /* statfs */
#include <sys/vfs.h> /* statfs */
#include <linux/magic.h>
#ifdef _LIBITE_LITE
# include <libite/lite.h>
@@ -108,6 +109,34 @@ static char *signames[] = {
"SYS",
};
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 }
};
/* https://freedesktop.org/software/systemd/man/systemd.exec.html#id-1.20.8 */
static char *exitcodes[] = {
"SUCCESS", /* 0: Std C exit OK */
@@ -483,6 +512,50 @@ char *code2str(int code)
return exitcodes[code];
}
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;
}
void do_sleep(unsigned int sec)
{
+6
View File
@@ -31,6 +31,7 @@
#include <poll.h>
#include <stdio.h>
#include <sys/reboot.h>
#include <sys/resource.h>
#include <termios.h>
#endif
@@ -55,6 +56,7 @@ extern char *prognm;
# include <lite/conio.h>
# include <lite/lite.h>
#endif
#include "log.h"
char *progname (char *arg0);
@@ -81,6 +83,10 @@ char *sig2str (int sig);
int str2sig (char *sig);
char *code2str (int code);
int str2rlim (char *str);
char *rlim2str (int rlim);
char *lim2str (struct rlimit *rlim);
void do_sleep (unsigned int sec);
void do_usleep (unsigned int usec);
long jiffies (void);