chiark / gitweb /
b5d733ca441a60d50443a04e926699f70993a259
[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", error.message);
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 */
118                 { '5', SPECIAL_RUNLEVEL5_TARGET },
119                 { '4', SPECIAL_RUNLEVEL4_TARGET },
120                 { '3', SPECIAL_RUNLEVEL3_TARGET },
121                 { '2', SPECIAL_RUNLEVEL2_TARGET },
122                 { '1', SPECIAL_RESCUE_TARGET },
123         };
124         const char
125                 *interface = "org.freedesktop.systemd1.Unit",
126                 *property = "ActiveState";
127
128         DBusMessage *m = NULL, *reply = NULL;
129         int r = 0;
130         unsigned i;
131         DBusError error;
132
133         assert(c);
134
135         dbus_error_init(&error);
136
137         for (i = 0; i < ELEMENTSOF(table); i++) {
138                 const char *path = NULL, *state;
139                 DBusMessageIter iter, sub;
140
141                 if (!(m = dbus_message_new_method_call(
142                                       "org.freedesktop.systemd1",
143                                       "/org/freedesktop/systemd1",
144                                       "org.freedesktop.systemd1.Manager",
145                                       "GetUnit"))) {
146                         log_error("Could not allocate message.");
147                         r = -ENOMEM;
148                         goto finish;
149                 }
150
151                 if (!dbus_message_append_args(m,
152                                               DBUS_TYPE_STRING, &table[i].special,
153                                               DBUS_TYPE_INVALID)) {
154                         log_error("Could not append arguments to message.");
155                         r = -ENOMEM;
156                         goto finish;
157                 }
158
159                 if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
160                         dbus_error_free(&error);
161                         continue;
162                 }
163
164                 if (!dbus_message_get_args(reply, &error,
165                                            DBUS_TYPE_OBJECT_PATH, &path,
166                                            DBUS_TYPE_INVALID)) {
167                         log_error("Failed to parse reply: %s", error.message);
168                         r = -EIO;
169                         goto finish;
170                 }
171
172                 dbus_message_unref(m);
173                 if (!(m = dbus_message_new_method_call(
174                                       "org.freedesktop.systemd1",
175                                       path,
176                                       "org.freedesktop.DBus.Properties",
177                                       "Get"))) {
178                         log_error("Could not allocate message.");
179                         r = -ENOMEM;
180                         goto finish;
181                 }
182
183                 if (!dbus_message_append_args(m,
184                                               DBUS_TYPE_STRING, &interface,
185                                               DBUS_TYPE_STRING, &property,
186                                               DBUS_TYPE_INVALID)) {
187                         log_error("Could not append arguments to message.");
188                         r = -ENOMEM;
189                         goto finish;
190                 }
191
192                 dbus_message_unref(reply);
193                 if (!(reply = dbus_connection_send_with_reply_and_block(c->bus, m, -1, &error))) {
194                         log_error("Failed to send command: %s", error.message);
195                         r = -EIO;
196                         goto finish;
197                 }
198
199                 if (!dbus_message_iter_init(reply, &iter) ||
200                     dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
201                         log_error("Failed to parse reply.");
202                         r = -EIO;
203                         goto finish;
204                 }
205
206                 dbus_message_iter_recurse(&iter, &sub);
207
208                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)  {
209                         log_error("Failed to parse reply.");
210                         r = -EIO;
211                         goto finish;
212                 }
213
214                 dbus_message_iter_get_basic(&sub, &state);
215
216                 if (streq(state, "active") || streq(state, "reloading"))
217                         r = table[i].runlevel;
218
219                 dbus_message_unref(m);
220                 dbus_message_unref(reply);
221                 m = reply = NULL;
222
223                 if (r)
224                         break;
225         }
226
227 finish:
228         if (m)
229                 dbus_message_unref(m);
230
231         if (reply)
232                 dbus_message_unref(reply);
233
234         dbus_error_free(&error);
235
236         return r;
237 }
238
239 static int on_reboot(Context *c) {
240         int r = 0, q;
241         usec_t t;
242
243         assert(c);
244
245         /* We finished start-up, so let's write the utmp
246          * record and send the audit msg */
247
248 #ifdef HAVE_AUDIT
249         if (c->audit_fd >= 0)
250                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "init", NULL, NULL, NULL, 1) < 0) {
251                         log_error("Failed to send audit message: %m");
252                         r = -errno;
253                 }
254 #endif
255
256         /* If this call fails it will return 0, which
257          * utmp_put_reboot() will then fix to the current time */
258         t = get_startup_time(c);
259
260         if ((q = utmp_put_reboot(t)) < 0) {
261                 log_error("Failed to write utmp record: %s", strerror(-q));
262                 r = q;
263         }
264
265         return r;
266 }
267
268 static int on_shutdown(Context *c) {
269         int r = 0, q;
270
271         assert(c);
272
273         /* We started shut-down, so let's write the utmp
274          * record and send the audit msg */
275
276 #ifdef HAVE_AUDIT
277         if (c->audit_fd >= 0)
278                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "init", NULL, NULL, NULL, 1) < 0) {
279                         log_error("Failed to send audit message: %m");
280                         r = -errno;
281                 }
282 #endif
283
284         if ((q = utmp_put_shutdown(0)) < 0) {
285                 log_error("Failed to write utmp record: %s", strerror(-q));
286                 r = q;
287         }
288
289         return r;
290 }
291
292 static int on_runlevel(Context *c) {
293         int r = 0, q, previous, runlevel;
294
295         assert(c);
296
297         /* We finished changing runlevel, so let's write the
298          * utmp record and send the audit msg */
299
300         /* First, get last runlevel */
301         if ((q = utmp_get_runlevel(&previous, NULL)) < 0) {
302
303                 if (q != -ESRCH && q != -ENOENT) {
304                         log_error("Failed to get current runlevel: %s", strerror(-q));
305                         return q;
306                 }
307
308                 /* Hmm, we didn't find any runlevel, that means we
309                  * have been rebooted */
310                 r = on_reboot(c);
311                 previous = 0;
312         }
313
314         /* Secondly, get new runlevel */
315         if ((runlevel = get_current_runlevel(c)) < 0)
316                 return runlevel;
317
318         if (previous == runlevel)
319                 return 0;
320
321 #ifdef HAVE_AUDIT
322         if (c->audit_fd >= 0) {
323                 char *s = NULL;
324
325                 if (asprintf(&s, "old-level=%c new-level=%c",
326                              previous > 0 ? previous : 'N',
327                              runlevel > 0 ? runlevel : 'N') < 0)
328                         return -ENOMEM;
329
330                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, NULL, NULL, NULL, 1) < 0) {
331                         log_error("Failed to send audit message: %m");
332                         r = -errno;
333                 }
334
335                 free(s);
336         }
337 #endif
338
339         if ((q = utmp_put_runlevel(0, runlevel, previous)) < 0) {
340                 log_error("Failed to write utmp record: %s", strerror(-q));
341                 r = q;
342         }
343
344         return r;
345 }
346
347 int main(int argc, char *argv[]) {
348         int r;
349         DBusError error;
350         Context c;
351
352         dbus_error_init(&error);
353
354         zero(c);
355 #ifdef HAVE_AUDIT
356         c.audit_fd = -1;
357 #endif
358
359         if (getppid() != 1) {
360                 log_error("This program should be invoked by init only.");
361                 return 1;
362         }
363
364         if (argc != 2) {
365                 log_error("This program requires one argument.");
366                 return 1;
367         }
368
369         log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
370         log_parse_environment();
371
372 #ifdef HAVE_AUDIT
373         if ((c.audit_fd = audit_open()) < 0)
374                 log_error("Failed to connect to audit log: %m");
375 #endif
376
377         if (bus_connect(DBUS_BUS_SYSTEM, &c.bus, NULL, &error) < 0) {
378                 log_error("Failed to get D-Bus connection: %s", error.message);
379                 r = -EIO;
380                 goto finish;
381         }
382
383         log_debug("systemd-update-utmp running as pid %lu", (unsigned long) getpid());
384
385         if (streq(argv[1], "reboot"))
386                 r = on_reboot(&c);
387         else if (streq(argv[1], "shutdown"))
388                 r = on_shutdown(&c);
389         else if (streq(argv[1], "runlevel"))
390                 r = on_runlevel(&c);
391         else {
392                 log_error("Unknown command %s", argv[1]);
393                 r = -EINVAL;
394         }
395
396         log_debug("systemd-update-utmp stopped as pid %lu", (unsigned long) getpid());
397
398 finish:
399 #ifdef HAVE_AUDIT
400         if (c.audit_fd >= 0)
401                 audit_close(c.audit_fd);
402 #endif
403
404         if (c.bus) {
405                dbus_connection_close(c.bus);
406                dbus_connection_unref(c.bus);
407         }
408
409         dbus_error_free(&error);
410         dbus_shutdown();
411
412         return r < 0 ? 1 : 0;
413 }