]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/initctl/initctl.c
Merge pull request #13365 from keszybz/fix-commits-from-pr-13246
[thirdparty/systemd.git] / src / initctl / initctl.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <ctype.h>
4 #include <errno.h>
5 #include <stdio.h>
6 #include <sys/epoll.h>
7 #include <sys/stat.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10
11 #include "sd-bus.h"
12 #include "sd-daemon.h"
13
14 #include "alloc-util.h"
15 #include "bus-error.h"
16 #include "bus-util.h"
17 #include "def.h"
18 #include "fd-util.h"
19 #include "format-util.h"
20 #include "initreq.h"
21 #include "list.h"
22 #include "log.h"
23 #include "memory-util.h"
24 #include "process-util.h"
25 #include "special.h"
26
27 #define SERVER_FD_MAX 16
28 #define TIMEOUT_MSEC ((int) (DEFAULT_EXIT_USEC/USEC_PER_MSEC))
29
30 typedef struct Fifo Fifo;
31
32 typedef struct Server {
33 int epoll_fd;
34
35 LIST_HEAD(Fifo, fifos);
36 unsigned n_fifos;
37
38 sd_bus *bus;
39
40 bool quit;
41 } Server;
42
43 struct Fifo {
44 Server *server;
45
46 int fd;
47
48 struct init_request buffer;
49 size_t bytes_read;
50
51 LIST_FIELDS(Fifo, fifo);
52 };
53
54 static const char *translate_runlevel(int runlevel, bool *isolate) {
55 static const struct {
56 const int runlevel;
57 const char *special;
58 bool isolate;
59 } table[] = {
60 { '0', SPECIAL_POWEROFF_TARGET, false },
61 { '1', SPECIAL_RESCUE_TARGET, true },
62 { 's', SPECIAL_RESCUE_TARGET, true },
63 { 'S', SPECIAL_RESCUE_TARGET, true },
64 { '2', SPECIAL_MULTI_USER_TARGET, true },
65 { '3', SPECIAL_MULTI_USER_TARGET, true },
66 { '4', SPECIAL_MULTI_USER_TARGET, true },
67 { '5', SPECIAL_GRAPHICAL_TARGET, true },
68 { '6', SPECIAL_REBOOT_TARGET, false },
69 };
70
71 unsigned i;
72
73 assert(isolate);
74
75 for (i = 0; i < ELEMENTSOF(table); i++)
76 if (table[i].runlevel == runlevel) {
77 *isolate = table[i].isolate;
78 if (runlevel == '6' && kexec_loaded())
79 return SPECIAL_KEXEC_TARGET;
80 return table[i].special;
81 }
82
83 return NULL;
84 }
85
86 static int change_runlevel(Server *s, int runlevel) {
87 const char *target;
88 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
89 const char *mode;
90 bool isolate = false;
91 int r;
92
93 assert(s);
94
95 target = translate_runlevel(runlevel, &isolate);
96 if (!target) {
97 log_warning("Got request for unknown runlevel %c, ignoring.", runlevel);
98 return 0;
99 }
100
101 if (isolate)
102 mode = "isolate";
103 else
104 mode = "replace-irreversibly";
105
106 log_debug("Running request %s/start/%s", target, mode);
107
108 r = sd_bus_call_method(
109 s->bus,
110 "org.freedesktop.systemd1",
111 "/org/freedesktop/systemd1",
112 "org.freedesktop.systemd1.Manager",
113 "StartUnit",
114 &error,
115 NULL,
116 "ss", target, mode);
117 if (r < 0)
118 return log_error_errno(r, "Failed to change runlevel: %s", bus_error_message(&error, -r));
119
120 return 0;
121 }
122
123 static void request_process(Server *s, const struct init_request *req) {
124 assert(s);
125 assert(req);
126
127 if (req->magic != INIT_MAGIC) {
128 log_error("Got initctl request with invalid magic. Ignoring.");
129 return;
130 }
131
132 switch (req->cmd) {
133
134 case INIT_CMD_RUNLVL:
135 if (!isprint(req->runlevel))
136 log_error("Got invalid runlevel. Ignoring.");
137 else
138 switch (req->runlevel) {
139
140 /* we are async anyway, so just use kill for reexec/reload */
141 case 'u':
142 case 'U':
143 if (kill(1, SIGTERM) < 0)
144 log_error_errno(errno, "kill() failed: %m");
145
146 /* The bus connection will be
147 * terminated if PID 1 is reexecuted,
148 * hence let's just exit here, and
149 * rely on that we'll be restarted on
150 * the next request */
151 s->quit = true;
152 break;
153
154 case 'q':
155 case 'Q':
156 if (kill(1, SIGHUP) < 0)
157 log_error_errno(errno, "kill() failed: %m");
158 break;
159
160 default:
161 (void) change_runlevel(s, req->runlevel);
162 }
163 return;
164
165 case INIT_CMD_POWERFAIL:
166 case INIT_CMD_POWERFAILNOW:
167 case INIT_CMD_POWEROK:
168 log_warning("Received UPS/power initctl request. This is not implemented in systemd. Upgrade your UPS daemon!");
169 return;
170
171 case INIT_CMD_CHANGECONS:
172 log_warning("Received console change initctl request. This is not implemented in systemd.");
173 return;
174
175 case INIT_CMD_SETENV:
176 case INIT_CMD_UNSETENV:
177 log_warning("Received environment initctl request. This is not implemented in systemd.");
178 return;
179
180 default:
181 log_warning("Received unknown initctl request. Ignoring.");
182 return;
183 }
184 }
185
186 static int fifo_process(Fifo *f) {
187 ssize_t l;
188
189 assert(f);
190
191 errno = EIO;
192 l = read(f->fd,
193 ((uint8_t*) &f->buffer) + f->bytes_read,
194 sizeof(f->buffer) - f->bytes_read);
195 if (l <= 0) {
196 if (errno == EAGAIN)
197 return 0;
198
199 return log_warning_errno(errno, "Failed to read from fifo: %m");
200 }
201
202 f->bytes_read += l;
203 assert(f->bytes_read <= sizeof(f->buffer));
204
205 if (f->bytes_read == sizeof(f->buffer)) {
206 request_process(f->server, &f->buffer);
207 f->bytes_read = 0;
208 }
209
210 return 0;
211 }
212
213 static void fifo_free(Fifo *f) {
214 assert(f);
215
216 if (f->server) {
217 assert(f->server->n_fifos > 0);
218 f->server->n_fifos--;
219 LIST_REMOVE(fifo, f->server->fifos, f);
220 }
221
222 if (f->fd >= 0) {
223 if (f->server)
224 epoll_ctl(f->server->epoll_fd, EPOLL_CTL_DEL, f->fd, NULL);
225
226 safe_close(f->fd);
227 }
228
229 free(f);
230 }
231
232 static void server_done(Server *s) {
233 assert(s);
234
235 while (s->fifos)
236 fifo_free(s->fifos);
237
238 s->epoll_fd = safe_close(s->epoll_fd);
239 s->bus = sd_bus_flush_close_unref(s->bus);
240 }
241
242 static int server_init(Server *s, unsigned n_sockets) {
243 int r;
244 unsigned i;
245
246 assert(s);
247 assert(n_sockets > 0);
248
249 zero(*s);
250
251 s->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
252 if (s->epoll_fd < 0) {
253 r = log_error_errno(errno,
254 "Failed to create epoll object: %m");
255 goto fail;
256 }
257
258 for (i = 0; i < n_sockets; i++) {
259 struct epoll_event ev;
260 Fifo *f;
261 int fd;
262
263 fd = SD_LISTEN_FDS_START+i;
264
265 r = sd_is_fifo(fd, NULL);
266 if (r < 0) {
267 log_error_errno(r, "Failed to determine file descriptor type: %m");
268 goto fail;
269 }
270
271 if (!r) {
272 log_error("Wrong file descriptor type.");
273 r = -EINVAL;
274 goto fail;
275 }
276
277 f = new0(Fifo, 1);
278 if (!f) {
279 r = -ENOMEM;
280 log_error_errno(errno, "Failed to create fifo object: %m");
281 goto fail;
282 }
283
284 f->fd = -1;
285
286 zero(ev);
287 ev.events = EPOLLIN;
288 ev.data.ptr = f;
289 if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
290 r = -errno;
291 fifo_free(f);
292 log_error_errno(errno, "Failed to add fifo fd to epoll object: %m");
293 goto fail;
294 }
295
296 f->fd = fd;
297 LIST_PREPEND(fifo, s->fifos, f);
298 f->server = s;
299 s->n_fifos++;
300 }
301
302 r = bus_connect_system_systemd(&s->bus);
303 if (r < 0) {
304 log_error_errno(r, "Failed to get D-Bus connection: %m");
305 r = -EIO;
306 goto fail;
307 }
308
309 return 0;
310
311 fail:
312 server_done(s);
313
314 return r;
315 }
316
317 static int process_event(Server *s, struct epoll_event *ev) {
318 int r;
319 Fifo *f;
320
321 assert(s);
322
323 if (!(ev->events & EPOLLIN))
324 return log_info_errno(SYNTHETIC_ERRNO(EIO),
325 "Got invalid event from epoll. (3)");
326
327 f = (Fifo*) ev->data.ptr;
328 r = fifo_process(f);
329 if (r < 0) {
330 log_info_errno(r, "Got error on fifo: %m");
331 fifo_free(f);
332 return r;
333 }
334
335 return 0;
336 }
337
338 int main(int argc, char *argv[]) {
339 Server server;
340 int r = EXIT_FAILURE, n;
341
342 if (getppid() != 1) {
343 log_error("This program should be invoked by init only.");
344 return EXIT_FAILURE;
345 }
346
347 if (argc > 1) {
348 log_error("This program does not take arguments.");
349 return EXIT_FAILURE;
350 }
351
352 log_setup_service();
353
354 umask(0022);
355
356 n = sd_listen_fds(true);
357 if (n < 0) {
358 log_error_errno(r, "Failed to read listening file descriptors from environment: %m");
359 return EXIT_FAILURE;
360 }
361
362 if (n <= 0 || n > SERVER_FD_MAX) {
363 log_error("No or too many file descriptors passed.");
364 return EXIT_FAILURE;
365 }
366
367 if (server_init(&server, (unsigned) n) < 0)
368 return EXIT_FAILURE;
369
370 log_debug("systemd-initctl running as pid "PID_FMT, getpid_cached());
371
372 sd_notify(false,
373 "READY=1\n"
374 "STATUS=Processing requests...");
375
376 while (!server.quit) {
377 struct epoll_event event;
378 int k;
379
380 k = epoll_wait(server.epoll_fd, &event, 1, TIMEOUT_MSEC);
381 if (k < 0) {
382 if (errno == EINTR)
383 continue;
384 log_error_errno(errno, "epoll_wait() failed: %m");
385 goto fail;
386 }
387
388 if (k <= 0)
389 break;
390
391 if (process_event(&server, &event) < 0)
392 goto fail;
393 }
394
395 r = EXIT_SUCCESS;
396
397 log_debug("systemd-initctl stopped as pid "PID_FMT, getpid_cached());
398
399 fail:
400 sd_notify(false,
401 "STOPPING=1\n"
402 "STATUS=Shutting down...");
403
404 server_done(&server);
405
406 return r;
407 }