Coverage Report

Created: 2026-10-28 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}