chiark / gitweb /
bus: log message parsing errors everywhere with a generalized bus_log_parse_error()
[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 #ifdef HAVE_AUDIT
29 #include <libaudit.h>
30 #endif
31
32 #include "sd-bus.h"
33
34 #include "log.h"
35 #include "macro.h"
36 #include "util.h"
37 #include "special.h"
38 #include "utmp-wtmp.h"
39 #include "bus-util.h"
40 #include "bus-error.h"
41
42 typedef struct Context {
43         sd_bus *bus;
44 #ifdef HAVE_AUDIT
45         int audit_fd;
46 #endif
47 } Context;
48
49 static usec_t get_startup_time(Context *c) {
50         usec_t t = 0;
51         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
52         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
53         int r;
54
55         assert(c);
56
57         r = sd_bus_call_method(
58                         c->bus,
59                         "org.freedesktop.systemd1",
60                         "/org/freedesktop/systemd1",
61                         "org.freedesktop.DBus.Properties",
62                         "Get",
63                         &error,
64                         &reply,
65                         "ss",
66                         "org.freedesktop.systemd1.Manager",
67                         "UserspaceTimestamp");
68         if (r < 0) {
69                 log_error("Failed to get timestamp: %s", bus_error_message(&error, -r));
70                 return t;
71         }
72
73         r = sd_bus_message_read(reply, "v", "t", &t);
74         if (r < 0)
75                 return bus_log_parse_error(r);
76
77         return t;
78 }
79
80 static int get_current_runlevel(Context *c) {
81         static const struct {
82                 const int runlevel;
83                 const char *special;
84         } table[] = {
85                 /* The first target of this list that is active or has
86                  * a job scheduled wins. We prefer runlevels 5 and 3
87                  * here over the others, since these are the main
88                  * runlevels used on Fedora. It might make sense to
89                  * change the order on some distributions. */
90                 { '5', SPECIAL_RUNLEVEL5_TARGET },
91                 { '3', SPECIAL_RUNLEVEL3_TARGET },
92                 { '4', SPECIAL_RUNLEVEL4_TARGET },
93                 { '2', SPECIAL_RUNLEVEL2_TARGET },
94                 { '1', SPECIAL_RESCUE_TARGET },
95         };
96
97         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
98         int r;
99         unsigned i;
100
101         assert(c);
102
103         for (i = 0; i < ELEMENTSOF(table); i++) {
104                 _cleanup_bus_message_unref_ sd_bus_message *reply1 = NULL, *reply2 = NULL;
105                 const char *path = NULL, *state;
106
107                 r = sd_bus_call_method(
108                                 c->bus,
109                                 "org.freedesktop.systemd1",
110                                 "/org/freedesktop/systemd1",
111                                 "org.freedesktop.systemd1.Manager",
112                                 "LoadUnit",
113                                 &error,
114                                 &reply1,
115                                 "s", table[i].special);
116                 if (r < 0) {
117                         log_error("Failed to get runlevel: %s", bus_error_message(&error, -r));
118                         if (r == -ENOMEM)
119                                 return r;
120                         else
121                                 continue;
122                 }
123
124                 r = sd_bus_message_read(reply1, "o", &path);
125                 if (r < 0)
126                         return bus_log_parse_error(r);
127
128                 r = sd_bus_call_method(
129                                 c->bus,
130                                 "org.freedesktop.systemd1",
131                                 path,
132                                 "org.freedesktop.DBus.Properties",
133                                 "Get",
134                                 &error,
135                                 &reply2,
136                                 "ss", "org.freedesktop.systemd1.Unit", "ActiveState");
137                 if (r < 0) {
138                         log_error("Failed to get state: %s", bus_error_message(&error, -r));
139                         return r;
140                 }
141
142                 r = sd_bus_message_read(reply2, "v", "s", &state);
143                 if (r < 0)
144                         return bus_log_parse_error(r);
145
146                 if (streq(state, "active") || streq(state, "reloading"))
147                         return table[i].runlevel;
148         }
149
150         return 0;
151 }
152
153 static int on_reboot(Context *c) {
154         int r = 0, q;
155         usec_t t;
156
157         assert(c);
158
159         /* We finished start-up, so let's write the utmp
160          * record and send the audit msg */
161
162 #ifdef HAVE_AUDIT
163         if (c->audit_fd >= 0)
164                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "init", NULL, NULL, NULL, 1) < 0 &&
165                     errno != EPERM) {
166                         log_error("Failed to send audit message: %m");
167                         r = -errno;
168                 }
169 #endif
170
171         /* If this call fails it will return 0, which
172          * utmp_put_reboot() will then fix to the current time */
173         t = get_startup_time(c);
174
175         if ((q = utmp_put_reboot(t)) < 0) {
176                 log_error("Failed to write utmp record: %s", strerror(-q));
177                 r = q;
178         }
179
180         return r;
181 }
182
183 static int on_shutdown(Context *c) {
184         int r = 0, q;
185
186         assert(c);
187
188         /* We started shut-down, so let's write the utmp
189          * record and send the audit msg */
190
191 #ifdef HAVE_AUDIT
192         if (c->audit_fd >= 0)
193                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "init", NULL, NULL, NULL, 1) < 0 &&
194                     errno != EPERM) {
195                         log_error("Failed to send audit message: %m");
196                         r = -errno;
197                 }
198 #endif
199
200         if ((q = utmp_put_shutdown()) < 0) {
201                 log_error("Failed to write utmp record: %s", strerror(-q));
202                 r = q;
203         }
204
205         return r;
206 }
207
208 static int on_runlevel(Context *c) {
209         int r = 0, q, previous, runlevel;
210
211         assert(c);
212
213         /* We finished changing runlevel, so let's write the
214          * utmp record and send the audit msg */
215
216         /* First, get last runlevel */
217         if ((q = utmp_get_runlevel(&previous, NULL)) < 0) {
218
219                 if (q != -ESRCH && q != -ENOENT) {
220                         log_error("Failed to get current runlevel: %s", strerror(-q));
221                         return q;
222                 }
223
224                 /* Hmm, we didn't find any runlevel, that means we
225                  * have been rebooted */
226                 r = on_reboot(c);
227                 previous = 0;
228         }
229
230         /* Secondly, get new runlevel */
231         if ((runlevel = get_current_runlevel(c)) < 0)
232                 return runlevel;
233
234         if (previous == runlevel)
235                 return 0;
236
237 #ifdef HAVE_AUDIT
238         if (c->audit_fd >= 0) {
239                 char *s = NULL;
240
241                 if (asprintf(&s, "old-level=%c new-level=%c",
242                              previous > 0 ? previous : 'N',
243                              runlevel > 0 ? runlevel : 'N') < 0)
244                         return -ENOMEM;
245
246                 if (audit_log_user_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, NULL, NULL, NULL, 1) < 0 &&
247                     errno != EPERM) {
248                         log_error("Failed to send audit message: %m");
249                         r = -errno;
250                 }
251
252                 free(s);
253         }
254 #endif
255
256         if ((q = utmp_put_runlevel(runlevel, previous)) < 0) {
257                 if (q != -ESRCH && q != -ENOENT) {
258                         log_error("Failed to write utmp record: %s", strerror(-q));
259                         r = q;
260                 }
261         }
262
263         return r;
264 }
265
266 int main(int argc, char *argv[]) {
267         int r;
268         Context c = {};
269
270 #ifdef HAVE_AUDIT
271         c.audit_fd = -1;
272 #endif
273
274         if (getppid() != 1) {
275                 log_error("This program should be invoked by init only.");
276                 return EXIT_FAILURE;
277         }
278
279         if (argc != 2) {
280                 log_error("This program requires one argument.");
281                 return EXIT_FAILURE;
282         }
283
284         log_set_target(LOG_TARGET_AUTO);
285         log_parse_environment();
286         log_open();
287
288         umask(0022);
289
290 #ifdef HAVE_AUDIT
291         if ((c.audit_fd = audit_open()) < 0 &&
292             /* If the kernel lacks netlink or audit support,
293              * don't worry about it. */
294             errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT)
295                 log_error("Failed to connect to audit log: %m");
296 #endif
297         r = bus_open_system_systemd(&c.bus);
298         if (r < 0) {
299                 log_error("Failed to get D-Bus connection: %s", strerror(-r));
300                 r = -EIO;
301                 goto finish;
302         }
303
304         log_debug("systemd-update-utmp running as pid %lu", (unsigned long) getpid());
305
306         if (streq(argv[1], "reboot"))
307                 r = on_reboot(&c);
308         else if (streq(argv[1], "shutdown"))
309                 r = on_shutdown(&c);
310         else if (streq(argv[1], "runlevel"))
311                 r = on_runlevel(&c);
312         else {
313                 log_error("Unknown command %s", argv[1]);
314                 r = -EINVAL;
315         }
316
317         log_debug("systemd-update-utmp stopped as pid %lu", (unsigned long) getpid());
318
319 finish:
320 #ifdef HAVE_AUDIT
321         if (c.audit_fd >= 0)
322                 audit_close(c.audit_fd);
323 #endif
324
325         if (c.bus)
326                 sd_bus_unref(c.bus);
327
328         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
329 }