]>
Commit | Line | Data |
---|---|---|
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 |
14 | typedef struct KernelHibernateLocation { |
15 | char *device; | |
16 | uint64_t offset; | |
17 | bool offset_set; | |
18 | } KernelHibernateLocation; | |
19 | ||
a628d933 MY |
20 | static 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 | ||
29 | DEFINE_TRIVIAL_CLEANUP_FUNC(KernelHibernateLocation*, kernel_hibernate_location_free); | |
30 | ||
0e363838 MY |
31 | typedef 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 |
44 | static 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 | ||
58 | DEFINE_TRIVIAL_CLEANUP_FUNC(EFIHibernateLocation*, efi_hibernate_location_free); | |
59 | ||
60 | void 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 | ||
67 | static 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 | ||
100 | static 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 | |
129 | static 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 | ||
156 | static 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 | ||
f1b622a0 | 195 | r = json_dispatch(v, dispatch_table, JSON_LOG, 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 | ||
217 | skip: | |
218 | *ret = NULL; | |
219 | return 0; | |
220 | } | |
221 | ||
222 | void 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 | ||
249 | int 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 | } |