]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/hibernate-resume/hibernate-resume-config.c
tree-wide: use JSON_ALLOW_EXTENSIONS when disptching at various places
[thirdparty/systemd.git] / src / hibernate-resume / hibernate-resume-config.c
CommitLineData
a628d933
MY
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
a628d933
MY
3#include "alloc-util.h"
4#include "device-nodes.h"
5#include "fstab-util.h"
6#include "hibernate-resume-config.h"
7#include "json.h"
8#include "os-util.h"
9#include "parse-util.h"
10#include "path-util.h"
11#include "proc-cmdline.h"
12#include "efivars.h"
13
0e363838
MY
14typedef struct KernelHibernateLocation {
15 char *device;
16 uint64_t offset;
17 bool offset_set;
18} KernelHibernateLocation;
19
a628d933
MY
20static KernelHibernateLocation* kernel_hibernate_location_free(KernelHibernateLocation *k) {
21 if (!k)
22 return NULL;
23
24 free(k->device);
25
26 return mfree(k);
27}
28
29DEFINE_TRIVIAL_CLEANUP_FUNC(KernelHibernateLocation*, kernel_hibernate_location_free);
30
0e363838
MY
31typedef struct EFIHibernateLocation {
32 char *device;
33
34 sd_id128_t uuid;
35 uint64_t offset;
36
37 char *kernel_version;
38 char *id;
39 char *image_id;
40 char *version_id;
41 char *image_version;
42} EFIHibernateLocation;
43
a628d933
MY
44static EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e) {
45 if (!e)
46 return NULL;
47
48 free(e->device);
49
50 free(e->kernel_version);
51 free(e->id);
52 free(e->image_id);
53 free(e->image_version);
54
55 return mfree(e);
56}
57
58DEFINE_TRIVIAL_CLEANUP_FUNC(EFIHibernateLocation*, efi_hibernate_location_free);
59
60void hibernate_info_done(HibernateInfo *info) {
61 assert(info);
62
63 kernel_hibernate_location_free(info->cmdline);
64 efi_hibernate_location_free(info->efi);
65}
66
67static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
68 KernelHibernateLocation *k = ASSERT_PTR(data);
69 int r;
70
71 assert(key);
72
73 if (streq(key, "resume")) {
74 _cleanup_free_ char *d = NULL;
75
76 if (proc_cmdline_value_missing(key, value))
77 return 0;
78
79 d = fstab_node_to_udev_node(value);
80 if (!d)
81 return log_oom();
82
83 free_and_replace(k->device, d);
84
85 } else if (proc_cmdline_key_streq(key, "resume_offset")) {
86
87 if (proc_cmdline_value_missing(key, value))
88 return 0;
89
90 r = safe_atou64(value, &k->offset);
91 if (r < 0)
92 return log_error_errno(r, "Failed to parse resume_offset=%s: %m", value);
93
94 k->offset_set = true;
95 }
96
97 return 0;
98}
99
100static int get_kernel_hibernate_location(KernelHibernateLocation **ret) {
101 _cleanup_(kernel_hibernate_location_freep) KernelHibernateLocation *k = NULL;
102 int r;
103
104 assert(ret);
105
106 k = new0(KernelHibernateLocation, 1);
107 if (!k)
108 return log_oom();
109
110 r = proc_cmdline_parse(parse_proc_cmdline_item, k, 0);
111 if (r < 0)
112 return log_error_errno(r, "Failed to parse kernel command line: %m");
113
114 if (!k->device) {
115 if (k->offset_set)
116 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
117 "Found resume_offset=%" PRIu64 " but resume= is unset, refusing.",
118 k->offset);
119
120 *ret = NULL;
121 return 0;
122 }
123
124 *ret = TAKE_PTR(k);
125 return 1;
126}
127
128#if ENABLE_EFI
129static bool validate_efi_hibernate_location(EFIHibernateLocation *e) {
fed0a899 130 _cleanup_free_ char *id = NULL, *image_id = NULL;
a628d933
MY
131 int r;
132
133 assert(e);
134
a628d933
MY
135 r = parse_os_release(NULL,
136 "ID", &id,
fed0a899 137 "IMAGE_ID", &image_id);
a628d933 138 if (r < 0)
fed0a899 139 log_warning_errno(r, "Failed to parse os-release: %m");
a628d933 140
fed0a899
ZJS
141 if (!streq_ptr(id, e->id) ||
142 !streq_ptr(image_id, e->image_id)) {
143 log_notice("HibernateLocation system identifier doesn't match currently running system, not resuming from it.");
a628d933
MY
144 return false;
145 }
146
fed0a899
ZJS
147 /*
148 * Note that we accept kernel version mismatches. Linux writes the old kernel to disk as part of the
149 * hibernation image, and thus resuming means the short-lived kernel that reads the image from the
150 * disk will be replaced by the original kernel and effectively removed from memory as part of that.
151 */
152
a628d933
MY
153 return true;
154}
155
156static int get_efi_hibernate_location(EFIHibernateLocation **ret) {
157
158 static const JsonDispatch dispatch_table[] = {
9942f855
LP
159 { "uuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(EFIHibernateLocation, uuid), JSON_MANDATORY },
160 { "offset", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(EFIHibernateLocation, offset), JSON_MANDATORY },
161 { "kernelVersion", JSON_VARIANT_STRING, json_dispatch_string, offsetof(EFIHibernateLocation, kernel_version), JSON_PERMISSIVE|JSON_DEBUG },
162 { "osReleaseId", JSON_VARIANT_STRING, json_dispatch_string, offsetof(EFIHibernateLocation, id), JSON_PERMISSIVE|JSON_DEBUG },
163 { "osReleaseImageId", JSON_VARIANT_STRING, json_dispatch_string, offsetof(EFIHibernateLocation, image_id), JSON_PERMISSIVE|JSON_DEBUG },
164 { "osReleaseVersionId", JSON_VARIANT_STRING, json_dispatch_string, offsetof(EFIHibernateLocation, version_id), JSON_PERMISSIVE|JSON_DEBUG },
165 { "osReleaseImageVersion", JSON_VARIANT_STRING, json_dispatch_string, offsetof(EFIHibernateLocation, image_version), JSON_PERMISSIVE|JSON_DEBUG },
a628d933
MY
166 {},
167 };
168
169 _cleanup_(efi_hibernate_location_freep) EFIHibernateLocation *e = NULL;
170 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
171 _cleanup_free_ char *location_str = NULL;
172 int r;
173
174 assert(ret);
175
176 if (!is_efi_boot())
177 goto skip;
178
179 r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), &location_str);
180 if (r == -ENOENT) {
181 log_debug_errno(r, "EFI variable HibernateLocation is not set, skipping.");
182 goto skip;
183 }
184 if (r < 0)
185 return log_error_errno(r, "Failed to get EFI variable HibernateLocation: %m");
186
187 r = json_parse(location_str, 0, &v, NULL, NULL);
188 if (r < 0)
189 return log_error_errno(r, "Failed to parse HibernateLocation JSON object: %m");
190
191 e = new0(EFIHibernateLocation, 1);
192 if (!e)
193 return log_oom();
194
f0e4244b 195 r = json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, e);
a628d933
MY
196 if (r < 0)
197 return r;
198
fed0a899
ZJS
199 log_info("Reported hibernation image:%s%s%s%s%s%s%s%s%s%s UUID="SD_ID128_UUID_FORMAT_STR" offset=%"PRIu64,
200 e->id ? " ID=" : "", strempty(e->id),
201 e->image_id ? " IMAGE_ID=" : "", strempty(e->image_id),
202 e->version_id ? " VERSION_ID=" : "", strempty(e->version_id),
203 e->image_version ? " IMAGE_VERSION=" : "", strempty(e->image_version),
204 e->kernel_version ? " kernel=" : "", strempty(e->kernel_version),
205 SD_ID128_FORMAT_VAL(e->uuid),
206 e->offset);
207
a628d933
MY
208 if (!validate_efi_hibernate_location(e))
209 goto skip;
210
211 if (asprintf(&e->device, "/dev/disk/by-uuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(e->uuid)) < 0)
212 return log_oom();
213
214 *ret = TAKE_PTR(e);
215 return 1;
216
217skip:
218 *ret = NULL;
219 return 0;
220}
221
222void compare_hibernate_location_and_warn(const HibernateInfo *info) {
223 int r;
224
225 assert(info);
a628d933 226
24ab77c3 227 if (!info->cmdline || !info->efi)
a628d933
MY
228 return;
229
24ab77c3
MY
230 assert(info->device == info->cmdline->device);
231
a628d933
MY
232 if (!path_equal(info->cmdline->device, info->efi->device)) {
233 r = devnode_same(info->cmdline->device, info->efi->device);
234 if (r < 0)
235 log_warning_errno(r,
236 "Failed to check if resume=%s is the same device as EFI HibernateLocation device '%s', ignoring: %m",
237 info->cmdline->device, info->efi->device);
238 if (r == 0)
239 log_warning("resume=%s doesn't match with EFI HibernateLocation device '%s', proceeding anyway with resume=.",
240 info->cmdline->device, info->efi->device);
241 }
242
243 if (info->cmdline->offset != info->efi->offset)
244 log_warning("resume_offset=%" PRIu64 " doesn't match with EFI HibernateLocation offset %" PRIu64 ", proceeding anyway with resume_offset=.",
245 info->cmdline->offset, info->efi->offset);
246}
a628d933
MY
247#endif
248
249int acquire_hibernate_info(HibernateInfo *ret) {
250 _cleanup_(hibernate_info_done) HibernateInfo i = {};
251 int r;
252
253 r = get_kernel_hibernate_location(&i.cmdline);
254 if (r < 0)
255 return r;
256
257#if ENABLE_EFI
258 r = get_efi_hibernate_location(&i.efi);
259 if (r < 0)
260 return r;
261#endif
262
263 if (i.cmdline) {
264 i.device = i.cmdline->device;
b7c1f9ea 265 i.offset = i.cmdline->offset;
a628d933
MY
266 } else if (i.efi) {
267 i.device = i.efi->device;
b7c1f9ea 268 i.offset = i.efi->offset;
a628d933
MY
269 } else
270 return -ENODEV;
271
272 *ret = TAKE_STRUCT(i);
273 return 0;
274}