Compare commits

...
7 Commits
Author SHA1 Message Date
Joachim Wiberg 5b9cebbfd3 Update ChangeLog and bump version for v4.5-rc4
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-10-03 16:51:03 +02:00
Joachim Wiberg 38791616e0 Add commented-out developer debug for svc_enabled()
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-10-03 16:39:46 +02:00
Joachim Wiberg fede3fd2e1 Fix #370: document conditional loading and execution config options
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-10-03 16:39:46 +02:00
Joachim Wiberg a46a33238d Display 'run' stanza progress only when they've completed
This is a follow-up to 4701ede.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-10-03 16:39:46 +02:00
Joachim Wiberg afe0488bf2 Add support for runtime evaluation of if:<condition> statements
The if: statement, introduced in v4.4, allows for discarding run/task/services
that depend on other services, based on that service's name, or condition.
Discarding in this context means unloading from the configuration.

At runtime, however, it has proven quite useful to be able to conditionally
qualify a run/task/service based on a condition.  Consider this example from
the Infix operating system:

run name:startup log:prio:user.notice \
	[S] <pid/sysrepo> confd -b --load startup-config -- Loading startup-config

run name:failure log:prio:user.critical if:<usr/fail-startup> \
	[S] <pid/sysrepo> confd --load failure-config -- Loading failure-config

The two run statements reside in the same .conf file so Finit runs them in true
sequence.  If loading the file `startup-config` fails confd sets the condition
usr/fail-startup, thus allowing the next run statement to load `failure-config`.

Notice the difference between <pid/sysrepo> condition and if:<usr/fail-startup>.
The former is a condition for starting and the latter is a condition to check if
a run/task/service is qualified to even be considerered.  The best comparison is
with the [runlevel] option, it too is used to qualify.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-10-03 16:39:46 +02:00
Joachim Wiberg ec5e42694d Silence noisy debug messages, useful only to developers
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-10-03 15:14:02 +02:00
Joachim Wiberg 2e73563eea Fix #369: allow runlevel change using initctl during bootstrap
This change opens up the runlevel change API from bootstrap.  The twist
is that the change is only queued, i.e., the call `initctl runlevel 9`
during bootstrap only changes the configured runlevel to go to after
bootstrap has completed.

