chiark / gitweb /
log: Fix previous commit to use correct level
[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: %s", strerror(errno));
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: %s", strerror(errno));
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: %s",
309                                   strerror(errno));
310                         goto fail;
311                 }
312
313                 f->fd = -1;
314
315                 zero(ev);
316                 ev.events = EPOLLIN;
317                 ev.data.ptr = f;
318                 if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
319                         r = -errno;
320                         fifo_free(f);
321                         log_error("Failed to add fifo fd to epoll object: %s",
322                                   strerror(errno));
323                         goto fail;
324                 }
325
326                 f->fd = fd;
327                 LIST_PREPEND(fifo, s->fifos, f);
328                 f->server = s;
329                 s->n_fifos ++;
330         }
331
332         r = bus_connect_system(&s->bus);
333         if (r < 0) {
334                 log_error("Failed to get D-Bus connection: %s", strerror(-r));
335                 r = -EIO;
336                 goto fail;
337         }
338
339         return 0;
340
341 fail:
342         server_done(s);
343
344         return r;
345 }
346
347 static int process_event(Server *s, struct epoll_event *ev) {
348         int r;
349         Fifo *f;
350
351         assert(s);
352
353         if (!(ev->events & EPOLLIN)) {
354                 log_info("Got invalid event from epoll. (3)");
355                 return -EIO;
356         }
357
358         f = (Fifo*) ev->data.ptr;
359         r = fifo_process(f);
360         if (r < 0) {
361                 log_info("Got error on fifo: %s", strerror(-r));
362                 fifo_free(f);
363                 return r;
364         }
365
366         return 0;
367 }
368
369 int main(int argc, char *argv[]) {
370         Server server;
371         int r = EXIT_FAILURE, n;
372
373         if (getppid() != 1) {
374                 log_error("This program should be invoked by init only.");
375                 return EXIT_FAILURE;
376         }
377
378         if (argc > 1) {
379                 log_error("This program does not take arguments.");
380                 return EXIT_FAILURE;
381         }
382
383         log_set_target(LOG_TARGET_AUTO);
384         log_parse_environment();
385         log_open();
386
387         umask(0022);
388
389         if ((n = sd_listen_fds(true)) < 0) {
390                 log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
391                 return EXIT_FAILURE;
392         }
393
394         if (n <= 0 || n > SERVER_FD_MAX) {
395                 log_error("No or too many file descriptors passed.");
396                 return EXIT_FAILURE;
397         }
398
399         if (server_init(&server, (unsigned) n) < 0)
400                 return EXIT_FAILURE;
401
402         log_debug("systemd-initctl running as pid %lu", (unsigned long) getpid());
403
404         sd_notify(false,
405                   "READY=1\n"
406                   "STATUS=Processing requests...");
407
408         while (!server.quit) {
409                 struct epoll_event event;
410                 int k;
411
412                 if ((k = epoll_wait(server.epoll_fd,
413                                     &event, 1,
414                                     TIMEOUT_MSEC)) < 0) {
415
416                         if (errno == EINTR)
417                                 continue;
418
419                         log_error("epoll_wait() failed: %s", strerror(errno));
420                         goto fail;
421                 }
422
423                 if (k <= 0)
424                         break;
425
426                 if (process_event(&server, &event) < 0)
427                         goto fail;
428         }
429
430         r = EXIT_SUCCESS;
431
432         log_debug("systemd-initctl stopped as pid %lu", (unsigned long) getpid());
433
434 fail:
435         sd_notify(false,
436                   "STATUS=Shutting down...");
437
438         server_done(&server);
439
440         return r;
441 }