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