Browse Source

Add daemon support to init(8).

This change implements a dependency tracking daemon(7) system in init with
overridable init(5) configuration, parallel startup, readiness signaling,
rotating logs, reliable stopping, and handling of leaked processes.

The /etc/init/target file is replaced by the new /etc/init/default per the
new init(5) format. The old configuration is migrated upon upgrade using an
upgrade hook.

extfs(8) now signals readiness using READYFD for fast mounting.

Filesystems that fail to be repaired are now mounted read-only.

The mounting and filesystem checking code is synchronized with sysinstall.

The duplicated array_add utility function now protects against overflows.

tix-iso-bootconfig(8) gains the --init-target option.

tix-iso-liveconfig(8) gains the --daemons option.
master
Jonas 'Sortie' Termansen 4 months ago
parent
commit
f2d50bbf9c
  1. 2
      Makefile
  2. 14
      ext/fsmarshall.cpp
  3. 256
      init/init.8
  4. 3372
      init/init.c
  5. 15
      libmount/util.h
  6. 15
      sh/util.c
  7. 1
      share/init/base
  8. 0
      share/init/local
  9. 8
      share/init/multi-user
  10. 2
      share/init/no-user
  11. 9
      share/init/single-user
  12. 8
      share/init/sysinstall
  13. 8
      share/init/sysupgrade
  14. 0
      share/init/time
  15. 587
      share/man/man5/init.5
  16. 113
      share/man/man7/daemon.7
  17. 15
      share/man/man7/following-development.7
  18. 4
      share/man/man7/portability.7
  19. 0
      share/sysinstall/hooks/sortix-1.1-init
  20. 1
      sysinstall/Makefile
  21. 155
      sysinstall/devices.c
  22. 39
      sysinstall/hooks.c
  23. 3
      sysinstall/sysinstall.c
  24. 10
      tix/tix-iso-bootconfig
  25. 18
      tix/tix-iso-bootconfig.8
  26. 14
      tix/tix-iso-liveconfig
  27. 10
      tix/tix-iso-liveconfig.8
  28. 8
      update-initrd/update-initrd

2
Makefile

@ -441,7 +441,7 @@ $(LIVE_INITRD): sysroot
mkdir -p $(LIVE_INITRD).d
mkdir -p $(LIVE_INITRD).d/etc
mkdir -p $(LIVE_INITRD).d/etc/init
echo single-user > $(LIVE_INITRD).d/etc/init/target
echo require single-user exit-code > $(LIVE_INITRD).d/etc/init/default
echo "root::0:0:root:/root:sh" > $(LIVE_INITRD).d/etc/passwd
echo "root::0:root" > $(LIVE_INITRD).d/etc/group
mkdir -p $(LIVE_INITRD).d/home

14
ext/fsmarshall.cpp

