/src/systemd/src/core/luo.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include "sd-json.h" |
4 | | |
5 | | #include "errno-util.h" |
6 | | #include "fd-util.h" |
7 | | #include "fdset.h" |
8 | | #include "json-util.h" |
9 | | #include "log.h" |
10 | | #include "luo.h" |
11 | | #include "luo-util.h" |
12 | | #include "manager.h" |
13 | | #include "serialize.h" |
14 | | #include "service.h" |
15 | | #include "unit.h" |
16 | | #include "unit-name.h" |
17 | | |
18 | 0 | static int luo_read_mapping(int session_fd, sd_json_variant **ret) { |
19 | 0 | int r; |
20 | |
|
21 | 0 | assert(session_fd >= 0); |
22 | 0 | assert(ret); |
23 | |
|
24 | 0 | _cleanup_close_ int mapping_fd = luo_session_retrieve_fd(session_fd, LUO_MAPPING_INDEX); |
25 | 0 | if (mapping_fd < 0) |
26 | 0 | return log_warning_errno(mapping_fd, "Failed to retrieve LUO mapping fd (fd_index 0): %m"); |
27 | | |
28 | 0 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; |
29 | 0 | r = sd_json_parse_fd( |
30 | 0 | "luo-mapping", |
31 | 0 | mapping_fd, |
32 | 0 | SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_REOPEN_FD, |
33 | 0 | &v, |
34 | 0 | /* reterr_line= */ NULL, |
35 | 0 | /* reterr_column= */ NULL); |
36 | 0 | if (r < 0) |
37 | 0 | return log_warning_errno(r, "Failed to parse LUO mapping JSON: %m"); |
38 | | |
39 | 0 | *ret = TAKE_PTR(v); |
40 | 0 | return 0; |
41 | 0 | } |
42 | | |
43 | 0 | static void luo_session_finishp(int *fd) { |
44 | 0 | assert(fd); |
45 | |
|
46 | 0 | if (*fd >= 0) |
47 | 0 | (void) luo_session_finish(*fd); |
48 | 0 | safe_close(*fd); |
49 | 0 | } |
50 | | |
51 | 0 | int manager_luo_restore_fd_stores(Manager *m) { |
52 | 0 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *mapping = NULL; |
53 | 0 | _cleanup_close_ int device_fd = -EBADF; |
54 | 0 | _cleanup_(luo_session_finishp) int session_fd = -EBADF; |
55 | 0 | const char *unit_id; |
56 | 0 | sd_json_variant *unit_json; |
57 | 0 | int r, n_total = 0; |
58 | |
|
59 | 0 | assert(m); |
60 | |
|
61 | 0 | if (MANAGER_IS_USER(m)) |
62 | 0 | return 0; |
63 | | |
64 | 0 | device_fd = luo_open_device(); |
65 | 0 | if (ERRNO_IS_NEG_DEVICE_ABSENT(device_fd)) { |
66 | 0 | log_debug_errno(device_fd, "No /dev/liveupdate device found, skipping LUO fd store restoration."); |
67 | 0 | return 0; |
68 | 0 | } |
69 | 0 | if (device_fd < 0) |
70 | 0 | return log_warning_errno(device_fd, "Failed to open /dev/liveupdate: %m"); |
71 | | |
72 | 0 | session_fd = luo_retrieve_session(device_fd, LUO_SESSION_NAME); |
73 | 0 | if (session_fd == -ENOENT) { |
74 | 0 | log_debug("No LUO session '%s' found, skipping fd store restoration.", LUO_SESSION_NAME); |
75 | 0 | return 0; |
76 | 0 | } |
77 | 0 | if (session_fd < 0) |
78 | 0 | return log_warning_errno(session_fd, "Failed to retrieve LUO session '%s': %m", LUO_SESSION_NAME); |
79 | | |
80 | 0 | log_debug("Found LUO session '%s', restoring fd stores.", LUO_SESSION_NAME); |
81 | |
|
82 | 0 | r = luo_read_mapping(session_fd, &mapping); |
83 | 0 | if (r < 0) |
84 | 0 | return r; |
85 | | |
86 | 0 | struct { |
87 | 0 | uint64_t version; |
88 | 0 | sd_json_variant *state; |
89 | 0 | sd_json_variant *units; |
90 | 0 | } q = {}; |
91 | |
|
92 | 0 | static const sd_json_dispatch_field mapping_dispatch_table[] = { |
93 | 0 | { "version", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(q, version), SD_JSON_MANDATORY }, |
94 | 0 | { "state", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, voffsetof(q, state), 0 }, |
95 | 0 | { "units", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, voffsetof(q, units), 0 }, |
96 | 0 | {} |
97 | 0 | }; |
98 | |
|
99 | 0 | r = sd_json_dispatch(mapping, mapping_dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &q); |
100 | 0 | if (r < 0) |
101 | 0 | return r; |
102 | | |
103 | 0 | if (q.version > LUO_PROTOCOL_VERSION) { |
104 | 0 | log_warning("LUO mapping has unsupported version %" PRIu64 ", skipping state restoration.", q.version); |
105 | 0 | return 0; |
106 | 0 | } |
107 | | |
108 | 0 | struct { |
109 | 0 | unsigned kexec_count; |
110 | 0 | dual_timestamp previous_shutdown_start, previous_shutdown_finish, |
111 | 0 | previous_shutdown_late_start, previous_shutdown_late_finish; |
112 | 0 | } state_data = {}; |
113 | |
|
114 | 0 | static const sd_json_dispatch_field state_dispatch_table[] = { |
115 | 0 | { "KExecCount", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, voffsetof(state_data, kexec_count), 0 }, |
116 | 0 | { "ShutdownStartTimestamp", SD_JSON_VARIANT_OBJECT, json_dispatch_dual_timestamp, voffsetof(state_data, previous_shutdown_start), 0 }, |
117 | 0 | { "ShutdownFinishTimestamp", SD_JSON_VARIANT_OBJECT, json_dispatch_dual_timestamp, voffsetof(state_data, previous_shutdown_finish), 0 }, |
118 | 0 | { "ShutdownLateStartTimestamp", SD_JSON_VARIANT_OBJECT, json_dispatch_dual_timestamp, voffsetof(state_data, previous_shutdown_late_start), 0 }, |
119 | 0 | { "ShutdownLateFinishTimestamp", SD_JSON_VARIANT_OBJECT, json_dispatch_dual_timestamp, voffsetof(state_data, previous_shutdown_late_finish), 0 }, |
120 | 0 | {} |
121 | 0 | }; |
122 | |
|
123 | 0 | r = sd_json_dispatch(q.state, state_dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &state_data); |
124 | 0 | if (r >= 0) { |
125 | 0 | m->kexec_count = state_data.kexec_count; |
126 | 0 | m->timestamps[MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_START] = state_data.previous_shutdown_start; |
127 | 0 | m->timestamps[MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_FINISH] = state_data.previous_shutdown_finish; |
128 | 0 | m->timestamps[MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_LATE_START] = state_data.previous_shutdown_late_start; |
129 | 0 | m->timestamps[MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_LATE_FINISH] = state_data.previous_shutdown_late_finish; |
130 | 0 | } |
131 | | |
132 | | /* If we found a LUO session then by definition we have just successfully kexec rebooted */ |
133 | 0 | (void) INC_SAFE(&m->kexec_count, 1); |
134 | | |
135 | | /* Retrieve all fds from the session and dispatch each to the named unit, eagerly loading the |
136 | | * unit if necessary. */ |
137 | 0 | JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, q.units) { |
138 | 0 | sd_json_variant *entry, *fds_json; |
139 | |
|
140 | 0 | if (!unit_name_is_valid(unit_id, UNIT_NAME_ANY)) { |
141 | 0 | log_warning("Invalid unit name '%s' in LUO mapping, skipping.", unit_id); |
142 | 0 | continue; |
143 | 0 | } |
144 | | |
145 | 0 | fds_json = sd_json_variant_by_key(unit_json, "fdstore"); |
146 | 0 | if (!sd_json_variant_is_array(fds_json)) { |
147 | 0 | log_warning("LUO mapping fdstore for unit '%s' is not a JSON array, skipping.", unit_id); |
148 | 0 | continue; |
149 | 0 | } |
150 | | |
151 | 0 | JSON_VARIANT_ARRAY_FOREACH(entry, fds_json) { |
152 | 0 | struct { |
153 | 0 | const char *type; |
154 | 0 | const char *name; |
155 | 0 | uint64_t token; |
156 | 0 | const char *session_name; |
157 | 0 | } p = { |
158 | 0 | .token = UINT64_MAX, |
159 | 0 | }; |
160 | |
|
161 | 0 | static const sd_json_dispatch_field dispatch_table[] = { |
162 | 0 | { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), SD_JSON_MANDATORY }, |
163 | 0 | { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, |
164 | 0 | { "token", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, token), 0 }, |
165 | 0 | { "sessionName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, session_name), 0 }, |
166 | 0 | {} |
167 | 0 | }; |
168 | |
|
169 | 0 | _cleanup_close_ int fd = -EBADF; |
170 | |
|
171 | 0 | r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); |
172 | 0 | if (r < 0) |
173 | 0 | continue; |
174 | | |
175 | 0 | if (streq(p.type, "fd")) { |
176 | 0 | if (p.token == UINT64_MAX) { |
177 | 0 | log_warning("LUO mapping for unit '%s' fd '%s': missing 'token' field.", unit_id, p.name); |
178 | 0 | continue; |
179 | 0 | } |
180 | 0 | if (p.token == LUO_MAPPING_INDEX) { |
181 | 0 | log_warning("LUO mapping for unit '%s' fd '%s': token %" PRIu64 " is reserved for the mapping memfd.", unit_id, p.name, p.token); |
182 | 0 | continue; |
183 | 0 | } |
184 | | |
185 | 0 | fd = luo_session_retrieve_fd(session_fd, p.token); |
186 | 0 | if (fd < 0) { |
187 | 0 | log_warning_errno(fd, "Failed to retrieve LUO fd for unit '%s' name '%s' token %" PRIu64 ": %m", |
188 | 0 | unit_id, p.name, p.token); |
189 | 0 | continue; |
190 | 0 | } |
191 | 0 | } else if (streq(p.type, "luo_session")) { |
192 | 0 | if (!p.session_name) { |
193 | 0 | log_warning("LUO mapping for unit '%s' fd '%s': missing sessionName.", unit_id, p.name); |
194 | 0 | continue; |
195 | 0 | } |
196 | | |
197 | 0 | fd = luo_retrieve_session(device_fd, p.session_name); |
198 | 0 | if (fd < 0) { |
199 | 0 | log_warning_errno(fd, "Failed to retrieve LUO session '%s' for unit '%s' name '%s': %m", |
200 | 0 | p.session_name, unit_id, p.name); |
201 | 0 | continue; |
202 | 0 | } |
203 | | |
204 | 0 | log_debug("Retrieved LUO session '%s' for unit fd store '%s'.", p.session_name, p.name); |
205 | 0 | } else { |
206 | 0 | log_warning("LUO mapping for unit '%s' fd '%s': unknown type '%s', skipping.", |
207 | 0 | unit_id, p.name, p.type); |
208 | 0 | continue; |
209 | 0 | } |
210 | | |
211 | 0 | r = manager_dispatch_external_fd_to_unit(m, unit_id, p.name, /* index= */ 0, TAKE_FD(fd), "LUO"); |
212 | 0 | if (r > 0) |
213 | 0 | n_total++; |
214 | | /* On error fd is already consumed by manager_dispatch_external_fd_to_unit. */ |
215 | 0 | } |
216 | 0 | } |
217 | | |
218 | 0 | if (n_total > 0) |
219 | 0 | log_debug("Restored %d fd(s) total from LUO session.", n_total); |
220 | |
|
221 | 0 | return n_total; |
222 | 0 | } |
223 | | |
224 | 0 | int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) { |
225 | 0 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL, *units = NULL; |
226 | 0 | _cleanup_fclose_ FILE *f = NULL; |
227 | 0 | _cleanup_fdset_free_ FDSet *fds = NULL; |
228 | 0 | Unit *u; |
229 | 0 | int r, n_serialized = 0; |
230 | |
|
231 | 0 | assert(m); |
232 | 0 | assert(ret_f); |
233 | 0 | assert(ret_fds); |
234 | |
|
235 | 0 | if (MANAGER_IS_USER(m)) { |
236 | 0 | *ret_f = NULL; |
237 | 0 | *ret_fds = NULL; |
238 | 0 | return 0; |
239 | 0 | } |
240 | | |
241 | 0 | fds = fdset_new(); |
242 | 0 | if (!fds) |
243 | 0 | return log_oom(); |
244 | | |
245 | | /* Build a JSON object: { "version": 1, |
246 | | * "units": { "unit_id": { "fdstore": [ { "type": "fd", "name": "...", "fd": N }, |
247 | | * { "type": "luo_session", "name": "...", "fd": N, "sessionName": "..." } ] }, ... } } |
248 | | * This is passed to systemd-shutdown which will create a LUO session and preserve the fds. */ |
249 | 0 | HASHMAP_FOREACH(u, m->units) { |
250 | 0 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *entries = NULL; |
251 | 0 | Service *s; |
252 | |
|
253 | 0 | if (u->type != UNIT_SERVICE) |
254 | 0 | continue; |
255 | | |
256 | 0 | s = SERVICE(u); |
257 | |
|
258 | 0 | if (!IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) |
259 | 0 | continue; |
260 | | |
261 | 0 | if (!s->fd_store) |
262 | 0 | continue; |
263 | | |
264 | 0 | LIST_FOREACH(fd_store, fs, s->fd_store) { |
265 | 0 | _cleanup_free_ char *session_name = NULL; |
266 | 0 | int copy; |
267 | | |
268 | | /* Check if this fd is itself a LUO session, as those cannot be nested and need |
269 | | * special handling */ |
270 | 0 | r = fd_get_luo_session_name(fs->fd, &session_name); |
271 | 0 | if (r < 0 && r != -EMEDIUMTYPE) { |
272 | 0 | log_warning_errno(r, "Failed to check if fd '%s' of unit '%s' is a LUO session, skipping: %m", |
273 | 0 | fs->fdname, u->id); |
274 | 0 | continue; |
275 | 0 | } |
276 | | |
277 | | /* Ensure nobody tries to hijack our session, as we will create this later before |
278 | | * kexec */ |
279 | 0 | if (streq_ptr(session_name, LUO_SESSION_NAME)) { |
280 | 0 | log_warning("Skipping fd '%s' of unit '%s' for LUO serialization, as the session name '%s' infringes systemd's namespace.", |
281 | 0 | fs->fdname, u->id, session_name); |
282 | 0 | continue; |
283 | 0 | } |
284 | | |
285 | 0 | copy = fdset_put_dup(fds, fs->fd); |
286 | 0 | if (copy < 0) |
287 | 0 | return log_error_errno(copy, "Failed to duplicate fd for LUO serialization: %m"); |
288 | | |
289 | 0 | r = sd_json_variant_append_arraybo( |
290 | 0 | &entries, |
291 | 0 | SD_JSON_BUILD_PAIR_STRING("type", session_name ? "luo_session" : "fd"), |
292 | 0 | SD_JSON_BUILD_PAIR_STRING("name", fs->fdname), |
293 | 0 | SD_JSON_BUILD_PAIR_INTEGER("fd", copy), |
294 | 0 | JSON_BUILD_PAIR_STRING_NON_EMPTY("sessionName", session_name)); |
295 | 0 | if (r < 0) |
296 | 0 | return log_error_errno(r, "Failed to build JSON for LUO serialization: %m"); |
297 | | |
298 | 0 | n_serialized++; |
299 | 0 | } |
300 | | |
301 | 0 | if (!entries) |
302 | 0 | continue; |
303 | | |
304 | 0 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *unit_json = NULL; |
305 | 0 | r = sd_json_buildo( |
306 | 0 | &unit_json, |
307 | 0 | SD_JSON_BUILD_PAIR_VARIANT("fdstore", entries)); |
308 | 0 | if (r < 0) |
309 | 0 | return log_error_errno(r, "Failed to build LUO unit object: %m"); |
310 | | |
311 | 0 | r = sd_json_variant_set_field(&units, u->id, unit_json); |
312 | 0 | if (r < 0) |
313 | 0 | return log_error_errno(r, "Failed to add unit to LUO serialization JSON: %m"); |
314 | 0 | } |
315 | | |
316 | 0 | r = sd_json_buildo( |
317 | 0 | &root, |
318 | 0 | SD_JSON_BUILD_PAIR_UNSIGNED("version", LUO_PROTOCOL_VERSION), |
319 | 0 | SD_JSON_BUILD_PAIR("state", |
320 | 0 | SD_JSON_BUILD_OBJECT( |
321 | 0 | SD_JSON_BUILD_PAIR_UNSIGNED("KExecCount", m->kexec_count), |
322 | 0 | JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ShutdownStartTimestamp", &m->timestamps[MANAGER_TIMESTAMP_SHUTDOWN_START]), |
323 | 0 | JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ShutdownFinishTimestamp", &m->timestamps[MANAGER_TIMESTAMP_SHUTDOWN_FINISH]))), |
324 | 0 | SD_JSON_BUILD_PAIR_CONDITION(!!units, "units", SD_JSON_BUILD_VARIANT(units))); |
325 | 0 | if (r < 0) |
326 | 0 | return log_error_errno(r, "Failed to build LUO serialization JSON: %m"); |
327 | | |
328 | 0 | r = open_serialization_file("luo-fd-store", &f); |
329 | 0 | if (r < 0) |
330 | 0 | return log_error_errno(r, "Failed to create LUO serialization file: %m"); |
331 | | |
332 | 0 | r = sd_json_variant_dump(root, /* flags= */ 0, f, /* prefix= */ NULL); |
333 | 0 | if (r < 0) |
334 | 0 | return log_error_errno(r, "Failed to dump LUO serialization JSON: %m"); |
335 | | |
336 | 0 | r = finish_serialization_file(f); |
337 | 0 | if (r < 0) |
338 | 0 | return log_error_errno(r, "Failed to finish LUO serialization file: %m"); |
339 | | |
340 | 0 | r = fd_cloexec(fileno(f), false); |
341 | 0 | if (r < 0) |
342 | 0 | return log_error_errno(r, "Failed to disable O_CLOEXEC for LUO serialization: %m"); |
343 | | |
344 | 0 | r = fdset_cloexec(fds, false); |
345 | 0 | if (r < 0) |
346 | 0 | return log_error_errno(r, "Failed to disable O_CLOEXEC for LUO serialization fds: %m"); |
347 | | |
348 | 0 | log_info("Serialized %d fd store entries for LUO.", n_serialized); |
349 | |
|
350 | 0 | *ret_f = TAKE_PTR(f); |
351 | 0 | *ret_fds = TAKE_PTR(fds); |
352 | 0 | return n_serialized; |
353 | 0 | } |