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