chiark / gitweb /
util: replace close_nointr_nofail() by a more useful safe_close()
[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                 safe_close(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         safe_close(s->epoll_fd);
261
262         if (s->bus) {
263                 sd_bus_flush(s->bus);
264                 sd_bus_unref(s->bus);
265         }
266 }
267
268 static int server_init(Server *s, unsigned n_sockets) {
269         int r;
270         unsigned i;
271
272         assert(s);
273         assert(n_sockets > 0);
274
275         zero(*s);
276
277         s->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
278         if (s->epoll_fd < 0) {
279                 r = -errno;
280                 log_error("Failed to create epoll object: %m");
281                 goto fail;
282         }
283
284         for (i = 0; i < n_sockets; i++) {
285                 struct epoll_event ev;
286                 Fifo *f;
287                 int fd;
288
289                 fd = SD_LISTEN_FDS_START+i;
290
291                 r = sd_is_fifo(fd, NULL);
292                 if (r < 0) {
293                         log_error("Failed to determine file descriptor type: %s",
294                                   strerror(-r));
295                         goto fail;
296                 }
297
298                 if (!r) {
299                         log_error("Wrong file descriptor type.");
300                         r = -EINVAL;
301                         goto fail;
302                 }
303
304                 f = new0(Fifo, 1);
305                 if (!f) {
306                         r = -ENOMEM;
307                         log_error("Failed to create fifo object: %m");
308                         goto fail;
309                 }
310
311                 f->fd = -1;
312
313                 zero(ev);
314                 ev.events = EPOLLIN;
315                 ev.data.ptr = f;
316                 if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
317                         r = -errno;
318                         fifo_free(f);
319                         log_error("Failed to add fifo fd to epoll object: %m");
320                         goto fail;
321                 }
322
323                 f->fd = fd;
324                 LIST_PREPEND(fifo, s->fifos, f);
325                 f->server = s;
326                 s->n_fifos ++;
327         }
328
329         r = bus_open_system_systemd(&s->bus);
330         if (r < 0) {
331                 log_error("Failed to get D-Bus connection: %s", strerror(-r));
332                 r = -EIO;
333                 goto fail;
334         }
335
336         return 0;
337
338 fail:
339         server_done(s);
340
341         return r;
342 }
343
344 static int process_event(Server *s, struct epoll_event *ev) {
345         int r;
346         Fifo *f;
347
348         assert(s);
349
350         if (!(ev->events & EPOLLIN)) {
351                 log_info("Got invalid event from epoll. (3)");
352                 return -EIO;
353         }
354
355         f = (Fifo*) ev->data.ptr;
356         r = fifo_process(f);
357         if (r < 0) {
358                 log_info("Got error on fifo: %s", strerror(-r));
359                 fifo_free(f);
360                 return r;
361         }
362
363         return 0;
364 }
365
366 int main(int argc, char *argv[]) {
367         Server server;
368         int r = EXIT_FAILURE, n;
369
370         if (getppid() != 1) {
371                 log_error("This program should be invoked by init only.");
372                 return EXIT_FAILURE;
373         }
374
375         if (argc > 1) {
376                 log_error("This program does not take arguments.");
377                 return EXIT_FAILURE;
378         }
379
380         log_set_target(LOG_TARGET_AUTO);
381         log_parse_environment();
382         log_open();
383
384         umask(0022);
385
386         if ((n = sd_listen_fds(true)) < 0) {
387                 log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
388                 return EXIT_FAILURE;
389         }
390
391         if (n <= 0 || n > SERVER_FD_MAX) {
392                 log_error("No or too many file descriptors passed.");
393                 return EXIT_FAILURE;
394         }
395
396         if (server_init(&server, (unsigned) n) < 0)
397                 return EXIT_FAILURE;
398
399         log_debug("systemd-initctl running as pid %lu", (unsigned long) getpid());
400
401         sd_notify(false,
402                   "READY=1\n"
403                   "STATUS=Processing requests...");
404
405         while (!server.quit) {
406                 struct epoll_event event;
407                 int k;
408
409                 if ((k = epoll_wait(server.epoll_fd,
410                                     &event, 1,
411                                     TIMEOUT_MSEC)) < 0) {
412
413                         if (errno == EINTR)
414                                 continue;
415
416                         log_error("epoll_wait() failed: %m");
417                         goto fail;
418                 }
419
420                 if (k <= 0)
421                         break;
422
423                 if (process_event(&server, &event) < 0)
424                         goto fail;
425         }
426
427         r = EXIT_SUCCESS;
428
429         log_debug("systemd-initctl stopped as pid %lu", (unsigned long) getpid());
430
431 fail:
432         sd_notify(false,
433                   "STATUS=Shutting down...");
434
435         server_done(&server);
436
437         return r;
438 }