diff --git a/src/shared/fileio.c b/src/shared/fileio.c index 90572119d..62920cc60 100644 --- a/src/shared/fileio.c +++ b/src/shared/fileio.c @@ -623,3 +623,37 @@ int executable_is_script(const char *path, char **interpreter) { *interpreter = ans; return 1; } + +/** + * Retrieve one field from a file like /proc/self/status. + * pattern should start with '\n' and end with ':'. Whitespace + * after ':' will be skipped. field must be freed afterwards. + */ +int get_status_field(const char *filename, const char *pattern, char **field) { + _cleanup_free_ char *status = NULL; + char *t; + size_t len; + int r; + + assert(filename); + assert(field); + + r = read_full_file(filename, &status, NULL); + if (r < 0) + return r; + + t = strstr(status, pattern); + if (!t) + return -ENOENT; + + t += strlen(pattern); + t += strspn(t, WHITESPACE); + + len = strcspn(t, WHITESPACE); + + *field = strndup(t, len); + if (!*field) + return -ENOMEM; + + return 0; +} diff --git a/src/shared/fileio.h b/src/shared/fileio.h index a0aae28ba..59e41502b 100644 --- a/src/shared/fileio.h +++ b/src/shared/fileio.h @@ -37,3 +37,5 @@ int load_env_file(const char *fname, const char *separator, char ***l); int write_env_file(const char *fname, char **l); int executable_is_script(const char *path, char **interpreter); + +int get_status_field(const char *filename, const char *pattern, char **field); diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index cd3238b40..5ec7cce45 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -163,6 +163,46 @@ int can_sleep_disk(char **types) { return false; } +#define HIBERNATION_SWAP_THRESHOLD 0.98 + +static bool enough_memory_for_hibernation(void) { + _cleanup_free_ char *active = NULL, *swapfree = NULL; + unsigned long long act, swap; + int r; + + r = get_status_field("/proc/meminfo", "\nSwapFree:", &swapfree); + if (r < 0) { + log_error("Failed to retrieve SwapFree from /proc/meminfo: %s", strerror(-r)); + return false; + } + + r = safe_atollu(swapfree, &swap); + if (r < 0) { + log_error("Failed to parse SwapFree from /proc/meminfo: %s: %s", + swapfree, strerror(-r)); + return false; + } + + r = get_status_field("/proc/meminfo", "\nActive(anon):", &active); + if (r < 0) { + log_error("Failed to retrieve Active(anon) from /proc/meminfo: %s", strerror(-r)); + return false; + } + + r = safe_atollu(active, &act); + if (r < 0) { + log_error("Failed to parse Active(anon) from /proc/meminfo: %s: %s", + active, strerror(-r)); + return false; + } + + r = act <= swap * HIBERNATION_SWAP_THRESHOLD; + log_debug("Hibernation is %spossible, Active(anon)=%llu kB, SwapFree=%llu kB, threshold=%.2g%%", + r ? "" : "im", act, swap, 100*HIBERNATION_SWAP_THRESHOLD); + + return r; +} + int can_sleep(const char *verb) { _cleanup_strv_free_ char **modes = NULL, **states = NULL; int r; @@ -175,5 +215,8 @@ int can_sleep(const char *verb) { if (r < 0) return false; - return can_sleep_state(states) && can_sleep_disk(modes); + if (!can_sleep_state(states) || !can_sleep_disk(modes)) + return false; + + return streq(verb, "suspend") || enough_memory_for_hibernation(); } diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index ce7730410..6c10d9f1f 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -226,9 +226,29 @@ static void test_executable_is_script(void) { unlink(t); } +static void test_status_field(void) { + _cleanup_free_ char *t = NULL, *p = NULL, *s = NULL; + unsigned long long total, buffers; + + assert_se(get_status_field("/proc/self/status", "\nThreads:", &t) == 0); + puts(t); + assert_se(streq(t, "1")); + + assert_se(get_status_field("/proc/meminfo", "MemTotal:", &p) == 0); + puts(p); + assert_se(safe_atollu(p, &total) == 0); + + assert_se(get_status_field("/proc/meminfo", "\nBuffers:", &s) == 0); + puts(s); + assert_se(safe_atollu(s, &buffers) == 0); + + assert(buffers < total); +} + int main(int argc, char *argv[]) { test_parse_env_file(); test_parse_multiline_env_file(); test_executable_is_script(); + test_status_field(); return 0; } diff --git a/src/test/test-sleep.c b/src/test/test-sleep.c index c3cb9c531..545dfab92 100644 --- a/src/test/test-sleep.c +++ b/src/test/test-sleep.c @@ -40,14 +40,14 @@ int main(int argc, char* argv[]) { **shutdown = strv_new("shutdown", NULL), **freez = strv_new("freeze", NULL); - log_info("Can Standby: %s", yes_no(can_sleep_state(standby) > 0)); - log_info("Can Suspend: %s", yes_no(can_sleep_state(mem) > 0)); - log_info("Can Hibernate: %s", yes_no(can_sleep_state(disk) > 0)); - log_info("Can Hibernate+Suspend (Hybrid-Sleep): %s", yes_no(can_sleep_disk(suspend) > 0)); - log_info("Can Hibernate+Reboot: %s", yes_no(can_sleep_disk(reboot) > 0)); - log_info("Can Hibernate+Platform: %s", yes_no(can_sleep_disk(platform) > 0)); - log_info("Can Hibernate+Shutdown: %s", yes_no(can_sleep_disk(shutdown) > 0)); - log_info("Can Freeze: %s", yes_no(can_sleep_disk(freez) > 0)); + log_info("Standby configured: %s", yes_no(can_sleep_state(standby) > 0)); + log_info("Suspend configured: %s", yes_no(can_sleep_state(mem) > 0)); + log_info("Hibernate configured: %s", yes_no(can_sleep_state(disk) > 0)); + log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(can_sleep_disk(suspend) > 0)); + log_info("Hibernate+Reboot configured: %s", yes_no(can_sleep_disk(reboot) > 0)); + log_info("Hibernate+Platform configured: %s", yes_no(can_sleep_disk(platform) > 0)); + log_info("Hibernate+Shutdown configured: %s", yes_no(can_sleep_disk(shutdown) > 0)); + log_info("Freeze configured: %s", yes_no(can_sleep_state(freez) > 0)); log_info("Suspend configured and possible: %s", yes_no(can_sleep("suspend") > 0)); log_info("Hibernation configured and possible: %s", yes_no(can_sleep("hibernate") > 0));