chiark / gitweb /
ec07b921256b1a0ad97f91cbd0b0e90ba85ba5e2
[elogind.git] / src / update-utmp.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 <assert.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27
28 #include <dbus/dbus.h>
29
30 #ifdef HAVE_AUDIT
31 #include <libaudit.h>
32 #endif
33
34 #include "log.h"
35 #include "macro.h"
36 #include "util.h"
37 #include "special.h"
38 #include "utmp-wtmp.h"
39 #include "dbus-common.h"
40
41 typedef struct Context {
42         DBusConnection *bus;
43 #ifdef HAVE_AUDIT
44         int audit_fd;
45 #endif
46 } Context;
47
48 static usec_t get_startup_time(Context *c) {
49         const char
50                 *interface = "org.freedesktop.systemd1.Manager",
51                 *property = "StartupTimestamp";
52
53         DBusError error;
54         usec_t t = 0;
55         DBusMessage *m = NULL, *reply = NULL;
56         DBusMessageIter iter, sub;
57
58         dbus_error_init(&error);
59
60         assert(c);
61
62         if (!(m = dbus_message_new_method_call(
63                               "org.freedesktop.systemd1",
64                               "/org/freedesktop/systemd1",
65                               "org.freedesktop.DBus.Properties",
66                               "Get"))) {
67                 log_error("Could not allocate message.");
68                 goto finish;
69         }
70
71         if (!dbus_message_append_args(m,
72                                       DBUS_TYPE_STRING, &interface,
73                                       DBUS_TYPE_STRING, &property,
74                                       DBUS_TYPE_INVALID)) {
75                 log_error("Could not append arguments to message.");
76                 goto finish;
77         }
78
79         if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
80                 log_error("Failed to send command: %s", bus_error_message(&error));
81                 goto finish;
82         }
83
84         if (!dbus_message_iter_init(reply, &iter) ||
85             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
86                 log_error("Failed to parse reply.");
87                 goto finish;
88         }
89
90         dbus_message_iter_recurse(&iter, &sub);
91
92         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64)  {
93                 log_error("Failed to parse reply.");
94                 goto finish;
95         }
96
97         dbus_message_iter_get_basic(&sub, &t);
98
99 finish:
100         if (m)
101                 dbus_message_unref(m);
102
103         if (reply)
104                 dbus_message_unref(reply);
105
106         dbus_error_free(&error);
107
108         return t;
109 }
110
111 static int get_current_runlevel(Context *c) {
112         static const struct {
113                 const int runlevel;
114                 const char *special;
115         } table[] = {
116                 /* The first target of this list that is active or has
117                  * a job scheduled wins. We prefer runlevels 5 and 3
118                  * here over the others, since these are the main
119                  * runlevels used on Fedora. It might make sense to
120                  * change the order on some distributions. */
121                 { '5', SPECIAL_RUNLEVEL5_TARGET },
122                 { '3', SPECIAL_RUNLEVEL3_TARGET },
123                 { '4', SPECIAL_RUNLEVEL4_TARGET },
124                 { '2', SPECIAL_RUNLEVEL2_TARGET },
125                 { 'S', SPECIAL_RESCUE_TARGET },
126         };
127         const char
128                 *interface = "org.freedesktop.systemd1.Unit",
129                 *property = "ActiveState";
130
131         DBusMessage *m = NULL, *reply = NULL;
132         int r = 0;
133         unsigned i;
134         DBusError error;
135
136         assert(c);
137
138         dbus_error_init(&error);
139
140         for (i = 0; i < ELEMENTSOF(table); i++) {
141                 const char *path = NULL, *state;
142                 DBusMessageIter iter, sub;
143
144                 if (!(m = dbus_message_new_method_call(
145                                       "org.freedesktop.systemd1",
146                                       "/org/freedesktop/systemd1",
147                                       "org.freedesktop.systemd1.Manager",
148                                       "GetUnit"))) {
149                         log_error("Could not allocate message.");
150                         r = -ENOMEM;
151                         goto finish;
152                 }
153
154                 if (!dbus_message_append_args(m,
155                                               DBUS_TYPE_STRING, &table[i].special,
156                                               DBUS_TYPE_INVALID)) {
157                         log_error("Could not append arguments to message.");
158                         r = -ENOMEM;
159                         goto finish;
160                 }
161
162                 if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
163                         dbus_error_free(&error);
164                         continue;
165                 }
166
167                 if (!dbus_message_get_args(reply, &error,
168                                            DBUS_TYPE_OBJECT_PATH, &path,
169                                            DBUS_TYPE_INVALID)) {
170                         log_error("Failed to parse reply: %s", bus_error_message(&error));
171                         r = -EIO;
172                         goto finish;
173                 }
174
175                 dbus_message_unref(m);
176                 if (!(m = dbus_message_new_method_call(
177                                       "org.freedesktop.systemd1",
178                                       path,
179                                       "org.freedesktop.DBus.Properties",
180                                       "Get"))) {
181                         log_error("Could not allocate message.");
182                         r = -ENOMEM;
183                         goto finish;
184                 }
185
186                 if (!dbus_message_append_args(m,
187                                               DBUS_TYPE_STRING, &interface,
188                                               DBUS_TYPE_STRING, &property,
189                                               DBUS_TYPE_INVALID)) {
190                         log_error("Could not append arguments to message.");
191                         r = -ENOMEM;
192                         goto finish;
193                 }
194
195                 dbus_message_unref(reply);
196                 if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
197                         log_error("Failed to send command: %s", bus_error_message(&error));
198                         r = -EIO;
199                         goto finish;
200                 }
201
202                 if (!dbus_message_iter_init(reply, &iter) ||
203                     dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
204                         log_error("Failed to parse reply.");
205                         r = -EIO;
206                         goto finish;
207                 }
208
209                 dbus_message_iter_recurse(&iter, &sub);
210
211                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
212                         log_error("Failed to parse reply.");
213                         r = -EIO;
214                         goto finish;
215                 }
216
217                 dbus_message_iter_get_basic(&sub, &state);
218
219                 if (streq(state, "active") || streq(state, "reloading"))
220                         r = table[i].runlevel;
221
222                 dbus_message_unref(m);
223                 dbus_message_unref(reply);
224                 m = reply = NULL;
225
226                 if (r)
227                         break;
228         }
229
230 finish:
231         if (m)
232                 dbus_message_unref(m);
233
234         if (reply)
235                 dbus_message_unref(reply);
236
237         dbus_error_free(&error);
238
239         return r;
240 }
241
242 static int on_reboot(Context *c) {
243         int r = 0, q;
244         usec_t t;
245
246         assert(c);
247
248         /* We finished start-up, so let's write the utmp
249          * record and send the audit msg */
250
251 #ifdef HAVE_AUDIT
252         if (c->audit_fd >= 0)
253                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "init", NULL, NULL, NULL, 1) < 0) {
254                         log_error("Failed to send audit message: %m");
255                         r = -errno;
256                 }
257 #endif
258
259         /* If this call fails it will return 0, which
260          * utmp_put_reboot() will then fix to the current time */
261         t = get_startup_time(c);
262
263         if ((q = utmp_put_reboot(t)) < 0) {
264                 log_error("Failed to write utmp record: %s", strerror(-q));
265                 r = q;
266         }
267
268         return r;
269 }
270
271 static int on_shutdown(Context *c) {
272         int r = 0, q;
273
274         assert(c);
275
276         /* We started shut-down, so let's write the utmp
277          * record and send the audit msg */
278
279 #ifdef HAVE_AUDIT
280         if (c->audit_fd >= 0)
281                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "init", NULL, NULL, NULL, 1) < 0) {
282                         log_error("Failed to send audit message: %m");
283                         r = -errno;
284                 }
285 #endif
286
287         if ((q = utmp_put_shutdown()) < 0) {
288                 log_error("Failed to write utmp record: %s", strerror(-q));
289                 r = q;
290         }
291
292         return r;
293 }
294
295 static int on_runlevel(Context *c) {
296         int r = 0, q, previous, runlevel;
297
298         assert(c);
299
300         /* We finished changing runlevel, so let's write the
301          * utmp record and send the audit msg */
302
303         /* First, get last runlevel */
304         if ((q = utmp_get_runlevel(&previous, NULL)) < 0) {
305
306                 if (q != -ESRCH && q != -ENOENT) {
307                         log_error("Failed to get current runlevel: %s", strerror(-q));
308                         return q;
309                 }
310
311                 /* Hmm, we didn't find any runlevel, that means we
312                  * have been rebooted */
313                 r = on_reboot(c);
314                 previous = 0;
315         }
316
317         /* Secondly, get new runlevel */
318         if ((runlevel = get_current_runlevel(c)) < 0)
319                 return runlevel;
320
321         if (previous == runlevel)
322                 return 0;
323
324 #ifdef HAVE_AUDIT
325         if (c->audit_fd >= 0) {
326                 char *s = NULL;
327
328                 if (asprintf(&s, "old-level=%c new-level=%c",
329                              previous > 0 ? previous : 'N',
330                              runlevel > 0 ? runlevel : 'N') < 0)
331                         return -ENOMEM;
332
333                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, NULL, NULL, NULL, 1) < 0) {
334                         log_error("Failed to send audit message: %m");
335                         r = -errno;
336                 }
337
338                 free(s);
339         }
340 #endif
341
342         if ((q = utmp_put_runlevel(runlevel, previous)) < 0) {
343                 log_error("Failed to write utmp record: %s", strerror(-q));
344                 r = q;
345         }
346
347         return r;
348 }
349
350 int main(int argc, char *argv[]) {
351         int r;
352         DBusError error;
353         Context c;
354
355         dbus_error_init(&error);
356
357         zero(c);
358 #ifdef HAVE_AUDIT
359         c.audit_fd = -1;
360 #endif
361
362         if (getppid() != 1) {
363                 log_error("This program should be invoked by init only.");
364                 return EXIT_FAILURE;
365         }
366
367         if (argc != 2) {
368                 log_error("This program requires one argument.");
369                 return EXIT_FAILURE;
370         }
371
372         log_set_target(LOG_TARGET_AUTO);
373         log_parse_environment();
374         log_open();
375
376         umask(0022);
377
378 #ifdef HAVE_AUDIT
379         if ((c.audit_fd = audit_open()) < 0 &&
380             /* If the kernel lacks netlink or audit support,
381              * don't worry about it. */
382             errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT)
383                 log_error("Failed to connect to audit log: %m");
384 #endif
385
386         if (bus_connect(DBUS_BUS_SYSTEM, &c.bus, NULL, &error) < 0) {
387                 log_error("Failed to get D-Bus connection: %s", bus_error_message(&error));
388                 r = -EIO;
389                 goto finish;
390         }
391
392         log_debug("systemd-update-utmp running as pid %lu", (unsigned long) getpid());
393
394         if (streq(argv[1], "reboot"))
395                 r = on_reboot(&c);
396         else if (streq(argv[1], "shutdown"))
397                 r = on_shutdown(&c);
398         else if (streq(argv[1], "runlevel"))
399                 r = on_runlevel(&c);
400         else {
401                 log_error("Unknown command %s", argv[1]);
402                 r = -EINVAL;
403         }
404
405         log_debug("systemd-update-utmp stopped as pid %lu", (unsigned long) getpid());
406
407 finish:
408 #ifdef HAVE_AUDIT
409         if (c.audit_fd >= 0)
410                 audit_close(c.audit_fd);
411 #endif
412
413         if (c.bus) {
414                 dbus_connection_flush(c.bus);
415                 dbus_connection_close(c.bus);
416                 dbus_connection_unref(c.bus);
417         }
418
419         dbus_error_free(&error);
420         dbus_shutdown();
421
422         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
423 }