]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/update-utmp/update-utmp.c
move all tools to subdirs
[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 = "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 }