]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/sleep-config.c
shared/sleep-config: two more error handling fixes, use structured initialization
[thirdparty/systemd.git] / src / shared / sleep-config.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 Copyright © 2018 Dell Inc.
4 ***/
5
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <linux/fs.h>
9 #include <stdbool.h>
10 #include <stddef.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <sys/ioctl.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <syslog.h>
17 #include <unistd.h>
18
19 #include "alloc-util.h"
20 #include "btrfs-util.h"
21 #include "conf-parser.h"
22 #include "def.h"
23 #include "env-util.h"
24 #include "errno-util.h"
25 #include "fd-util.h"
26 #include "fileio.h"
27 #include "log.h"
28 #include "macro.h"
29 #include "parse-util.h"
30 #include "path-util.h"
31 #include "sleep-config.h"
32 #include "stdio-util.h"
33 #include "string-util.h"
34 #include "strv.h"
35 #include "time-util.h"
36
37 int parse_sleep_config(SleepConfig **ret_sleep_config) {
38 _cleanup_(free_sleep_configp) SleepConfig *sc;
39 int allow_suspend = -1, allow_hibernate = -1,
40 allow_s2h = -1, allow_hybrid_sleep = -1;
41
42 sc = new0(SleepConfig, 1);
43 if (!sc)
44 return log_oom();
45
46 const ConfigTableItem items[] = {
47 { "Sleep", "AllowSuspend", config_parse_tristate, 0, &allow_suspend },
48 { "Sleep", "AllowHibernation", config_parse_tristate, 0, &allow_hibernate },
49 { "Sleep", "AllowSuspendThenHibernate", config_parse_tristate, 0, &allow_s2h },
50 { "Sleep", "AllowHybridSleep", config_parse_tristate, 0, &allow_hybrid_sleep },
51
52 { "Sleep", "SuspendMode", config_parse_strv, 0, &sc->suspend_modes },
53 { "Sleep", "SuspendState", config_parse_strv, 0, &sc->suspend_states },
54 { "Sleep", "HibernateMode", config_parse_strv, 0, &sc->hibernate_modes },
55 { "Sleep", "HibernateState", config_parse_strv, 0, &sc->hibernate_states },
56 { "Sleep", "HybridSleepMode", config_parse_strv, 0, &sc->hybrid_modes },
57 { "Sleep", "HybridSleepState", config_parse_strv, 0, &sc->hybrid_states },
58
59 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_sec},
60 {}
61 };
62
63 (void) config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
64 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
65 "Sleep\0", config_item_table_lookup, items,
66 CONFIG_PARSE_WARN, NULL);
67
68 /* use default values unless set */
69 sc->allow_suspend = allow_suspend != 0;
70 sc->allow_hibernate = allow_hibernate != 0;
71 sc->allow_hybrid_sleep = allow_hybrid_sleep >= 0 ? allow_hybrid_sleep
72 : (allow_suspend != 0 && allow_hibernate != 0);
73 sc->allow_s2h = allow_s2h >= 0 ? allow_s2h
74 : (allow_suspend != 0 && allow_hibernate != 0);
75
76 if (!sc->suspend_states)
77 sc->suspend_states = strv_new("mem", "standby", "freeze");
78 if (!sc->hibernate_modes)
79 sc->hibernate_modes = strv_new("platform", "shutdown");
80 if (!sc->hibernate_states)
81 sc->hibernate_states = strv_new("disk");
82 if (!sc->hybrid_modes)
83 sc->hybrid_modes = strv_new("suspend", "platform", "shutdown");
84 if (!sc->hybrid_states)
85 sc->hybrid_states = strv_new("disk");
86 if (sc->hibernate_delay_sec == 0)
87 sc->hibernate_delay_sec = 2 * USEC_PER_HOUR;
88
89 /* ensure values set for all required fields */
90 if (!sc->suspend_states || !sc->hibernate_modes
91 || !sc->hibernate_states || !sc->hybrid_modes || !sc->hybrid_states)
92 return log_oom();
93
94 *ret_sleep_config = TAKE_PTR(sc);
95
96 return 0;
97 }
98
99 int can_sleep_state(char **types) {
100 char **type;
101 int r;
102 _cleanup_free_ char *p = NULL;
103
104 if (strv_isempty(types))
105 return true;
106
107 /* If /sys is read-only we cannot sleep */
108 if (access("/sys/power/state", W_OK) < 0)
109 return false;
110
111 r = read_one_line_file("/sys/power/state", &p);
112 if (r < 0)
113 return false;
114
115 STRV_FOREACH(type, types) {
116 const char *word, *state;
117 size_t l, k;
118
119 k = strlen(*type);
120 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
121 if (l == k && memcmp(word, *type, l) == 0)
122 return true;
123 }
124
125 return false;
126 }
127
128 int can_sleep_disk(char **types) {
129 char **type;
130 int r;
131 _cleanup_free_ char *p = NULL;
132
133 if (strv_isempty(types))
134 return true;
135
136 /* If /sys is read-only we cannot sleep */
137 if (access("/sys/power/disk", W_OK) < 0) {
138 log_debug_errno(errno, "/sys/power/disk is not writable: %m");
139 return false;
140 }
141
142 r = read_one_line_file("/sys/power/disk", &p);
143 if (r < 0) {
144 log_debug_errno(r, "Couldn't read /sys/power/disk: %m");
145 return false;
146 }
147
148 STRV_FOREACH(type, types) {
149 const char *word, *state;
150 size_t l, k;
151
152 k = strlen(*type);
153 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
154 if (l == k && memcmp(word, *type, l) == 0)
155 return true;
156
157 if (l == k + 2 &&
158 word[0] == '[' &&
159 memcmp(word + 1, *type, l - 2) == 0 &&
160 word[l-1] == ']')
161 return true;
162 }
163 }
164
165 return false;
166 }
167
168 #define HIBERNATION_SWAP_THRESHOLD 0.98
169
170 SwapEntry* swap_entry_free(SwapEntry *se) {
171 if (!se)
172 return NULL;
173
174 free(se->device);
175 free(se->type);
176
177 return mfree(se);
178 }
179
180 HibernateLocation* hibernate_location_free(HibernateLocation *hl) {
181 if (!hl)
182 return NULL;
183
184 swap_entry_free(hl->swap);
185 free(hl->resume);
186
187 return mfree(hl);
188 }
189
190 static int swap_device_to_major_minor(const SwapEntry *swap, char **ret) {
191 _cleanup_free_ char *major_minor = NULL;
192 _cleanup_close_ int fd = -1;
193 struct stat sb;
194 dev_t swap_dev;
195 int r;
196
197 assert(swap);
198 assert(swap->device);
199 assert(swap->type);
200
201 fd = open(swap->device, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
202 if (fd < 0)
203 return log_debug_errno(errno, "Unable to open '%s': %m", swap->device);
204
205 r = fstat(fd, &sb);
206 if (r < 0)
207 return log_debug_errno(errno, "Unable to stat %s: %m", swap->device);
208
209 swap_dev = streq(swap->type, "partition") ? sb.st_rdev : sb.st_dev;
210 if (asprintf(&major_minor, "%u:%u", major(swap_dev), minor(swap_dev)) < 0)
211 return log_oom();
212
213 *ret = TAKE_PTR(major_minor);
214
215 return 0;
216 }
217
218 static int calculate_swap_file_offset(const SwapEntry *swap, uint64_t *ret_offset) {
219 _cleanup_close_ int fd = -1;
220 _cleanup_free_ struct fiemap *fiemap = NULL;
221 struct stat sb;
222 int r, btrfs;
223
224 assert(swap);
225 assert(swap->device);
226 assert(streq(swap->type, "file"));
227
228 fd = open(swap->device, O_RDONLY|O_CLOEXEC|O_NOCTTY);
229 if (fd < 0)
230 return log_error_errno(errno, "Failed to open %s: %m", swap->device);
231
232 if (fstat(fd, &sb) < 0)
233 return log_error_errno(errno, "Failed to stat %s: %m", swap->device);
234
235 btrfs = btrfs_is_filesystem(fd);
236 if (btrfs < 0)
237 return log_error_errno(btrfs, "Error checking %s for Btrfs filesystem: %m", swap->device);
238 else if (btrfs > 0) {
239 log_debug("Detection of swap file offset on Btrfs is not supported: %s; skipping", swap->device);
240 *ret_offset = 0;
241 return 0;
242 }
243
244 r = read_fiemap(fd, &fiemap);
245 if (r < 0)
246 return log_debug_errno(r, "Unable to read extent map for '%s': %m", swap->device);
247
248 *ret_offset = fiemap->fm_extents[0].fe_physical / page_size();
249
250 return 0;
251 }
252
253 static int read_resume_files(char **ret_resume, uint64_t *ret_resume_offset) {
254 _cleanup_free_ char *resume, *resume_offset_str = NULL;
255 uint64_t resume_offset = 0;
256 int r;
257
258 r = read_one_line_file("/sys/power/resume", &resume);
259 if (r < 0)
260 return log_debug_errno(r, "Error reading from /sys/power/resume: %m");
261
262 r = read_one_line_file("/sys/power/resume_offset", &resume_offset_str);
263 if (r == -ENOENT)
264 log_debug("Kernel does not support resume_offset; swap file offset detection will be skipped.");
265 else if (r < 0)
266 return log_debug_errno(r, "Error reading from /sys/power/resume_offset: %m");
267 else {
268 r = safe_atou64(resume_offset_str, &resume_offset);
269 if (r < 0)
270 return log_error_errno(r, "Failed to parse value in /sys/power/resume_offset \"%s\": %m", resume_offset_str);
271 }
272
273 if (resume_offset > 0 && streq(*ret_resume, "0:0")) {
274 log_debug("Found offset in /sys/power/resume_offset: %" PRIu64 "; no device id found in /sys/power/resume; ignoring resume_offset", resume_offset);
275 resume_offset = 0;
276 }
277
278 *ret_resume = TAKE_PTR(resume);
279 *ret_resume_offset = resume_offset;
280
281 return 0;
282 }
283
284 static bool location_is_resume_device(const HibernateLocation *location, const char *sys_resume, const uint64_t sys_offset) {
285 assert(location);
286 assert(location->resume);
287 assert(sys_resume);
288
289 return streq(sys_resume, location->resume) && sys_offset == location->resume_offset;
290 }
291
292 /*
293 * Attempt to find the hibernation location by parsing /proc/swaps, /sys/power/resume, and
294 * /sys/power/resume_offset.
295 *
296 * Returns:
297 * 1 - HibernateLocation matches values found in /sys/power/resume & /sys/power/resume_offset
298 * 0 - HibernateLocation is highest priority swap with most remaining space; no valid values exist in /sys/power/resume & /sys/power/resume_offset
299 * negative value in the case of error
300 */
301 int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
302 _cleanup_fclose_ FILE *f = NULL;
303 _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
304 _cleanup_free_ char *sys_resume = NULL;
305 uint64_t sys_offset = 0;
306 unsigned i;
307 int r;
308
309 /* read the /sys/power/resume & /sys/power/resume_offset values */
310 r = read_resume_files(&sys_resume, &sys_offset);
311 if (r < 0)
312 return r;
313
314 f = fopen("/proc/swaps", "re");
315 if (!f) {
316 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
317 "Failed to open /proc/swaps: %m");
318 return negative_errno();
319 }
320
321 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
322 for (i = 1;; i++) {
323 _cleanup_(swap_entry_freep) SwapEntry *swap = NULL;
324 uint64_t swap_offset = 0;
325 int k;
326
327 swap = new0(SwapEntry, 1);
328 if (!swap)
329 return log_oom();
330
331 k = fscanf(f,
332 "%ms " /* device/file */
333 "%ms " /* type of swap */
334 "%" PRIu64 /* swap size */
335 "%" PRIu64 /* used */
336 "%i\n", /* priority */
337 &swap->device, &swap->type, &swap->size, &swap->used, &swap->priority);
338 if (k == EOF)
339 break;
340 if (k != 5) {
341 log_warning("Failed to parse /proc/swaps:%u", i);
342 continue;
343 }
344
345 if (streq(swap->type, "file")) {
346 if (endswith(swap->device, "\\040(deleted)")) {
347 log_warning("Ignoring deleted swap file '%s'.", swap->device);
348 continue;
349 }
350
351 r = calculate_swap_file_offset(swap, &swap_offset);
352 if (r < 0)
353 return r;
354 } else if (streq(swap->type, "partition")) {
355 const char *fn;
356
357 fn = path_startswith(swap->device, "/dev/");
358 if (fn && startswith(fn, "zram")) {
359 log_debug("Ignoring compressed RAM swap device '%s'.", swap->device);
360 continue;
361 }
362 } else {
363 log_debug("Swap type %s is unsupported for hibernation: %s; skipping", swap->type, swap->device);
364 continue;
365 }
366
367 /* prefer resume device or highest priority swap with most remaining space */
368 if (!hibernate_location || swap->priority > hibernate_location->swap->priority
369 || ((swap->priority == hibernate_location->swap->priority)
370 && (swap->size - swap->used) > (hibernate_location->swap->size - hibernate_location->swap->used))) {
371
372 _cleanup_free_ char *swap_device_id = NULL;
373 r = swap_device_to_major_minor(swap, &swap_device_id);
374 if (r < 0)
375 return r;
376
377 hibernate_location = hibernate_location_free(hibernate_location);
378 hibernate_location = new(HibernateLocation, 1);
379 if (!hibernate_location)
380 return log_oom();
381
382 *hibernate_location = (HibernateLocation) {
383 .resume = TAKE_PTR(swap_device_id),
384 .resume_offset = swap_offset,
385 .swap = TAKE_PTR(swap),
386 };
387
388 /* if the swap is the resume device, stop looping swaps */
389 if (location_is_resume_device(hibernate_location, sys_resume, sys_offset))
390 break;
391 }
392 }
393
394 if (!hibernate_location)
395 return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "No swap partitions or files were found");
396
397 if (!streq(sys_resume, "0:0") && !location_is_resume_device(hibernate_location, sys_resume, sys_offset))
398 return log_warning_errno(SYNTHETIC_ERRNO(ENOSYS), "/sys/power/resume and /sys/power/resume_offset has no matching entry in /proc/swaps; Hibernation will fail: resume=%s, resume_offset=%" PRIu64,
399 sys_resume, sys_offset);
400
401 log_debug("Hibernation will attempt to use swap entry with path: %s, device: %s, offset: %" PRIu64 ", priority: %i",
402 hibernate_location->swap->device, hibernate_location->resume, hibernate_location->resume_offset, hibernate_location->swap->priority);
403
404 *ret_hibernate_location = TAKE_PTR(hibernate_location);
405
406 if (location_is_resume_device(*ret_hibernate_location, sys_resume, sys_offset))
407 return 1;
408
409 return 0;
410 }
411
412 static bool enough_swap_for_hibernation(void) {
413 _cleanup_free_ char *active = NULL;
414 _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
415 unsigned long long act = 0;
416 int r;
417
418 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
419 return true;
420
421 r = find_hibernate_location(&hibernate_location);
422 if (r < 0)
423 return false;
424
425 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
426 if (r < 0) {
427 log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
428 return false;
429 }
430
431 r = safe_atollu(active, &act);
432 if (r < 0) {
433 log_debug_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", active);
434 return false;
435 }
436
437 r = act <= (hibernate_location->swap->size - hibernate_location->swap->used) * HIBERNATION_SWAP_THRESHOLD;
438 log_debug("%s swap for hibernation, Active(anon)=%llu kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%",
439 r ? "Enough" : "Not enough", act, hibernate_location->swap->size, hibernate_location->swap->used, 100*HIBERNATION_SWAP_THRESHOLD);
440
441 return r;
442 }
443
444 int read_fiemap(int fd, struct fiemap **ret) {
445 _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
446 struct stat statinfo;
447 uint32_t result_extents = 0;
448 uint64_t fiemap_start = 0, fiemap_length;
449 const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
450 size_t fiemap_allocated = n_extra, result_fiemap_allocated = n_extra;
451
452 if (fstat(fd, &statinfo) < 0)
453 return log_debug_errno(errno, "Cannot determine file size: %m");
454 if (!S_ISREG(statinfo.st_mode))
455 return -ENOTTY;
456 fiemap_length = statinfo.st_size;
457
458 /* Zero this out in case we run on a file with no extents */
459 fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
460 if (!fiemap)
461 return -ENOMEM;
462
463 result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
464 if (!result_fiemap)
465 return -ENOMEM;
466
467 /* XFS filesystem has incorrect implementation of fiemap ioctl and
468 * returns extents for only one block-group at a time, so we need
469 * to handle it manually, starting the next fiemap call from the end
470 * of the last extent
471 */
472 while (fiemap_start < fiemap_length) {
473 *fiemap = (struct fiemap) {
474 .fm_start = fiemap_start,
475 .fm_length = fiemap_length,
476 .fm_flags = FIEMAP_FLAG_SYNC,
477 };
478
479 /* Find out how many extents there are */
480 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
481 return log_debug_errno(errno, "Failed to read extents: %m");
482
483 /* Nothing to process */
484 if (fiemap->fm_mapped_extents == 0)
485 break;
486
487 /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
488 * the extents for the whole file. Add space for the initial struct fiemap. */
489 if (!greedy_realloc0((void**) &fiemap, &fiemap_allocated,
490 n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
491 return -ENOMEM;
492
493 fiemap->fm_extent_count = fiemap->fm_mapped_extents;
494 fiemap->fm_mapped_extents = 0;
495
496 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
497 return log_debug_errno(errno, "Failed to read extents: %m");
498
499 /* Resize result_fiemap to allow us to copy in the extents */
500 if (!greedy_realloc((void**) &result_fiemap, &result_fiemap_allocated,
501 n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
502 return -ENOMEM;
503
504 memcpy(result_fiemap->fm_extents + result_extents,
505 fiemap->fm_extents,
506 sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
507
508 result_extents += fiemap->fm_mapped_extents;
509
510 /* Highly unlikely that it is zero */
511 if (_likely_(fiemap->fm_mapped_extents > 0)) {
512 uint32_t i = fiemap->fm_mapped_extents - 1;
513
514 fiemap_start = fiemap->fm_extents[i].fe_logical +
515 fiemap->fm_extents[i].fe_length;
516
517 if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
518 break;
519 }
520 }
521
522 memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
523 result_fiemap->fm_mapped_extents = result_extents;
524 *ret = TAKE_PTR(result_fiemap);
525 return 0;
526 }
527
528 static int can_sleep_internal(const char *verb, bool check_allowed, const SleepConfig *sleep_config);
529
530 static bool can_s2h(const SleepConfig *sleep_config) {
531 const char *p;
532 int r;
533
534 if (!clock_supported(CLOCK_BOOTTIME_ALARM)) {
535 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
536 "CLOCK_BOOTTIME_ALARM is not supported");
537 return false;
538 }
539
540 FOREACH_STRING(p, "suspend", "hibernate") {
541 r = can_sleep_internal(p, false, sleep_config);
542 if (IN_SET(r, 0, -ENOSPC, -EADV)) {
543 log_debug("Unable to %s system.", p);
544 return false;
545 }
546 if (r < 0)
547 return log_debug_errno(r, "Failed to check if %s is possible: %m", p);
548 }
549
550 return true;
551 }
552
553 static int can_sleep_internal(const char *verb, bool check_allowed, const SleepConfig *sleep_config) {
554 bool allow;
555 char **modes = NULL, **states = NULL;
556 int r;
557
558 assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
559
560 r = sleep_settings(verb, sleep_config, &allow, &modes, &states);
561 if (r < 0)
562 return false;
563
564 if (check_allowed && !allow) {
565 log_debug("Sleep mode \"%s\" is disabled by configuration.", verb);
566 return false;
567 }
568
569 if (streq(verb, "suspend-then-hibernate"))
570 return can_s2h(sleep_config);
571
572 if (!can_sleep_state(states) || !can_sleep_disk(modes))
573 return false;
574
575 if (streq(verb, "suspend"))
576 return true;
577
578 if (!enough_swap_for_hibernation())
579 return -ENOSPC;
580
581 return true;
582 }
583
584 int can_sleep(const char *verb) {
585 _cleanup_(free_sleep_configp) SleepConfig *sleep_config = NULL;
586 int r;
587
588 r = parse_sleep_config(&sleep_config);
589 if (r < 0)
590 return r;
591
592 return can_sleep_internal(verb, true, sleep_config);
593 }
594
595 int sleep_settings(const char *verb, const SleepConfig *sleep_config, bool *ret_allow, char ***ret_modes, char ***ret_states) {
596
597 assert(verb);
598 assert(sleep_config);
599 assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
600
601 if (streq(verb, "suspend")) {
602 *ret_allow = sleep_config->allow_suspend;
603 *ret_modes = sleep_config->suspend_modes;
604 *ret_states = sleep_config->suspend_states;
605 } else if (streq(verb, "hibernate")) {
606 *ret_allow = sleep_config->allow_hibernate;
607 *ret_modes = sleep_config->hibernate_modes;
608 *ret_states = sleep_config->hibernate_states;
609 } else if (streq(verb, "hybrid-sleep")) {
610 *ret_allow = sleep_config->allow_hybrid_sleep;
611 *ret_modes = sleep_config->hybrid_modes;
612 *ret_states = sleep_config->hybrid_states;
613 } else if (streq(verb, "suspend-then-hibernate")) {
614 *ret_allow = sleep_config->allow_s2h;
615 *ret_modes = *ret_states = NULL;
616 }
617
618 /* suspend modes empty by default */
619 if ((!ret_modes && !streq(verb, "suspend")) || !ret_states)
620 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No modes or states set for %s; Check sleep.conf", verb);
621
622 return 0;
623 }
624
625 void free_sleep_config(SleepConfig *sc) {
626 if (!sc)
627 return;
628
629 strv_free(sc->suspend_modes);
630 strv_free(sc->suspend_states);
631
632 strv_free(sc->hibernate_modes);
633 strv_free(sc->hibernate_states);
634
635 strv_free(sc->hybrid_modes);
636 strv_free(sc->hybrid_states);
637
638 free(sc);
639 }