chiark / gitweb /
logind: extend comment about X11 socket symlink
[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 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 <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 }