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