1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
14 #include "alloc-util.h"
15 #include "bus-error.h"
16 #include "bus-locator.h"
18 #include "format-util.h"
21 #include "main-func.h"
22 #include "process-util.h"
23 #include "random-util.h"
25 #include "stdio-util.h"
27 #include "unit-name.h"
28 #include "utmp-wtmp.h"
31 typedef struct Context
{
38 static void context_clear(Context
*c
) {
41 c
->bus
= sd_bus_flush_close_unref(c
->bus
);
44 audit_close(c
->audit_fd
);
49 static int get_startup_monotonic_time(Context
*c
, usec_t
*ret
) {
50 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
56 r
= bus_get_property_trivial(
59 "UserspaceTimestampMonotonic",
63 return log_warning_errno(r
, "Failed to get timestamp, ignoring: %s", bus_error_message(&error
, r
));
68 static int get_current_runlevel(Context
*c
) {
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. */
76 { '5', SPECIAL_GRAPHICAL_TARGET
},
77 { '3', SPECIAL_MULTI_USER_TARGET
},
78 { '1', SPECIAL_RESCUE_TARGET
},
84 for (unsigned n_attempts
= 0;;) {
85 FOREACH_ARRAY(e
, table
, ELEMENTSOF(table
)) {
86 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
87 _cleanup_free_
char *state
= NULL
, *path
= NULL
;
89 path
= unit_dbus_path_from_name(e
->special
);
93 r
= sd_bus_get_property_string(
95 "org.freedesktop.systemd1",
97 "org.freedesktop.systemd1.Unit",
101 if ((r
== -ENOTCONN
||
102 sd_bus_error_has_names(&error
,
103 SD_BUS_ERROR_NO_REPLY
,
104 SD_BUS_ERROR_DISCONNECTED
)) &&
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. */
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
));
115 (void) usleep_safe(usec
);
119 return log_warning_errno(r
, "Failed to get state of %s: %s", e
->special
, bus_error_message(&error
, r
));
121 if (STR_IN_SET(state
, "active", "reloading"))
128 c
->bus
= sd_bus_flush_close_unref(c
->bus
);
129 r
= bus_connect_system_systemd(&c
->bus
);
131 return log_error_errno(r
, "Failed to reconnect to system bus: %m");
135 static int on_reboot(int argc
, char *argv
[], void *userdata
) {
136 Context
*c
= ASSERT_PTR(userdata
);
137 usec_t t
= 0, boottime
;
140 /* We finished start-up, so let's write the utmp record and send the audit msg. */
143 if (c
->audit_fd
>= 0)
144 if (audit_log_user_comm_message(c
->audit_fd
, AUDIT_SYSTEM_BOOT
, "", "systemd-update-utmp", NULL
, NULL
, NULL
, 1) < 0 &&
146 q
= log_error_errno(errno
, "Failed to send audit message: %m");
149 /* If this call fails, then utmp_put_reboot() will fix to the current time. */
150 (void) get_startup_monotonic_time(c
, &t
);
151 boottime
= map_clock_usec(t
, CLOCK_MONOTONIC
, CLOCK_REALTIME
);
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. */
157 r
= utmp_put_reboot(boottime
);
159 return log_error_errno(r
, "Failed to write utmp record: %m");
164 static int on_shutdown(int argc
, char *argv
[], void *userdata
) {
167 /* We started shut-down, so let's write the utmp record and send the audit msg. */
170 Context
*c
= ASSERT_PTR(userdata
);
172 if (c
->audit_fd
>= 0)
173 if (audit_log_user_comm_message(c
->audit_fd
, AUDIT_SYSTEM_SHUTDOWN
, "", "systemd-update-utmp", NULL
, NULL
, NULL
, 1) < 0 &&
175 q
= log_error_errno(errno
, "Failed to send audit message: %m");
178 r
= utmp_put_shutdown();
180 return log_error_errno(r
, "Failed to write utmp record: %m");
185 static int on_runlevel(int argc
, char *argv
[], void *userdata
) {
186 Context
*c
= ASSERT_PTR(userdata
);
187 int r
, q
= 0, previous
, runlevel
;
189 /* We finished changing runlevel, so let's write the utmp record and send the audit msg. */
191 /* First, get last runlevel */
192 r
= utmp_get_runlevel(&previous
, NULL
);
194 if (!IN_SET(r
, -ESRCH
, -ENOENT
))
195 return log_error_errno(r
, "Failed to get the last runlevel from utmp: %m");
200 /* Secondly, get new runlevel */
201 runlevel
= get_current_runlevel(c
);
205 log_warning("Failed to get the current runlevel, utmp update skipped.");
209 if (previous
== runlevel
)
213 if (c
->audit_fd
>= 0) {
214 char s
[STRLEN("old-level=_ new-level=_") + 1];
216 xsprintf(s
, "old-level=%c new-level=%c",
217 previous
> 0 ? previous
: 'N',
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
)
222 q
= log_error_errno(errno
, "Failed to send audit message: %m");
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");
233 static int run(int argc
, char *argv
[]) {
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
},
241 _cleanup_(context_clear
) Context c
= {
253 /* If the kernel lacks netlink or audit support, don't worry about it. */
254 c
.audit_fd
= audit_open();
256 log_full_errno(IN_SET(errno
, EAFNOSUPPORT
, EPROTONOSUPPORT
) ? LOG_DEBUG
: LOG_WARNING
,
257 errno
, "Failed to connect to audit log, ignoring: %m");
259 r
= bus_connect_system_systemd(&c
.bus
);
261 return log_error_errno(r
, "Failed to get D-Bus connection: %m");
263 return dispatch_verb(argc
, argv
, verbs
, &c
);
266 DEFINE_MAIN_FUNCTION(run
);