Compare commits

..
47 Commits
Author SHA1 Message Date
Joachim Wiberg eb6291c943 Update ChangeLog and bump version for v4.5-rc1
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-13 13:26:33 +02:00
Joachim Wiberg 756b7c3fb6 Document advanced hooks/plugins to trigger rescue.conf
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-13 13:03:20 +02:00
Joachim Wiberg 263aec292a Support for disabling invocation of rescue mode from kernel cmdline
The rescue mode that can be invoked from the kernel command line is
potentially unsafe.  Many systems lock the root user account, or use
another account for managing, e.g. 'admin'.  The sulogin program(s)
would on such systems give the user a root prompt.

In #357 we added support for setting a custom sulogin user, but for some
systems that is not enough.  On many embedded systems the /etc/passwd
and shadow files are populated at bootstrap and at the time rescue mode
runs, these files will be unpopulated.

The only, truly safe, approach on such systems is to disable rescue mode
completely.  Otherwise intricate Finit plugins have to be used that run
before rescue mode is started -- increasing the complexity of the system
as a whole.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-13 12:45:03 +02:00
Joachim Wiberg 5a946a4ea7 doc: clarify what single user mode does
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-13 12:44:33 +02:00
Joachim Wiberg c90b4d797b doc: clarify eval order of sulogins in traditional rescue mode
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-13 12:44:00 +02:00
Joachim Wiberg 6e7a2fb6d5 Fix #357: allow sulogin with user different from 'root'
In issue #357 there's a proposal for optionally allowing /bin/login, but
after intense discussions we've agreed that would be opening up for all
sorts of potential (security) issues.

it's better to keep things as-is but with the added twist of supporting
a custom user at configure time, e.g., 'admin'.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-13 11:37:15 +02:00
Joachim Wiberg ddedcf77e8 Follow-up to 4701ede: reschedule bootstrap check while not done
Non-trivial systems, like Infix[1], require more than one lap around the
track to warm up.  This is a follow-up to issue #362, commit 4701ede, in
fixing an obvious oversight in the new sm_check_bootstrap() work.  While
there are still outstanding bootstrap tasks or services that have not
yet started, we must reschedule the worker.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-13 09:35:02 +02:00
Joachim Wiberg 924ec2ef80 test: add missing runlevel/ref.log
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 21:18:28 +02:00
Joachim Wiberg a49f27c191 initctl: show runlevel 'N S' instead of 'N 10' during bootstrap
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 14:05:11 +02:00
Joachim Wiberg b41b4ce989 test: new, verify runparts order
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 14:04:26 +02:00
Joachim Wiberg 3d74e93fa2 test: when rc.local is busy, delay test until it's free
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 14:03:43 +02:00
Joachim Wiberg 443700a88e test: new, verify that a run task can call initctl
Follow-up to #362

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:30:57 +02:00
Joachim Wiberg 4701edef4c Fix #362: prevent blocking main loop when starting run tasks
Drop complete() logic, waiting for run tasks to finish, from the
service_start() funciton to the general service_monitor().  This
refactor frees up the main loop and allows us to answer any API
calls from initctl even from the run task itself.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg e6da0a22e7 Helper script for development/test
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg 7be2bbbc30 doc: add SysV Init Compatibility section
Minor updates and fixes to other sections as well.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg 3e269c632f Start runparts with -d in debug mode, otherwise -p for progress
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg f2507d9fd7 runparts: fix arg handling and skip backup files
For some specialized forms of runparts, e.g., start-stop scripts in SysV
init replacements, there is a need to append an argument.  Typically
this is 'start' or 'stop'.

This functionality has been broken for quite some time, meaning we
should consider it being unused by current users of Finit.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg e9f403bceb runparts: simplify and adjust log levels
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg c03176c42a doc: relocate text to common TODO file
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg 9707bfb103 TODO: things to fix before release
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg 11430f83c8 Use unbuffered stderr for runparts logging result and progress
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg a22a794f55 Clarify usage text slightly
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg 5556501287 dbg(): add missing \n for stderr logginge
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg ab554568b2 test: handle very short tests that exit before reaching runlevel 2
Bootstrap-only tests that just verify basic functionality are often very
short.  This change handles that by retrying SIGUSR2 until Finit has had
the chance to finalize bootstrap and enable signals.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg b39a4498a3 test: enable bootstrap-crash, regression test for #351
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-09-10 13:16:09 +02:00
Joachim Wiberg dead2444e2 doc: 'run' command limitation, blocks Finit completely
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-30 10:21:07 +02:00
Joachim Wiberg a58002588e Refactor initial startup, run runparts and rc.local in background
The code refactored in this commit has long been an eyesore.  The
hand-over, from the sequential main() function, to the event loop
was clumsy at best and was very difficult to debug.

Instead of using a series of workers we now delegate everything to
the big state machine by introducing a new SM_BOOTSRTAP_WAIT_STATE.

Both the runparts and rc.local scripts now run in the background, no
longer blocking the event loop from responding to initctl calls.  To
ensure they run at the same point in time two things have been done:

 1. A new <int/bootstrap> condition has been added which triggers
    runparts, which now is a regular task created by conf_init()
 2. /etc/rc.local is started from SM_RUNLEVEL_WAIT_STATE when we
    transition from runlevel S to any other runlevel.

Fixes #356

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-30 10:09:34 +02:00
Joachim Wiberg 28e101aaae Minor, ensure we can call exec_runtask() with NULL args
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-29 23:22:07 +02:00
Joachim Wiberg 6da8af8136 Minor, add debug logs and clarify comment
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-29 23:21:27 +02:00
Joachim Wiberg 3069e753e1 getty: add missing '-p' in usage text
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-27 13:34:45 +02:00
Joachim Wiberg aafd5c1010 Allow overriding SCREEN_WIDTH
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-27 13:33:57 +02:00
Joachim Wiberg 2f51c8ea0d test: minor, log() -> inf()
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-27 13:33:22 +02:00
Joachim Wiberg d1130473fc Follow-up to 1fc0f36, missing %s argument
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-27 13:33:00 +02:00
Joachim Wiberg b682a5e4fe Fix #360: Bash completion for initctl
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-17 07:31:59 +02:00
Joachim Wiberg 1fc0f36c6c initctl: check [enable | disable] for absolute path, not supported
For reference, see issue #359.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-11 08:06:46 +02:00
Joachim Wiberg 5df5b22327 man: update finit.conf(5) with info on enabled and available
This should clarify the messup on the behalf of the mainatiner in not
documenting this properly, including the deviations introduced in v4.4

See issue #359 for a background.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-11 07:57:57 +02:00
Joachim Wiberg 4153c7966d Fix #358: fix modify events for /etc/finit.conf
- iwatch events return filenames in dir field, ignore file for those
 - improve warning message on failure
 - drop bogus "Out of memory" error log message

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-10 14:20:36 +02:00
Joachim Wiberg 458d68533f Update ChangeLog
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-10 13:37:25 +02:00
Joachim Wiberg 63b5db38df Refactor, ensure pre:/post:/ready: scripts run in the same cgroup
Related to issue #361.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-10 13:25:11 +02:00
Joachim Wiberg 2135639508 Update changelog with latest fixes
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-10 10:27:29 +02:00
Joachim Wiberg 9c8cd6c024 Improved logging on failure to execvp() in forked child
The execvp() call always fails with -1, so only logging exit code when
collecting the PID is not enough.  When debugging the user needs to see
the errno from execvp(), e.g., "No such file or directory", when the
command was missing the absolute path to the executable.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-10 10:23:20 +02:00
Joachim Wiberg 450f498061 Fix #361: cgroup move fail if starting service as non-root
Regression in v4.4

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-07-10 10:23:20 +02:00
Joachim Wiberg 5769500afe Bump devel version and update ChangeLog for v4.5 release cycle
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-06-27 12:33:16 +02:00
Joachim Wiberg dc61969164 Check in service_stop() if the process is already dead
Related to 6f0d448 in that we check if the kernel knows about this
process before we try to stop it.  If it's already dead we clean up
and return.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-06-27 12:22:59 +02:00
Joachim Wiberg 6f0d448765 Fix #227: delayed service_kill() may stall shutdown/reboot
This turns out to be the root-cause of #227.  Finit is waiting forever
for proceeses to stop at shutdown/reboot, while a subreaper has already
collected the PID, or Finit for some reason did not collect the PID.

When the process timeout calls service_kill() we now check if the kernel
actually knows of this process or not.  If it's already been collected,
we can notify Finit of this by calling service_monitor() to clean up the
'svc' and in turn call sm_step() to finalize the state transition.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-06-27 12:18:09 +02:00
Joachim Wiberg 0c9b7a67e0 doc/plugins.md: fix script hook path
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-06-27 12:14:26 +02:00
Joachim Wiberg 5db2fb2c32 initctl: let -f force-skip check for built-in service
When calling `initctl -b create` from a start script at bootstrap you
risk blocking the boot since Finit currently cannot reply to IPC during
that period.