@ -720,6 +720,18 @@ void TerminationHandler(int)
should_terminate = true;
}
static void ready(void)
{
const char* readyfd_env = getenv("READYFD");
if ( !readyfd_env )
return;
int readyfd = atoi(readyfd_env);
char c = '\n';
write(readyfd, &c, 1);
close(readyfd);
unsetenv("READYFD");
}
int fsmarshall_main(const char* argv0,
const char* mount_path,
bool foreground,
@ -756,6 +768,8 @@ int fsmarshall_main(const char* argv0,
exit(0);
setpgid(0, 0);
}
else
ready();
dev->SpawnSyncThread();

256
init/init.8

@ -6,16 +6,27 @@
.Nd system initialization
.Sh SYNOPSIS
.Nm init
.Op Fl \-target Ns "=" Ns Ar init-target
.Op Fl qsv
.Op Fl \-target Ns "=" Ns Ar default-daemon
.Op Fl \-
.Op Ar chain-init ...
.Sh DESCRIPTION
.Nm
is the first program run after system startup and is responsible for
initializing the operating system and starting the specified
.Ar init-target .
This is normally a login screen, a root shell, or a dedicated special purpose
program.
initializing the operating system.
.Pp
Each
.Xr daemon 7
is started in order as its dependencies become ready per its
.Xr init 5
configuration.
The
.Sy default
daemon is automatically started and its recursive dependencies constitute the
operating system.
The
.Sy default
daemon's single dependency is referred to as the target.
.Pp
The
.Xr kernel 7
@ -34,56 +45,30 @@ If the system is installed on a harddisk, then the initrd is a minimal system
made with
.Xr update-initrd 8
that will search for the actual root filesystem and chain init it.
The next stage init will recognize it as the intended system and complete the
system startup.
.Ss Initialization Target
.Nm
first determines its target from the
.Fl \-target
option if specified or
.Pa /etc/init/target
otherwise.
Supported targets are:
The next stage init will recognize itself as the intended system and complete
the system startup.
.Pp
.Bl -tag -width "single-user" -compact -offset indent
.It Sy chain
mount real root filesystem and run its
.Nm .
.It Sy chain-merge
like
.Sy chain
but run
.Pa /sysmerge/sbin/init
with the
.Sy merge
target.
.It Sy merge
finish a
.Xr sysmerge 8
upgrade and then execute the real
.Nm
with its default target.
.It Sy multi-user
boot to
.Xr login 8 .
.It Sy single-user
boot to root shell without password (not secure).
.It Sy sysinstall
boot to operating system installer (not secure).
.It Sy sysupgrade
boot to operating system upgrader (not secure).
The options are as follows:
.Bl -tag -width "12345678"
.It Fl q , \-quiet
Write status updates to the terminal only about failed daemons.
This behavior is the default.
.It Fl s , \-silent
Never write status updates about daemons to the terminal.
.It Fl t , \-target Ns "=" Ns Ar default-daemon
Boot
.Ar default-daemon
as the target.
The
.Sy default
daemon configuration is changed to only require the
.Ar default-daemon
dependency with the
.Sy exit-only
flag.
.It Fl v , \-verbose
Write all status updates about daemons starting and stopping to the terminal
.El
.Pp
It is a full system compromise if unauthenticated users are able to boot the
wrong target.
The kernel command line can specify the path to
.Nm
and its arguments.
Unprivileged users can change the kernel command line from the bootloader
command line if it hasn't been password protected.
Likewise unprivileged users can use their own replacement bootloader by booting
a portable device under their control if the firmware configuration has not been
password protected.
.Ss Cleanup of /tmp and /var/run
.Nm
deletes everything inside of
@ -104,23 +89,24 @@ will scan every block device for valid partition tables and create the
corresponding partition devices in
.Pa /dev .
.Ss Chain Initialization
The
If the target is
.Sy chain
target mounts the root filesystem as in
or
.Sy chain-merge ,
then the real operating system is chain initialized.
.Pp
The root filesystem is mounted per
.Pa /etc/fstab
(see
.Xr fstab 5 )
and runs the next
.Nm
program.
This is used by
.Xr fstab 5 ) .
This configuration file is a copy of the real file made by
.Xr update-initrd 8
to make a bootstrap
when it makes the bootstrap
.Xr initrd 7 .
.Pp
Every block device and partition is scanned to determine if it is the root
filesystem.
It is checked for consistency if necessary.
The root filesystem is found by searching each block device and partition.
It is checked for consistency if necessary and mounted read-only if the check
fails.
It is mounted at
.Pa /tmp/fs.XXXXXX
and the
@ -133,6 +119,34 @@ Finally the
program (or
.Ar chain-init
if specified) of the target root filesystem is run inside a chroot.
If the target is
.Sy chain-merge ,
then the
.Fl \-target=merge
option is passed to the next
.Nm .
.Ss Mountpoints
.Nm
mounts all the filesystems according to
.Xr fstab 5 .
The filesystems are checked for consistency if necessary and mounted read-only
if the check fails.
.Ss Logging
Logging to
.Pa /var/log
begins once the filesystems are mounted and
.Nm
writes the log entries from early boot to its
.Pa /var/log/init.log .
.Ss Random Seed
.Nm
will write 256 bytes of randomness to
.Pa /boot/random.seed ,
which serves as the initial entropy for the
.Xr kernel 7
on the next boot.
The file is also written on system shutdown where the system has the most
entropy.
.Ss Configuration
Once the
.Nm
@ -150,46 +164,52 @@ set keyboard layout (see
set graphics resolution (see
.Xr videomode 5 )
.El
.Ss Mountpoints
.Nm
mounts all the filesystems according to
.Xr fstab 5 .
.Ss Random Seed
.Nm
will write 256 bytes of randomness to
.Pa /boot/random.seed ,
which serves as the initial entropy for the
.Xr kernel 7
on the next boot.
The file is also written on system shutdown where the system has the most
entropy.
.Ss Merge
The
.Sy merge
target completes a delayed system upgrade by invoking the
If the target is
.Sy merge ,
then a delayed system upgrade is completed by invoking
.Xr sysmerge 8
at
.Pa /sysmerge/sbin/sysmerge
with the
.Ar --booting
option.
.Pp
If the upgrade succeeds, the temporary
.Nm
.Pa /sysmerge/sbin/init
deinitializes the system and invokes the real (now upgraded)
.Nm
.Pa /sbin/init ,
which will restart system initialization in the normal fashion.
.Ss Session
Finally
.Nm
will start the target program according to its initialization target.
This will be a login screen, a root shell, or something else.
If the process exits abnormally
.Nm
will automatically restart it.
.Ss Daemons
The
.Sy default
.Xr daemon 7
is started per its
.Pa /etc/init/default
.Xr init 5
configuration file, which constitutes the operating system, and once it exits
then
.Nm
will exit with the same exit status as the process if it exits normally.
The kernel decides whether to power off, reboot or halt based on this exit
status.
exits with the same error code and the kernel shuts down the machine.
The
.Sy default
daemon is meant to be a virtual daemon depending on a single top level daemon
(the target), which provide the desired operating system functionality
(e.g. booting to a single user shell or a multi user login screen).
.Pp
The daemons are configured per
.Xr init 5
where
.Pa /etc/init
contains the installation's local configuration, which overrides the operating
system's default configuration in
.Pa /share/init .
The daemons are started in order as their dependencies become ready and are
stopped in order when they are no longer required.
.Pp
The
.Sy local
daemon is meant to start the installation's local daemon requirements.
.Sh ENVIRONMENT
.Nm
sets the following environment variables.
@ -213,21 +233,44 @@ root
.Sh FILES
.Bl -tag -width "/boot/random.seed" -compact
.It Pa /boot/random.seed
initial kernel entropy
.It Pa /etc/init/target
default initialization target
Initial kernel entropy
.It Pa /etc/init/
Daemon configuration for the local system (first in search path) (see
.Xr init 5 )
.It Pa /etc/init/default
Configuration for the default daemon (see
.Xr init 5 )
.It Pa /etc/fstab
filesystem table (see
Filesystem table (see
.Xr fstab 5 )
.It Pa /etc/hostname
hostname (see
Hostname (see
.Xr hostname 5 )
.It Pa /etc/kblayout
keyboard layout (see
Keyboard layout (see
.Xr kblayout 5 )
.It Pa /etc/videomode
graphics resolution (see
Graphics resolution (see
.Xr videomode 5 )
.It Pa /share/init/
Default daemon configuration provided by the operating system (second in
search path) (see
.Xr init 5 )
.It Pa /var/log/
Daemon log files (see
.Xr init 5 )
.It Pa /var/log/init.log
.Nm Ns 's
own log.
.El
.Sh ASYNCHRONOUS EVENTS
.Bl -tag -width "SIGUSR1"
.It Dv SIGTERM
Request system poweroff.
.It Dv SIGINT
Request system reboot.
.It Dv SIGQUIT
Request system halt.
.El
.Sh EXIT STATUS
.Nm
@ -243,10 +286,23 @@ exits with the same exit status as its target session if it terminates normally.
.Sh SEE ALSO
.Xr fstab 5 ,
.Xr hostname 5 ,
.Xr init 5 ,
.Xr kblayout 5 ,
.Xr videomode 5 ,
.Xr daemon 7 ,
.Xr initrd 7 ,
.Xr kernel 7 ,
.Xr login 8 ,
.Xr sysmerge 8 ,
.Xr update-initrd 8
.Sh SECURITY CONSIDERATIONS
It is a full system compromise if unauthenticated users are able to boot the
wrong target.
The kernel command line can specify the path to
.Nm
and its arguments.
Unprivileged users can change the kernel command line from the bootloader
command line if it hasn't been password protected.
Likewise unprivileged users can use their own replacement bootloader by booting
a portable device under their control if the firmware configuration has not been
password protected.

3372
init/init.c

File diff suppressed because it is too large Load Diff

15
libmount/util.h

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -47,18 +47,15 @@ static bool array_add(void*** array_ptr,
if ( *used_ptr == *length_ptr )
{
// TODO: Avoid overflow.
size_t new_length = 2 * *length_ptr;
if ( !new_length )
new_length = 16;
// TODO: Avoid overflow and use reallocarray.
size_t new_size = new_length * sizeof(void*);
void** new_array = (void**) realloc(array, new_size);
size_t length = *length_ptr;
if ( !length )
length = 4;
void** new_array = reallocarray(array, length, 2 * sizeof(void*));
if ( !new_array )
return false;
array = new_array;
memcpy(array_ptr, &array, sizeof(array)); // Strict aliasing.
*length_ptr = new_length;
*length_ptr = length * 2;
}
memcpy(array + (*used_ptr)++, &value, sizeof(value)); // Strict aliasing.

15
sh/util.c

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -49,18 +49,15 @@ bool array_add(void*** array_ptr,
if ( *used_ptr == *length_ptr )
{
// TODO: Avoid overflow.
size_t new_length = 2 * *length_ptr;
if ( !new_length )
new_length = 16;
// TODO: Avoid overflow and use reallocarray.
size_t new_size = new_length * sizeof(void*);
void** new_array = (void**) realloc(array, new_size);
size_t length = *length_ptr;
if ( !length )
length = 4;
void** new_array = reallocarray(array, length, 2 * sizeof(void*));
if ( !new_array )
return false;
array = new_array;
memcpy(array_ptr, &array, sizeof(array)); // Strict aliasing.
*length_ptr = new_length;
*length_ptr = length * 2;
}
memcpy(array + (*used_ptr)++, &value, sizeof(value)); // Strict aliasing.

1
share/init/base

@ -0,0 +1 @@
require time optional

0
share/init/local

8
share/init/multi-user

@ -0,0 +1,8 @@
require base no-await
require local no-await
tty tty1
need tty
exit-code-meaning poweroff-reboot
exec login

2
share/init/no-user

@ -0,0 +1,2 @@
require base no-await
require local no-await exit-code

9
share/init/single-user

@ -0,0 +1,9 @@
require base no-await
require local no-await
tty tty1
need tty
cd "$HOME"
exit-code-meaning poweroff-reboot
exec "$SHELL"

8
share/init/sysinstall

@ -0,0 +1,8 @@
require base no-await
require local no-await
tty tty1
need tty
exec sysinstall
exit-code-meaning poweroff-reboot

8
share/init/sysupgrade

@ -0,0 +1,8 @@
require base no-await
require local no-await
tty tty1
need tty
exec sysupgrade
exit-code-meaning poweroff-reboot

0
share/init/time

587
share/man/man5/init.5

@ -0,0 +1,587 @@
.Dd July 29, 2018
.Dt INIT 5
.Os
.Sh NAME
.Nm init
.Nd system initialization configuration
.Sh SYNOPSIS
.Nm /etc/init/
.Nm /share/init/
.Sh DESCRIPTION
.Xr init 8
starts each
.Xr daemon 7
(system background process) according to the daemon's
configuration file, which specifies the daemon's dependencies and how to run the
daemon.
.Pp
Configuration files are searched for in the
.Pa /etc/init/
directory (local initialization configuration owned by the system administrator,
which may be modified) and the
.Pa /share/init/
directory (default initialization configuration owned by the operating system,
which should not be modified).
The file name of each configuration file is that of the daemon without any file
extension.
For instance, the daemon
.Sy exampled
might come with the default configuration file
.Pa /share/init/exampled
that the system administrator can override in
.Pa /etc/init/exampled .
.Pp
.Xr init 8
initially starts the
.Sy default
daemon which is configured in
.Pa /etc/init/default ,
which then depends on the daemons constituting the operating system (which in
turn depend on the
.Sy local
daemon).
The
.Pa /etc/init/default
file also defines default settings such as logging that are implicitly inherited
by all other deamons, as well as
.Xr init 8 Ns 's
own
.Pa /var/log/init.log
file.
.Pp
Local system daemons should be started by overriding the
.Sy local
daemon in
.Pa /etc/init/local ,
which then depends on the locally required daemons.
System provided daemons can be customized by making
.Pa /etc/init/exampled
which starts with the
.Sy furthermore
statement to include the default
.Pa /etc/share/exampled
configuration and then change the desired properties.
.Sh DAEMONS
The
.Sy default
daemon should
.Sy require
exactly one top level daemon with
.Sy exit-code
and nothing else.
.Pp
The following daemons are top level daemons that start the operating system.
They are mutually exclusive and only a single one should be depended on:
.Bl -tag -width "12345678"
.It Sy multi-user
Starts the operating system in the multi-user mode.
It starts the
.Sy login
foreground daemon that provides a login screen and exits with login's exit code
when login exits.
This is a secure operating system mode where only authorized users have access.
It depends on the
.Sy base
and
.Sy local
daemons.
.It Sy no-user
Starts the operating system in the no-user mode.
This is a secure operating system mode where no user is granted access.
Additional daemons can be started by configuring the
.Sy local
daemon.
It depends on the
.Sy base
and
.Sy local
daemons.
The dependency on
.Sy local
is marked
.Sy exit-code ,
letting the system administrator fully control the
.Sy default
daemon's exit code and when the system completes.
.It Sy single-user
Starts the operating system in the single user mode.
This foreground daemon starts the
.Sy sh
program that directly provides a root shell and exits with the shell's exit code
when the shell exits.
This operating system mode is insecure because it boots straight to root access
without a password.
It depends on the
.Sy base
and
.Sy local
daemons.
.It Sy sysinstall
Starts the operating system installer.
This foreground daemon starts the
.Sy sysinstall
program that provides the operating system installer and exits with the
installer's exit code when the installer exits.
This operating system mode is insecure because it boots straight to root access
without a password.
It depends on the
.Sy base
and
.Sy local
daemons.
.It Sy sysupgrade
Starts the operating system upgrader.
This foreground daemon starts the
.Sy sysupgrade
program that provides the operating system upgrader and exits with the
upgrader's exit code when the upgrader exits.
This operating system mode is insecure because it boots straight to root access
without a password.
It depends on the
.Sy base
and
.Sy local
daemons.
.El
.Pp
The following daemons are provided by the system:
.Bl -tag -width "12345678"
.It Sy base
Virtual daemon that depends on the core operating system daemons.
It depends on the
.Sy time
daemon.
.It Sy local
Virtual daemon that starts daemons pertinent to the local system.
The system provides a default implementation that does nothing.
The system administrator is meant to override the daemon in
.Pa /etc/init/local
by depending on daemons outside of the base system that should run on the local
system.
.It Sy time
Virtual daemon that becomes ready when the current date and time has been
established.
The system provides a default implementation that does nothing, as the base
system does not contain a daemon that obtains the current date and time.
The system administrator is meant to override the daemon in
.Pa /etc/init/time
by depending on a daemon that obtains the current date and time and sets the
system time.
Daemons can depend on this daemon if they need the current date and time to have
been established before they start.
.El
.Sh FORMAT
Daemon configuration files are processed line by line.
Each line specifies a property of the daemon.
Lines are tokenized like shell commands on white space with support for single
qoutes, double quotes, and backslash escape sequences (\\\\, \\', \\", \\a, \\b,
\\e, \\f, \\n, \\r, \\t, \\v).
The # character starts a comment and the rest of the line is ignored.
.Bl -tag -width "12345678"
.It Sy cd Ar directory
The working directory to run the deamon inside.
(Default is
.Pa / )
.It Sy exec Ar command
The command line that starts the daemon.
The daemon becomes ready when it writes
a newline to the file descriptor mentioned in the
.Ev READYFD
environment variable as described in
.Xr daemon 7 .
.Pp
If this property isn't specified, then the daemon is a virtual daemon.
Virtual deamons become ready when all their dependencies are ready and finish
when all their dependencies are finished.
Virtual daemons exit 0 (success) if every dependency finished successfully,
otherwise they exit 3 (failed).
.It Sy exit-code-meaning Oo Sy default "|" poweroff-reboot Oc
This property specifies how to interpret the exit code.
.Pp
The
.Sy default
meaning is that exiting 0 is successful.
Any other exit means the daemon failed.
.Pp
The
.Sy poweroff-reboot
meaning is that exiting 0 means the system should power off, exiting 1 means the
system should reboot, exiting 2 means the system should halt, and any other exit
means the daemon failed.
.Pp
Daemons are considered successful if they exit by
.Sy SIGTERM
if
.Xr init 8
stopped the daemon by sending
.Sy SIGTERM.
.It Sy furthermore
The current daemon configuration file extends an existing daemon that is defined
in a configuration file by the same name later in the search path.
The later configuration file is included into the current configuration file.
This statement can only be used once per configuration file, any subsequent uses
are silently ignored, but it can be used recursively.
Customizing an existing daemon should be done by adding a new daemon file
earlier in the search path that starts with the
.Sy furthermore
statement, followed by additional configuration.
.Pp
This is not a property and cannot be
.Sy unset .
.It Sy log-control-messages Oo Sy false "|" true Oc
Includes control messages such as the start and stop of the daemon and loss of
log data.
Control messages are inserted as entries from the daemon
.Sy init .
.Pp
The default is
.Sy true
and is
inherited from the
.Sy default
deamon.
.It Sy log-file-mode Ar octal
Sets the log file permissions to the
.Ar octal
mode with
.Xr chmod 2 .
.Pp
The default value is
.Sy 644
and is inherited from the
.Sy default
deamon.
.It Sy log-format Ar format
Selects the
.Ar format
of the log:
.Bl -tag -width "nanoseconds"
.It Sy none
The log is exactly as written by the daemon with no additional formatting.
.It Sy seconds
"YYYY-dd-mm HH:MM:SS +0000: "
.Pp
Each line is prefixed with a timestamp with second precision and the timezone
offset.
.It Sy nanoseconds
"YYYY-dd-mm HH:MM:SS.nnnnnnnnn +0000: "
.Pp
Each line is prefixed with a timestamp with nanosecond precision and the
timezone offset.
.It Sy basic
"YYYY-dd-mm HH:MM:SS.nnnnnnnnn +0000 daemon: "
.Pp
Each line is prefixed with a timestamp with nanosecond precision and the
timezone offset followed by the name of the daemon.
.It Sy full
"YYYY-dd-mm HH:MM:SS.nnnnnnnnn +0000 hostname daemon: "
.Pp
Each line is prefixed with a timestamp with nanosecond precision and the
timezone offset followed
by the hostname and name of the daemon.
.It Sy syslog
"<ppp>1 YYYY-dd-mmTHH:MM:SS.uuuuuuZ hostname daemon pid - - "
.Pp
Each line is prefixed in the RFC 5424 syslog version 1 format with the priority,
the timestamp with microsecond precision and the timezone offset, the hostname,
the daemon name, and the process id.
.El
.Pp
The default format is
.Sy nanoseconds
and is inherited from the
.Sy default
deamon.
.It Sy log-line-size Ar line-size
When using the
.Sy rotate
log method, log files are cut at newlines if the lines don't exceed
.Ar line-size
bytes.
.Pp
The default value is 4096 bytes and is inherited from the
.Sy default
deamon.
.It Sy log-method Oo Sy none "|" append "|" rotate Oc
Selects the method for logging:
.Bl -tag -width "12345678"
.It Sy none
Disable logging.
.It Sy append
Always append the log data to the log file without any rotation.
For instance,
.Pa exampled.log
will contain all the log entries ever produced by the
.Sy exampled
daemon.
.Pp
This method does not lose log data but it will fail when filesystem space is
exhausted.
.It Sy rotate
Append lines to the log file until it becomes too large, in which case the
daemon's logs are rotated.
.Pp
Rotation is done by deleting the oldest log (if there are too many), each of the
remaining log files are renamed with the subsequent number, and a new log file
is begun.
The logs are cut on a newline boundary if the lines doesn't exceed
.Sy log-line-size .
.Pp
For instance,
.Pa exampled.log.2
is deleted,
.Pa exampled.log.1
becomes
.Pa exampled.log.2 ,
.Pa exampled.log.1
becomes
.Pa exampled.log.2 ,
and a new
.Pa exampled.log
is begun.
.Pp
This method will lose old log data.
.El
.Pp
The default format is
.Sy rotate
and is inherited from the
.Sy default
deamon.
.It Sy log-rotate-on-start Oo Sy false "|" true Oc
When starting the daemon, rotate the logs (when using the
.Sy rotate
log method) or empty the log (when using the
.Sy append
log method), such that the daemon starts out with a new log.
.Pp
The default value is
.Sy false
and is inherited from the
.Sy default
deamon.
.It Sy log-size Ar size
When using the
.Sy rotate
log method, keep each log file below
.Ar size
bytes.
.Pp
The default value is 1048576 bytes and is inherited from the
.Sy default
deamon.
.It Sy need tty
Specifies that the daemon is not a background daemon, but instead is the
foreground daemon controlling the terminal in the
.Sy tty
property.
The daemon is made a process group leader.
The terminal's foreground process group is set to that of the daemon.
The terminal is enabled by setting
.Sy CREAD .
The daemon is not logged, and the standard input, output, and error are instead
connected to the terminal
Foreground daemons are automatically considered ready and don't participate in
the
.Ev READYFD
daemon readiness protocol.
Upon exit, the original terminal settings are restored and
.Xr init 8
reclaims ownership of the terminal.
.It Sy require Ar dependency Oo Ar flag ... Oc
When the daemon is needed, start the
.Ar dependency
first.
The daemon starts when all its dependencies have become ready or have finished.
Dependencies are started in parallel whenever possible.
If the daemon hasn't started yet, and any non-optional dependency finishes
unsuccessfully, then the daemon doesn't start and instead directly finishes
unsuccessfully.
If the daemon has started, it is the daemon's responsibility to detect failures
in its dependencies.
.Pp
The dependency can be customized with zero or more flags:
.Bl -tag -width "12345678"
.It Sy exit-code
If the daemon is a virtual daemon, then the daemon's exit code is that of the
specific
.Ar dependency
rather than whether all dependencies succeeded.
The daemon exits as soon as the
.Ar dependency
exits, rather than waiting for all dependencies to exit.
The
.Sy exit-code-meaning
field is set to that of the dependency.
.Sy exit-code
can at most be used on a single dependency for a daemon.
.It Sy no-await
Don't wait for the
.Ar dependency
to become ready before starting this daemon.
This flag is meant for dependencies that the daemon can make use of, but isn't
essential to the daemon itself becoming ready.
It shouldn't be used if the daemon polls for the the dependency to come online,
as it is more efficient to only start the daemon once the dependency is ready.
.It Sy optional
Start the daemon even if the
.Ar dependency
fails.
The dependency is assumed to exist and a warning occurs if it doesn't exist.
.El
.Pp
Dependencies can be forgotten using
.Sy unset require Ar dependency .
Flags on a dependency can be be unset using
.Sy unset require Ar dependency flag ... .
.It Sy unset Ar property
Reset the given property to its default value.
.It Sy tty Ar device
If the daemon is a foreground daemon
.Sy ( need tty
is set), then connect the daemon to the terminal named
.Ar device .
.Pp
The default value is the terminal
.Xr init 8
is attached to, usually
.Pa tty1 .
.El
.Sh ENVIRONMENT
Daemons inherit their environment from
.Xr init 8
with this additional environment:
.Bl -tag -width "READYFD"
.It Ev READYFD
Daemons signal they are ready by writing a newline to the file descriptor
mentioned in the
.Ev READYFD
environment variable as described in
.Xr daemon 7 .
.El
.Sh FILES
.Bl -tag -width /share/init/default -compact
.It Pa /etc/init/
Daemon configuration for the local system (first in search path).
.It Pa /etc/init/default
The configuration file for the
.Sy default
daemon.
.It Pa /etc/init/local
The configuration file for the
.Sy local
daemon which depends on the installation's local daemons.
.It Pa /share/init/
Default daemon configuration provided by the operating system (second in search
path).
.It Pa /var/log/
Daemon log files.
.El
.Sh EXAMPLES
.Ss Configuring a daemon to start on boot
The local system can be configured to start the
.Sy exampled
daemon by creating
.Pa /etc/init/local
with the following contents:
.Bd -literal
require exampled optional
.Ed
.Pp
Additional lines can be included for any daemon you wish to start.
The
.Sy optional
flag means the
.Sy local
daemon doesn't fail if the daemon fails.
The top level daemons
.Sy ( multi-user , single-user , ... )
fails if the
.Sy local
daemon fails, which will shut down the operating system.
The
.Sy optional
flag should only be omitted if a local daemon is critical and the boot should
fail if the daemon fails.
.Ss Creating a new virtual daemon
The
.Sy exampled
daemon, which depends on the
.Sy food , bard ,
and
.Sy quxd
daemons and whose program file is called
.Pa exampled ,
can then be configured by creating
.Pa /etc/init/exampled
with the following contents:
.Bd -literal
require food
require bard
require quxd
exec exampled
.Ed
.Ss Changing the log format
The default log format of daemons and
.Xr init 8 Ns 's
own can be set by setting the properties in
.Pa /etc/init/default .
A few examples:
.Bd -literal
log-format full
log-method append
.Ed
.Pp
Uses the
.Sy full
log format and grows the log without limit, never losing data unless the
filesystem space is exhausted.
.Bd -literal
log-control-messages false
log-format none
log-method rotate
log-rotate-on-start true
.Ed
.Pp
Provides plain rotated log files, by disabling control messages from
.Xr init 8
about starting/stopping the daemon, turning off log metadata, and also rotates
the log when the deamon is started.
.Ss Configuring a multi-user system
The system can be configured to boot into multi-user mode by creating
.Pa /etc/init/default
with the following contents:
.Bd -literal
require multi-user exit-code
.Ed
.Ss Configuring an unattended system
A fully unattended system that only starts the base system and the
.Sy exampled
daemon, shutting down when the
.Sy exampled
daemon finishes, can be done by first creating
.Pa /etc/init/default
with the following contents:
.Bd -literal
require no-user exit-code
.Ed
.Pp
And then secondly creating
.Pa /etc/init/local
with the following contents:
.Bd -literal
require exampled exit-code
.Ed
.Sh SEE ALSO
.Xr daemon 7 ,
.Xr init 8
.Sh BUGS
The control messages mentioned in
.Sy log-control-messages
aren't implemented yet.
.Pp
The
.Sy tty
property isn't implemented yet and must be
.Pa tty1
if set.

113
share/man/man7/daemon.7

@ -0,0 +1,113 @@
.Dd September 19, 2022
.Dt DAEMON 7
.Os
.Sh NAME
.Nm daemon
.Nd system background process
.Sh DESCRIPTION
A daemon is a system background process that performs a task or continuously
provides a service.
.Xr init 8
starts daemons on system startup per the
.Xr init 5
configuration and stops them on system shutdown.
.Pp
Conventions for daemons have varied between traditional init systems and this
document describes the modern design of daemons suitable for this operating
system.
Daemons should default to the behavior described below, or offer the behavior
through options if they need to be compatible with historic default behavior.
.Pp
A daemon is implemented as a system program, usually in
.Pa /sbin
inside the appropriate prefix,
whose name conventionally is the name of the service it implements plus the
letter d (as opposed to a client program).
Its runtime dependencies on other daemons are declared ahead of time in the
init system's configuration, so the daemons can be started in the right order.
.Pp
The process will be started per the init system's configuration with the
appropriate command line arguments, environment variables, working directory,
user, group, and so on.
.Pp
The process must remain in the foreground such that
.Xr init 8
can manage its lifetime.
It must not
.Xr fork 2
to become a background process and escape
the init system.
The process should have no need to escape the controlling terminal by starting a
new session using
.Xr setsid 2 .
Daemons should not write a pid file but instead be administered through the init
system.
.Pp
Logs should be written to the standard error as it is non-buffered and is meant
to contain messages that are not process output.
Alternatively logs may be written to the standard output.
The standard output may be the same file description as the standard error.
The standard input should not be used and will be
.Pa /dev/null .
The log entries should be formatted as plain line; or if the program wants to
supply additional meta data, one of the log formats described in
.Xr init 5
or the syslog format.
.Xr syslog 3
is discouraged but may be used if the program has additional meta data.
On this operating system,
.Xr syslog 3
will write the log entry to the standard error instead of sending it to a
centralized log service, which is unreliable on other operating systems.
Daemons should prefer letting the init system manage the log files but may
provide their own logging as appropriate.
.Pp
The process may be executed as root per the init system configuration.
Privileges should be dropped after acquiring the needed protected resources.
The main loop should run with the least privileges required, ideally as another
user, potentially in a
.Xr chroot 2
or sandboxed environment.
.Pp
Continuous daemons providing a service should signal their readiness once the
main loop is serving requests, such that the init system will start dependent
daemons.
Unfortunately there is no portable convention and this operating system uses the
.Ev READYFD
environment variable containing a file descriptor pointing to a writing pipe,
where the daemon must write a newline upon readiness.
Alternatively closing the pipe is considered readiness as a discouraged
fallback.
.Pp
The process must exit 0 if the daemon has concluded its work and exit non-zero
in the case of errors.
The daemon may be restarted by the init system
upon error per the configuration.
.Pp
The process must exit unconditionally when sent
.Dv SIGTERM
and should gracefully conclude its work immediately and recursively terminate
any child processes.
In this case, dying by the
.Dv SIGTERM
signal is considered a successful exit.
The process is killed with
.Dv SIGKILL
if it does not gracefully terminate within a high system-specific timeout.
.Sh EXAMPLES
A daemon can signal readiness using this utility function:
.Bd -literal -offset indent
static void ready(void) {
const char *readyfd_env = getenv("READYFD");
if ( !readyfd_env )
return;
int readyfd = atoi(readyfd_env);
char c = '\n';
write(readyfd, &c, 1);
close(readyfd);
unsetenv("READYFD");
}
.Ed
.Sh SEE ALSO
.Xr init 5 ,
.Xr init 8

15
share/man/man7/following-development.7

@ -69,6 +69,21 @@ releasing Sortix x.y, foo." to allow the maintainer to easily
.Xr grep 1
for it after a release.
.Sh CHANGES
.Ss Add daemon support to init(8)
.Xr init 8
has gained
.Xr daemon 7
support with the new
.Xr init 5
configuration format.
.Pp
The old
.Pa /etc/init/target
configuration file is replaced by the
.Sy default
daemon in
.Pa /etc/init/default .
An upgrade hook will migrate the configuration.
.Ss Add ports to the Sortix repository
The ports have been moved from the porttix/srctix repositories into the
.Pa ports/

4
share/man/man7/portability.7

@ -46,7 +46,9 @@ is the modern replacement with nanosecond precision.
is the standard replacement.
.Ss daemon
Daemons should not background by double forking but rather stay in the
foreground and be managed by
foreground as described in
.Xr daemon 7
and be managed by
.Xr init 8 .
.Ss __dead
.Dv noreturn

0
share/sysinstall/hooks/sortix-1.1-init

1
sysinstall/Makefile

@ -54,6 +54,7 @@ install: all
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-random-seed
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-tix-manifest-mode
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-leaked-files
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-init
sysinstall: $(SYSINSTALL_OBJS)
$(CC) $(SYSINSTALL_OBJS) -o $@ -lmount

155
sysinstall/devices.c

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, 2021 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2016, 2021, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -198,25 +198,29 @@ bool fsck(struct filesystem* fs)
const char* bdev_path = path_of_blockdevice(fs->bdev);
printf("%s: Repairing filesystem due to inconsistency...\n", bdev_path);
assert(fs->fsck);
pid_t child_pid = fork();
if ( child_pid < 0 )
pid_t pid = fork();
if ( pid < 0 )
{
warn("%s: Mandatory repair failed: fork", bdev_path);
return false;
}
if ( child_pid == 0 )
if ( pid == 0 )
{
execlp(fs->fsck, fs->fsck, "-fp", "--", bdev_path, (const char*) NULL);
warn("%s: Failed to load filesystem checker: %s", bdev_path, fs->fsck);
_Exit(127);
}
int code;
if ( waitpid(child_pid, &code, 0) < 0 )
{
if ( waitpid(pid, &code, 0) < 0 )
warn("waitpid");
return false;
else if ( WIFEXITED(code) &&
(WEXITSTATUS(code) == 0 || WEXITSTATUS(code) == 1) )
{
// Successfully checked filesystem.
fs->flags &= ~(FILESYSTEM_FLAG_FSCK_SHOULD | FILESYSTEM_FLAG_FSCK_MUST);
return true;
}
if ( WIFSIGNALED(code) )
else if ( WIFSIGNALED(code) )
warnx("%s: Mandatory repair failed: %s: %s", bdev_path,
fs->fsck, strsignal(WTERMSIG(code)));
else if ( !WIFEXITED(code) )
@ -228,14 +232,9 @@ bool fsck(struct filesystem* fs)
else if ( WEXITSTATUS(code) & 2 )
warnx("%s: Mandatory repair: %s: %s", bdev_path,
fs->fsck, "System reboot is necessary");
else if ( WEXITSTATUS(code) != 0 && WEXITSTATUS(code) != 1 )
else
warnx("%s: Mandatory repair failed: %s: %s", bdev_path,
fs->fsck, "Filesystem checker was unsuccessful");
else
{
fs->flags &= ~(FILESYSTEM_FLAG_FSCK_SHOULD | FILESYSTEM_FLAG_FSCK_MUST);
return true;
}
return false;
}
@ -338,82 +337,106 @@ bool mountpoint_mount(struct mountpoint* mountpoint)
warnx("Failed to fsck %s", bdev_path);
return false;
}
const char* pretend_where = mountpoint->entry.fs_file;
const char* where = mountpoint->absolute;
if ( !fs->driver )
{
warnx("%s: Don't know how to mount a %s filesystem",
bdev_path, fs->fstype_name);
warnx("Failed mounting %s on %s: "
"Don't know how to mount a %s filesystem",
bdev_path, pretend_where, fs->fstype_name);
return false;
}
const char* pretend_where = mountpoint->entry.fs_file;
const char* where = mountpoint->absolute;
struct stat st;
if ( stat(where, &st) < 0 )
{
warn("stat: %s", where);
warn("Failed mounting %s on %s: stat: %s",
bdev_path, pretend_where, where);
return false;
}
int readyfds[2];
if ( pipe(readyfds) < 0 )
{
warn("Failed mounting %s on %s: pipe", bdev_path, pretend_where);
return false;
}
if ( (mountpoint->pid = fork()) < 0 )
{
warn("%s: Unable to mount: fork", bdev_path);
warn("Failed mounting %s on %s: fork", bdev_path, pretend_where);
close(readyfds[0]);
close(readyfds[1]);
return false;
}
// TODO: This design is broken. The filesystem should tell us when it is
// ready instead of having to poll like this.
if ( mountpoint->pid == 0 )
{
close(readyfds[0]);
char readyfdstr[sizeof(int) * 3];
snprintf(readyfdstr, sizeof(readyfdstr), "%d", readyfds[1]);
if ( setenv("READYFD", readyfdstr, 1) < 0 )
{
warn("Failed mounting %s on %s: setenv",
bdev_path, pretend_where);
_exit(127);
}
execlp(fs->driver, fs->driver, "--foreground", bdev_path, where,
"--pretend-mount-path", pretend_where, (const char*) NULL);
warn("%s: Failed to load filesystem driver: %s", bdev_path, fs->driver);
warn("Failed mount %s on %s: execvp: %s",
bdev_path, pretend_where, fs->driver);
_exit(127);
}
while ( true )
close(readyfds[1]);
char c;
struct stat newst;
ssize_t amount = read(readyfds[0], &c, 1);
close(readyfds[0]);
if ( 0 <= amount )
{
struct stat newst;
if ( stat(where, &newst) < 0 )
{
warn("stat: %s", where);
if ( unmount(where, 0) < 0 && errno != ENOMOUNT )
warn("unmount: %s", where);
else if ( errno == ENOMOUNT )
kill(mountpoint->pid, SIGQUIT);
int code;
waitpid(mountpoint->pid, &code, 0);
mountpoint->pid = -1;
return false;
}
if ( newst.st_dev != st.st_dev || newst.st_ino != st.st_ino )
break;
int code;
pid_t child = waitpid(mountpoint->pid, &code, WNOHANG);
if ( child < 0 )
{
warn("waitpid");
return false;
}
if ( child != 0 )
if ( !stat(where, &newst) )
{
mountpoint->pid = -1;
if ( WIFSIGNALED(code) )
warnx("%s: Mount failed: %s: %s", bdev_path, fs->driver,
strsignal(WTERMSIG(code)));
else if ( !WIFEXITED(code) )
warnx("%s: Mount failed: %s: %s", bdev_path, fs->driver,
"Unexpected unusual termination");
else if ( WEXITSTATUS(code) == 127 )
warnx("%s: Mount failed: %s: %s", bdev_path, fs->driver,
"Filesystem driver is absent");
else if ( WEXITSTATUS(code) == 0 )
warnx("%s: Mount failed: %s: Unexpected successful exit",
bdev_path, fs->driver);
if ( newst.st_dev != st.st_dev || newst.st_ino != st.st_ino )
return true;
else
warnx("%s: Mount failed: %s: Exited with status %i", bdev_path,
fs->driver, WEXITSTATUS(code));
return false;
warnx("Failed mount %s on %s: %s: "
"No mounted filesystem appeared: %s",
bdev_path, pretend_where, fs->driver, where);
}
struct timespec delay = timespec_make(0, 50L * 1000L * 1000L);
nanosleep(&delay, NULL);
else
warn("Failed mounting %s on %s: %s, stat: %s",
bdev_path, pretend_where, fs->driver, where);
}
return true;
else
warn("Failed mounting %s on %s: %s, Failed to read readiness",
bdev_path, pretend_where, fs->driver);
if ( unmount(where, 0) < 0 )
{
if ( errno != ENOMOUNT )
warn("Failed mounting %s on %s: unmount: %s",
bdev_path, pretend_where, where);
kill(mountpoint->pid, SIGQUIT);
}
int code;
pid_t child = waitpid(mountpoint->pid, &code, 0);
mountpoint->pid = -1;
if ( child < 0 )
warn("Failed mounting %s on %s: %s: waitpid",
bdev_path, pretend_where, fs->driver);
else if ( WIFSIGNALED(code) )
warnx("Failed mounting %s on %s: %s: %s",
bdev_path, pretend_where, fs->driver,
strsignal(WTERMSIG(code)));
else if ( !WIFEXITED(code) )
warnx("Failed mounting %s on %s: %s: Unexpected unusual termination",
bdev_path, pretend_where, fs->driver);
else if ( WEXITSTATUS(code) == 127 )
warnx("Failed mounting %s on %s: %s: "
"Filesystem driver could not be executed",
bdev_path, pretend_where, fs->driver);
else if ( WEXITSTATUS(code) == 0 )
warnx("Failed mounting %s on %s: %s: Unexpected successful exit",
bdev_path, pretend_where, fs->driver);
else