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