1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
---
title: Storage Daemons for the Root File System
category: Interfaces
layout: default
SPDX-License-Identifier: LGPL-2.1-or-later
---
# systemd and Storage Daemons for the Root File System
a.k.a. _Pax Cellae pro Radix Arbor_
(or something like that, my Latin is a bit rusty)
A number of complex storage technologies on Linux (e.g. RAID, volume
management, networked storage) require user space services to run while the
storage is active and mountable. This requirement becomes tricky as soon as the
root file system of the Linux operating system is stored on such storage
technology. Previously no clear path to make this work was available. This text
tries to clear up the resulting confusion, and what is now supported and what
is not.
## A Bit of Background
When complex storage technologies are used as backing for the root file system
this needs to be set up by the initrd, i.e. on Fedora by Dracut. In newer
systemd versions tear-down of the root file system backing is also done by the
initrd: after terminating all remaining running processes and unmounting all
file systems it can (which means excluding the root fs) systemd will jump back
into the initrd code allowing it to unmount the final file systems (and its
storage backing) that could not be unmounted as long as the OS was still
running from the main root file system. The initrd' job is to detach/unmount
the root fs, i.e. inverting the exact commands it used to set them up in the
first place. This is not only cleaner, but also allows for the first time
arbitrary complex stacks of storage technology.
Previous attempts to handle root file system setups with complex storage as
backing usually tried to maintain the root storage with program code stored on
the root storage itself, thus creating a number of dependency loops. Safely
detaching such a root file system becomes messy, since the program code on the
storage needs to stay around longer than the storage, which is technically
contradicting.
## What's new?
As a result, we hereby clarify that we do not support storage technology setups
where the storage daemons are being run from the storage it maintains
itself. In other words: a storage daemon backing the root file system cannot be
stored on the root file system itself.
What we do support instead is that these storage daemons are started from the
initrd, stay running all the time during normal operation and are terminated
only after we returned control back to the initrd and by the initrd. As such,
storage daemons involved with maintaining the root file system storage
conceptually are more like kernel threads than like normal system services:
from the perspective of the init system (i.e. systemd) these services have been
started before systemd got initialized and stay around until after systemd is
already gone. These daemons can only be updated by updating the initrd and
rebooting, a takeover from initrd-supplied services to replacements from the
root file system is not supported.
## What does this mean?
Near the end of system shutdown, systemd executes a small tool called
systemd-shutdown, replacing its own process. This tool (which runs as PID 1, as
it entirely replaces the systemd init process) then iterates through the
mounted file systems and running processes (as well as a couple of other
resources) and tries to unmount/read-only mount/detach/kill them. It continues
to do this in a tight loop as long as this results in any effect. From this
killing spree a couple of processes are automatically excluded: PID 1 itself of
course, as well as all kernel threads. After the killing/unmounting spree
control is passed back to the initrd, whose job is then to unmount/detach
whatever might be remaining.
The same killing spree logic (but not the unmount/detach/read-only logic) is
applied during the transition from the initrd to the main system (i.e. the
"`switch_root`" operation), so that no processes from the initrd survive to the
main system.
To implement the supported logic proposed above (i.e. where storage daemons
needed for the root fs which are started by the initrd stay around during
normal operation and are only killed after control is passed back to the
initrd) we need to exclude these daemons from the shutdown/switch_root killing
spree. To accomplish this the following logic is available starting with
systemd 38:
Processes (run by the root user) whose first character of the zeroth command
line argument is `@` are excluded from the killing spree, much the same way as
kernel threads are excluded too. Thus, a daemon which wants to take advantage
of this logic needs to place the following at the top of its `main()` function:
```c
...
argv[0][0] = '@';
...
```
And that's already it. Note that this functionality is only to be used by
programs running from the initrd, and **not** for programs running from the
root file system itself. Programs which use this functionality and are running
from the root file system are considered buggy since they effectively prohibit
clean unmounting/detaching of the root file system and its backing storage.
_Again: if your code is being run from the root file system, then this logic
suggested above is **NOT** for you. Sorry. Talk to us, we can probably help you
to find a different solution to your problem._
The recommended way to distinguish between run-from-initrd and run-from-rootfs
for a daemon is to check for `/etc/initrd-release` (which exists on all modern
initrd implementations, see the [initrd Interface](INITRD_INTERFACE.md) for
details) which when exists results in `argv[0][0]` being set to `@`, and
otherwise doesn't. Something like this:
```c
#include <unistd.h>
int main(int argc, char *argv[]) {
...
if (access("/etc/initrd-release", F_OK) >= 0)
argv[0][0] = '@';
...
}
```
Why `@`? Why `argv[0][0]`? First of all, a technique like this is not without
precedent: traditionally Unix login shells set `argv[0][0]` to `-` to clarify
they are login shells. This logic is also very easy to implement. We have been
looking for other ways to mark processes for exclusion from the killing spree,
but could not find any that was equally simple to implement and quick to read
when traversing through `/proc/`. Also, as a side effect replacing the first
character of `argv[0]` with `@` also visually invalidates the path normally
stored in `argv[0]` (which usually starts with `/`) thus helping the
administrator to understand that your daemon is actually not originating from
the actual root file system, but from a path in a completely different
namespace (i.e. the initrd namespace). Other than that we just think that `@`
is a cool character which looks pretty in the ps output... 😎
Note that your code should only modify `argv[0][0]` and leave the comm name
(i.e. `/proc/self/comm`) of your process untouched.
## To which technologies does this apply?
These recommendations apply to those storage daemons which need to stay around
until after the storage they maintain is unmounted. If your storage daemon is
fine with being shut down before its storage device is unmounted you may ignore
the recommendations above.
This all applies to storage technology only, not to daemons with any other
(non-storage related) purposes.
## What else to keep in mind?
If your daemon implements the logic pointed out above it should work nicely
from initrd environments. In many cases it might be necessary to additionally
support storage daemons to be started from within the actual OS, for example
when complex storage setups are used for auxiliary file systems, i.e. not the
root file system, or created by the administrator during runtime. Here are a
few additional notes for supporting these setups:
* If your storage daemon is run from the main OS (i.e. not the initrd) it will
also be terminated when the OS shuts down (i.e. before we pass control back
to the initrd). Your daemon needs to handle this properly.
* It is not acceptable to spawn off background processes transparently from
user commands or udev rules. Whenever a process is forked off on Unix it
inherits a multitude of process attributes (ranging from the obvious to the
not-so-obvious such as security contexts or audit trails) from its parent
process. It is practically impossible to fully detach a service from the
process context of the spawning process. In particular, systemd tracks which
processes belong to a service or login sessions very closely, and by spawning
off your storage daemon from udev or an administrator command you thus make
it part of its service/login. Effectively this means that whenever udev is
shut down, your storage daemon is killed too, resp. whenever the login
session goes away your storage might be terminated as well. (Also note that
recent udev versions will automatically kill all long running background
processes forked off udev rules now.) So, in summary: double-forking off
processes from user commands or udev rules is **NOT** OK!
* To automatically spawn storage daemons from udev rules or administrator
commands, the recommended technology is socket-based activation as
implemented by systemd. Transparently for your client code connecting to the
socket of your storage daemon will result in the storage to be started. For
that it is simply necessary to inform systemd about the socket you'd like it
to listen on on behalf of your daemon and minimally modify the daemon to
receive the listening socket for its services from systemd instead of
creating it on its own. Such modifications can be minimal, and are easily
written in a way that does not negatively impact usability on non-systemd
systems. For more information on making use of socket activation in your
program consult this blog story: [Socket
Activation](https://0pointer.de/blog/projects/socket-activation.html)
* Consider having a look at the [initrd Interface of systemd](INITRD_INTERFACE.md).
|