chiark / gitweb /
macro: hookup assert logic with log logic
[elogind.git] / initctl.c
1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
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 General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU 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
38 #include "util.h"
39 #include "log.h"
40 #include "list.h"
41 #include "initreq.h"
42
43 #define SERVER_FD_START 3
44 #define SERVER_FD_MAX 16
45 #define TIMEOUT ((int) (10*MSEC_PER_SEC))
46
47 typedef struct Fifo Fifo;
48
49 typedef struct Server {
50         int epoll_fd;
51
52         LIST_HEAD(Fifo, fifos);
53         unsigned n_fifos;
54
55         DBusConnection *bus;
56 } Server;
57
58 struct Fifo {
59         Server *server;
60
61         int fd;
62
63         struct init_request buffer;
64         size_t bytes_read;
65
66         LIST_FIELDS(Fifo, fifo);
67 };
68
69 static const char *translate_runlevel(int runlevel) {
70
71         switch (runlevel) {
72
73         case '0':
74                 return "halt.target";
75
76         case '1':
77         case 's':
78         case 'S':
79                 return "rescue.target";
80
81         case '2':
82                 return "runlevel2.target";
83
84         case '3':
85                 return "runlevel3.target";
86
87         case '4':
88                 return "runlevel4.target";
89
90         case '5':
91                 return "runlevel5.target";
92
93         case '6':
94                 return "reboot.target";
95
96         default:
97                 return NULL;
98         }
99 }
100
101 static void change_runlevel(Server *s, int runlevel) {
102         const char *target;
103         DBusMessage *m = NULL, *reply = NULL;
104         DBusError error;
105         const char *path, *replace = "replace";
106
107         assert(s);
108
109         dbus_error_init(&error);
110
111         if (!(target = translate_runlevel(runlevel))) {
112                 log_warning("Got request for unknown runlevel %c, ignoring.", runlevel);
113                 goto finish;
114         }
115
116         log_debug("Running request %s", target);
117
118         if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1", "GetUnit"))) {
119                 log_error("Could not allocate message.");
120                 goto finish;
121         }
122
123         if (!dbus_message_append_args(m,
124                                       DBUS_TYPE_STRING, &target,
125                                       DBUS_TYPE_INVALID)) {
126                 log_error("Could not attach group information to signal message.");
127                 goto finish;
128         }
129
130         if (!(reply = dbus_connection_send_with_reply_and_block(s->bus, m, -1, &error))) {
131                 log_error("Failed to get unit path: %s", error.message);
132                 goto finish;
133         }
134
135         if (!dbus_message_get_args(reply, &error,
136                                    DBUS_TYPE_OBJECT_PATH, &path,
137                                    DBUS_TYPE_INVALID)) {
138                 log_error("Failed to parse unit path: %s", error.message);
139                 goto finish;
140         }
141
142         dbus_message_unref(m);
143         if (!(m = dbus_message_new_method_call("org.freedesktop.systemd1", path, "org.freedesktop.systemd1.Unit", "Start"))) {
144                 log_error("Could not allocate message.");
145                 goto finish;
146         }
147
148         if (!dbus_message_append_args(m,
149                                       DBUS_TYPE_STRING, &replace,
150                                       DBUS_TYPE_INVALID)) {
151                 log_error("Could not attach group information to signal message.");
152                 goto finish;
153         }
154
155         dbus_message_unref(reply);
156         if (!(reply = dbus_connection_send_with_reply_and_block(s->bus, m, -1, &error))) {
157                 log_error("Failed to start unit: %s", error.message);
158                 goto finish;
159         }
160
161 finish:
162         if (m)
163                 dbus_message_unref(m);
164
165         if (reply)
166                 dbus_message_unref(reply);
167
168         dbus_error_free(&error);
169 }
170
171 static void request_process(Server *s, const struct init_request *req) {
172         assert(s);
173         assert(req);
174
175         if (req->magic != INIT_MAGIC) {
176                 log_error("Got initctl request with invalid magic. Ignoring.");
177                 return;
178         }
179
180         switch (req->cmd) {
181
182         case INIT_CMD_RUNLVL:
183                 if (!isprint(req->runlevel))
184                         log_error("Got invalid runlevel. Ignoring.");
185                 else
186                         change_runlevel(s, req->runlevel);
187                 return;
188
189         case INIT_CMD_POWERFAIL:
190         case INIT_CMD_POWERFAILNOW:
191         case INIT_CMD_POWEROK:
192                 log_warning("Received UPS/power initctl request. This is not implemented in systemd. Upgrade your UPS daemon!");
193                 return;
194
195         case INIT_CMD_CHANGECONS:
196                 log_warning("Received console change initctl request. This is not implemented in systemd.");
197                 return;
198
199         case INIT_CMD_SETENV:
200         case INIT_CMD_UNSETENV:
201                 log_warning("Received environment initctl request. This is not implemented in systemd.");
202                 return;
203
204         default:
205                 log_warning("Received unknown initctl request. Ignoring.");
206                 return;
207         }
208 }
209
210 static int fifo_process(Fifo *f) {
211         ssize_t l;
212
213         assert(f);
214
215         errno = EIO;
216         if ((l = read(f->fd, ((uint8_t*) &f->buffer) + f->bytes_read, sizeof(f->buffer) - f->bytes_read)) <= 0) {
217
218                 if (errno == EAGAIN)
219                         return 0;
220
221                 log_warning("Failed to read from fifo: %s", strerror(errno));
222                 return -1;
223         }
224
225         f->bytes_read += l;
226         assert(f->bytes_read <= sizeof(f->buffer));
227
228         if (f->bytes_read == sizeof(f->buffer)) {
229                 request_process(f->server, &f->buffer);
230                 f->bytes_read = 0;
231         }
232
233         return 0;
234 }
235
236 static void fifo_free(Fifo *f) {
237         assert(f);
238
239         if (f->server) {
240                 assert(f->server->n_fifos > 0);
241                 f->server->n_fifos--;
242                 LIST_REMOVE(Fifo, fifo, f->server->fifos, f);
243         }
244
245         if (f->fd >= 0) {
246                 if (f->server)
247                         epoll_ctl(f->server->epoll_fd, EPOLL_CTL_DEL, f->fd, NULL);
248
249                 assert_se(close_nointr(f->fd) == 0);
250         }
251
252         free(f);
253 }
254
255 static int verify_environment(unsigned *n_sockets) {
256         unsigned long long pid;
257         const char *e;
258         int r;
259         unsigned ns;
260
261         assert_se(n_sockets);
262
263         if (!(e = getenv("LISTEN_PID"))) {
264                 log_error("Missing $LISTEN_PID environment variable.");
265                 return -ENOENT;
266         }
267
268         if ((r = safe_atollu(e, &pid)) < 0) {
269                 log_error("Failed to parse $LISTEN_PID: %s", strerror(-r));
270                 return r;
271         }
272
273         if (pid != (unsigned long long) getpid()) {
274                 log_error("Socket nor for me.");
275                 return -ENOENT;
276         }
277
278         if (!(e = getenv("LISTEN_FDS"))) {
279                 log_error("Missing $LISTEN_FDS environment variable.");
280                 return -ENOENT;
281         }
282
283         if ((r = safe_atou(e, &ns)) < 0) {
284                 log_error("Failed to parse $LISTEN_FDS: %s", strerror(-r));
285                 return -E2BIG;
286         }
287
288         if (ns <= 0 || ns > SERVER_FD_MAX) {
289                 log_error("Wrong number of file descriptors passed: %s", e);
290                 return -E2BIG;
291         }
292
293         *n_sockets = ns;
294
295         return 0;
296 }
297
298 static void server_done(Server *s) {
299         assert(s);
300
301         while (s->fifos)
302                 fifo_free(s->fifos);
303
304         if (s->epoll_fd >= 0)
305                 assert_se(close_nointr(s->epoll_fd) == 0);
306
307         if (s->bus)
308                 dbus_connection_unref(s->bus);
309 }
310
311 static int server_init(Server *s, unsigned n_sockets) {
312         int r;
313         unsigned i;
314         DBusError error;
315
316         assert(s);
317         assert(n_sockets > 0);
318
319         dbus_error_init(&error);
320
321         zero(*s);
322
323         if ((s->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) {
324                 r = -errno;
325                 log_error("Failed to create epoll object: %s", strerror(errno));
326                 goto fail;
327         }
328
329         for (i = 0; i < n_sockets; i++) {
330                 struct epoll_event ev;
331                 Fifo *f;
332
333                 if (!(f = new0(Fifo, 1))) {
334                         r = -ENOMEM;
335                         log_error("Failed to create fifo object: %s", strerror(errno));
336                         goto fail;
337                 }
338
339                 f->fd = -1;
340
341                 zero(ev);
342                 ev.events = EPOLLIN;
343                 ev.data.ptr = f;
344                 if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, SERVER_FD_START+i, &ev) < 0) {
345                         r = -errno;
346                         fifo_free(f);
347                         log_error("Failed to add fifo fd to epoll object: %s", strerror(errno));
348                         goto fail;
349                 }
350
351                 f->fd = SERVER_FD_START+i;
352                 LIST_PREPEND(Fifo, fifo, s->fifos, f);
353                 f->server = s;
354                 s->n_fifos ++;
355         }
356
357         if (!(s->bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) {
358                 log_error("Failed to get D-Bus connection: %s", error.message);
359                 goto fail;
360         }
361
362         return 0;
363
364 fail:
365         server_done(s);
366
367         dbus_error_free(&error);
368         return r;
369 }
370
371 static int process_event(Server *s, struct epoll_event *ev) {
372         int r;
373         Fifo *f;
374
375         assert(s);
376
377         if (!(ev->events & EPOLLIN)) {
378                 log_info("Got invalid event from epoll. (3)");
379                 return -EIO;
380         }
381
382         f = (Fifo*) ev->data.ptr;
383
384         if ((r = fifo_process(f)) < 0) {
385                 log_info("Got error on fifo: %s", strerror(-r));
386                 fifo_free(f);
387                 return r;
388         }
389
390         return 0;
391 }
392
393 int main(int argc, char *argv[]) {
394         Server server;
395         int r = 3;
396         unsigned n;
397
398         log_info("systemd-initctl running as pid %llu", (unsigned long long) getpid());
399
400         if (verify_environment(&n) < 0)
401                 return 1;
402
403         if (server_init(&server, n) < 0)
404                 return 2;
405
406         for (;;) {
407                 struct epoll_event event;
408                 int k;
409
410                 if ((k = epoll_wait(server.epoll_fd,
411                                     &event, 1,
412                                     TIMEOUT)) < 0) {
413
414                         if (errno == EINTR)
415                                 continue;
416
417                         log_error("epoll_wait() failed: %s", strerror(errno));
418                         goto fail;
419                 }
420
421                 if (k <= 0)
422                         break;
423
424                 if ((k = process_event(&server, &event)) < 0)
425                         goto fail;
426         }
427         r = 0;
428
429 fail:
430         server_done(&server);
431
432         log_info("systemd-initctl stopped as pid %llu", (unsigned long long) getpid());
433
434         dbus_shutdown();
435
436         return r;
437 }