From cd3a0de881419577325a0443c1f0216d47505526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 17 Mar 2013 16:03:05 -0400 Subject: [PATCH 2/2] systemd-sleep: add support for freeze and standby https://bugs.freedesktop.org/show_bug.cgi?id=57793 https://git.kernel.org/?p=linux/kernel/git/torvalds/linux.git;a=commit;h=7e73c5ae6e7991a6c01f6d096ff8afaef4458c36 http://lists.freedesktop.org/archives/systemd-devel/2013-February/009238.html --- man/systemd-suspend.service.xml | 110 +++++++++++++++++- man/systemd.xml | 2 +- src/sleep/sleep.c | 252 +++++++++++++++++++++++++++++++++------- 3 files changed, 313 insertions(+), 51 deletions(-) diff --git a/man/systemd-suspend.service.xml b/man/systemd-suspend.service.xml index 9b8bad4..aacfba0 100644 --- a/man/systemd-suspend.service.xml +++ b/man/systemd-suspend.service.xml @@ -60,10 +60,54 @@ Description + systemd supports three general + power-saving modes: + + + + suspend + + a low-power state + where execution of the OS is paused, + and complete power loss might result + in lost data, and which is fast to + enter and exit. This corresponds to + suspend, standby, or freeze states as + understood by the kernel. + + + + + hibernate + + a low-power state + where execution of the OS is paused, + and complete power loss does not + result in lost data, and which might + be slow to enter and exit. This + corresponds to the hibernation as + understood by the kernel. + + + + + hybrid-sleep + + a low-power state + where execution of the OS is paused, + which might be slow to enter, and on + complete power loss does not result in + lost data but might be slower to exit + in that case. This mode is called + suspend-to-both by the kernel. + + + + systemd-suspend.service is a system service that is pulled in by suspend.target and is responsible - for the actual system suspend. Similar, + for the actual system suspend. Similarly, systemd-hibernate.service is pulled in by hibernate.target to execute the actual hibernation. Finally, @@ -88,8 +132,8 @@ but the first argument is now "post". All executables in this directory are executed in parallel, and execution of - the action is not continued before all executables - finished. + the action is not continued until all executables + have finished. Note that scripts or binaries dropped in /usr/lib/systemd/system-sleep/ @@ -100,7 +144,7 @@ Note that systemd-suspend.service, - systemd-hibernate.service and + systemd-hibernate.service, and systemd-hybrid-sleep.service should never be executed directly. Instead, trigger system sleep states with a command such as @@ -108,9 +152,63 @@ similar. Internally, this service will echo a string like - mem into + "mem" into /sys/power/state, to trigger the - actual system suspend. + actual system suspend. What exactly is written + where can be configured by changing + systemd-suspend.service, + systemd-hibernate.service, or + systemd-hybrid-sleep.service + to execute systemd-sleep with + options to modify the + string written to /sys/power/disk + and to modify the + string written to /sys/power/state. + + + + + Options + + systemd-sleep understands the + following options: + + + + + + + Print a short help + text and exit. + + + + + Print the systemd version + identifier and exit. + + + + + Configure the string + written to + /sys/power/disk. + Should be one of the words in + /sys/power/disk. + + + + + + + Configure the string + written to + /sys/power/state. + Should be one of the words in + /sys/power/state. + + + diff --git a/man/systemd.xml b/man/systemd.xml index cd38c16..d009ed8 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -508,7 +508,7 @@ Directories - + System unit directories diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 0707625..41a5d8e 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -22,69 +22,136 @@ #include #include #include +#include -#include "log.h" -#include "util.h" #include "systemd/sd-id128.h" #include "systemd/sd-messages.h" +#include "log.h" +#include "util.h" +#include "strv.h" #include "fileio.h" +#include "build.h" -int main(int argc, char *argv[]) { - const char *verb; - char* arguments[4]; - int r; - FILE *f; +static char* arg_mode = NULL; +static char* arg_action = NULL; +static char* arg_command = NULL; - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); +static int write_mode(void) { + char **modes = NULL; + char _cleanup_strv_free_ **split_modes = NULL; + char **mode; + int r = 0; - if (argc != 2) { - log_error("Invalid number of arguments."); - r = -EINVAL; - goto finish; + if (arg_mode) { + modes = split_modes = strv_split(arg_mode, ","); + if (!modes) + return log_oom(); + } else if (streq(arg_command, "hibernate")) { + static char* _modes[] = {(char*) "platform", + (char*) "shutdown", + NULL}; + modes = _modes; + } else if (streq(arg_command, "hybrid-sleep")) { + static char* _modes[] = {(char*) "suspend", + (char*) "platform", + (char*) "shutdown", + NULL}; + modes = _modes; } - if (streq(argv[1], "suspend")) - verb = "mem"; - else if (streq(argv[1], "hibernate") || streq(argv[1], "hybrid-sleep")) - verb = "disk"; - else { - log_error("Unknown action '%s'.", argv[1]); - r = -EINVAL; - goto finish; + STRV_FOREACH(mode, modes) { + int k = write_one_line_file("/sys/power/disk", *mode); + if (k == 0) + return 0; + log_debug("Failed to write '%s' to /sys/power/disk: %s", + *mode, strerror(-k)); + if (r == 0) + r = k; } - /* Configure the hibernation mode */ - if (streq(argv[1], "hibernate")) { - if (write_one_line_file("/sys/power/disk", "platform") < 0) - write_one_line_file("/sys/power/disk", "shutdown"); - } else if (streq(argv[1], "hybrid-sleep")) { - if (write_one_line_file("/sys/power/disk", "suspend") < 0) - if (write_one_line_file("/sys/power/disk", "platform") < 0) - write_one_line_file("/sys/power/disk", "shutdown"); + if (r < 0) + log_error("Failed to write mode to /sys/power/disk: %s", + strerror(-r)); + + return r; +} + +static int write_action(FILE *f0) { + FILE _cleanup_fclose_ *f = f0; + char **verbs = NULL; + char _cleanup_strv_free_ **split_verbs = NULL; + char **verb; + int r = 0; + + if (arg_action) { + verbs = split_verbs = strv_split(arg_action, ","); + if (!verbs) + return log_oom(); + } else if (streq(arg_command, "suspend")) { + static char* _verbs[] = {(char*) "mem", + (char*) "standby", + (char*) "freeze", + NULL}; + verbs = _verbs; + } else if (streq(arg_command, "hibernate") || + streq(arg_command, "hybrid-sleep")) { + static char* _verbs[] = {(char*) "disk", + NULL}; + verbs = _verbs; + } else + assert(false); + + STRV_FOREACH(verb, verbs) { + int k; + + k = write_one_line_to_file(f, *verb); + if (k == 0) + return 0; + if (r == 0) + r = k; + + fclose(f); + f = fopen("/sys/power/state", "we"); + if (!f) { + log_error("Failed to open /sys/power/state: %m"); + return -errno; + } } + return r; +} + +static int execute(void) { + char* arguments[4]; + int r; + FILE *f; + + /* This file is opened first, so that if we hit an error, + * we can abort before modyfing any state. */ f = fopen("/sys/power/state", "we"); if (!f) { log_error("Failed to open /sys/power/state: %m"); - r = -errno; - goto finish; + return -errno; } + /* Configure the hibernation mode */ + r = write_mode(); + if (r < 0) + return r; + arguments[0] = NULL; arguments[1] = (char*) "pre"; - arguments[2] = argv[1]; + arguments[2] = arg_command; arguments[3] = NULL; execute_directory(SYSTEM_SLEEP_PATH, NULL, arguments); - if (streq(argv[1], "suspend")) + if (streq(arg_command, "suspend")) log_struct(LOG_INFO, MESSAGE_ID(SD_MESSAGE_SLEEP_START), "MESSAGE=Suspending system...", "SLEEP=suspend", NULL); - else if (streq(argv[1], "hibernate")) + else if (streq(arg_command, "hibernate")) log_struct(LOG_INFO, MESSAGE_ID(SD_MESSAGE_SLEEP_START), "MESSAGE=Hibernating system...", @@ -97,13 +164,11 @@ int main(int argc, char *argv[]) { "SLEEP=hybrid-sleep", NULL); - fputs(verb, f); - fputc('\n', f); - fflush(f); + r = write_action(f); + if (r < 0) + return r; - r = ferror(f) ? -errno : 0; - - if (streq(argv[1], "suspend")) + if (streq(arg_command, "suspend")) log_struct(LOG_INFO, MESSAGE_ID(SD_MESSAGE_SLEEP_STOP), "MESSAGE=System resumed.", @@ -119,10 +184,109 @@ int main(int argc, char *argv[]) { arguments[1] = (char*) "post"; execute_directory(SYSTEM_SLEEP_PATH, NULL, arguments); - fclose(f); + return r; +} + +static int help(void) { + printf("%s [OPTIONS...]\n\n" + "Hibernate, suspend or put the system to sleep.\n\n" + "Options:\n" + " -m --mode=MODE Target mode\n" + " -a --action=ACTION Mode of execution\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" + , program_invocation_short_name + ); -finish: + return 0; +} - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_MODE, + ARG_ACTION + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "mode", required_argument, NULL, ARG_MODE }, + { "action", required_argument, NULL, ARG_ACTION }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+h", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0 /* done */; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0 /* done */; + + case ARG_MODE: + if (arg_mode) { + log_error("Cannot specify --mode more than once"); + return -EINVAL; + } + arg_mode = optarg; + break; + + case ARG_ACTION: + if (arg_action) { + log_error("Cannot specify --action more than once"); + return -EINVAL; + } + arg_action = optarg; + break; + + case '?': + return -EINVAL; + + default: + log_error("Unknown option code %c", c); + return -EINVAL; + } + + if (argc - optind != 1) { + log_error("Usage: %s [OPTION...] COMMAND", + program_invocation_short_name); + return -EINVAL; + } + + arg_command = argv[optind]; + if (!streq(arg_command, "suspend") && + !streq(arg_command, "hibernate") && + !streq(arg_command, "hybrid-sleep")) { + log_error("Unknown action '%s'.", arg_command); + return -EINVAL; + } + + return 1 /* work to do */; +} + +int main(int argc, char *argv[]) { + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = execute(); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } -- 1.8.1.4