#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
-#include <linux/fiemap.h>
#include <poll.h>
-#include <sys/stat.h>
-#include <sys/types.h>
#include <sys/timerfd.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
#include <unistd.h>
#include "sd-bus.h"
+#include "sd-device.h"
+#include "sd-id128.h"
#include "sd-messages.h"
-#include "btrfs-util.h"
+#include "battery-capacity.h"
+#include "battery-util.h"
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-util.h"
#include "constants.h"
#include "devnum-util.h"
+#include "efivars.h"
#include "exec-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
+#include "hibernate-util.h"
+#include "id128-util.h"
#include "io-util.h"
+#include "json.h"
#include "log.h"
#include "main-func.h"
+#include "os-util.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "sleep-config.h"
static SleepOperation arg_operation = _SLEEP_OPERATION_INVALID;
-static int write_hibernate_location_info(const HibernateLocation *hibernate_location) {
- char offset_str[DECIMAL_STR_MAX(uint64_t)];
- const char *resume_str;
- int r;
+static int write_efi_hibernate_location(const HibernationDevice *hibernation_device, bool required) {
+ int log_level = required ? LOG_ERR : LOG_DEBUG;
- assert(hibernate_location);
- assert(hibernate_location->swap);
+#if ENABLE_EFI
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_free_ char *formatted = NULL, *id = NULL, *image_id = NULL,
+ *version_id = NULL, *image_version = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ const char *uuid_str;
+ sd_id128_t uuid;
+ struct utsname uts = {};
+ int r, log_level_ignore = required ? LOG_WARNING : LOG_DEBUG;
- resume_str = FORMAT_DEVNUM(hibernate_location->devno);
+ assert(hibernation_device);
- r = write_string_file("/sys/power/resume", resume_str, WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (!is_efi_boot())
+ return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Not an EFI boot, passing HibernateLocation via EFI variable is not possible.");
+
+ r = sd_device_new_from_devnum(&device, 'b', hibernation_device->devno);
if (r < 0)
- return log_debug_errno(r, "Failed to write partition device to /sys/power/resume for '%s': '%s': %m",
- hibernate_location->swap->device, resume_str);
+ return log_full_errno(log_level, r, "Failed to create sd-device object for '%s': %m",
+ hibernation_device->path);
- log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->device, resume_str);
+ r = sd_device_get_property_value(device, "ID_FS_UUID", &uuid_str);
+ if (r < 0)
+ return log_full_errno(log_level, r, "Failed to get filesystem UUID for device '%s': %m",
+ hibernation_device->path);
- /* if it's a swap partition, we're done */
- if (streq(hibernate_location->swap->type, "partition"))
- return r;
+ r = sd_id128_from_string(uuid_str, &uuid);
+ if (r < 0)
+ return log_full_errno(log_level, r, "Failed to parse ID_FS_UUID '%s' for device '%s': %m",
+ uuid_str, hibernation_device->path);
- if (!streq(hibernate_location->swap->type, "file"))
- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
- "Invalid hibernate type: %s", hibernate_location->swap->type);
+ if (uname(&uts) < 0)
+ log_full_errno(log_level_ignore, errno, "Failed to get kernel info, ignoring: %m");
- /* Only available in 4.17+ */
- if (hibernate_location->offset > 0 && access("/sys/power/resume_offset", W_OK) < 0) {
- if (errno == ENOENT) {
- log_debug("Kernel too old, can't configure resume_offset for %s, ignoring: %" PRIu64,
- hibernate_location->swap->device, hibernate_location->offset);
- return 0;
- }
-
- return log_debug_errno(errno, "/sys/power/resume_offset not writable: %m");
- }
+ r = parse_os_release(NULL,
+ "ID", &id,
+ "IMAGE_ID", &image_id,
+ "VERSION_ID", &version_id,
+ "IMAGE_VERSION", &image_version);
+ if (r < 0)
+ log_full_errno(log_level_ignore, r, "Failed to parse os-release, ignoring: %m");
+
+ r = json_build(&v, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_UUID("uuid", uuid),
+ JSON_BUILD_PAIR_UNSIGNED("offset", hibernation_device->offset),
+ JSON_BUILD_PAIR_CONDITION(!isempty(uts.release), "kernelVersion", JSON_BUILD_STRING(uts.release)),
+ JSON_BUILD_PAIR_CONDITION(id, "osReleaseId", JSON_BUILD_STRING(id)),
+ JSON_BUILD_PAIR_CONDITION(image_id, "osReleaseImageId", JSON_BUILD_STRING(image_id)),
+ JSON_BUILD_PAIR_CONDITION(version_id, "osReleaseVersionId", JSON_BUILD_STRING(version_id)),
+ JSON_BUILD_PAIR_CONDITION(image_version, "osReleaseImageVersion", JSON_BUILD_STRING(image_version))));
+ if (r < 0)
+ return log_full_errno(log_level, r, "Failed to build JSON object: %m");
- xsprintf(offset_str, "%" PRIu64, hibernate_location->offset);
- r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
+ r = json_variant_format(v, 0, &formatted);
if (r < 0)
- return log_debug_errno(r, "Failed to write swap file offset to /sys/power/resume_offset for '%s': '%s': %m",
- hibernate_location->swap->device, offset_str);
+ return log_full_errno(log_level, r, "Failed to format JSON object: %m");
- log_debug("Wrote resume_offset= value for %s to /sys/power/resume_offset: %s", hibernate_location->swap->device, offset_str);
+ r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), formatted);
+ if (r < 0)
+ return log_full_errno(log_level, r, "Failed to set EFI variable HibernateLocation: %m");
+ log_debug("Set EFI variable HibernateLocation to '%s'.", formatted);
return 0;
+#else
+ return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "EFI support not enabled, passing HibernateLocation via EFI variable is not possible.");
+#endif
}
static int write_mode(char **modes) {
return r;
}
+/* Return true if wakeup type is APM timer */
+static int check_wakeup_type(void) {
+ static const char dmi_object_path[] = "/sys/firmware/dmi/entries/1-0/raw";
+ uint8_t wakeup_type_byte, tablesize;
+ _cleanup_free_ char *buf = NULL;
+ size_t bufsize;
+ int r;
+
+ /* implementation via dmi/entries */
+ r = read_full_virtual_file(dmi_object_path, &buf, &bufsize);
+ if (r < 0)
+ return log_debug_errno(r, "Unable to read %s: %m", dmi_object_path);
+ if (bufsize < 25)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Only read %zu bytes from %s (expected 25)",
+ bufsize, dmi_object_path);
+
+ /* index 1 stores the size of table */
+ tablesize = (uint8_t) buf[1];
+ if (tablesize < 25)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Table size less than the index[0x18] where waketype byte is available.");
+
+ wakeup_type_byte = (uint8_t) buf[24];
+ /* 0 is Reserved and 8 is AC Power Restored. As per table 12 in
+ * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.4.0.pdf */
+ if (wakeup_type_byte >= 128)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Expected value in range 0-127");
+
+ if (wakeup_type_byte == 3) {
+ log_debug("DMI BIOS System Information indicates wakeup type is APM Timer");
+ return true;
+ }
+
+ return false;
+}
+
static int lock_all_homes(void) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
NULL
};
- _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
+ _cleanup_(hibernation_device_done) HibernationDevice hibernation_device = {};
_cleanup_fclose_ FILE *f = NULL;
char **modes, **states;
int r;
setvbuf(f, NULL, _IONBF, 0);
/* Configure hibernation settings if we are supposed to hibernate */
- if (!strv_isempty(modes)) {
- r = find_hibernate_location(&hibernate_location);
+ if (sleep_operation_is_hibernation(operation)) {
+ bool resume_set;
+
+ r = find_suitable_hibernation_device(&hibernation_device);
if (r < 0)
return log_error_errno(r, "Failed to find location to hibernate to: %m");
- if (r == 0) { /* 0 means: no hibernation location was configured in the kernel so far, let's
- * do it ourselves then. > 0 means: kernel already had a configured hibernation
- * location which we shouldn't touch. */
- r = write_hibernate_location_info(hibernate_location);
+ resume_set = r > 0;
+
+ r = write_efi_hibernate_location(&hibernation_device, !resume_set);
+ if (!resume_set) {
+ if (r == -EOPNOTSUPP)
+ return log_error_errno(r, "No valid 'resume=' option found, refusing to hibernate.");
if (r < 0)
+ return r;
+
+ r = write_resume_config(hibernation_device.devno, hibernation_device.offset, hibernation_device.path);
+ if (r < 0) {
+ if (is_efi_boot())
+ (void) efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0);
+
return log_error_errno(r, "Failed to prepare for hibernation: %m");
+ }
}
r = write_mode(modes);
}
static int parse_argv(int argc, char *argv[]) {
+
enum {
ARG_VERSION = 0x100,
};
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
switch (c) {
+
case 'h':
return help();
default:
assert_not_reached();
+
}
if (argc - optind != 1)
}
static int run(int argc, char *argv[]) {
- _cleanup_(free_sleep_configp) SleepConfig *sleep_config = NULL;
+ _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL;
int r;
log_setup();
default:
r = execute(sleep_config, arg_operation, NULL);
break;
+
}
return r;