1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "analyze.h"
4 #include "analyze-time-data.h"
5 #include "bus-error.h"
6 #include "bus-locator.h"
7 #include "bus-map-properties.h"
8 #include "bus-unit-util.h"
9 #include "special.h"
10
subtract_timestamp(usec_t * a,usec_t b)11 static void subtract_timestamp(usec_t *a, usec_t b) {
12 assert(a);
13
14 if (*a > 0) {
15 assert(*a >= b);
16 *a -= b;
17 }
18 }
19
acquire_boot_times(sd_bus * bus,BootTimes ** ret)20 int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
21 static const struct bus_properties_map property_map[] = {
22 { "FirmwareTimestampMonotonic", "t", NULL, offsetof(BootTimes, firmware_time) },
23 { "LoaderTimestampMonotonic", "t", NULL, offsetof(BootTimes, loader_time) },
24 { "KernelTimestamp", "t", NULL, offsetof(BootTimes, kernel_time) },
25 { "InitRDTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_time) },
26 { "UserspaceTimestampMonotonic", "t", NULL, offsetof(BootTimes, userspace_time) },
27 { "FinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, finish_time) },
28 { "SecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_start_time) },
29 { "SecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_finish_time) },
30 { "GeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_start_time) },
31 { "GeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_finish_time) },
32 { "UnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_start_time) },
33 { "UnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_finish_time) },
34 { "InitRDSecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_security_start_time) },
35 { "InitRDSecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_security_finish_time) },
36 { "InitRDGeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_start_time) },
37 { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_finish_time) },
38 { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_start_time) },
39 { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_finish_time) },
40 {},
41 };
42 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
43 static BootTimes times;
44 static bool cached = false;
45 int r;
46
47 if (cached)
48 goto finish;
49
50 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
51
52 r = bus_map_all_properties(
53 bus,
54 "org.freedesktop.systemd1",
55 "/org/freedesktop/systemd1",
56 property_map,
57 BUS_MAP_STRDUP,
58 &error,
59 NULL,
60 ×);
61 if (r < 0)
62 return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
63
64 if (times.finish_time <= 0)
65 return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
66 "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
67 "Please try again later.\n"
68 "Hint: Use 'systemctl%s list-jobs' to see active jobs",
69 times.finish_time,
70 arg_scope == LOOKUP_SCOPE_SYSTEM ? "" : " --user");
71
72 if (arg_scope == LOOKUP_SCOPE_SYSTEM && times.security_start_time > 0) {
73 /* security_start_time is set when systemd is not running under container environment. */
74 if (times.initrd_time > 0)
75 times.kernel_done_time = times.initrd_time;
76 else
77 times.kernel_done_time = times.userspace_time;
78 } else {
79 /*
80 * User-instance-specific or container-system-specific timestamps processing
81 * (see comment to reverse_offset in BootTimes).
82 */
83 times.reverse_offset = times.userspace_time;
84
85 times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time =
86 times.userspace_time = times.security_start_time = times.security_finish_time = 0;
87
88 subtract_timestamp(×.finish_time, times.reverse_offset);
89
90 subtract_timestamp(×.generators_start_time, times.reverse_offset);
91 subtract_timestamp(×.generators_finish_time, times.reverse_offset);
92
93 subtract_timestamp(×.unitsload_start_time, times.reverse_offset);
94 subtract_timestamp(×.unitsload_finish_time, times.reverse_offset);
95 }
96
97 cached = true;
98
99 finish:
100 *ret = ×
101 return 0;
102 }
103
bus_get_uint64_property(sd_bus * bus,const char * path,const char * interface,const char * property,uint64_t * val)104 static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
105 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
106 int r;
107
108 assert(bus);
109 assert(path);
110 assert(interface);
111 assert(property);
112 assert(val);
113
114 r = sd_bus_get_property_trivial(
115 bus,
116 "org.freedesktop.systemd1",
117 path,
118 interface,
119 property,
120 &error,
121 't', val);
122 if (r < 0)
123 return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, r));
124
125 return 0;
126 }
127
pretty_boot_time(sd_bus * bus,char ** ret)128 int pretty_boot_time(sd_bus *bus, char **ret) {
129 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
130 _cleanup_free_ char *path = NULL, *unit_id = NULL, *text = NULL;
131 usec_t activated_time = USEC_INFINITY;
132 BootTimes *t;
133 int r;
134
135 r = acquire_boot_times(bus, &t);
136 if (r < 0)
137 return r;
138
139 path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET);
140 if (!path)
141 return log_oom();
142
143 r = sd_bus_get_property_string(
144 bus,
145 "org.freedesktop.systemd1",
146 path,
147 "org.freedesktop.systemd1.Unit",
148 "Id",
149 &error,
150 &unit_id);
151 if (r < 0)
152 log_warning_errno(r, "default.target doesn't seem to exist, ignoring: %s", bus_error_message(&error, r));
153
154 r = bus_get_uint64_property(bus, path,
155 "org.freedesktop.systemd1.Unit",
156 "ActiveEnterTimestampMonotonic",
157 &activated_time);
158 if (r < 0)
159 log_warning_errno(r, "Could not get time to reach default.target, ignoring: %m");
160
161 text = strdup("Startup finished in ");
162 if (!text)
163 return log_oom();
164
165 if (t->firmware_time > 0 && !strextend(&text, FORMAT_TIMESPAN(t->firmware_time - t->loader_time, USEC_PER_MSEC), " (firmware) + "))
166 return log_oom();
167 if (t->loader_time > 0 && !strextend(&text, FORMAT_TIMESPAN(t->loader_time, USEC_PER_MSEC), " (loader) + "))
168 return log_oom();
169 if (t->kernel_done_time > 0 && !strextend(&text, FORMAT_TIMESPAN(t->kernel_done_time, USEC_PER_MSEC), " (kernel) + "))
170 return log_oom();
171 if (t->initrd_time > 0 && !strextend(&text, FORMAT_TIMESPAN(t->userspace_time - t->initrd_time, USEC_PER_MSEC), " (initrd) + "))
172 return log_oom();
173
174 if (!strextend(&text, FORMAT_TIMESPAN(t->finish_time - t->userspace_time, USEC_PER_MSEC), " (userspace) "))
175 return log_oom();
176
177 if (t->kernel_done_time > 0)
178 if (!strextend(&text, "= ", FORMAT_TIMESPAN(t->firmware_time + t->finish_time, USEC_PER_MSEC), " "))
179 return log_oom();
180
181 if (unit_id && timestamp_is_set(activated_time)) {
182 usec_t base = t->userspace_time > 0 ? t->userspace_time : t->reverse_offset;
183
184 if (!strextend(&text, "\n", unit_id, " reached after ", FORMAT_TIMESPAN(activated_time - base, USEC_PER_MSEC), " in userspace."))
185 return log_oom();
186
187 } else if (unit_id && activated_time == 0) {
188
189 if (!strextend(&text, "\n", unit_id, " was never reached."))
190 return log_oom();
191
192 } else if (unit_id && activated_time == USEC_INFINITY) {
193
194 if (!strextend(&text, "\nCould not get time to reach ", unit_id, "."))
195 return log_oom();
196
197 } else if (!unit_id) {
198
199 if (!strextend(&text, "\ncould not find default.target."))
200 return log_oom();
201 }
202
203 *ret = TAKE_PTR(text);
204 return 0;
205 }
206
unit_times_free_array(UnitTimes * t)207 UnitTimes* unit_times_free_array(UnitTimes *t) {
208 if (!t)
209 return NULL;
210
211 for (UnitTimes *p = t; p->has_data; p++)
212 free(p->name);
213
214 return mfree(t);
215 }
216
acquire_time_data(sd_bus * bus,UnitTimes ** out)217 int acquire_time_data(sd_bus *bus, UnitTimes **out) {
218 static const struct bus_properties_map property_map[] = {
219 { "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activating) },
220 { "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activated) },
221 { "ActiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivating) },
222 { "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivated) },
223 {},
224 };
225 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
226 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
227 _cleanup_(unit_times_free_arrayp) UnitTimes *unit_times = NULL;
228 BootTimes *boot_times = NULL;
229 size_t c = 0;
230 UnitInfo u;
231 int r;
232
233 r = acquire_boot_times(bus, &boot_times);
234 if (r < 0)
235 return r;
236
237 r = bus_call_method(bus, bus_systemd_mgr, "ListUnits", &error, &reply, NULL);
238 if (r < 0)
239 return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
240
241 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
242 if (r < 0)
243 return bus_log_parse_error(r);
244
245 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
246 UnitTimes *t;
247
248 if (!GREEDY_REALLOC(unit_times, c + 2))
249 return log_oom();
250
251 unit_times[c + 1].has_data = false;
252 t = &unit_times[c];
253 t->name = NULL;
254
255 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
256
257 r = bus_map_all_properties(
258 bus,
259 "org.freedesktop.systemd1",
260 u.unit_path,
261 property_map,
262 BUS_MAP_STRDUP,
263 &error,
264 NULL,
265 t);
266 if (r < 0)
267 return log_error_errno(r, "Failed to get timestamp properties of unit %s: %s",
268 u.id, bus_error_message(&error, r));
269
270 subtract_timestamp(&t->activating, boot_times->reverse_offset);
271 subtract_timestamp(&t->activated, boot_times->reverse_offset);
272 subtract_timestamp(&t->deactivating, boot_times->reverse_offset);
273 subtract_timestamp(&t->deactivated, boot_times->reverse_offset);
274
275 if (t->activated >= t->activating)
276 t->time = t->activated - t->activating;
277 else if (t->deactivated >= t->activating)
278 t->time = t->deactivated - t->activating;
279 else
280 t->time = 0;
281
282 if (t->activating == 0)
283 continue;
284
285 t->name = strdup(u.id);
286 if (!t->name)
287 return log_oom();
288
289 t->has_data = true;
290 c++;
291 }
292 if (r < 0)
293 return bus_log_parse_error(r);
294
295 *out = TAKE_PTR(unit_times);
296 return c;
297 }
298