Effectively, this change allows overriding the `runlevel` directive in
/etc/finit.conf without having to change the file.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-10-03 15:11:33 +02:00
10 changed files with 149 additions and 34 deletions
+8 -1
View File
@@ -4,7 +4,7 @@ Change Log
All relevant changes are documented in this file.
[4.5][] - 2023-09-24
[4.5][] - 2023-10-03
--------------------
### Changes
@@ -16,6 +16,9 @@ All relevant changes are documented in this file.
- Add `initctl -f` to force-skip asking Finit for existing services
when creating new services during bootstrap, e.g. `/etc/rc.local`
- `initctl runlevel` now returns `N S` instead of `N 10` in bootstrap
- `initctl runlevel N` during bootstrap is now allowed. It changes
the next runlevel to go to when bootstrap has completed. Effectively
overriding the `runlevel N` statement in `/etc/finit.conf`
- Improved logging on failure to `execvp()` a run/task/service, now
with `errno`, e.g., "No such file or directory" when the command
is missing from `$PATH`
@@ -30,6 +33,10 @@ All relevant changes are documented in this file.
- Increased MAX path for commands, and arguments: 64 -> 256
- The bundled watchdog daemon no longer tries to log at startup, because
syslog is not available yet, any startup message leak to console
- Extend `if:` option with runtime evaluation of conditions. E.g., start
a task only `if:<run/foo/failure>` (here the run task 'foo' failed)
- Document new `if:`, `conflict:`, `nowarn` options for run/task/service
first introduced in v4.4
### Fixes
- Fix #227: believed to have been fixed in v4.3, the root cause was
+2 -1
View File
@@ -1,4 +1,5 @@
AC_INIT([Finit],[4.5-rc3],[https://github.com/troglobit/finit/issues],[finit],[https://troglobit.com/projects/finit/])
AC_INIT([Finit], [4.5-rc4], [https://github.com/troglobit/finit/issues],
[finit], [https://troglobit.com/projects/finit/])
AC_CONFIG_AUX_DIR(aux)
AM_INIT_AUTOMAKE([1.11 foreign dist-xz subdir-objects])
AM_SILENT_RULES([yes])
+78 -8
View File
@@ -164,14 +164,19 @@ everything is OK.
task [S] /lib/console-setup/console-setup.sh
service [S12345] env:-/etc/default/rsyslog rsyslogd -n $RSYSLOGD_ARGS
After runlevel S, Finit proceeds to runlevel 2. (This can be changed in
`/etc/finit.conf` using the `runlevel N` directive.) Before starting
the services in runlevel 2, Finit first stops everything that is not
allowed to run in 2, and then brings up networking. Networking is
expected to be available in all runlevels except: S, 1 (single user
level), 6, and 0. Networking is enabled either by the `network script`
directive, or if you have an `/etc/network/interfaces` file, Finit calls
`ifup -a` -- at the very least the loopback interface is brought up.
After runlevel S, Finit proceeds to runlevel 2. This can be changed in
`/etc/finit.conf` using the `runlevel N` directive, or by a script
running in runlevel S that calls, e.g., `initctl runlevel 9`. The
latter is useful if startup scripts detect problems outside of Finit's
control, e.g., critical services/devices missing or hardware problems.
Before starting the services in runlevel 2, Finit first stops everything
that is not allowed to run in 2, and then brings up networking.
Networking is expected to be available in all runlevels except: S, 1
(single user level), 6, and 0. Networking is enabled either by the
`network script` directive, or if you have an `/etc/network/interfaces`
file, Finit calls `ifup -a` -- at the very least the loopback interface
is brought up.
> **Note:** when moving from runlevel S to 2, all run/task/services that
> were constrained to runlevel S only are dropped from bookkeeping. So
@@ -839,6 +844,71 @@ These script actions *must terminate*, so they have a default execution
time of 3 seconds before they are SIGKILLed, this can be adjusted using
the above `kill:SEC` syntax.
#### Conditional Loading
Finit support conditional loading of stanzas. The following example is
take from the `system/hotplug.conf` file in the Finit distribution.
Here we only show a simplified subset.
Starting with the `nowarn` option.
service nowarn name:udevd pid:udevd /lib/systemd/systemd-udevd
service nowarn name:udevd pid:udevd udevd
When loading the .conf file Finit looks for `/lib/systemd/systemd-udevd`
if that is not found Finit automatically logs a warning. The `nowarn`
option disables this warning so that the second line can be evaluated,
which also provides a service named `udevd`.
run nowarn if:udevd <pid/udevd> udevadm settle -t 0
This line is only loaded if we know of a service named `udevd`. Again,
we do not warn if `udevadm` is not found, Execution will also stop here
until the PID condition is asserted, i.e., Finit detecting udevd has
started.
run nowarn conflict:udevd [S] mdev -s -- Populating device tree
If `udevd` is not available, we try to run `mdev`, but if that is not
found, again we do not warn.
Conditional loading statements can also be negated, so the previous stanza
can also be written as:
run nowarn if:!udevd [S] mdev -s -- Populating device tree
The reason for using `confict` in this example is that a conflict can be
resolved. Stanzas marked with `conflict:foo` are rechecked at runtime.
#### Conditional Execution
Similar to conditional loading of stanzas there is conditional runtime
execution. This can be confusing at first, since Finit already has a
condition subsystem, but this is more akin to the qualification to a
runlevel. E.g., a `task [123]` is qualified to run only in runlevel 1,
2, and 3. It is not considered for other runlevels.
Conditional execution qualify a run/task/service based on a condition.
Consider this (simplified) example from the Infix operating system:
run [S] name:startup <pid/sysrepo> confd -b --load startup-config
run [S] if:<usr/fail-startup> name:failure <pid/sysrepo> confd --load failure-config
The two run statements reside in the same .conf file so Finit runs them
in true sequence. If loading the file `startup-config` fails confd sets
the condition `usr/fail-startup`, thus allowing the next run statement
to load `failure-config`.
Notice critical the difference between `<pid/sysrepo>` condition and
`if:<usr/fail-startup>`. The former is a condition for starting and the
latter is a condition to check if a run/task/service is qualified to
even be considered.
Conditional execution statements can also be negated, so provided the
file loaded did the opposite, i.e., set a condition on success, the
previous stanza can also be written as:
run [S] if:<!usr/startup-ok> name:failure <pid/sysrepo> confd ...
### Run-parts Scripts
+5
View File
@@ -240,6 +240,11 @@ Show top-like listing based on cgroups.
List installed plugins.
.It Nm Ar runlevel Op Ar 0-9
Show or set runlevel: 0 halt, 6 reboot.
.Pp
If called at boot (runlevel S) to set the runlevel, Finit only schedules
the change, effectively overriding the configured runlevel from
.Pa /etc/finit.conf .
Useful to trigger a fail-safe mode, or similar.
.It Nm Ar reboot
Reboot system, default if
.Cm reboot
+15 -3
View File
@@ -367,7 +367,6 @@ static void api_cb(uev_t *w, void *arg, int events)
}
switch (rq.cmd) {
case INIT_CMD_RUNLVL:
case INIT_CMD_RELOAD:
case INIT_CMD_START_SVC:
case INIT_CMD_RESTART_SVC:
@@ -378,7 +377,7 @@ static void api_cb(uev_t *w, void *arg, int events)
case INIT_CMD_POWEROFF:
case INIT_CMD_SUSPEND:
if (IS_RESERVED_RUNLEVEL(runlevel)) {
warnx("Unsupported command in runlevel S and 6.");
warnx("Unsupported command in runlevel S and 6/0.");
goto leave;
}
default:
@@ -387,6 +386,14 @@ static void api_cb(uev_t *w, void *arg, int events)
switch (rq.cmd) {
case INIT_CMD_RUNLVL:
/* Allow changing cfglevel in runlevel S */
if (IS_RESERVED_RUNLEVEL(runlevel)) {
if (runlevel != INIT_LEVEL) {
warnx("Cannot abort runlevel 6/0.");
break;
}
}
switch (rq.runlevel) {
case 's':
case 'S':
@@ -400,7 +407,12 @@ static void api_cb(uev_t *w, void *arg, int events)
halt = SHUT_OFF;
if (lvl == 6)
halt = SHUT_REBOOT;
service_runlevel(lvl);
/* User requested change in next runlevel */
if (runlevel == INIT_LEVEL)
cfglevel = lvl;
else
service_runlevel(lvl);
break;
default:
+1 -1
View File
@@ -237,7 +237,7 @@ static void devmon_cb(uev_t *w, void *arg, int events)
continue;
}
dbg("Got %s", ev->name);
// dbg("Got %s", ev->name);
if (ev->mask & (IN_CREATE | IN_DELETE))
devmon_update_conds(iwp->path, ev->name, ev->mask);
}
+1 -1
View File
@@ -288,7 +288,7 @@ static void generic_io_cb(uev_t *w, void *arg, int events)
/* Stop watcher, callback may close descriptor on us ... */
uev_io_stop(w);
dbg("Calling I/O %s from runloop...", basename(p->name));
// dbg("Calling I/O %s from runloop...", basename(p->name));
p->io.cb(p->io.arg, w->fd, events);
/* Update fd, may be changed by plugin callback, e.g., if FIFO */
+11 -7
View File
@@ -836,7 +836,7 @@ static int service_start(svc_t *svc)
fail:
sigprocmask(SIG_SETMASK, &omask, NULL);
if (do_progress)
if (do_progress && !run_block_pid)
print_result(result);
return result;
@@ -1847,11 +1847,6 @@ void service_monitor(pid_t lost, int status)
return;
}
if (lost == run_block_pid) {
svc_mark_clean(svc); /* done, regardless of exit status */
run_block_pid = 0;
}
switch (svc->state) {
case SVC_CLEANUP_STATE:
case SVC_SETUP_STATE:
@@ -1900,6 +1895,13 @@ done:
/* No longer running, update books. */
svc->start_time = svc->pid = 0;
cont:
if (lost == run_block_pid) {
svc_mark_clean(svc); /* done, regardless of exit status */
run_block_pid = 0;
if (svc->desc[0])
print_result(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
}
if (!service_step(svc)) {
/* Clean out any bootstrap tasks, they've had their time in the sun. */
if (svc_clean_bootstrap(svc))
@@ -1955,7 +1957,7 @@ void service_mark_unavail(void)
if (!svc->ifstmt[0])
continue;
if (!svc_ifthen(svc_ident(svc, buf, sizeof(buf)), svc->ifstmt))
if (!svc_ifthen(1, svc_ident(svc, buf, sizeof(buf)), svc->ifstmt))
svc_mark(svc);
}
}
@@ -2294,9 +2296,11 @@ restart:
svc_set_state(svc, SVC_WAITING_STATE);
else {
if (svc_is_conflict(svc)) {
#if 0
logit(svc->nowarn ? LOG_DEBUG : LOG_INFO,
"%s in conflict with %s, checking again ...",
svc_ident(svc, NULL, 0), svc->conflict);
#endif
if (!svc_conflicts(svc))
svc_unblock(svc);
}
+27 -11
View File
@@ -634,17 +634,30 @@ int svc_enabled(svc_t *svc)
if (!svc)
return 0;
if (!svc_in_runlevel(svc, runlevel))
if (!svc_in_runlevel(svc, runlevel)) {
// dbg("Not in runlevel");
return 0;
}
if (svc_is_removed(svc) || svc_is_blocked(svc))
if (svc_is_removed(svc) || svc_is_blocked(svc)) {
// dbg("removed (%d) / blocked (%d)", svc->removed, svc->block);
return 0;
}
if (svc_is_missing(svc))
if (svc_is_missing(svc)) {
// dbg("missing");
return 0;
}
if (svc_is_tty(svc) && bootstrap)
if (!svc_ifthen(0, NULL, svc->ifstmt)) {
// dbg("ifthen:%s>", svc->ifstmt[0] ? svc->ifstmt : "<");
return 0;
}
if (svc_is_tty(svc) && bootstrap) {
// dbg("is tty in bootstrap");
return 0;
}
return 1;
}
@@ -688,13 +701,14 @@ int svc_conflicts(svc_t *svc)
/**
* svc_ifthen - Check if: statement in declaration
* @is_conf: Set when called by service_mark_unavail()
* @ident: svc_t identififcation (name:id)
* @stmt: if: statement from .conf file
*
* Returns:
* %TRUE(1) yes, use this svc_t, %FALSE(0) prune
*/
int svc_ifthen(const char *ident, char *stmt)
int svc_ifthen(int is_conf, const char *ident, char *stmt)
{
int not = 0;
svc_t *svc;
@@ -707,6 +721,9 @@ int svc_ifthen(const char *ident, char *stmt)
char *ptr = ++stmt;
int i = 0;
if (is_conf)
return 1;
if (stmt[0] == '!') {
stmt++;
not++;
@@ -717,17 +734,16 @@ int svc_ifthen(const char *ident, char *stmt)
ptr[i] = 0;
cond = cond_get(stmt);
if (not && cond == COND_ON) {
logit(LOG_NOTICE, "skipping %s, cond %s is on.", ident, stmt);
if (not && cond == COND_ON)
return 0;
}
if (!not && cond == COND_OFF) {
logit(LOG_NOTICE, "skipping %s, cond %s is off.", ident, stmt);
if (!not && cond == COND_OFF)
return 0;
}
return 1;
}
if (!is_conf)
return 1;
if (stmt[0] == '!') {
stmt++;
not++;
+1 -1
View File
@@ -241,7 +241,7 @@ void svc_prune_bootstrap (void);
void svc_enable (svc_t *svc);
int svc_enabled (svc_t *svc);
int svc_conflicts (svc_t *svc);
int svc_ifthen (const char *ident, char *ifstm);
int svc_ifthen (int is_conf, const char *ident, char *stmt);
int svc_parse_jobstr (char *str, size_t len, void *user_data, int (*found)(svc_t *, void *), int (not_found)(char *, char *, void *));