Files
finit/src/legacy.c
T
Joachim Wiberg f1393ff8a1 conf: instantiate %i templates for the block format
A template in the block format registered garbage.  conf_parse_file()
routed every file with an '@' in its name straight to the legacy
parser, which read the block line by line: the section header became a
service whose command was the section title, and each key = value line
below it became an environment variable.

    service serv:%i { ... }   ->  service 'serv:eth0' with argument '{'

Substitute %i over the whole file before parsing instead, so format
detection and both parsers see finished text.  A bare name@.conf is
still skipped, it is the template rather than an instance of one.

The legacy parser no longer opens the file or substitutes per line, it
is handed the instantiated buffer, so the template convention now has
one implementation instead of two.  conf_is_template() applies
basenm(), a directory with an '@' in its name is not a template.

libconfuse cannot name a buffer it parses before 3.4, so a typo in a
template would be reported against "[buf]".  Parse through fmemopen()
with the file name preset until the floor moves.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:26 +02:00

375 lines
9.1 KiB
C

/* Legacy one-liner parser for /etc/finit.conf and the finit.d hierarchy
*
* NOTE: this parser is frozen at the Finit 4.x feature set. It is
* kept, indefinitely, for backwards compatibility -- existing
* configurations must keep working. All new directives and
* options land in the libconfuse-based format, see conf.c
*
* Copyright (c) 2012-2025 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/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 "finit.h"
#include "conf.h"
#include "legacy.h"
#include "private.h"
#include "service.h"
#include "tty.h"
#include "helpers.h"
#include "util.h"
/* 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)) {
conf_set_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 conf_parse_file(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;
}
if (MATCH_CMD(line, "reboot-watchdog ", x)) {
wdtreboot = istrue(strip_line(x), 0);
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)) {
char *group, *token, *saveptr;
char settings[128] = {0};
/* Reset cgroup state */
cgroup_settings_current[0] = '\0';
cgroup_delegate_current = 0;
/* First token is the group name (system/user/init) */
group = strtok_r(x, ",", &saveptr);
if (group)
strlcpy(cgroup_current, group, sizeof(cgroup_current));
else
strlcpy(cgroup_current, x, sizeof(cgroup_current));
/* Parse any remaining comma-separated options */
while ((token = strtok_r(NULL, ",", &saveptr)) != NULL) {
if (strncmp(token, "name:", 5) == 0) {
/* Cgroup leaf name override - only valid in per-service directives */
warnx("cgroup %s: name: option not valid in global directive!", group);
} else if (strcmp(token, "delegate") == 0) {
cgroup_delegate_current = 1;
} else {
/* Other settings (cpu.weight:500, memory.max:1G, etc.) */
if (settings[0])
strlcat(settings, ",", sizeof(settings));
strlcat(settings, token, sizeof(settings));
}
}
if (settings[0])
strlcpy(cgroup_settings_current, settings, sizeof(cgroup_settings_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;
}
/*
* Parse FILE, or BUF when the frontend has already read and
* instantiated it, i.e. for a template. Either way the text arriving
* here is final, no %i is left to substitute.
*/
int legacy_parse_conf(char *file, char *buf, int is_rcsd)
{
struct rlimit rlimit[RLIMIT_NLIMITS];
FILE *fp;
if (buf)
fp = fmemopen(buf, strlen(buf), "r");
else
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);
if (!parse_static(line, is_rcsd))
;
else if (!parse_dynamic(line, is_rcsd ? rlimit : global_rlimit, file))
;
else
conf_set_env(line);
free(line);
}
fclose(fp);
return 0;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/