]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/hibernate-resume/hibernate-resume-generator.c
hibernate-resume: refuse resume if resume_offset= is set but not resume=
[thirdparty/systemd.git] / src / hibernate-resume / hibernate-resume-generator.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
d2c68822 2
d2c68822 3#include <errno.h>
07630cea 4#include <stdio.h>
9deeca12 5#include <sys/utsname.h>
ca78ad1d 6#include <unistd.h>
d2c68822 7
9deeca12
MY
8#include "sd-id128.h"
9
b5efdb8a 10#include "alloc-util.h"
ff757c9d 11#include "dropin.h"
9deeca12 12#include "efivars.h"
760e99bb
MY
13#include "fd-util.h"
14#include "fileio.h"
6550203e 15#include "fstab-util.h"
afe44c8f 16#include "generator.h"
9deeca12 17#include "id128-util.h"
baa6a42d 18#include "initrd-util.h"
9deeca12 19#include "json.h"
d2c68822 20#include "log.h"
77182cc6 21#include "main-func.h"
9deeca12 22#include "os-util.h"
760e99bb 23#include "parse-util.h"
4e731273 24#include "proc-cmdline.h"
07630cea
LP
25#include "special.h"
26#include "string-util.h"
d2c68822
IS
27#include "unit-name.h"
28
b8110a3e 29static const char *arg_dest = NULL;
1d84ad94 30static char *arg_resume_device = NULL;
8b6805a2 31static char *arg_resume_options = NULL;
70e843fe 32static char *arg_root_options = NULL;
e83419d0 33static bool arg_noresume = false;
760e99bb 34static uint64_t arg_resume_offset = 0;
c089af84 35static bool arg_resume_offset_set = false;
d2c68822 36
77182cc6 37STATIC_DESTRUCTOR_REGISTER(arg_resume_device, freep);
8b6805a2 38STATIC_DESTRUCTOR_REGISTER(arg_resume_options, freep);
70e843fe 39STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
77182cc6 40
9deeca12
MY
41#if ENABLE_EFI
42typedef struct EFIHibernateLocation {
43 sd_id128_t uuid;
44 uint64_t offset;
45 const char *kernel_version;
46 const char *id;
47 const char *image_id;
48 const char *version_id;
49 const char *image_version;
50} EFIHibernateLocation;
51#endif
52
96287a49 53static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
760e99bb 54 int r;
7410616c 55
760e99bb 56 if (proc_cmdline_key_streq(key, "resume")) {
1d84ad94
LP
57 char *s;
58
59 if (proc_cmdline_value_missing(key, value))
60 return 0;
61
62 s = fstab_node_to_udev_node(value);
63 if (!s)
d2c68822 64 return log_oom();
1d84ad94 65
e83419d0
ZJS
66 free_and_replace(arg_resume_device, s);
67
760e99bb
MY
68 } else if (proc_cmdline_key_streq(key, "resume_offset")) {
69
70 if (proc_cmdline_value_missing(key, value))
71 return 0;
72
73 r = safe_atou64(value, &arg_resume_offset);
74 if (r < 0)
75 return log_error_errno(r, "Failed to parse resume_offset=%s: %m", value);
76
c089af84
MY
77 arg_resume_offset_set = true;
78
760e99bb 79 } else if (proc_cmdline_key_streq(key, "resumeflags")) {
8b6805a2
JR
80
81 if (proc_cmdline_value_missing(key, value))
82 return 0;
83
c2bc710b 84 if (!strextend_with_separator(&arg_resume_options, ",", value))
8b6805a2
JR
85 return log_oom();
86
760e99bb 87 } else if (proc_cmdline_key_streq(key, "rootflags")) {
70e843fe
JR
88
89 if (proc_cmdline_value_missing(key, value))
90 return 0;
91
c2bc710b 92 if (!strextend_with_separator(&arg_root_options, ",", value))
70e843fe
JR
93 return log_oom();
94
760e99bb 95 } else if (proc_cmdline_key_streq(key, "noresume")) {
e83419d0
ZJS
96 if (value) {
97 log_warning("\"noresume\" kernel command line switch specified with an argument, ignoring.");
98 return 0;
99 }
100
101 arg_noresume = true;
d2c68822
IS
102 }
103
104 return 0;
105}
106
9deeca12
MY
107static int parse_efi_hibernate_location(void) {
108 int r = 0;
109
110#if ENABLE_EFI
111 static const JsonDispatch dispatch_table[] = {
112 { "uuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(EFIHibernateLocation, uuid), JSON_MANDATORY },
113 { "offset", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(EFIHibernateLocation, offset), JSON_MANDATORY },
114 { "kernelVersion", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, kernel_version), JSON_PERMISSIVE|JSON_DEBUG },
115 { "osReleaseId", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, id), JSON_PERMISSIVE|JSON_DEBUG },
116 { "osReleaseImageId", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, image_id), JSON_PERMISSIVE|JSON_DEBUG },
117 { "osReleaseVersionId", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, version_id), JSON_PERMISSIVE|JSON_DEBUG },
118 { "osReleaseImageVersion", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, image_version), JSON_PERMISSIVE|JSON_DEBUG },
119 {},
120 };
121
122 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
123 _cleanup_free_ char *location_str = NULL, *device = NULL, *id = NULL, *image_id = NULL,
124 *version_id = NULL, *image_version = NULL;
125 struct utsname uts = {};
126 EFIHibernateLocation location = {};
127
128 r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), &location_str);
129 if (r == -ENOENT) {
130 log_debug_errno(r, "EFI variable HibernateLocation is not set, skipping.");
131 return 0;
132 }
133 if (r < 0)
134 return log_error_errno(r, "Failed to get EFI variable HibernateLocation: %m");
135
136 r = json_parse(location_str, 0, &v, NULL, NULL);
137 if (r < 0)
138 return log_error_errno(r, "Failed to parse HibernateLocation JSON object: %m");
139
140 r = json_dispatch(v, dispatch_table, NULL, JSON_LOG, &location);
141 if (r < 0)
142 return r;
143
144 if (uname(&uts) < 0)
145 log_warning_errno(errno, "Failed to get kernel info, ignoring: %m");
146
147 r = parse_os_release(NULL,
148 "ID", &id,
149 "IMAGE_ID", &image_id,
150 "VERSION_ID", &version_id,
151 "IMAGE_VERSION", &image_version);
152 if (r < 0)
153 log_warning_errno(r, "Failed to parse os-release, ignoring: %m");
154
155 if (!streq(uts.release, strempty(location.kernel_version)) ||
156 !streq_ptr(id, location.id) ||
157 !streq_ptr(image_id, location.image_id) ||
158 !streq_ptr(version_id, location.version_id) ||
159 !streq_ptr(image_version, location.image_version)) {
160
161 log_notice("HibernateLocation system info doesn't match with current running system, not resuming from it.");
162 return 0;
163 }
164
165 if (asprintf(&device, "/dev/disk/by-uuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(location.uuid)) < 0)
166 return log_oom();
167
168 if (!arg_resume_device) {
169 arg_resume_device = TAKE_PTR(device);
170 arg_resume_offset = location.offset;
171 } else {
172 if (!streq(arg_resume_device, device))
173 log_warning("resume=%s doesn't match with HibernateLocation device '%s', proceeding anyway with resume=.",
174 arg_resume_device, device);
175
176 if (arg_resume_offset != location.offset)
177 log_warning("resume_offset=%" PRIu64 " doesn't match with HibernateLocation offset %" PRIu64 ", proceeding anyway with resume_offset=.",
178 arg_resume_offset, location.offset);
179 }
180
181 r = efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0);
182 if (r < 0)
183 log_warning_errno(r, "Failed to clear EFI variable HibernateLocation, ignoring: %m");
184#endif
185
186 return r;
187}
188
d2c68822 189static int process_resume(void) {
760e99bb
MY
190 _cleanup_free_ char *device_unit = NULL;
191 _cleanup_fclose_ FILE *f = NULL;
7410616c 192 int r;
d2c68822 193
1d84ad94 194 if (!arg_resume_device)
b5884878
LP
195 return 0;
196
ff757c9d
ZJS
197 r = unit_name_from_path(arg_resume_device, ".device", &device_unit);
198 if (r < 0)
760e99bb 199 return log_error_errno(r, "Failed to generate device unit name from path '%s': %m", arg_resume_device);
ff757c9d
ZJS
200
201 r = write_drop_in(arg_dest, device_unit, 40, "device-timeout",
90198bcb 202 "# Automatically generated by systemd-hibernate-resume-generator\n\n"
a9b837aa
LP
203 "[Unit]\n"
204 "JobTimeoutSec=infinity\n");
ff757c9d 205 if (r < 0)
760e99bb
MY
206 log_warning_errno(r, "Failed to write device timeout drop-in, ignoring: %m");
207
208 r = generator_open_unit_file(arg_dest, NULL, SPECIAL_HIBERNATE_RESUME_SERVICE, &f);
209 if (r < 0)
210 return r;
211
212 fprintf(f,
213 "[Unit]\n"
214 "Description=Resume from hibernation\n"
215 "Documentation=man:systemd-hibernate-resume.service(8)\n"
216 "DefaultDependencies=no\n"
217 "BindsTo=%1$s\n"
218 "Wants=local-fs-pre.target\n"
219 "After=%1$s\n"
220 "Before=local-fs-pre.target\n"
221 "AssertPathExists=/etc/initrd-release\n"
222 "\n"
223 "[Service]\n"
224 "Type=oneshot\n"
225 "ExecStart=" ROOTLIBEXECDIR "/systemd-hibernate-resume %2$s %3$" PRIu64,
226 device_unit,
227 arg_resume_device,
228 arg_resume_offset);
229
230 r = fflush_and_check(f);
231 if (r < 0)
232 return log_error_errno(r, "Failed to create " SPECIAL_HIBERNATE_RESUME_SERVICE ": %m");
233
234 r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SPECIAL_HIBERNATE_RESUME_SERVICE);
235 if (r < 0)
236 return r;
8b6805a2 237
ff757c9d
ZJS
238 r = generator_write_timeouts(arg_dest,
239 arg_resume_device,
240 arg_resume_device,
241 arg_resume_options ?: arg_root_options,
242 NULL);
70e843fe
JR
243 if (r < 0)
244 return r;
245
d2c68822
IS
246 return 0;
247}
248
b8110a3e 249static int run(const char *dest, const char *dest_early, const char *dest_late) {
760e99bb 250 int r;
d2c68822 251
b8110a3e 252 arg_dest = ASSERT_PTR(dest);
d2c68822 253
32e27670 254 /* Don't even consider resuming outside of initrd. */
a79858bf 255 if (!in_initrd()) {
0b20c56e 256 log_debug("Not running in an initrd, exiting.");
77182cc6 257 return 0;
a79858bf 258 }
d2c68822 259
1d84ad94 260 r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0);
b5884878 261 if (r < 0)
da927ba9 262 log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
d2c68822 263
e83419d0 264 if (arg_noresume) {
0b20c56e 265 log_info("Found \"noresume\" on the kernel command line, exiting.");
77182cc6 266 return 0;
e83419d0
ZJS
267 }
268
c089af84
MY
269 if (!arg_resume_device && arg_resume_offset_set)
270 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
271 "Found resume_offset=%" PRIu64 " but resume= is unset, refusing.",
272 arg_resume_offset);
273
9deeca12
MY
274 r = parse_efi_hibernate_location();
275 if (r == -ENOMEM)
276 return r;
277
77182cc6 278 return process_resume();
d2c68822 279}
77182cc6 280
b8110a3e 281DEFINE_MAIN_GENERATOR_FUNCTION(run);