]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/update-utmp/update-utmp.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / update-utmp / update-utmp.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #if HAVE_AUDIT
26 #include <libaudit.h>
27 #endif
28
29 #include "sd-bus.h"
30
31 #include "alloc-util.h"
32 #include "bus-error.h"
33 #include "bus-util.h"
34 #include "format-util.h"
35 #include "log.h"
36 #include "macro.h"
37 #include "process-util.h"
38 #include "special.h"
39 #include "strv.h"
40 #include "unit-name.h"
41 #include "util.h"
42 #include "utmp-wtmp.h"
43
44 typedef struct Context {
45 sd_bus *bus;
46 #if HAVE_AUDIT
47 int audit_fd;
48 #endif
49 } Context;
50
51 static usec_t get_startup_time(Context *c) {
52 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
53 usec_t t = 0;
54 int r;
55
56 assert(c);
57
58 r = sd_bus_get_property_trivial(
59 c->bus,
60 "org.freedesktop.systemd1",
61 "/org/freedesktop/systemd1",
62 "org.freedesktop.systemd1.Manager",
63 "UserspaceTimestamp",
64 &error,
65 't', &t);
66 if (r < 0) {
67 log_error_errno(r, "Failed to get timestamp: %s", bus_error_message(&error, r));
68 return 0;
69 }
70
71 return t;
72 }
73
74 static int get_current_runlevel(Context *c) {
75 static const struct {
76 const int runlevel;
77 const char *special;
78 } table[] = {
79 /* The first target of this list that is active or has
80 * a job scheduled wins. We prefer runlevels 5 and 3
81 * here over the others, since these are the main
82 * runlevels used on Fedora. It might make sense to
83 * change the order on some distributions. */
84 { '5', SPECIAL_GRAPHICAL_TARGET },
85 { '3', SPECIAL_MULTI_USER_TARGET },
86 { '1', SPECIAL_RESCUE_TARGET },
87 };
88
89 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
90 int r;
91 unsigned i;
92
93 assert(c);
94
95 for (i = 0; i < ELEMENTSOF(table); i++) {
96 _cleanup_free_ char *state = NULL, *path = NULL;
97
98 path = unit_dbus_path_from_name(table[i].special);
99 if (!path)
100 return log_oom();
101
102 r = sd_bus_get_property_string(
103 c->bus,
104 "org.freedesktop.systemd1",
105 path,
106 "org.freedesktop.systemd1.Unit",
107 "ActiveState",
108 &error,
109 &state);
110 if (r < 0)
111 return log_warning_errno(r, "Failed to get state: %s", bus_error_message(&error, r));
112
113 if (STR_IN_SET(state, "active", "reloading"))
114 return table[i].runlevel;
115 }
116
117 return 0;
118 }
119
120 static int on_reboot(Context *c) {
121 int r = 0, q;
122 usec_t t;
123
124 assert(c);
125
126 /* We finished start-up, so let's write the utmp
127 * record and send the audit msg */
128
129 #if HAVE_AUDIT
130 if (c->audit_fd >= 0)
131 if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 &&
132 errno != EPERM) {
133 r = log_error_errno(errno, "Failed to send audit message: %m");
134 }
135 #endif
136
137 /* If this call fails it will return 0, which
138 * utmp_put_reboot() will then fix to the current time */
139 t = get_startup_time(c);
140
141 q = utmp_put_reboot(t);
142 if (q < 0) {
143 log_error_errno(q, "Failed to write utmp record: %m");
144 r = q;
145 }
146
147 return r;
148 }
149
150 static int on_shutdown(Context *c) {
151 int r = 0, q;
152
153 assert(c);
154
155 /* We started shut-down, so let's write the utmp
156 * record and send the audit msg */
157
158 #if HAVE_AUDIT
159 if (c->audit_fd >= 0)
160 if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 &&
161 errno != EPERM) {
162 r = log_error_errno(errno, "Failed to send audit message: %m");
163 }
164 #endif
165
166 q = utmp_put_shutdown();
167 if (q < 0) {
168 log_error_errno(q, "Failed to write utmp record: %m");
169 r = q;
170 }
171
172 return r;
173 }
174
175 static int on_runlevel(Context *c) {
176 int r = 0, q, previous, runlevel;
177
178 assert(c);
179
180 /* We finished changing runlevel, so let's write the
181 * utmp record and send the audit msg */
182
183 /* First, get last runlevel */
184 q = utmp_get_runlevel(&previous, NULL);
185
186 if (q < 0) {
187 if (!IN_SET(q, -ESRCH, -ENOENT))
188 return log_error_errno(q, "Failed to get current runlevel: %m");
189
190 previous = 0;
191 }
192
193 /* Secondly, get new runlevel */
194 runlevel = get_current_runlevel(c);
195
196 if (runlevel < 0)
197 return runlevel;
198
199 if (previous == runlevel)
200 return 0;
201
202 #if HAVE_AUDIT
203 if (c->audit_fd >= 0) {
204 _cleanup_free_ char *s = NULL;
205
206 if (asprintf(&s, "old-level=%c new-level=%c",
207 previous > 0 ? previous : 'N',
208 runlevel > 0 ? runlevel : 'N') < 0)
209 return log_oom();
210
211 if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && errno != EPERM)
212 r = log_error_errno(errno, "Failed to send audit message: %m");
213 }
214 #endif
215
216 q = utmp_put_runlevel(runlevel, previous);
217 if (q < 0 && !IN_SET(q, -ESRCH, -ENOENT)) {
218 log_error_errno(q, "Failed to write utmp record: %m");
219 r = q;
220 }
221
222 return r;
223 }
224
225 int main(int argc, char *argv[]) {
226 Context c = {
227 #if HAVE_AUDIT
228 .audit_fd = -1
229 #endif
230 };
231 int r;
232
233 if (getppid() != 1) {
234 log_error("This program should be invoked by init only.");
235 return EXIT_FAILURE;
236 }
237
238 if (argc != 2) {
239 log_error("This program requires one argument.");
240 return EXIT_FAILURE;
241 }
242
243 log_set_target(LOG_TARGET_AUTO);
244 log_parse_environment();
245 log_open();
246
247 umask(0022);
248
249 #if HAVE_AUDIT
250 /* If the kernel lacks netlink or audit support,
251 * don't worry about it. */
252 c.audit_fd = audit_open();
253 if (c.audit_fd < 0 && !IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT))
254 log_error_errno(errno, "Failed to connect to audit log: %m");
255 #endif
256 r = bus_connect_system_systemd(&c.bus);
257 if (r < 0) {
258 log_error_errno(r, "Failed to get D-Bus connection: %m");
259 r = -EIO;
260 goto finish;
261 }
262
263 log_debug("systemd-update-utmp running as pid "PID_FMT, getpid_cached());
264
265 if (streq(argv[1], "reboot"))
266 r = on_reboot(&c);
267 else if (streq(argv[1], "shutdown"))
268 r = on_shutdown(&c);
269 else if (streq(argv[1], "runlevel"))
270 r = on_runlevel(&c);
271 else {
272 log_error("Unknown command %s", argv[1]);
273 r = -EINVAL;
274 }
275
276 log_debug("systemd-update-utmp stopped as pid "PID_FMT, getpid_cached());
277
278 finish:
279 #if HAVE_AUDIT
280 if (c.audit_fd >= 0)
281 audit_close(c.audit_fd);
282 #endif
283
284 sd_bus_flush_close_unref(c.bus);
285 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
286 }