]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/update-utmp/update-utmp.c
9184025554f03dc162c1096562aa1e81df496434
[thirdparty/systemd.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 = "UserspaceTimestamp";
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 #ifdef HAVE_AUDIT
321 c.audit_fd = -1;
322 #endif
323
324 if (getppid() != 1) {
325 log_error("This program should be invoked by init only.");
326 return EXIT_FAILURE;
327 }
328
329 if (argc != 2) {
330 log_error("This program requires one argument.");
331 return EXIT_FAILURE;
332 }
333
334 log_set_target(LOG_TARGET_AUTO);
335 log_parse_environment();
336 log_open();
337
338 umask(0022);
339
340 #ifdef HAVE_AUDIT
341 if ((c.audit_fd = audit_open()) < 0 &&
342 /* If the kernel lacks netlink or audit support,
343 * don't worry about it. */
344 errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT)
345 log_error("Failed to connect to audit log: %m");
346 #endif
347
348 if (bus_connect(DBUS_BUS_SYSTEM, &c.bus, NULL, &error) < 0) {
349 log_error("Failed to get D-Bus connection: %s", bus_error_message(&error));
350 r = -EIO;
351 goto finish;
352 }
353
354 log_debug("systemd-update-utmp running as pid %lu", (unsigned long) getpid());
355
356 if (streq(argv[1], "reboot"))
357 r = on_reboot(&c);
358 else if (streq(argv[1], "shutdown"))
359 r = on_shutdown(&c);
360 else if (streq(argv[1], "runlevel"))
361 r = on_runlevel(&c);
362 else {
363 log_error("Unknown command %s", argv[1]);
364 r = -EINVAL;
365 }
366
367 log_debug("systemd-update-utmp stopped as pid %lu", (unsigned long) getpid());
368
369 finish:
370 #ifdef HAVE_AUDIT
371 if (c.audit_fd >= 0)
372 audit_close(c.audit_fd);
373 #endif
374
375 if (c.bus) {
376 dbus_connection_flush(c.bus);
377 dbus_connection_close(c.bus);
378 dbus_connection_unref(c.bus);
379 }
380
381 dbus_error_free(&error);
382 dbus_shutdown();
383
384 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
385 }