diff --git a/src/cgroup.h b/src/cgroup.h index 7c792bde..b7b1c66d 100644 --- a/src/cgroup.h +++ b/src/cgroup.h @@ -26,6 +26,9 @@ #include +#define CGROUP_NAME_SIZE 16 +#define CGROUP_SETTINGS_SIZE 128 + /* Forward declaration */ typedef struct svc svc_t; diff --git a/src/conf.c b/src/conf.c index 8699c628..34d87458 100644 --- a/src/conf.c +++ b/src/conf.c @@ -58,7 +58,9 @@ #include #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 (=> ) + * 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 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: 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) { diff --git a/src/conf.h b/src/conf.h index 80491e7b..b2576ff8 100644 --- a/src/conf.h +++ b/src/conf.h @@ -27,6 +27,10 @@ #include "cgroup.h" #include "svc.h" +#include + +#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_ */ diff --git a/src/helpers.c b/src/helpers.c index 42a03f89..3b81c1d2 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -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 */ diff --git a/src/helpers.h b/src/helpers.h index 442d75dc..babff4f0 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -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; diff --git a/src/legacy.c b/src/legacy.c index d32c9e16..c817886e 100644 --- a/src/legacy.c +++ b/src/legacy.c @@ -40,9 +40,7 @@ # include /* 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 (=> ) - * 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: 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 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); } diff --git a/src/legacy.h b/src/legacy.h index 083018f0..da16c18d 100644 --- a/src/legacy.h +++ b/src/legacy.h @@ -24,27 +24,8 @@ #ifndef FINIT_LEGACY_H_ #define FINIT_LEGACY_H_ -#include - -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_ */ diff --git a/src/util.c b/src/util.c index 3d8a0b22..0dd7737d 100644 --- a/src/util.c +++ b/src/util.c @@ -43,8 +43,9 @@ #ifdef HAVE_SYS_IOCTL_H # include #endif +#include #include /* sysinfo() */ -#include /* statfs */ +#include /* statfs */ #include #ifdef _LIBITE_LITE # include @@ -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) { diff --git a/src/util.h b/src/util.h index 7e8df0b2..2af4c9b5 100644 --- a/src/util.h +++ b/src/util.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #endif @@ -55,6 +56,7 @@ extern char *prognm; # include # include #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);