]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
4927fcae | 2 | |
4927fcae | 3 | #include <errno.h> |
ca78ad1d ZJS |
4 | #include <sys/stat.h> |
5 | #include <sys/types.h> | |
4927fcae LP |
6 | #include <unistd.h> |
7 | ||
349cc4a5 | 8 | #if HAVE_AUDIT |
830f6caa LP |
9 | #include <libaudit.h> |
10 | #endif | |
11 | ||
89456fce TG |
12 | #include "sd-bus.h" |
13 | ||
b5efdb8a LP |
14 | #include "alloc-util.h" |
15 | #include "bus-error.h" | |
b7147168 | 16 | #include "bus-locator.h" |
b5efdb8a | 17 | #include "bus-util.h" |
f97b34a6 | 18 | #include "format-util.h" |
4927fcae LP |
19 | #include "log.h" |
20 | #include "macro.h" | |
72682957 | 21 | #include "main-func.h" |
df0ff127 | 22 | #include "process-util.h" |
fcb23431 | 23 | #include "random-util.h" |
4927fcae | 24 | #include "special.h" |
ecfd9a99 | 25 | #include "stdio-util.h" |
dfc7f594 | 26 | #include "strv.h" |
e7fb33ff | 27 | #include "unit-name.h" |
b5efdb8a | 28 | #include "utmp-wtmp.h" |
230f663e | 29 | #include "verbs.h" |
4927fcae LP |
30 | |
31 | typedef struct Context { | |
89456fce | 32 | sd_bus *bus; |
349cc4a5 | 33 | #if HAVE_AUDIT |
4927fcae LP |
34 | int audit_fd; |
35 | #endif | |
36 | } Context; | |
37 | ||
3664cbab YW |
38 | static void context_clear(Context *c) { |
39 | assert(c); | |
40 | ||
41 | c->bus = sd_bus_flush_close_unref(c->bus); | |
42 | #if HAVE_AUDIT | |
43 | if (c->audit_fd >= 0) | |
44 | audit_close(c->audit_fd); | |
254d1313 | 45 | c->audit_fd = -EBADF; |
3664cbab YW |
46 | #endif |
47 | } | |
48 | ||
3950d8f6 | 49 | static int get_startup_monotonic_time(Context *c, usec_t *ret) { |
4afd3348 | 50 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
89456fce | 51 | int r; |
4927fcae | 52 | |
4927fcae | 53 | assert(c); |
3950d8f6 | 54 | assert(ret); |
4927fcae | 55 | |
3950d8f6 YW |
56 | r = bus_get_property_trivial( |
57 | c->bus, | |
58 | bus_systemd_mgr, | |
59 | "UserspaceTimestampMonotonic", | |
60 | &error, | |
61 | 't', ret); | |
62 | if (r < 0) | |
63 | return log_warning_errno(r, "Failed to get timestamp, ignoring: %s", bus_error_message(&error, r)); | |
4927fcae | 64 | |
3950d8f6 | 65 | return 0; |
4927fcae LP |
66 | } |
67 | ||
68 | static int get_current_runlevel(Context *c) { | |
69 | static const struct { | |
70 | const int runlevel; | |
71 | const char *special; | |
72 | } table[] = { | |
309491e4 YW |
73 | /* The first target of this list that is active or has a job scheduled wins. We prefer |
74 | * runlevels 5 and 3 here over the others, since these are the main runlevels used on Fedora. | |
75 | * It might make sense to change the order on some distributions. */ | |
d5d8429a LP |
76 | { '5', SPECIAL_GRAPHICAL_TARGET }, |
77 | { '3', SPECIAL_MULTI_USER_TARGET }, | |
78 | { '1', SPECIAL_RESCUE_TARGET }, | |
4927fcae | 79 | }; |
89456fce | 80 | int r; |
4927fcae LP |
81 | |
82 | assert(c); | |
83 | ||
fcb23431 | 84 | for (unsigned n_attempts = 0;;) { |
85471164 | 85 | FOREACH_ELEMENT(e, table) { |
fcb23431 YW |
86 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
87 | _cleanup_free_ char *state = NULL, *path = NULL; | |
88 | ||
89 | path = unit_dbus_path_from_name(e->special); | |
90 | if (!path) | |
91 | return log_oom(); | |
92 | ||
93 | r = sd_bus_get_property_string( | |
94 | c->bus, | |
95 | "org.freedesktop.systemd1", | |
96 | path, | |
97 | "org.freedesktop.systemd1.Unit", | |
98 | "ActiveState", | |
99 | &error, | |
100 | &state); | |
101 | if ((r == -ENOTCONN || | |
102 | sd_bus_error_has_names(&error, | |
103 | SD_BUS_ERROR_NO_REPLY, | |
104 | SD_BUS_ERROR_DISCONNECTED)) && | |
105 | ++n_attempts < 64) { | |
106 | ||
107 | /* systemd might have dropped off momentarily, let's not make this an error, | |
108 | * and wait some random time. Let's pick a random time in the range 0ms…250ms, | |
109 | * linearly scaled by the number of failed attempts. */ | |
110 | ||
111 | usec_t usec = random_u64_range(UINT64_C(10) * USEC_PER_MSEC + | |
112 | UINT64_C(240) * USEC_PER_MSEC * n_attempts/64); | |
113 | log_debug_errno(r, "Failed to get state of %s, retrying after %s: %s", | |
114 | e->special, FORMAT_TIMESPAN(usec, USEC_PER_MSEC), bus_error_message(&error, r)); | |
4251512e | 115 | (void) usleep_safe(usec); |
fcb23431 YW |
116 | goto reconnect; |
117 | } | |
118 | if (r < 0) | |
119 | return log_warning_errno(r, "Failed to get state of %s: %s", e->special, bus_error_message(&error, r)); | |
120 | ||
121 | if (STR_IN_SET(state, "active", "reloading")) | |
122 | return e->runlevel; | |
123 | } | |
124 | ||
125 | return 0; | |
126 | ||
127 | reconnect: | |
128 | c->bus = sd_bus_flush_close_unref(c->bus); | |
129 | r = bus_connect_system_systemd(&c->bus); | |
f0960da0 | 130 | if (r < 0) |
fcb23431 | 131 | return log_error_errno(r, "Failed to reconnect to system bus: %m"); |
4927fcae | 132 | } |
4927fcae LP |
133 | } |
134 | ||
230f663e YW |
135 | static int on_reboot(int argc, char *argv[], void *userdata) { |
136 | Context *c = ASSERT_PTR(userdata); | |
3950d8f6 | 137 | usec_t t = 0, boottime; |
4c40ab20 | 138 | int r, q = 0; |
4927fcae | 139 | |
309491e4 | 140 | /* We finished start-up, so let's write the utmp record and send the audit msg. */ |
4927fcae | 141 | |
349cc4a5 | 142 | #if HAVE_AUDIT |
4927fcae | 143 | if (c->audit_fd >= 0) |
0aa281df | 144 | if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && |
38cd55b0 | 145 | errno != EPERM) |
4c40ab20 | 146 | q = log_error_errno(errno, "Failed to send audit message: %m"); |
4927fcae LP |
147 | #endif |
148 | ||
3950d8f6 YW |
149 | /* If this call fails, then utmp_put_reboot() will fix to the current time. */ |
150 | (void) get_startup_monotonic_time(c, &t); | |
f813b623 | 151 | boottime = map_clock_usec(t, CLOCK_MONOTONIC, CLOCK_REALTIME); |
309491e4 YW |
152 | /* We query the recorded monotonic time here (instead of the system clock CLOCK_REALTIME), even |
153 | * though we actually want the system clock time. That's because there's a likely chance that the | |
154 | * system clock wasn't set right during early boot. By manually converting the monotonic clock to the | |
155 | * system clock here we can compensate for incorrectly set clocks during early boot. */ | |
f813b623 | 156 | |
4c40ab20 YW |
157 | r = utmp_put_reboot(boottime); |
158 | if (r < 0) | |
159 | return log_error_errno(r, "Failed to write utmp record: %m"); | |
4927fcae | 160 | |
4c40ab20 | 161 | return q; |
4927fcae LP |
162 | } |
163 | ||
230f663e | 164 | static int on_shutdown(int argc, char *argv[], void *userdata) { |
4c40ab20 | 165 | int r, q = 0; |
4927fcae | 166 | |
309491e4 | 167 | /* We started shut-down, so let's write the utmp record and send the audit msg. */ |
4927fcae | 168 | |
349cc4a5 | 169 | #if HAVE_AUDIT |
230f663e YW |
170 | Context *c = ASSERT_PTR(userdata); |
171 | ||
4927fcae | 172 | if (c->audit_fd >= 0) |
0aa281df | 173 | if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && |
38cd55b0 | 174 | errno != EPERM) |
4c40ab20 | 175 | q = log_error_errno(errno, "Failed to send audit message: %m"); |
4927fcae LP |
176 | #endif |
177 | ||
4c40ab20 YW |
178 | r = utmp_put_shutdown(); |
179 | if (r < 0) | |
180 | return log_error_errno(r, "Failed to write utmp record: %m"); | |
4927fcae | 181 | |
4c40ab20 | 182 | return q; |
4927fcae LP |
183 | } |
184 | ||
230f663e YW |
185 | static int on_runlevel(int argc, char *argv[], void *userdata) { |
186 | Context *c = ASSERT_PTR(userdata); | |
4c40ab20 | 187 | int r, q = 0, previous, runlevel; |
4927fcae | 188 | |
309491e4 | 189 | /* We finished changing runlevel, so let's write the utmp record and send the audit msg. */ |
4927fcae LP |
190 | |
191 | /* First, get last runlevel */ | |
4c40ab20 | 192 | r = utmp_get_runlevel(&previous, NULL); |
4c40ab20 YW |
193 | if (r < 0) { |
194 | if (!IN_SET(r, -ESRCH, -ENOENT)) | |
87ae5453 | 195 | return log_error_errno(r, "Failed to get the last runlevel from utmp: %m"); |
4927fcae | 196 | |
4927fcae LP |
197 | previous = 0; |
198 | } | |
199 | ||
830f6caa | 200 | /* Secondly, get new runlevel */ |
1cea22a5 LP |
201 | runlevel = get_current_runlevel(c); |
202 | if (runlevel < 0) | |
4927fcae | 203 | return runlevel; |
c413bb28 | 204 | if (runlevel == 0) { |
87ae5453 | 205 | log_warning("Failed to get the current runlevel, utmp update skipped."); |
c413bb28 ZJS |
206 | return 0; |
207 | } | |
e57cd3fb | 208 | |
4927fcae LP |
209 | if (previous == runlevel) |
210 | return 0; | |
211 | ||
349cc4a5 | 212 | #if HAVE_AUDIT |
4927fcae | 213 | if (c->audit_fd >= 0) { |
ecfd9a99 | 214 | char s[STRLEN("old-level=_ new-level=_") + 1]; |
4927fcae | 215 | |
ecfd9a99 ZJS |
216 | xsprintf(s, "old-level=%c new-level=%c", |
217 | previous > 0 ? previous : 'N', | |
218 | runlevel); | |
4927fcae | 219 | |
ecfd9a99 ZJS |
220 | if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, |
221 | "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && errno != EPERM) | |
4c40ab20 | 222 | q = log_error_errno(errno, "Failed to send audit message: %m"); |
4927fcae LP |
223 | } |
224 | #endif | |
225 | ||
4c40ab20 YW |
226 | r = utmp_put_runlevel(runlevel, previous); |
227 | if (r < 0 && !IN_SET(r, -ESRCH, -ENOENT)) | |
228 | return log_error_errno(r, "Failed to write utmp record: %m"); | |
4927fcae | 229 | |
4c40ab20 | 230 | return q; |
4927fcae LP |
231 | } |
232 | ||
72682957 | 233 | static int run(int argc, char *argv[]) { |
230f663e YW |
234 | static const Verb verbs[] = { |
235 | { "reboot", 1, 1, 0, on_reboot }, | |
236 | { "shutdown", 1, 1, 0, on_shutdown }, | |
237 | { "runlevel", 1, 1, 0, on_runlevel }, | |
238 | {} | |
239 | }; | |
240 | ||
3664cbab | 241 | _cleanup_(context_clear) Context c = { |
349cc4a5 | 242 | #if HAVE_AUDIT |
254d1313 | 243 | .audit_fd = -EBADF, |
4927fcae | 244 | #endif |
1cea22a5 LP |
245 | }; |
246 | int r; | |
4927fcae | 247 | |
d2acb93d | 248 | log_setup(); |
4927fcae | 249 | |
4c12626c LP |
250 | umask(0022); |
251 | ||
349cc4a5 | 252 | #if HAVE_AUDIT |
72682957 | 253 | /* If the kernel lacks netlink or audit support, don't worry about it. */ |
1cea22a5 | 254 | c.audit_fd = audit_open(); |
72682957 | 255 | if (c.audit_fd < 0) |
86da32ee YW |
256 | log_full_errno(IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT) ? LOG_DEBUG : LOG_WARNING, |
257 | errno, "Failed to connect to audit log, ignoring: %m"); | |
4927fcae | 258 | #endif |
266f3e26 | 259 | r = bus_connect_system_systemd(&c.bus); |
72682957 ZJS |
260 | if (r < 0) |
261 | return log_error_errno(r, "Failed to get D-Bus connection: %m"); | |
4927fcae | 262 | |
230f663e | 263 | return dispatch_verb(argc, argv, verbs, &c); |
4927fcae | 264 | } |
72682957 ZJS |
265 | |
266 | DEFINE_MAIN_FUNCTION(run); |