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