]>
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" |
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 | 29 | static const char *arg_dest = NULL; |
1d84ad94 | 30 | static char *arg_resume_device = NULL; |
8b6805a2 | 31 | static char *arg_resume_options = NULL; |
70e843fe | 32 | static char *arg_root_options = NULL; |
e83419d0 | 33 | static bool arg_noresume = false; |
760e99bb | 34 | static uint64_t arg_resume_offset = 0; |
c089af84 | 35 | static bool arg_resume_offset_set = false; |
d2c68822 | 36 | |
77182cc6 | 37 | STATIC_DESTRUCTOR_REGISTER(arg_resume_device, freep); |
8b6805a2 | 38 | STATIC_DESTRUCTOR_REGISTER(arg_resume_options, freep); |
70e843fe | 39 | STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep); |
77182cc6 | 40 | |
9deeca12 MY |
41 | #if ENABLE_EFI |
42 | typedef 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 | 53 | static 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 |
107 | static 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 | 189 | static 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 | 249 | static 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 | 281 | DEFINE_MAIN_GENERATOR_FUNCTION(run); |