This patch allows -f to override this builtin check for the following
initctl commands:

 - touch
 - show
 - edit
 - create
 - delete

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2023-05-20 10:25:02 +02:00
43 changed files with 1088 additions and 399 deletions
+41 -1
View File
@@ -4,6 +4,45 @@ Change Log
All relevant changes are documented in this file.
[4.5][] - 2023-09-14
--------------------
### Changes
- Refactor `runparts` and `/etc/rc.local` to no longer block the main
loop, allowing these scripts to call `initctl` to interact with Finit.
Still in a limited way due to them being used at bootstrap, issue #356
- Refactor the `run` stanza to no longer block the main loop, issue #362
- Allow `sulogin` with a user different from `root`, issue #357
- Allow disabling invocation of rescue mode from kernel command line
- 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
- 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`
- Add support for Bash completion to `initctl`, issue #360
- Handle absolute path to `initctl [enable|disable]`, not supported
- Update `finit.conf(5)` man page with the recommended directory
hierarchy in `/etc/finit.d/`
- The `runparts` code has been split into `/libexec/finit/runparts`
- Add SysV Init Compatibility section to documentation
### Fixes
- Fix #227: believed to have been fixed in [4.3][], the root cause was
actually that Finit was waiting for a process that was no longer in
the system. The fix is to ask the kernel on process-stop-timeout and
replay the lost PID so that Finit can continue with reboot/shutdown
- Fix #358: fix inotify events for `/etc/finit.conf`, improved log
messages and error handling
- Fix #361: cgroup move fail if run/task/services start as non-root.
Regression in the [4.4][] release cycle while adding support for the
pre:/post:/ready: scripts. Now the latter scripts also properly run
in their correct cgroup
- The `runparts` executor now skips backup files (`foo~`)
- The `runparts` stanza now properly appends ` start` to scripts that
start with `S[0-9]+`. This has been broken for a very long time.
[4.4][] - 2023-05-15
--------------------
@@ -1510,7 +1549,8 @@ Major bug fix release.
* Initial release
[UNRELEASED]: https://github.com/troglobit/finit/compare/4.3...HEAD
[UNRELEASED]: https://github.com/troglobit/finit/compare/4.5...HEAD
[4.5]: https://github.com/troglobit/finit/compare/4.4...4.5
[4.4]: https://github.com/troglobit/finit/compare/4.3...4.4
[4.3]: https://github.com/troglobit/finit/compare/4.2...4.3
[4.2]: https://github.com/troglobit/finit/compare/4.1...4.2
-1
View File
@@ -1 +0,0 @@
Executable
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
# Run test suite with the correct configure params
make distclean
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
--enable-x11-common-plugin --with-watchdog --with-keventd
make -j17 clean check
+19 -6
View File
@@ -1,4 +1,4 @@
AC_INIT([Finit],[4.4],[https://github.com/troglobit/finit/issues],[finit],[https://troglobit.com/projects/finit/])
AC_INIT([Finit],[4.5-rc1],[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])
@@ -71,7 +71,7 @@ AC_ARG_ENABLE(redirect,
enable_redirect=yes])
AC_ARG_ENABLE(logrotate,
AS_HELP_STRING([--disable-logrotate], [Disable built-in rotation of /var/log/wtmp, default enabled]),,[
AS_HELP_STRING([--disable-logrotate], [Disable built-in rotation of /var/log/wtmp]),,[
enable_logrotate=yes])
AC_ARG_ENABLE(doc,
@@ -82,6 +82,10 @@ AC_ARG_ENABLE(contrib,
AS_HELP_STRING([--disable-contrib], [Disable build and install of contrib section]),,[
enable_contrib=yes])
AC_ARG_ENABLE([rescue],
AS_HELP_STRING([--disable-rescue], [Disable potentially unsafe rescue mode]),,
[enable_rescue=yes])
# Check for extra plugins to enable
AC_ARG_ENABLE(all_plugins,
AS_HELP_STRING([--enable-all-plugins], [Enable all plugins, default: auto]),
@@ -145,10 +149,10 @@ AC_ARG_WITH(keventd,
AS_HELP_STRING([--with-keventd], [Enable built-in keventd, default: no]),, [with_keventd=no])
AC_ARG_WITH(sulogin,
AS_HELP_STRING([--with-sulogin], [Enable built-in sulogin, default: no.]),, [with_sulogin=no])
AS_HELP_STRING([--with-sulogin@<:@=USER@:>@], [Enable built-in sulogin, optional USER to request password for (default root), default: no.]),[sulogin=$withval],[with_sulogin=no])
AC_ARG_WITH(watchdog,
AS_HELP_STRING([--with-watchdog=[DEV]], [Enable built-in watchdog, default: /dev/watchdog]),
AS_HELP_STRING([--with-watchdog@<:@=DEV@:>@], [Enable built-in watchdog, default: /dev/watchdog]),
[watchdog=$withval], [with_watchdog=no watchdog=])
AC_ARG_WITH(hook-scripts-path,
@@ -185,6 +189,9 @@ AS_IF([test "x$enable_redirect" = "xyes"], [
AS_IF([test "x$enable_logrotate" != "xno"], [
AC_DEFINE(LOGROTATE_ENABLED, 1, [Enable built-in rotation of /var/log/wtmp et al.])])
AS_IF([test "x$enable_rescue" != "xno"], [
AC_DEFINE([RESCUE_MODE], 1, [Define to enable support for rescue mode.])])
AM_CONDITIONAL(LOGROTATE, [test "x$enable_logrotate" = "xyes"])
### With features ##############################################################################
@@ -252,7 +259,12 @@ AS_IF([test "x$rtc_date" != "xno"], [
AS_IF([test "x$with_keventd" != "xno"], [with_keventd=yes])
AS_IF([test "x$with_sulogin" != "xno"], [with_sulogin=yes])
AS_IF([test "x$with_sulogin" != "xno"], [
AS_IF([test "x$sulogin" = "xyes"], [
sulogin=root])
with_sulogin=yes
AC_DEFINE_UNQUOTED(SULOGIN_USER, "$sulogin", [Built-in sulogin user, default: root])], [
sulogin=])
AS_IF([test "x$with_watchdog" != "xno"], [
AS_IF([test "x$watchdog" = "xyes"], [
@@ -344,12 +356,13 @@ Optional features:
Install doc/..........: $enable_doc
Install contrib/......: $enable_contrib
Built-in keventd......: $with_keventd
Built-in sulogin......: $with_sulogin
Built-in sulogin......: $with_sulogin $sulogin
Built-in watchdogd....: $with_watchdog $watchdog
Built-in logrotate....: $enable_logrotate
Skip fsck check.......: $enable_fastboot
Run fsck fix mode.....: $enable_fsckfix
Redirect output.......: $enable_redirect
Rescue mode...........: $enable_rescue
Default hostname......: $hostname
Default group.........: $group
Default runlevel......: $runlevel
+10
View File
@@ -9,6 +9,16 @@ value in the project turned out to be:
> systems, container, and dedicated server applications.
Before Next Release
-------------------
* When did runparts +arg break?
* Should we update the =runparts DIR= syntax to =runparts [progress] [debug] DIR=?
* Add SNN-only option top runparts, for Buildroot
* Calling `initctl runlevel` at bootstrap => N 10, what should it be?
* Reindent all shell scripts in true emacs shell-script-mode (4 chars)
Misc TODOs
----------
+7 -4
View File
@@ -86,13 +86,16 @@ The `bool` setting is one of `on, off, true false, 1, 0`.
the system booted in a limited fallback mode. See [config.md][]
for more information.
This option can be disabled with `configure --without-rescue`
**Note:** in this mode `initctl` will not work. Correct the problem
and use `reboot -f` to force reboot.
* `single`, or `S`: Single user mode, runlevel 1, in Finit. Useful to
debug problems with the regular runlevel. All services and TTYs in
`[1]` will be started, so a `tty [1] @console nologin` configuration
presents you with a root console without login.
* `single`, or `S`: Overrides the configured runlevel (default: 2) to go
to after bootstrap by forcing it to runlevel 1, this is also known
as single user mode. Useful to debug startup problems. All services
and TTYs in `[1]` will be started, so a `tty [1] @console nologin`
configuration presents you with a root console without login.
* `1-9`, except `6`: override the configured `runlevel`. Like the `S`
and `rescue`, giving a single number on the kernel command line tells
+122 -26
View File
@@ -28,6 +28,7 @@ Finit Configuration
* [TTYs and Consoles](#ttys-and-consoles)
* [Non-privileged Services](#non-privileged-services)
* [Redirecting Output](#redirecting-output)
* [SysV Init Compatibility](#sysv-init-compatibility)
* [Rescue Mode](#rescue-mode)
* [Limitations](#limitations)
* [/etc/finit.conf](#etcfinitconf)
@@ -45,14 +46,15 @@ Plugins start at [hook points](plugins.md#hooks) and can run various set
up, or install event handlers that later provide runtime services, e.g.,
PID file monitoring, or [conditions](conditions.md).
> **Tip:** see [SysV Init Compatibility](#sysv-init-compatibility) for
> help to quickly get going with an existing SysV or BusyBox init setup.
### Configuration Files
Originally Finit was configured using a single file, `/etc/finit.conf`.
Although still possible, today we recommended using the Apache-style
`available/*.conf` and `enabled/*.conf` layaout. These are called
dynamic services, in contrast to static -- the only difference being
where they are installed and if the `initctl` tool can manage them.
Although still possible to use this single configuration file, today the
following familiar layout is recommended:
/etc/
|- finit.d/
@@ -62,9 +64,15 @@ where they are installed and if the `initctl` tool can manage them.
: | `- my-service.conf -> ../available/my-service.conf
: :
: |- static-service.conf
: `- another-static.conf # eg. bootstrap run/task commands
: `- another-static.conf
:
`- finit.conf # global settings
`- finit.conf
Services in the `available/` and `enabled/` sub-directories are called
dynamic services, in contrast to static services -- the only difference
being where they are installed and if the `initctl` tool can manage them
with the `enable` and `disable` commands. An administrator can always
create files and symlinks manually.
At bootstrap, and `initctl reload`, all .conf files are read, starting
with `finit.conf`, then, in alphabetical order, all `finit.d/*.conf`
@@ -131,8 +139,8 @@ as listed above, provided their respective mount point exists.
With all filesystems mounted, Finit calls `swapon`.
At shutdown, and after having stopped all services and other lingering
processes killed, filesystems are unmounted in the reverse order, and
`swapoff` is called.
processes have been killed, filesystems are unmounted in the reverse
order, and `swapoff` is called.
[FHS]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html
@@ -574,19 +582,37 @@ Finit disables networking in this mode.
**Syntax:** `run [LVLS] <COND> /path/to/cmd ARGS -- Optional description`
One-shot command to run in sequence when entering a runlevel, with
optional arguments and description.
`run` commands are guaranteed to be completed before running the next
command. Highly useful if true serialization is needed.
> `<COND>` is described in the [Services](#services) section.
One-shot command to run in sequence when entering a runlevel, with
optional arguments and description. `run` commands are guaranteed to be
completed before running the next command. Useful when serialization is
required.
> **Warning:** try to avoid the `run` command. It blocks much of the
> functionality in Finit, like (re)starting other (perhaps crashing)
> services while a `run` task is executing. Use other synchronization
> mechanisms instead, like conditions.
Incomplete list of unsupported `initctl` commands in `run` tasks:
- `initctl runlevel N`, setting runlevel
- `initctl reboot`
- `initctl halt`
- `initctl poweroff`
- `initctl suspend`
To prevent `initctl` from calling Finit when enabling and disabling
services from inside a `run` task, use the `--force` option. See
also the `--quiet` and `--batch` options.
### One-shot Commands (parallel)
**Syntax:** `task [LVLS] <COND> /path/to/cmd ARGS -- Optional description`
> `<COND>` is described in the [Services](#services) section.
One-shot like 'run', but starts in parallel with the next command.
Both `run` and `task` commands are run in a shell, so pipes and
@@ -594,13 +620,13 @@ redirects can be freely used:
task [s] echo "foo" | cat >/tmp/bar
> `<COND>` is described in the [Services](#services) section.
### SysV Init Scripts
**Syntax:** `sysv [LVLS] <COND> /path/to/init-script -- Optional description`
> `<COND>` is described in the [Services](#services) section.
Similar to `task` is the `sysv` stanza, which can be used to call SysV
style scripts. The primary intention for this command is to be able to
re-use much of existing setup and init scripts in Linux distributions.
@@ -618,7 +644,7 @@ file, but rather watch that file for the resulting forked-off PID. This
syntax also works for forking daemons that do not have a command line
option to run it in the foreground, more on this below in `service`.
> `<COND>` is described in the [Services](#services) section.
> **Tip:** see also [SysV Init Compatibility](#sysv-init-compatibility).
### Services
@@ -826,7 +852,7 @@ The scripts in this directory are executed at the very end of runlevel
A common use-case for runparts scripts is to create and enable/disable
services, which Finit will then apply when changing runlevel from S to
whatever the next runlevel is set to be (default 2). E.g., generate a
`/etc/chrony.conf` and `initctl enable chronyd`.
`/etc/chrony.conf` and call `initctl enable chronyd`.
**Limitations:**
@@ -1044,6 +1070,70 @@ sync(2) has been called, twice.
> finally written.
SysV Init Compatibility
-----------------------
Finit comes with a few SysV Init compatibility features to ease the
transition from a serialized boot process.
### `runparts DIRECTORY`
For a directory with traditional start/stop scripts that should run, in
order, at bootstrap, Finit provides the `runparts` directive. It runs
in runlevel S, at the very end of it (before calling `/etc/rc.local`)
making it perfect for most scenarios.
For syntax details, see [Run-parts Scripts](#run-parts-scripts) above.
Here is an example take from a Debian installation:
runparts /etc/rc2.d
Files in these directories are usually named `SNNfoo` and `KNNfoo`,
which Finit knows about and automatically appends the correct argument:
/bin/sh -c /etc/rc2.d/S01openbsd-inetd start
or
/bin/sh -c /etc/rc0.d/K01openbsd-inetd stop
Files that do not match this pattern are started similarly but without
the extra command line argument.
### Start/Stop Scripts
For syntax details, see [SysV Init Scripts](#sysv-init-scripts), above.
Here follows an example taken from a Debian installation:
sysv [2345] <pid/syslogd> /etc/init.d/openbsd-inetd -- OpenBSD inet daemon
The init script header could be parsed to extract `Default-Start:` and
other parameters for the `sysv` command to Finit. There is currently no
way to detail a generic syslogd dependency in Finit, so `Should-Start:`
in the header must be mapped to the condition system in Finit using an
absolute reference, here we depend on the sysklogd project's syslogd.
### `/etc/rc.local`
One often requested feature, early on, was a way to run a site specific
script to set up, e.g., static routes or firewall rules. A user can add
a `task` or `run` command in the Finit configuration for this, but for
compatibility reasons the more widely know `/etc/rc.local` is used if
it exists, and is executable. It is called very late in the boot process
when the system has left runlevel S, stopped all old and started all new
services in the target runlevel (default 2).
> In Finit releases before v4.5 this script blocked Finit execution and
> made it as good as impossible to call `initctl` during that time.
### `init q`
When `/sbin/finit` is installed as `/sbin/init`, it is possible to use
`init q` to reload the configuration. This is the same as calling
`initctl reload`.
Rescue Mode
-----------
@@ -1051,6 +1141,10 @@ Finit supports a rescue mode which is activated by the `rescue` option
on the kernel command line. See [cmdline docs](cmdline.md) for how to
activate it.
This rescue mode can be disabled at configure time using:
configure --without-rescue
The rescue mode comes in two flavors; *traditional* and *fallback*.
> **Note:** in this mode `initctl` will not work. Use the `-f` flag to
@@ -1060,15 +1154,17 @@ The rescue mode comes in two flavors; *traditional* and *fallback*.
### Traditional
This is what most users expect. A very early maintenance login prompt,
served by the bundled `/libexec/finit/sulogin` program, or the standard
`sulogin` from util-linux or BusyBox is searched for in the UNIX default
`$PATH`. If a successful login is made, or the user exits (Ctrl-D), the
rescue mode is ended and the system boots up normally.
served by the system `sulogin` program from util-linux, or BusyBox. If
that is not found in `$PATH`, the bundled `/libexec/finit/sulogin`
program is used instead. If a successful login is made, or if the user
exits (Ctrl-D), the rescue mode is ended and the system boots up
normally.
> **Note:** if the user (UID 0 and GID 0) does not have a password, or
> __the account is locked__, the user is presented with a password-less
> `"Press enter to enter maintenance mode."`, prompt which opens up a
> root shell.
> **Note:** the bundled sulogin in Finit can at configure time be given
> another user than the default (root). If the sulogin user does not
> have a password, or __the account is locked__, the user is presented
> with a password-less `"Press enter to enter maintenance mode."`,
> prompt which opens up a root shell.
### Fallback
+2 -2
View File
@@ -136,7 +136,7 @@ hook points:
**Example:**
$ mkdir -p /libexec/finit/hook/sys/down
$ cat <<EOF >/libexec/finit/hook/sys/foo.sh
$ cat <<EOF >/libexec/finit/hook/sys/down/foo.sh
#!/bin/sh
echo 'I run just before the reboot() syscall at shutdown/reboot'
echo 'I have access to /dev since devtmpfs is exempt from umount'
@@ -146,7 +146,7 @@ hook points:
fi
exit 0
EOF
$ chmod +x /libexec/finit/hook/sys/foo.sh
$ chmod +x /libexec/finit/hook/sys/down/foo.sh
> **Note:** to use hook scripts, even for pre-bootstrap and pre-shutdown
> tasks, you must build with `configure --enable-hook-scripts-plugin`.
+141
View File
@@ -0,0 +1,141 @@
_cond()
{
initctl -pt cond dump | awk '{print $4}' | sed 's/<\(.*\)>/\1/'
}
_ident()
{
if [ -n "$1" ]; then
initctl ident | grep -q $1
else
initctl ident
fi
}
_svc()
{
initctl ls -pt | grep $1 | sed "s/.*\/\(.*\)/\1/g" | sort -u
}
_enabled()
{
echo "$(_svc enabled)"
}
_available()
{
all=$(mktemp)
ena=$(mktemp)
echo "$(_svc available)" >$all
echo "$(_svc enabled)" >$ena
grep -v -f $ena $all
rm $all $ena
}
# Determine first non-option word. Usually the command
_firstword() {
local firstword i
firstword=
for ((i = 1; i < ${#COMP_WORDS[@]}; ++i)); do
if [[ ${COMP_WORDS[i]} != -* ]]; then
firstword=${COMP_WORDS[i]}
break
fi
done
echo $firstword
}
# Determine last non-option word. Uusally a sub-command
_lastword() {
local lastword i
lastword=
for ((i = 1; i < ${#COMP_WORDS[@]}; ++i)); do
if [[ ${COMP_WORDS[i]} != -* ]] && [[ -n ${COMP_WORDS[i]} ]] && [[ ${COMP_WORDS[i]} != $cur ]]; then
lastword=${COMP_WORDS[i]}
fi
done
echo $lastword
}
_initctl()
{
local cur command
cur=${COMP_WORDS[COMP_CWORD]}
prev="${COMP_WORDS[COMP_CWORD-1]}"
firstword=$(_firstword)
lastword=$(_lastword)
commands="status cond debug help kill ls log version list enable \
disable touch show cat edit create delete reload start \
stop restart signal cgroup ps top plugins runlevel reboot \
halt poweroff suspend utmp"
cond_cmds="set get clear status dump"
cond_types="hook net pid service task usr"
signals="int term hup stop tstp cont usr1 usr2 pwr"
options="-b --batch \
-c --create \
-d --debug \
-f --force \
-h --help \
-j --json \
-n --noerr \
-1 --once \
-p --plain \
-q --quiet \
-t --no-heading \
-v --verbose \
-V --version"
case "${firstword}" in
enable)
COMPREPLY=($(compgen -W "$(_available)" -- $cur))
;;
disable|touch)
COMPREPLY=($(compgen -W "$(_enabled)" -- $cur))
;;
show|cat|edit|delete)
COMPREPLY=($(compgen -W "$(_svc .)" -- $cur))
;;
start|stop|restart|log|status)
COMPREPLY=($(compgen -W "$(_ident)" -- $cur))
;;
signal|kill)
if $(_ident "${prev}"); then
COMPREPLY=($(compgen -W "$signals" -- $cur))
else
COMPREPLY=($(compgen -W "$(_ident)" -- $cur))
fi
;;
cond)
case "${lastword}" in
set|clear)
compopt -o nospace
COMPREPLY=($(compgen -W "usr/" -- $cur))
;;
get)
COMPREPLY=($(compgen -W "$(_cond)" -- $cur))
;;
dump)
COMPREPLY=($(compgen -W "$cond_types" -- $cur))
;;
*)
COMPREPLY=($(compgen -W "$cond_cmds" -- $cur))
;;
esac
;;
*)
COMPREPLY=($(compgen -W "$commands" -- $cur))
;;
esac
if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W "$options" -- $cur))
fi
}
complete -F _initctl initctl
+50 -26
View File
@@ -2,7 +2,7 @@
.\" First parameter, NAME, should be all caps
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
.\" other parameters are allowed: see man(7), man(1)
.Dd Apr 24, 2023
.Dd July 11, 2023
.Dt FINIT.CONF 5 SMM
.Os Linux
.Sh NAME
@@ -12,40 +12,64 @@
.Nm /etc/finit.conf
.Nm /etc/finit.d/*.conf
.Nm /etc/finit.d/available/*.conf
.Nm /etc/finit.d/enabled/*.conf
.Sh DESCRIPTION
.Nm finit
can be configured using only the original
.Pa /etc/finit.conf
file or in combination with
.Pa /etc/finit.d/*.conf .
Useful for package-based Linux distributions \(em each package can
provide its own "script" file.
.Nm Finit
based systems can be set up with a single file:
.Pa /etc/finit.conf .
This is the traditional way of doing it which can be ideal for some
setups since it gives a great overview of the system configuration.
.Pp
.Bl -tag -width /etc/finit.d/*.conf -offset indent -compact
.It Pa /etc/finit.conf
Main configuration file
.It Pa /etc/finit.d/*.conf
Snippets, usually one service per file
.El
More comprehensive setups, however, require more careful planning.
The recommendation is per-package
.Cm *.conf
files in
.Pa /etc/finit.d/available/ .
This allows end users to enable and disable parts of the system
configuration at runtime. Finit tracks which tasks and services belong
to a given file, so that when the user calls
.Cm initctl reload
to activate the changes they have made, only the affected tasks and
services are stopped, started, or restarted. Hence, this approach is
useful for modern package-based Linux distributions.
.Pp
Not all configuration directives are available in
.Pa /etc/finit.d/*.conf
and some directives are only available at bootstrap, runlevel
.Cm S ,
see the section
.Sx Limitations
below for details.
The following sections describe the more of this in detail, starting
with files, the file format, and available directives. Remember to
also visit the other manual pages (references at the bottom).
.Sh FILES
.Bl -tag -width /etc/finit.d/available/*.conf -compact
.It Pa /etc/finit.conf
Main configuration file
Main configuration file, optional
.It Pa /etc/finit.d/*.conf
Static service definitions
Static (system) service definitions
.It Pa /etc/finit.d/available/*.conf
Available services
Available (installed) services
.It Pa /etc/finit.d/enabled/*.conf
Enabled services, symlinks from available
Enabled services (symlink back)
.El
.Pp
Static services,
.Cm .conf
files in
.Pa /etc/finit.d/ ,
cannot be enabled or disabled using
.Cm initctl [enable | disable] service[.conf] .
An enabled service is a symlink back to the corresponding
.Cm ../available/
service
.Cm .conf
file. Please use the
.Cm initctl
tool to manage these symlinks to ensure proper operation.
.Pp
.Sy NOTE:
Previous versions of
.Nm Finit
created symlinks in
.Pa /etc/finit.d/
if
.Pa /etc/finit.d/enabled/
was missing, this is as of v4.4 not supported.
.Sh FILE FORMAT
The file format is line based, empty lines and comments, lines starting
with `#', are ignored. A configuration directive starts with a keyword
@@ -960,6 +984,6 @@ settings in
.Xr finit 8 ,
.Xr initctl 8
.Sh AUTHORS
.Nm finit
.Nm Finit
was conceived and reverse engineered by Claudio Matsuoka. Since v1.0,
maintained by Joachim Wiberg, with contributions by many others.
+1
View File
@@ -5,6 +5,7 @@ initctl
keventd
logit
reboot
runparts
sulogin
watchdogd
Makefile
+7 -2
View File
@@ -8,7 +8,7 @@ AM_LDFLAGS = -export-dynamic
endif
sbin_PROGRAMS = finit initctl
pkglibexec_PROGRAMS = getty logit
pkglibexec_PROGRAMS = getty logit runparts
if SULOGIN
pkglibexec_PROGRAMS += sulogin
endif
@@ -32,6 +32,11 @@ keventd_CFLAGS = -W -Wall -Wextra -std=gnu99
keventd_CFLAGS += $(lite_CFLAGS)
keventd_LDADD = $(lite_LIBS)
runparts_SOURCES = runparts.c
runparts_CFLAGS = -W -Wall -Wextra -std=gnu99
runparts_CFLAGS += $(lite_CFLAGS)
runparts_LDADD = $(lite_LIBS)
sulogin_SOURCES = sulogin.c
sulogin_CFLAGS = -W -Wall -Wextra -std=gnu99
sulogin_LDADD = -lcrypt
@@ -54,7 +59,7 @@ finit_SOURCES = api.c cgroup.c cgroup.h \
mdadm.c mount.c \
pid.c pid.h \
plugin.c plugin.h private.h \
schedule.c schedule.h \
runparts.c schedule.c schedule.h \
service.c service.h \
sig.c sig.h \
sm.c sm.h \
+30 -6
View File
@@ -246,10 +246,12 @@ static void parse_arg(char *arg)
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;
@@ -862,7 +864,7 @@ static int parse_static(char *line, int is_rcsd)
service_interval = val * 1000; /* to milliseconds */
if (disabled)
service_init();
service_init(NULL);
}
return 0;
}
@@ -1083,6 +1085,14 @@ int conf_reload(void)
*/
memcpy(global_rlimit, initial_rlimit, sizeof(global_rlimit));
/*
* When built with --disable-rescue mode many other 'if (rescue)'
* code paths are #ifdeffed out. This one, and the ones in the
* plugins, are not because a hook or plugin can still trigger
* the alternative rescue.conf instead of finit.conf. This for
* very advanced use-cases where files on /etc are generated at
* bootstrap, including users and passwords, from other sources.
*/
if (rescue) {
int rc;
char line[80] = "tty [12345789] rescue";
@@ -1235,7 +1245,10 @@ static int conf_change_act(char *dir, char *name, uint32_t mask)
struct conf_change *node;
char *rp = NULL;
paste(fn, sizeof(fn), dir, name);
if (name[0])
paste(fn, sizeof(fn), dir, name);
else
strlcpy(fn, dir, sizeof(fn));
dbg("path: %s mask: %08x", fn, mask);
/* Handle disabling/removal of service */
@@ -1266,7 +1279,7 @@ static int conf_change_act(char *dir, char *name, uint32_t mask)
dbg("event registered for %s, mask 0x%x", rp, mask);
return 0;
fail:
warn("failed registering %s event", fn);
warn("failed registering %s event mask %08x", fn, mask);
return 1;
}
@@ -1329,10 +1342,8 @@ static int conf_iwatch_read(int fd)
if (!iwp)
continue;
if (conf_change_act(iwp->path, ev->name, ev->mask)) {
err(1, " Out of memory");
if (conf_change_act(iwp->path, ev->name, ev->mask))
break;
}
}
return 0;
@@ -1447,6 +1458,19 @@ int conf_init(uev_ctx_t *ctx)
return 1;
}
/*
* Background startup scripts in the runparts directory, if any.
*/
if (runparts && fisdir(runparts) && !rescue) {
char conf[sizeof(_PATH_RUNPARTS) + strlen(runparts) + 100];
char *args = debug ? "-d" : "-p";
snprintf(conf, sizeof(conf), "[S] <int/bootstrap> log:console %s %s %s"
" -- Calling runparts %s in the background",
_PATH_RUNPARTS, args, runparts, runparts);
service_register(SVC_TYPE_TASK, conf, global_rlimit, NULL);
}
return 0;
}
+1 -1
View File
@@ -243,7 +243,7 @@ static void devmon_cb(uev_t *w, void *arg, int events)
}
}
void devmon_init(void)
void devmon_init(uev_ctx_t *ctx)
{
char dir[MAX_ARG_LEN];
+1 -1
View File
@@ -27,6 +27,6 @@
void devmon_add_cond(const char *cond);
void devmon_del_cond(const char *cond);
void devmon_init(void);
void devmon_init(uev_ctx_t *ctx);
#endif /* FINIT_DEVMON_H_ */
+15 -93
View File
@@ -24,8 +24,6 @@
#include "config.h" /* Generated by configure script */
#include <ctype.h> /* isdigit() */
#include <dirent.h>
#include <err.h>
#include <stdarg.h>
#include <sysexits.h>
@@ -59,11 +57,11 @@ int complete(char *cmd, int pid)
if (waitpid(pid, &status, 0) == -1) {
if (errno == EINTR)
errx(1, "Caught unblocked signal waiting for %s, aborting", cmd);
warnx("Caught unblocked signal waiting for %s, aborting", cmd);
else if (errno == ECHILD)
errx(1, "Caught SIGCHLD waiting for %s, aborting", cmd);
warnx("Caught SIGCHLD waiting for %s, aborting", cmd);
else
errx(1, "Failed starting %s, error %d: %s", cmd, errno, strerror (errno));
warnx("Failed starting %s, error %d: %s", cmd, errno, strerror(errno));
return -1;
}
@@ -186,7 +184,7 @@ int run(char *cmd, char *log)
} else if (WIFSIGNALED(status)) {
dbg("Process '%s' terminated by signal %d", cmd, WTERMSIG(status));
if (!result)
result = 1; /* Must alert callee that the command did complete successfully.
result = 1; /* Must alert callee the command did not complete successfully.
* This is necessary since not all programs trap signals and
* change their return code accordingly. --Jocke */
}
@@ -272,7 +270,7 @@ int exec_runtask(char *cmd, char *args[])
size_t i;
strlcat(buf, cmd, sizeof(buf));
for (i = 1; args[i]; i++) {
for (i = 1; args && args[i]; i++) {
strlcat(buf, " ", sizeof(buf));
strlcat(buf, args[i], sizeof(buf));
}
@@ -443,96 +441,20 @@ pid_t run_sh(char *tty, int noclear, int nowait, struct rlimit rlimit[])
return rc;
}
static void run_env(const char *env[])
pid_t run_bg(char *cmd, char *args[])
{
size_t i = 0;
pid_t pid;
if (!env)
return;
if (access(cmd, X_OK))
return 0;
while (env[i]) {
size_t j = i + 1;
print(-1, "Calling %s in the background", cmd);
pid = fork();
if (!pid)
_exit(exec_runtask(cmd, args));
print_result(pid == -1);
if (!env[j])
break;
setenv(env[i], env[j], 1);
i += 2;
}
}
int run_parts(char *dir, char *cmd, const char *env[], int progress)
{
struct dirent **e;
int i, num;
num = scandir(dir, &e, NULL, alphasort);
if (num < 0) {
dbg("No files found in %s, skipping ...", dir);
return -1;
}
for (i = 0; i < num; i++) {
const char *name = e[i]->d_name;
char path[strlen(dir) + strlen(name) + 2];
struct stat st;
char *argv[4] = {
"sh",
"-c",
path,
NULL
};
pid_t pid = 0;
int status;
paste(path, sizeof(path), dir, name);
if (stat(path, &st)) {
dbg("Failed stat(%s): %s", path, strerror(errno));
continue;
}
if (!S_ISEXEC(st.st_mode) || S_ISDIR(st.st_mode)) {
if (strcmp(name, ".") && strcmp(name, ".."))
dbg("Skipping %s ...", path);
continue;
}
/* If the callee didn't supply a run_parts() argument */
if (!cmd) {
/* Check if S<NUM>service or K<NUM>service notation is used */
// dbg("Checking if %s is a sysvinit startstop script ...", name);
if (name[0] == 'S' && isdigit(name[1]))
strlcat(path, " start", sizeof(path));
else if (name[0] == 'K' && isdigit(name[1]))
strlcat(path, " stop", sizeof(path));
} else {
strlcat(path, cmd, sizeof(path));
}
if (progress)
print_desc("Calling ", path);
pid = fork();
if (!pid) {
sig_unblock();
run_env(env);
return execvp(_PATH_BSHELL, argv);
}
status = complete(path, pid);
if (WIFEXITED(status))
warnx("%s exited with status %d", path, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
warnx("%s terminated by signad %d", path, WTERMSIG(status));
if (progress)
print_result(WEXITSTATUS(status));
}
while (num--)
free(e[num]);
free(e);
return 0;
return pid;
}
/**
+10 -125
View File
@@ -58,7 +58,6 @@
#include "tty.h"
#include "util.h"
#include "utmp-api.h"
#include "schedule.h"
int runlevel = INIT_LEVEL; /* Bootstrap 'S' */
int cfglevel = RUNLEVEL; /* Fallback if no configured runlevel */
@@ -521,113 +520,6 @@ static void fs_init(void)
}
/*
* Handle bootstrap transition to configured runlevel, start TTYs
*
* This is the final stage of bootstrap. It changes to the default
* (configured) runlevel, calls all external start scripts and final
* bootstrap hooks before bringing up TTYs.
*
* We must ensure that all declared `task [S]` and `run [S]` jobs in
* finit.conf, or *.conf in finit.d/, run to completion before we
* finalize the bootstrap process by calling this function.
*/
static void finalize(void *unused)
{
/* Clean up bootstrap-only tasks/services that never started */
dbg("Clean up all bootstrap-only tasks/services ...");
svc_prune_bootstrap();
/* All services/tasks/etc. in configure runlevel have started */
dbg("Running svc up hooks ...");
plugin_run_hooks(HOOK_SVC_UP);
service_step_all(SVC_TYPE_ANY);
/* Convenient SysV compat for when you just don't care ... */
if (!access(FINIT_RC_LOCAL, X_OK) && !rescue)
run_interactive(FINIT_RC_LOCAL, "Calling %s", FINIT_RC_LOCAL);
/* Hooks that should run at the very end */
dbg("Calling all system up hooks ...");
plugin_run_hooks(HOOK_SYSTEM_UP);
service_step_all(SVC_TYPE_ANY);
/* Disable progress output at normal runtime */
enable_progress(0);
/* System bootrapped, launch TTYs et al */
bootstrap = 0;
service_step_all(SVC_TYPE_RESPAWN);
}
/*
* Start cranking the big state machine
*/
static void crank_worker(void *unused)
{
/*
* Initialize state machine and start all bootstrap tasks
* NOTE: no network available!
*/
sm_init(&sm);
sm_step(&sm);
}
/*
* Wait for system bootstrap to complete, all SVC_TYPE_RUNTASK must be
* allowed to complete their work in [S], or timeout, before we switch
* to the configured runlevel and call finalize(), should not take more
* than 120 sec.
*/
static void bootstrap_worker(void *work)
{
static struct wq final = {
.cb = finalize,
.delay = 10
};
static int cnt = 120 * 10; /* We run with 100ms period */
int level = cfglevel;
dbg("Step all services ...");
service_step_all(SVC_TYPE_ANY);
if (cnt-- > 0 && !service_completed()) {
dbg("Not all bootstrap run/tasks have completed yet ... %d", cnt);
schedule_work(work);
return;
}
if (cnt > 0)
dbg("All run/task have completed, resuming bootstrap.");
else
dbg("Timeout, resuming bootstrap.");
dbg("Starting runlevel change finalize ...");
schedule_work(&final);
/*
* Run startup scripts in the runparts directory, if any.
*/
if (runparts && fisdir(runparts) && !rescue)
run_parts(runparts, NULL, NULL, 1);
dbg("Flushing pending .conf file events ...");
conf_flush_events();
/*
* Start all tasks/services in the configured runlevel, or jump
* into the runlevel selected from the command line.
*/
if (cmdlevel) {
dbg("Runlevel %d requested from command line, starting all services ...", cmdlevel);
level = cmdlevel;
} else
dbg("Change to default runlevel(%d), starting all services ...", cfglevel);
service_runlevel(level);
}
static int version(int rc)
{
puts(PACKAGE_STRING);
@@ -658,7 +550,7 @@ static int usage(int rc)
" 2-9 Change runlevel\n"
" q, Q Reload /etc/finit.conf and/or any *.conf in /etc/finit.d/\n"
" if modified, same as initctl reload or SIGHUP to PID 1\n"
" 1, s, S Enter system rescue mode, runlevel 1\n"
" 1, s, S Enter single user mode, no networking, runlevel 1\n"
"\n", prognm);
return rc;
@@ -708,14 +600,6 @@ static int telinit(int argc, char *argv[])
int main(int argc, char *argv[])
{
struct wq crank_work = {
.cb = crank_worker,
.delay = 10
};
struct wq bootstrap_work = {
.cb = bootstrap_worker,
.delay = 100
};
uev_ctx_t loop;
/* user calling telinit or init */
@@ -765,8 +649,10 @@ int main(int argc, char *argv[])
/*
* In case of emergency.
*/
#ifdef RESCUE_MODE
if (rescue)
rescue = sulogin(0);
#endif
/*
* Load plugins early, the first hook is in banner(), so we
@@ -819,7 +705,7 @@ int main(int argc, char *argv[])
* Initialize .conf system and load static /etc/finit.conf then
* tell the world what we used.
*/
devmon_init();
devmon_init(&loop);
conf_init(&loop);
conf_saverc();
@@ -832,16 +718,15 @@ int main(int argc, char *argv[])
dbg("Starting initctl API responder ...");
api_init(&loop);
dbg("Starting the big state machine ...");
schedule_work(&crank_work);
dbg("Starting bootstrap finalize timer ...");
schedule_work(&bootstrap_work);
dbg("Starting service interval monitor ...");
service_init(&loop);
/*
* Background service tasks
* Initialize state machine and start all bootstrap tasks
* NOTE: no network available!
*/
service_init();
sm_init(&sm);
sm_step(&sm);
/*
* Enter main loop to monitor /dev/initctl and services
+1
View File
@@ -56,6 +56,7 @@
/* To use these, include config.h, or define FINIT_EXECPATH_ */
#define _PATH_LOGIT FINIT_EXECPATH_ "/logit"
#define _PATH_RUNPARTS FINIT_EXECPATH_ "/runparts"
#define _PATH_SULOGIN FINIT_EXECPATH_ "/sulogin"
#define _PATH_GETTY FINIT_EXECPATH_ "/getty"
+1 -1
View File
@@ -369,7 +369,7 @@ restart:
static int usage(int rc)
{
warnx("usage: getty [-h?] tty [speed [term]]");
warnx("usage: getty [-h?p] tty [speed [term]]");
return rc;
}
+1
View File
@@ -88,6 +88,7 @@ int run_interactive (char *cmd, char *fmt, ...) __attribute__ ((format (prin
int exec_runtask (char *cmd, char *args[]);
pid_t run_getty (char *tty, char *cmd, char *args[], int noclear, int nowait, struct rlimit rlimit[]);
pid_t run_sh (char *tty, int noclear, int nowait, struct rlimit rlimit[]);
pid_t run_bg (char *cmd, char *args[]);
int run_parts (char *dir, char *cmd, const char *env[], int progress);
/*
+10 -3
View File
@@ -209,19 +209,26 @@ static int do_runlevel(char *arg)
if (!arg) {
int prevlevel = 0;
int currlevel;
char prev;
char prev, curr;
currlevel = runlevel_get(&prevlevel);
if (255 == currlevel) {
switch (currlevel) {
case 255:
printf("unknown\n");
return 0;
case INIT_LEVEL:
curr = 'S';
break;
default:
curr = currlevel + '0';
break;
}
prev = prevlevel + '0';
if (prev <= '0' || prev > '9')
prev = 'N';
printf("%c %d\n", prev , currlevel);
printf("%c %c\n", prev , curr);
return 0;
}
+1
View File
@@ -47,6 +47,7 @@ static __attribute__ ((format (printf, 1, 2))) inline void dbg(char *fmt, ...)
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fputs("\n", stderr);
va_end(ap);
}
#define info(fmt, args...) warnx(fmt, ##args)
+222
View File
@@ -0,0 +1,222 @@
#include <ctype.h> /* isdigit() */
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#ifdef _LIBITE_LITE
# include <libite/conio.h>
# include <libite/lite.h>
#else
# include <lite/conio.h>
# include <lite/lite.h>
#endif
#include "log.h"
#include "sig.h"
#include "util.h"
#ifdef __FINIT__
#include "helpers.h"
#else
#include <sys/prctl.h>
int debug;
static void print_desc(const char *prefix, const char *msg)
{
fprintf(stderr, "\e[1m[\e[0m\e[1;33m ⋯ \e[0m\e[1m]\e[0m %s %s", prefix, msg);
}
static void print_result(int rc)
{
if (rc)
fputs("\r\e[1m[\e[0m\e[1;31mFAIL\e[0m\e[1m]\e[0m\n", stderr);
else
fputs("\r\e[1m[\e[0m\e[1;32m OK \e[0m\e[1m]\e[0m\n", stderr);
}
void sig_unblock(void)
{
struct sigaction sa;
sigset_t nmask;
int i;
sigemptyset(&nmask);
sigaddset(&nmask, SIGHUP);
sigaddset(&nmask, SIGCHLD);
sigaddset(&nmask, SIGINT);
sigaddset(&nmask, SIGPWR);
sigaddset(&nmask, SIGSTOP);
sigaddset(&nmask, SIGTSTP);
sigaddset(&nmask, SIGCONT);
sigaddset(&nmask, SIGTERM);
sigaddset(&nmask, SIGUSR1);
sigaddset(&nmask, SIGUSR2);
sigprocmask(SIG_UNBLOCK, &nmask, NULL);
/* Reset signal handlers that were set by the parent process */
for (i = 1; i < NSIG; i++)
DFLSIG(sa, i, 0);
}
#endif /* __FINIT__ */
static void run_env(const char *env[])
{
size_t i = 0;
if (!env)
return;
while (env[i]) {
size_t j = i + 1;
if (!env[j])
break;
setenv(env[i], env[j], 1);
i += 2;
}
}
int run_parts(char *dir, char *cmd, const char *env[], int progress)
{
size_t cmdlen = cmd ? strlen(cmd) : strlen("start");
struct dirent **d;
int i, num;
int rc = 0;
num = scandir(dir, &d, NULL, alphasort);
if (num < 0) {
dbg("No files found in %s, skipping ...", dir);
return -1;
}
for (i = 0; i < num; i++) {
char path[strlen(dir) + strlen(d[i]->d_name) + 3 + cmdlen];
const char *name = d[i]->d_name;
char *argv[4] = {
"sh",
"-c",
path,
NULL
};
struct stat st;
int result = 0;
pid_t pid = 0;
int status;
/* skip backup files */
if (name[strlen(name) - 1] == '~')
continue;
/* cannot rely on d_type, not supported on all filesystems */
paste(path, sizeof(path), dir, name);
if (stat(path, &st)) {
warn("failed stat(%s)", path);
continue;
}
/* skip non-executable files and directories */
if (!S_ISEXEC(st.st_mode) || S_ISDIR(st.st_mode)) {
dbg("skipping %s not an executable or is a directory", path);
continue;
}
/* If the callee didn't supply a run_parts() argument */
if (!cmd) {
/* Check if S<NUM>service or K<NUM>service notation is used */
if (name[0] == 'S' && isdigit(name[1]))
strlcat(path, " start", sizeof(path));
else if (name[0] == 'K' && isdigit(name[1]))
strlcat(path, " stop", sizeof(path));
} else {
strlcat(path, " ", sizeof(path));
strlcat(path, cmd, sizeof(path));
}
if (progress)
print_desc("Calling", path);
pid = fork();
if (!pid) {
sig_unblock();
run_env(env);
_exit(execvp(_PATH_BSHELL, argv));
}
if (waitpid(pid, &status, 0) == -1) {
warnx("failed starting %s, error %d: %s", path, errno, strerror(errno));
result = 1;
} else {
if (WIFEXITED(status)) {
result = WEXITSTATUS(status);
dbg("%s exited with status %d", path, result);
} else if (WIFSIGNALED(status)) {
warnx("%s terminated by signal %d", path, WTERMSIG(status));
result = 1;
} else
result = 1;
}
if (progress)
print_result(result);
rc += result;
}
while (num--)
free(d[num]);
free(d);
return rc;
}
#ifndef __FINIT__
static int usage(int rc)
{
warnx("usage: runparts [-dh?] DIRECTORY");
return rc;
}
int main(int argc, char *argv[])
{
int rc, c, progress = 0;
char *dir;
while ((c = getopt(argc, argv, "dh?p")) != EOF) {
switch(c) {
case 'd':
debug = 1;
break;
case 'h':
case '?':
return usage(0);
case 'p':
progress = 1;
break;
default:
return usage(1);
}
}
if (optind >= argc)
return usage(1);
dir = argv[optind];
if (!fisdir(dir))
return usage(1);
prctl(PR_SET_CHILD_SUBREAPER, 1);
rc = run_parts(dir, NULL, NULL, progress);
if (rc == -1)
err(1, "failed run-parts %s", dir);
return rc;
}
#endif /* __FINIT__ */
+14
View File
@@ -41,6 +41,10 @@ static int is_builtin(char *arg)
{
svc_t *svc;
/* skip check, we may be in non-responsive bootstrap */
if (iforce)
return 0;
svc = client_svc_find(arg);
if (!svc)
return 0;
@@ -262,6 +266,11 @@ int serv_enable(char *arg)
return serv_list("available");
}
if (arg[0] == '/') {
WARNX("absolute paths not supported, this command manages %s/available/", finit_rcsd);
return serv_list("available");
}
len = strlen(arg);
serv = alloca(len + 6);
if (!serv)
@@ -322,6 +331,11 @@ int do_disable(char *arg, int check)
return serv_list("enabled");
}
if (arg[0] == '/') {
WARNX("absolute paths not supported, this command manages %s/enabled/", finit_rcsd);
return serv_list("available");
}
len = strlen(arg);
serv = alloca(len + 6);
if (!serv)
+74 -43
View File
@@ -56,6 +56,12 @@
#include "utmp-api.h"
#include "schedule.h"
/*
* run tasks block other tasks/services from starting, we track the
* current run task here. For service_step() and service_start().
*/
static pid_t run_block_pid;
static struct wq work = {
.cb = service_worker,
};
@@ -560,6 +566,15 @@ static pid_t service_fork(svc_t *svc)
source_env(svc);
}
if (pid > 1) {
char grnam[80];
if (svc_is_tty(svc))
cgroup_user("getty", pid);
else
cgroup_service(group_name(svc, grnam, sizeof(grnam)), pid, &svc->cgroup);
}
return pid;
}
@@ -575,7 +590,6 @@ static int service_start(svc_t *svc)
int result = 0, do_progress = 1;
char cmdline[CMD_SIZE] = "";
sigset_t nmask, omask;
char grnam[80];
pid_t pid;
size_t i;
@@ -586,6 +600,10 @@ static int service_start(svc_t *svc)
if (is_norespawn())
return 1;
/* Waiting for a run task to complete */
if (run_block_pid)
return 1;
/* Don't try and start service if it doesn't exist. */
if (!whichp(svc->cmd)) {
logit(LOG_WARNING, "%s: missing %s or not in $PATH", svc_ident(svc, NULL, 0), svc->cmd);
@@ -644,19 +662,15 @@ static int service_start(svc_t *svc)
goto fail;
}
if (pid > 1) {
dbg("Starting %s as PID %d", svc_ident(svc, NULL, 0), pid);
svc->pid = pid;
svc->start_time = jiffies();
} else if (pid == 0) {
char *args[MAX_NUM_SVC_ARGS + 1];
int status;
pid = getpid();
if (svc_is_tty(svc)) {
cgroup_user("getty", pid);
} else {
cgroup_service(group_name(svc, grnam, sizeof(grnam)), pid, &svc->cgroup);
if (!svc_is_tty(svc))
redirect(svc);
}
if (!svc_is_sysv(svc)) {
wordexp_t we = { 0 };
@@ -788,7 +802,7 @@ static int service_start(svc_t *svc)
*/
pid = setsid();
if (pid < 1)
logit(LOG_ERR, "failed setsid(), pid %d: %s", pid, strerror(errno));
syslog(LOG_ERR, "failed setsid(), pid %d: %s", pid, strerror(errno));
sig_unblock();
@@ -799,6 +813,8 @@ static int service_start(svc_t *svc)
else
status = execvp(args[0], &args[1]);
syslog(LOG_ERR, "failed starting %s, exit code %d: %s", svc_ident(svc, NULL, 0),
status, strerror(errno));
_exit(status);
} else if (debug) {
dbg("Starting PID %d: %s", svc->pid, cmdline);
@@ -809,27 +825,11 @@ static int service_start(svc_t *svc)
switch (svc->type) {
case SVC_TYPE_RUN:
svc->status = complete(svc->cmd, pid);
dbg("collected %s(%d), normal exit: %d, signaled: %d, exit code: %d",
svc_ident(svc, NULL, 0), pid, WIFEXITED(svc->status),
WIFSIGNALED(svc->status), WEXITSTATUS(svc->status));
if (WIFEXITED(svc->status)) {
svc->started = 1;
result = WEXITSTATUS(svc->status);
} else {
svc->started = 0;
result = 1;
}
svc->start_time = svc->pid = 0;
svc->once++;
svc_mark_clean(svc); /* done, regardless of exit status */
svc_set_state(svc, SVC_STOPPING_STATE);
run_block_pid = pid;
break;
case SVC_TYPE_SERVICE:
pid_file_create(svc);
break;
default:
break;
}
@@ -850,17 +850,26 @@ fail:
*/
static void service_kill(svc_t *svc)
{
char *nm, *id = svc_ident(svc, NULL, 0);
service_timeout_cancel(svc);
if (svc->pid <= 1) {
/* Avoid killing ourselves or all processes ... */
dbg("%s: Aborting SIGKILL, already terminated.", svc_ident(svc, NULL, 0));
dbg("%s: Aborting SIGKILL, already terminated.", id);
return;
}
dbg("%s: Sending SIGKILL to process group %d", pid_get_name(svc->pid, NULL, 0), svc->pid);
logit(LOG_CONSOLE | LOG_NOTICE, "Stopping %s[%d], sending SIGKILL ...",
svc_ident(svc, NULL, 0), svc->pid);
nm = pid_get_name(svc->pid, NULL, 0);
if (!nm) {
/* PID possibly monitored by someone else? */
dbg("%s: Aborting SIGKILL, PID[%d] no longer exists.", id, svc->pid);
service_monitor(svc->pid, 0);
return;
}
dbg("%s: Sending SIGKILL to process group %d", nm, svc->pid);
logit(LOG_CONSOLE | LOG_NOTICE, "Stopping %s[%d], sending SIGKILL ...", id, svc->pid);
if (runlevel != 1)
print_desc("Killing ", svc->desc);
@@ -928,13 +937,21 @@ int service_stop(svc_t *svc)
compose_cmdline(svc, cmdline, sizeof(cmdline));
if (!svc_is_sysv(svc)) {
char *nm = pid_get_name(svc->pid, NULL, 0);
const char *sig = sig_name(svc->sighalt);
char *id = svc_ident(svc, NULL, 0);
if (svc->pid <= 1)
return 1;
if (!nm) {
dbg("Skipping stop (%s) of %s, PID[%d] no longer exists.", sig, id, svc->pid);
service_cleanup(svc);
svc_set_state(svc, SVC_HALTED_STATE);
return 0;
}
dbg("Sending %s to pid:%d name:%s", sig_name(svc->sighalt),
svc->pid, pid_get_name(svc->pid, NULL, 0));
logit(LOG_CONSOLE | LOG_NOTICE, "Stopping %s[%d], sending %s ...",
svc_ident(svc, NULL, 0), svc->pid, sig_name(svc->sighalt));
dbg("Sending %s to pid:%d name:%s", sig, svc->pid, nm);
logit(LOG_CONSOLE | LOG_NOTICE, "Stopping %s[%d], sending %s ...", id, svc->pid, sig);
} else {
logit(LOG_CONSOLE | LOG_NOTICE, "Calling '%s stop' ...", cmdline);
}
@@ -1830,6 +1847,11 @@ 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:
@@ -2251,11 +2273,11 @@ void service_ready(svc_t *svc)
*/
int service_step(svc_t *svc)
{
cond_state_t cond;
svc_state_t old_state;
svc_cmd_t enabled;
char *restart_cnt = (char *)&svc->restart_cnt;
int changed = 0;
int changed = 0, waiting = 0;
svc_state_t old_state;
cond_state_t cond;
svc_cmd_t enabled;
int err;
restart:
@@ -2372,6 +2394,10 @@ restart:
err = service_start(svc);
if (err) {
/* Busy, waiting for run task, try again later */
if (run_block_pid)
break;
if (svc_is_missing(svc)) {
svc_set_state(svc, SVC_HALTED_STATE);
break;
@@ -2413,6 +2439,7 @@ restart:
if (svc_is_runtask(svc)) { /* only run/task, not sysv here */
svc_set_state(svc, SVC_STOPPING_STATE);
svc->restart_tot++;
svc->once++;
break;
}
@@ -2490,6 +2517,9 @@ restart:
break;
}
/* Are we waiting for a run task to complete? */
waiting = run_block_pid;
if (svc->state != old_state) {
dbg("%20s(%4d): -> %8s", svc_ident(svc, NULL, 0), svc->pid, svc_status(svc));
changed++;
@@ -2501,7 +2531,7 @@ done:
* When a run/task/service changes state, e.g. transitioning from
* waiting to running, other services may need to change state too.
*/
if (changed)
if (changed || waiting)
schedule_work(&work);
return 0;
@@ -2691,23 +2721,24 @@ static void service_interval_cb(uev_t *w, void *arg, int events)
}
}
service_init();
service_init(NULL);
}
/*
* The service_interval may change (conf) between invocations, so we
* periodically reset the one-shot timer instead of using a periodic.
*/
void service_init(void)
void service_init(uev_ctx_t *ctx)
{
static int initialized = 0;
static uev_t watcher;
if (!initialized) {
if (!initialized)
uev_timer_init(ctx, &watcher, service_interval_cb, NULL, service_interval, 0);
initialized = 1;
} else
else
uev_timer_set(&watcher, service_interval, 0);
initialized = 1;
}
/**
+1 -1
View File
@@ -52,7 +52,7 @@ void service_worker (void *unused);
int service_completed (void);
void service_notify_reconf (void);
void service_init (void);
void service_init (uev_ctx_t *ctx);
#endif /* FINIT_SERVICE_H_ */
+109 -12
View File
@@ -31,6 +31,7 @@
#include "conf.h"
#include "helpers.h"
#include "private.h"
#include "schedule.h"
#include "service.h"
#include "sig.h"
#include "tty.h"
@@ -43,12 +44,53 @@ sm_t sm;
#define FINIT_NOLOGIN_PATH _PATH_NOLOGIN /* Stop user logging in. */
#endif
void sm_init(sm_t *sm)
/*
* Wait for system bootstrap to complete, all SVC_TYPE_RUNTASK must be
* allowed to complete their work in [S], or timeout, before we switch
* to the configured runlevel and call finalize(), should not take more
* than 120 sec.
*/
static void sm_check_bootstrap(void *work)
{
sm->state = SM_BOOTSTRAP_STATE;
sm->newlevel = -1;
sm->reload = 0;
sm->in_teardown = 0;
static int cnt = 120 * 10; /* We run with 100ms period */
int level = cfglevel;
dbg("Step all services ...");
service_step_all(SVC_TYPE_ANY);
if (cnt-- > 0 && !service_completed()) {
dbg("Not all bootstrap run/tasks have completed yet ... %d", cnt);
schedule_work(work);
return;
}
if (cnt > 0)
dbg("All run/task have completed, resuming bootstrap.");
else
dbg("Timeout, resuming bootstrap.");
dbg("Flushing pending .conf file events ...");
conf_flush_events();
/*
* Start all tasks/services in the configured runlevel, or jump
* into the runlevel selected from the command line.
*/
if (cmdlevel) {
dbg("Runlevel %d requested from command line, starting all services ...", cmdlevel);
level = cmdlevel;
} else
dbg("Change to default runlevel(%d), starting all services ...", cfglevel);
service_runlevel(level);
/* Clean up bootstrap-only tasks/services that never started */
dbg("Clean up all bootstrap-only tasks/services ...");
svc_prune_bootstrap();
/* All services/tasks/etc. in configure runlevel have started */
dbg("Running svc up hooks ...");
plugin_run_hooks(HOOK_SVC_UP);
}
static char *sm_status(sm_state_t state)
@@ -100,6 +142,22 @@ static void nologin(void)
erase(FINIT_NOLOGIN_PATH);
}
void sm_init(sm_t *sm)
{
static struct wq work = {
.cb = sm_check_bootstrap,
.delay = 1000
};
sm->state = SM_BOOTSTRAP_STATE;
sm->newlevel = -1;
sm->reload = 0;
sm->in_teardown = 0;
dbg("Starting bootstrap finalize timer ...");
schedule_work(&work);
}
void sm_set_runlevel(sm_t *sm, int newlevel)
{
sm->newlevel = newlevel;
@@ -117,8 +175,8 @@ int sm_is_in_teardown(sm_t *sm)
void sm_step(sm_t *sm)
{
svc_t *svc;
sm_state_t old_state;
svc_t *svc;
restart:
old_state = sm->state;
@@ -130,10 +188,49 @@ restart:
case SM_BOOTSTRAP_STATE:
dbg("Bootstrapping all services in runlevel S from %s", finit_conf);
service_step_all(SVC_TYPE_RUNTASK | SVC_TYPE_SERVICE);
sm->state = SM_BOOTSTRAP_WAIT_STATE;
break;
/*
* Handle bootstrap transition to configured runlevel, start TTYs
*
* This is the final stage of bootstrap. It changes to the default
* (configured) runlevel, calls all external start scripts and final
* bootstrap hooks before bringing up TTYs.
*
* We must ensure that all declared `task [S]` and `run [S]` jobs in
* finit.conf, or *.conf in finit.d/, run to completion before we
* finalize the bootstrap process by calling this function.
*/
case SM_BOOTSTRAP_WAIT_STATE:
/* We come here from bootstrap, runlevel change and conf reload */
service_step_all(SVC_TYPE_ANY);
/* Allow runparts to start */
cond_set("int/bootstrap");
if (sm->newlevel == -1)
break;
/* Hooks that should run at the very end */
dbg("Calling all system up hooks ...");
plugin_run_hooks(HOOK_SYSTEM_UP);
service_step_all(SVC_TYPE_ANY);
/* Disable progress output at normal runtime */
enable_progress(0);
/* System bootrapped, launch TTYs et al */
bootstrap = 0;
service_step_all(SVC_TYPE_RESPAWN);
sm->state = SM_RUNNING_STATE;
break;
case SM_RUNNING_STATE:
/* We come here from bootstrap, runlevel change and conf reload */
service_step_all(SVC_TYPE_ANY);
/* runlevel changed? */
if (sm->newlevel >= 0 && sm->newlevel <= 9) {
if (runlevel == sm->newlevel) {
@@ -143,6 +240,7 @@ restart:
sm->state = SM_RUNLEVEL_CHANGE_STATE;
break;
}
/* reload ? */
if (sm->reload) {
sm->reload = 0;
@@ -210,17 +308,18 @@ restart:
/* Cleanup stale services */
svc_clean_dynamic(service_unregister);
/* Compat SysV init */
if (prevlevel == INIT_LEVEL && !rescue)
run_bg(FINIT_RC_LOCAL, NULL);
/*
* "I've seen things you people wouldn't believe. Attack ships on fire off
* the shoulder of Orion. I watched C-beams glitter in the dark near the
* Tannhäuser Gate. All those .. moments .. will be lost in time, like
* tears ... in ... rain."
*/
if (runlevel == 0 || runlevel == 6) {
if (runlevel == 0 || runlevel == 6)
do_shutdown(halt);
sm->state = SM_RUNNING_STATE;
break;
}
sm->state = SM_RUNNING_STATE;
break;
@@ -266,9 +365,7 @@ restart:
dbg("Update configuration generation of unmodified non-native services ...");
service_notify_reconf();
service_step_all(SVC_TYPE_ANY);
dbg("Reconfiguration done");
sm->state = SM_RUNNING_STATE;
break;
}
+1
View File
@@ -26,6 +26,7 @@
typedef enum {
SM_BOOTSTRAP_STATE = 0, /* Init state, bootstrap services */
SM_BOOTSTRAP_WAIT_STATE, /* Waiting for bootstrap to complete */
SM_RUNNING_STATE, /* Normal state, services running */
SM_RUNLEVEL_CHANGE_STATE, /* A runlevel change has occurred */
SM_RUNLEVEL_WAIT_STATE, /* Waiting for all stopped runlevel processes to be halted */
+9 -3
View File
@@ -21,6 +21,8 @@
* THE SOFTWARE.
*/
#include "config.h"
#include <crypt.h>
#include <err.h>
#include <paths.h>
@@ -32,6 +34,10 @@
#include <unistd.h>
#include <sys/ioctl.h>
#ifndef SULOGIN_USER
#define SULOGIN_USER "root"
#endif
/* getpwnam() cannot be used when statically linked */
static int get_passwd(struct passwd *pw)
{
@@ -44,13 +50,13 @@ static int get_passwd(struct passwd *pw)
return 1;
while ((tmp = fgets(buf, sizeof(buf), fp))) {
if (!strstr(buf, ":0:0:"))
continue; /* not uid 0 */
ptr = strsep(&tmp, ":");
if (!ptr)
break;
pw->pw_name = strdup(ptr);
if (!pw->pw_name || strcmp(pw->pw_name, SULOGIN_USER))
continue;
ptr = strsep(&tmp, ":");
if (!ptr)
+3
View File
@@ -42,7 +42,10 @@ extern int ttcols;
extern char *prognm;
/* Define for printheader() from conio.h, used in initctl */
#ifndef SCREEN_WIDTH
#define SCREEN_WIDTH ttcols
#endif
#ifdef _LIBITE_LITE
# include <libite/conio.h>
# include <libite/lite.h>
+5
View File
@@ -37,6 +37,8 @@ EXTRA_DIST += ready-serv.sh
EXTRA_DIST += restart-self.sh
EXTRA_DIST += runlevel.sh
EXTRA_DIST += run-restart-forever.sh
EXTRA_DIST += run-task-tricks.sh
EXTRA_DIST += runparts.sh
EXTRA_DIST += start-stop-service.sh
EXTRA_DIST += start-stop-service-sub-config.sh
EXTRA_DIST += start-kill-service.sh
@@ -61,6 +63,7 @@ TEST_EXTENSIONS = .sh
TESTS = checkself.sh
TESTS += add-remove-dynamic-service.sh
TESTS += add-remove-dynamic-service-sub-config.sh
TESTS += bootstrap-crash.sh
TESTS += cond-start-task.sh
TESTS += crashing.sh
TESTS += depserv.sh
@@ -76,6 +79,8 @@ TESTS += ready-serv.sh
TESTS += restart-self.sh
TESTS += runlevel.sh
TESTS += run-restart-forever.sh
TESTS += run-task-tricks.sh
TESTS += runparts.sh
TESTS += start-stop-service.sh
TESTS += start-stop-service-sub-config.sh
TESTS += start-kill-service.sh
+6 -10
View File
@@ -4,7 +4,8 @@
#set -eu
TEST_DIR=$(dirname "$0")
DEBUG=1
#DEBUG=1
# shellcheck disable=SC2034
BOOTSTRAP="run [S] crasher.sh -- Party crasher"
# shellcheck source=/dev/null
@@ -12,22 +13,17 @@ BOOTSTRAP="run [S] crasher.sh -- Party crasher"
test_setup()
{
say "Test start $(date)"
say "Test start $(date)"
}
test_teardown()
{
say "Test done $(date)"
say "Running test teardown."
run "rm -f $TEST_CONF"
say "Test done $(date)"
}
say "Hello SMTP Spoken here"
run initctl
run initctl status crasher.sh
sleep 10
say "We are done"
#run initctl
#run initctl status crasher.sh
exit 0
+38 -11
View File
@@ -52,6 +52,11 @@ assert_new_pid()
assert "Finit has registered new PID" "$(texec initctl |grep $1 | awk '{print $1;}')" -eq "$(texec cat $2)"
}
assert_pidiff()
{
assert "PID has changed (old PID $2)" "$(texec initctl |awk -v svc="$1" '$0 ~ svc {print $1}')" -ne "$2"
}
assert_restarts()
{
assert "Finit has registered restarts" "$(texec initctl status "$2" | awk '/Restarts/{print $3;}')" -ge "$1"
@@ -59,7 +64,7 @@ assert_restarts()
assert_restart_cnt()
{
assert "Finit restart cnt $1" "$(texec initctl status "$2" | awk '/Restarts/{print $4;}')" = "($1)"
assert "Finit restart total $1 cnt $2" "$(texec initctl status "$3" | awk '/Restarts/{print $3 $4;}')" = "$1($2)"
}
assert_norestart()
@@ -226,7 +231,8 @@ wdstart()
wdkill()
{
kill -KILL $wdpid
say "Stopping test watchdog, pid $wdpid"
kill -KILL $wdpid
}
teardown()
@@ -247,11 +253,15 @@ teardown()
log "$fg_red" 'TEST FAIL' ''
fi
say "Telling Finit to shut down (set -$-) ..."
set +e
if [ -n "${finit_pid+x}" ]; then
texec kill -SIGUSR2 1
fi
say "Telling Finit to shut down (set -$-) ..."
while [ -n "${finit_pid+x}" ]; do
if ! kill -USR2 "${finit_pid}" 2>/dev/null; then
break
fi
say "Retrying shutdown ..."
sleep 1
done
say "Waiting for Finit to shut down ..."
wait
@@ -259,6 +269,7 @@ teardown()
say "Final cleanup ..."
rm -f "$SYSROOT"/running_test.pid
rm -f "$SYSROOT$FINIT_CONF"
rm -f "$SYSROOT/etc/rc.local"
say "EXIT: $test_status"
exit $test_status
@@ -287,14 +298,30 @@ else
fi
set +u
rm -f "$SYSROOT$FINIT_CONF"
touch "$SYSROOT$FINIT_CONF"
if [ -n "$BOOTSTRAP" ]; then
say "Setting up bootstrap tasks ..."
echo "$BOOTSTRAP" > "$SYSROOT$FINIT_CONF"
say "Setting up bootstrap tasks ..."
echo "$BOOTSTRAP" >> "$SYSROOT$FINIT_CONF"
fi
if [ -n "$RUNPARTS" ]; then
say "Setting up runparts task ..."
echo "runparts /etc/rc.d" >> "$SYSROOT$FINIT_CONF"
mkdir "$SYSROOT/etc/rc.d"
echo "$RUNPARTS" > "$SYSROOT/etc/rc.d/start.sh"
chmod +x "$SYSROOT/etc/rc.d/start.sh"
fi
if [ -n "$RCLOCAL" ]; then
say "Setting up /etc/rc.local ..."
echo "$RCLOCAL" > "$SYSROOT/etc/rc.local"
chmod +x "$SYSROOT/etc/rc.local"
while [ -f "$SYSROOT/etc/rc.local" ]; do
echo "$SYSROOT/etc/rc.local busy ... waiting ..."
sleep 1
done
say "Setting up /etc/rc.local ..."
echo "$RCLOCAL" > "$SYSROOT/etc/rc.local"
chmod +x "$SYSROOT/etc/rc.local"
fi
set -u
-1
View File
@@ -18,7 +18,6 @@ test_setup()
test_teardown()
{
say "Test done $(date)"
run "rm -f $SYSROOT/etc/rc.local"
}
sleep 1
+1 -7
View File
@@ -22,12 +22,6 @@ test_teardown()
run "rm -f $FINIT_CONF"
}
check_restarts()
{
assert "serv restarts" "$(texec cat "$1" | awk '{print $1;}')" -ge "$2"
}
# shellcheck source=/dev/null
. "$TEST_DIR/lib/setup.sh"
@@ -43,7 +37,7 @@ test_one()
run "initctl reload"
# run "initctl status fail.sh"
assert_restart_cnt "$num/10" fail.sh
assert_restart_cnt "$num" "0/10" fail.sh
}
sep
+59
View File
@@ -0,0 +1,59 @@
#!/bin/sh
#
# Verifies that a run task can call initctl at bootstrap and runlevel 2,
# without blocking, performing serveral tasks:
#
# 1. Query running services
# 2. Stop/Start a running service
# 3. Restart a service
#
TEST_DIR=$(dirname "$0")
#DEBUG=1
# shellcheck disable=SC2034
BOOTSTRAP="run [S] /bin/ready.sh -- Ready steady go\n\
run [S] name:one initctl -- Query status\n\
service [S234] /sbin/service.sh -- Background service\n\
run [2] name:two initctl stop service.sh -- Stopping service\n\
run [3] name:three initctl start service.sh -- Starting service again\n\
run [4] name:four initctl restart service.sh -- Restart service"
# shellcheck disable=SC2034
RCLOCAL="echo \"$0 ==============\" &1>2"
# shellcheck source=/dev/null
. "$TEST_DIR/lib/setup.sh"
test_setup()
{
say "Test start $(date)"
}
test_teardown()
{
say "Test done $(date)"
}
say "$FINIT_CONF:"
run "cat $FINIT_CONF"
say "/etc/rc.local:"
run "cat /etc/rc.local"
say "Waiting for runlevel 2"
sleep 2
assert_status "service.sh" "stopped"
say "Changing to runlevel 3"
run "initctl runlevel 3"
sleep 2
assert_status "service.sh" "running"
run "initctl"
oldpid=$(texec initctl |awk '/service.sh/{print $1}')
say "Changing to runlevel 4"
run "initctl runlevel 4"
sleep 2
assert_status "service.sh" "running"
assert_pidiff "service.sh" "$oldpid"
run "initctl"
+41
View File
@@ -0,0 +1,41 @@
#!/bin/sh
#
# Verifies basic runparts behavior: to run all scripts in a directory at
# the end of system bootstrap, right before calling /etc/rc.local:
#
# - only executable scripts should be called
# - always in alphabetical order
# - SNNfoo should be called with a 'start' argument
# - KNNfoo should be called with a 'stop' argument
#
TEST_DIR=$(dirname "$0")
#DEBUG=1
# shellcheck disable=SC2034
BOOTSTRAP="runparts /etc/rc.d"
# shellcheck source=/dev/null
. "$TEST_DIR/lib/setup.sh"
test_setup()
{
say "Test start $(date)"
}
test_teardown()
{
say "Test done $(date)"
}
while true; do
lvl=$(texec initctl runlevel)
say "Current runlevel $lvl"
if [ "$lvl" = "N 2" ]; then
break;
fi
sleep 1
done
texec diff -u /usr/share/runlevel/ref.log /tmp/runparts.log
texec cmp /usr/share/runlevel/ref.log /tmp/runparts.log || fail "runparts in wrong order"
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
echo "$0 $*" >> /tmp/runparts.log
+1
View File
@@ -0,0 +1 @@
S01abc.sh
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
echo "$0" >> /tmp/runparts.log
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
echo "$0" >> /tmp/runparts.log
+4
View File
@@ -0,0 +1,4 @@
/etc/rc.d/S01abc.sh start
/etc/rc.d/S02def.sh start
/etc/rc.d/foo.sh
/etc/rc.d/test.sh
+13 -13
View File
@@ -21,7 +21,7 @@
#define err(rc, fmt, args...) do {fprintf(stderr, "%s: " fmt ": %s\n", ident, ##args, strerror(errno)); exit(rc);} while(0)
#define errx(rc, fmt, args...) do {fprintf(stderr, "%s: " fmt "\n", ident, ##args); exit(rc);} while(0)
#define log(fmt, args...) do {fprintf(stderr, "%s: " fmt "\n", ident, ##args);} while(0)
#define inf(fmt, args...) do {fprintf(stderr, "%s: " fmt "\n", ident, ##args);} while(0)
volatile sig_atomic_t reloading = 1;
volatile sig_atomic_t running = 1;
@@ -89,7 +89,7 @@ static void cleanup(void)
static void sig(int signo)
{
log("Got signal %d ...", signo);
inf("Got signal %d ...", signo);
switch (signo) {
case SIGHUP:
@@ -120,7 +120,7 @@ static int checkfn(char *fn)
static void mine(char *fn)
{
log("Mining for spice in %s", fn);
inf("Mining for spice in %s", fn);
writefn(fn, 4711);
}
@@ -136,11 +136,11 @@ static void pidfile(char *pidfn)
pid_t pid;
pid = getpid();
log("Creating PID file %s with %d", pidfn, pid);
inf("Creating PID file %s with %d", pidfn, pid);
writefn(pidfn, pid);
atexit(cleanup);
} else {
log("Touching PID file %s", pidfn);
inf("Touching PID file %s", pidfn);
utimensat(0, fn, NULL, 0);
}
}
@@ -265,27 +265,27 @@ int main(int argc, char *argv[])
}
if (do_crash) {
log("Simulating crash, exiting with code %d", EX_SOFTWARE);
inf("Simulating crash, exiting with code %d", EX_SOFTWARE);
exit(EX_SOFTWARE);
}
log("Entering while(1) loop");
inf("Entering while(1) loop");
while (running) {
if (spice) {
log("Checking for spice in %s ...", spice);
inf("Checking for spice in %s ...", spice);
if (!checkfn(spice))
err(1, "Melange");
}
if (reloading) {
if (do_notify > 0) {
log("Delaying notify by 3 seconds for notify.sh ...");
inf("Delaying notify by 3 seconds for notify.sh ...");
sleep(3);
log("Notifying Finit on socket %d, READY=1", do_notify);
inf("Notifying Finit on socket %d, READY=1", do_notify);
if (write(do_notify, "READY=1\n", 8) == -1)
err(1, "Failed sending ready notification to Finit");
if (notify_s6) {
log("s6 notify, closing socket %d ...", do_notify);
inf("s6 notify, closing socket %d ...", do_notify);
close(do_notify);
do_notify = 0;
}
@@ -305,12 +305,12 @@ int main(int argc, char *argv[])
if (melange && vanish++ > 0) {
if (checkfn(melange)) {
log("Oh no, sandworms! Harvest interrupted ...");
inf("Oh no, sandworms! Harvest interrupted ...");
remove(melange);
}
}
}
log("Leaving ...");
inf("Leaving ...");
return 0;
}