-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
192 lines (160 loc) · 9.08 KB
/
README
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
cross-init-tools: Improve compatibility between init systems
Status: experimental. It should work, but a review is required. Since
this code might be executed with privileges, a proper review should
look at security aspects, which hasn't been done so far. (Somebody
definitely needs to review the mkdtemp part, if that is really correct
in the way it's used.)
Prerequisits: dbus-1 library (the low-level one)
cross-init-bridge:
Maps between upstart and systemd types of readiness notifications
and socket activation. It automatically detects the init system it
is run on. An option named --target specifies which init system the
daemon was designed for (upstart, systemd) and the program translates
between that and the init system currently in use (it is essentially
a NOOP if both are the same).
::: Compile:
gcc -Wall -o cross-init-bridge cross-init-bridge.c \
-I$DBUS_INCLUDE_PATH -L$DBUS_LIBRARY_PATH -ldbus-1
For example, under x86_64 Kubuntu Saucy Salamander, use:
gcc -Wall -o cross-init-bridge cross-init-bridge.c \
-I/usr/include/dbus-1.0 \
-I/usr/lib/x86_64-linux-gnu/dbus-1.0/include \
-ldbus-1
Under Fedora 19, use:
gcc -Wall -o cross-init-bridge cross-init-bridge.c \
-I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include \
-ldbus-1
Under Fedora 20, use:
gcc -Wall -o cross-init-bridge cross-init-bridge.c \
-I/usr/include/dbus-1.0 -I/usr/lib64/dbus-1.0/include \
-ldbus-1
::: Usage:
For using a daemon that was designed for upstart from a systemd
unit file, use:
ExecStart=/.../cross-init-bridge -t upstart -- /.../daemon
If the daemon uses raise(SIGSTOP) to notify upstart of its readiness,
use
Type=notify
ExecStart=/.../cross-init-bridge -t upstart -N -- /.../daemon
For using a daemon that was designed for systemd from an upstart
job file, use:
exec /.../cross-init-bridge -t systemd -- /.../daemon
If the daemon uses systemd's sd_notify for readiness notification,
use
expect stop
exec /.../cross-init-bridge -t systemd -N -- /.../daemon
Sockets will be translated if passed from the init system
(autodetected).
Notifications are not autodetected, since the upstart notification
type changes process semantics quite a bit so that startup is
completely broken if the init system (and by extension, this bridge)
and the daemon don't agree on the notification scheme. Therefore the
system administrator / packager should be responsible for explicitly
requesting support for that.
::: Tested on:
Kubuntu 13.10 (Saucy Salamander) for Upstart (Upstart 1.10)
Fedora 19 (Schrödinger's Cat) for systemd (systemd 204)
(WARNING for systemd 204: see unter "Bugs")
Fedora 20 (Heisenbug) for systemd (systemd 208)
::: Peculiarities:
systemd allows for arbitrary messages to be passed over the
notification interface. It also is not restricted to daemon startup.
For this reason, a babysitter process is kept alive for the lifetime
of the daemon to provide the socket. Only the first notification with
READY=1 is going to be relevant, all other data coming through this
socket is currently ignored - but is actually read from the socket so
that the kernel buffer doesn't fill up.
There is an option to end the babysitter process early, by specifying
--end-after-ready on the command line. If the daemon does not use the
notification socket again after it has sent READY=1 over the socket,
this is fine. But if the daemon actually does use the notification
socket after signaling readiness, the babysitter should continue to
run, providing the socket as a black hole, for two reasons:
- The daemon might not be able to properly cope with the socket
gone all of a sudden (this does not happen in the systemd case,
because the socket will be there for the entire lifetime of the
system).
- If some other process re-creates the socket afterwards and the
daemon tries to access that socket again, the other process will
get all the notification data. If the socket is initially created
under /run, this is not too big of a problem, since usually only
privileged processes allowed to do so, but if the user verb is
used in the upstart job file, /run may actually not be writable
for the bridge and the socket may be created in /tmp. After the
babysitter exits, the socket could in principle be recreated by
an attacker, leading to two possbile kinds of attacks:
information disclosure (which might or might not be an issue) and
possibly denial of service for the daemon if the attacker simply
creates the socket but never reads from it, the daemon might just
block on the socket while notifying.
Also note that each instance of a systemd daemon with notifications
on top of upstart will have it's own socket (in contrast to systemd
itself, which uses a single global socket), since this was the
easiest design. At least /tmp has to be writable, otherwise the
bridge will not work, because it doesn't have anywhere to create the
socket. (By default, it first tries to create it under /run, but has
/tmp as a fallback, since /run is usually only writable for root.)
::: Bugs:
1. When running an upstart daemon on top of systemd with readiness
notification, this doesn't work in systemd 204, at least the one
that's shipped with Fedora 19. With systemd 208 everything works
properly. In systemd 204, it kind of breaks the internal state of
systemd, so that it doesn't detect that the process is gone.
A previous version in git was somewhat better for earlier versions
of systemd (internal state not totally broken, but no proper
detection of service failure either), but will cause warning messages
to be generated.
::: WONTFIX/CANTFIX:
1. When running systemd-type notification daemon on top of upstart,
all messages through the notification interface will be ignored,
except for the initial READY=1 signal. This is usually harmless
(although some information the administrator is interested in might
get lost, so one could think about sending it to syslog(3) instead of
eating it), but systemd allows for one important message to be sent
via the socket that can't be properly handled: if the process
specifies that it has changed its main process id via the MAINPID=
message. Upstart doesn't appear to have a mechanism to notify it of
such things, so it is doubtful this can ever be supported. (And if
one actually implements a mechanism in upstart for this, the ĺogical
thing would be to use systemd's notification protocol for this
instead of inventing yet another thing, making this bridge obsolete.)
Ironically, this bridge uses systemd's MAINPID= support for precisely
this when emulating upstart's protocol for systemd.
2. Upstart only supports one socket at a time for activation. Also,
only AF_INET (IPv4) and AF_LOCAL (UNIX domain / abstract) sockets are
supported. For example, AF_INET6 (IPv6) is not supported.
Additionally, it can only create SOCK_STREAM sockets (in IPv4 that
means only TCP, no UDP). The bridge will check that all sockets it
receives from systemd are SOCK_STREAM of type AF_INET and/or
AF_LOCAL. If that is not the case (or the FD is a FIFO, which systemd
supports but Upstart doesn't), this bridge is conservative and
will refuse to start the daemon. (It also only allows one socket in
total.)
3. Upstart only passes the socket when activated via the socket,
the socket is not passed when started manually or because of another
event. systemd always passes the socket, regardless of the way the
program was started. This could lead to problems in both directions:
3a. If the service was started manually or due to some other reason
that was not an activation event on a the socket, the socket will not
be passed to the daemon. This might not seem problematic, but if no
socket is passed, for a daemon to be useful it would have to create
the socket itself, which it can't, because upstart already holds it.
This is not specifically tied to this bridge, and appears to be a
severe limitation in upstart's socket activation design, and unless
this is fixed, this bridge cannot do anything about it.
(Solving this problem would also probably also make it trivial to
implement multiple sockets in upstart, but it is unclear as to how
easy making the required changes will be.)
3b. An upstart service might expect that if a socket has been passed,
that it will always have a client on the other end of the socket. If
started in a systemd environment, a manual start will still pass the
socket, but no client on the other end of it. A well-written daemon
should cope with this regardless (since spurious select/poll/epoll
events can happen anyway), but some daemons might not work properly
because of this. In the opinion of the author of the bridge this type
of behaviour should be considered a bug in the respective daemon,
because it could already occur with upstart on a spurious polling
event.
TODO:
- proper build system
- wait for dbus name (for systemd Type=dbus services on upstart)