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