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                         &times);
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(&times.finish_time, times.reverse_offset);
89 
90                 subtract_timestamp(&times.generators_start_time, times.reverse_offset);
91                 subtract_timestamp(&times.generators_finish_time, times.reverse_offset);
92 
93                 subtract_timestamp(&times.unitsload_start_time, times.reverse_offset);
94                 subtract_timestamp(&times.unitsload_finish_time, times.reverse_offset);
95         }
96 
97         cached = true;
98 
99 finish:
100         *ret = &times;
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