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