LCOV - code coverage report
Current view: top level - shared - sleep-config.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 138 264 52.3 %
Date: 2019-08-22 15:41:25 Functions: 9 13 69.2 %

          Line data    Source code
       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 "conf-parser.h"
      21             : #include "def.h"
      22             : #include "env-util.h"
      23             : #include "errno-util.h"
      24             : #include "fd-util.h"
      25             : #include "fileio.h"
      26             : #include "log.h"
      27             : #include "macro.h"
      28             : #include "parse-util.h"
      29             : #include "path-util.h"
      30             : #include "sleep-config.h"
      31             : #include "string-util.h"
      32             : #include "strv.h"
      33             : #include "time-util.h"
      34             : 
      35           5 : int parse_sleep_config(SleepConfig **ret_sleep_config) {
      36           5 :         _cleanup_(free_sleep_configp) SleepConfig *sc;
      37           5 :         int allow_suspend = -1, allow_hibernate = -1,
      38           5 :             allow_s2h = -1, allow_hybrid_sleep = -1;
      39             : 
      40           5 :         sc = new0(SleepConfig, 1);
      41           5 :         if (!sc)
      42           0 :                 return log_oom();
      43             : 
      44          40 :         const ConfigTableItem items[] = {
      45             :                 { "Sleep", "AllowSuspend",              config_parse_tristate, 0, &allow_suspend },
      46             :                 { "Sleep", "AllowHibernation",          config_parse_tristate, 0, &allow_hibernate },
      47             :                 { "Sleep", "AllowSuspendThenHibernate", config_parse_tristate, 0, &allow_s2h },
      48             :                 { "Sleep", "AllowHybridSleep",          config_parse_tristate, 0, &allow_hybrid_sleep },
      49             : 
      50           5 :                 { "Sleep", "SuspendMode",               config_parse_strv, 0, &sc->suspend_modes  },
      51           5 :                 { "Sleep", "SuspendState",              config_parse_strv, 0, &sc->suspend_states },
      52           5 :                 { "Sleep", "HibernateMode",             config_parse_strv, 0, &sc->hibernate_modes  },
      53           5 :                 { "Sleep", "HibernateState",            config_parse_strv, 0, &sc->hibernate_states },
      54           5 :                 { "Sleep", "HybridSleepMode",           config_parse_strv, 0, &sc->hybrid_modes  },
      55           5 :                 { "Sleep", "HybridSleepState",          config_parse_strv, 0, &sc->hybrid_states },
      56             : 
      57           5 :                 { "Sleep", "HibernateDelaySec",         config_parse_sec,  0, &sc->hibernate_delay_sec},
      58             :                 {}
      59             :         };
      60             : 
      61           5 :         (void) config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
      62             :                                         CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
      63             :                                         "Sleep\0", config_item_table_lookup, items,
      64             :                                         CONFIG_PARSE_WARN, NULL);
      65             : 
      66             :         /* use default values unless set */
      67           5 :         sc->allow_suspend = allow_suspend != 0;
      68           5 :         sc->allow_hibernate = allow_hibernate != 0;
      69          10 :         sc->allow_hybrid_sleep = allow_hybrid_sleep >= 0 ? allow_hybrid_sleep
      70           5 :                 : (allow_suspend != 0 && allow_hibernate != 0);
      71          10 :         sc->allow_s2h = allow_s2h >= 0 ? allow_s2h
      72           5 :                 : (allow_suspend != 0 && allow_hibernate != 0);
      73             : 
      74           5 :         if (!sc->suspend_states)
      75           5 :                 sc->suspend_states = strv_new("mem", "standby", "freeze");
      76           5 :         if (!sc->hibernate_modes)
      77           5 :                 sc->hibernate_modes = strv_new("platform", "shutdown");
      78           5 :         if (!sc->hibernate_states)
      79           5 :                 sc->hibernate_states = strv_new("disk");
      80           5 :         if (!sc->hybrid_modes)
      81           5 :                 sc->hybrid_modes = strv_new("suspend", "platform", "shutdown");
      82           5 :         if (!sc->hybrid_states)
      83           5 :                 sc->hybrid_states = strv_new("disk");
      84           5 :         if (sc->hibernate_delay_sec == 0)
      85           5 :                 sc->hibernate_delay_sec = 2 * USEC_PER_HOUR;
      86             : 
      87             :         /* ensure values set for all required fields */
      88           5 :         if (!sc->suspend_states || !sc->hibernate_modes
      89           5 :             || !sc->hibernate_states || !sc->hybrid_modes || !sc->hybrid_states)
      90           0 :                 return log_oom();
      91             : 
      92           5 :         *ret_sleep_config = TAKE_PTR(sc);
      93             : 
      94           5 :         return 0;
      95             : }
      96             : 
      97           8 : int can_sleep_state(char **types) {
      98             :         char **type;
      99             :         int r;
     100           8 :         _cleanup_free_ char *p = NULL;
     101             : 
     102           8 :         if (strv_isempty(types))
     103           0 :                 return true;
     104             : 
     105             :         /* If /sys is read-only we cannot sleep */
     106           8 :         if (access("/sys/power/state", W_OK) < 0)
     107           8 :                 return false;
     108             : 
     109           0 :         r = read_one_line_file("/sys/power/state", &p);
     110           0 :         if (r < 0)
     111           0 :                 return false;
     112             : 
     113           0 :         STRV_FOREACH(type, types) {
     114             :                 const char *word, *state;
     115             :                 size_t l, k;
     116             : 
     117           0 :                 k = strlen(*type);
     118           0 :                 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
     119           0 :                         if (l == k && memcmp(word, *type, l) == 0)
     120           0 :                                 return true;
     121             :         }
     122             : 
     123           0 :         return false;
     124             : }
     125             : 
     126           4 : int can_sleep_disk(char **types) {
     127             :         char **type;
     128             :         int r;
     129           4 :         _cleanup_free_ char *p = NULL;
     130             : 
     131           4 :         if (strv_isempty(types))
     132           0 :                 return true;
     133             : 
     134             :         /* If /sys is read-only we cannot sleep */
     135           4 :         if (access("/sys/power/disk", W_OK) < 0) {
     136           4 :                 log_debug_errno(errno, "/sys/power/disk is not writable: %m");
     137           4 :                 return false;
     138             :         }
     139             : 
     140           0 :         r = read_one_line_file("/sys/power/disk", &p);
     141           0 :         if (r < 0) {
     142           0 :                 log_debug_errno(r, "Couldn't read /sys/power/disk: %m");
     143           0 :                 return false;
     144             :         }
     145             : 
     146           0 :         STRV_FOREACH(type, types) {
     147             :                 const char *word, *state;
     148             :                 size_t l, k;
     149             : 
     150           0 :                 k = strlen(*type);
     151           0 :                 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
     152           0 :                         if (l == k && memcmp(word, *type, l) == 0)
     153           0 :                                 return true;
     154             : 
     155           0 :                         if (l == k + 2 &&
     156           0 :                             word[0] == '[' &&
     157           0 :                             memcmp(word + 1, *type, l - 2) == 0 &&
     158           0 :                             word[l-1] == ']')
     159           0 :                                 return true;
     160             :                 }
     161             :         }
     162             : 
     163           0 :         return false;
     164             : }
     165             : 
     166             : #define HIBERNATION_SWAP_THRESHOLD 0.98
     167             : 
     168             : /* entry in /proc/swaps */
     169             : typedef struct SwapEntry {
     170             :         char *device;
     171             :         char *type;
     172             :         uint64_t size;
     173             :         uint64_t used;
     174             :         int priority;
     175             : } SwapEntry;
     176             : 
     177           0 : static SwapEntry* swap_entry_free(SwapEntry *se) {
     178           0 :         if (!se)
     179           0 :                 return NULL;
     180             : 
     181           0 :         free(se->device);
     182           0 :         free(se->type);
     183             : 
     184           0 :         return mfree(se);
     185             : }
     186             : 
     187           0 : DEFINE_TRIVIAL_CLEANUP_FUNC(SwapEntry*, swap_entry_free);
     188             : 
     189           0 : int find_hibernate_location(char **device, char **type, uint64_t *size, uint64_t *used) {
     190           0 :         _cleanup_fclose_ FILE *f;
     191           0 :         _cleanup_(swap_entry_freep) SwapEntry *selected_swap = NULL;
     192             :         unsigned i;
     193             : 
     194           0 :         f = fopen("/proc/swaps", "re");
     195           0 :         if (!f) {
     196           0 :                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
     197             :                          "Failed to retrieve open /proc/swaps: %m");
     198           0 :                 return negative_errno();
     199             :         }
     200             : 
     201           0 :         (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
     202             : 
     203           0 :         for (i = 1;; i++) {
     204           0 :                 _cleanup_(swap_entry_freep) SwapEntry *swap = NULL;
     205             :                 int k;
     206             : 
     207           0 :                 swap = new0(SwapEntry, 1);
     208           0 :                 if (!swap)
     209           0 :                         return log_oom();
     210             : 
     211           0 :                 k = fscanf(f,
     212             :                            "%ms "       /* device/file */
     213             :                            "%ms "       /* type of swap */
     214             :                            "%" PRIu64   /* swap size */
     215             :                            "%" PRIu64   /* used */
     216             :                            "%i\n",      /* priority */
     217           0 :                            &swap->device, &swap->type, &swap->size, &swap->used, &swap->priority);
     218           0 :                 if (k == EOF)
     219           0 :                         break;
     220           0 :                 if (k != 5) {
     221           0 :                         log_warning("Failed to parse /proc/swaps:%u", i);
     222           0 :                         continue;
     223             :                 }
     224             : 
     225           0 :                 if (streq(swap->type, "file")) {
     226             : 
     227           0 :                         if (endswith(swap->device, "\\040(deleted)")) {
     228           0 :                                 log_warning("Ignoring deleted swap file '%s'.", swap->device);
     229           0 :                                 continue;
     230             :                         }
     231             : 
     232           0 :                 } else if (streq(swap->type, "partition")) {
     233             :                         const char *fn;
     234             : 
     235           0 :                         fn = path_startswith(swap->device, "/dev/");
     236           0 :                         if (fn && startswith(fn, "zram")) {
     237           0 :                                 log_debug("Ignoring compressed RAM swap device '%s'.", swap->device);
     238           0 :                                 continue;
     239             :                         }
     240             :                 }
     241             : 
     242             :                 /* prefer highest priority or swap with most remaining space when same priority */
     243           0 :                 if (!selected_swap || swap->priority > selected_swap->priority
     244           0 :                     || ((swap->priority == selected_swap->priority)
     245           0 :                         && (swap->size - swap->used) > (selected_swap->size - selected_swap->used))) {
     246           0 :                         selected_swap = swap_entry_free(selected_swap);
     247           0 :                         selected_swap = TAKE_PTR(swap);
     248             :                 }
     249             :         }
     250             : 
     251           0 :         if (!selected_swap)
     252           0 :                 return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "No swap partitions or files were found.");
     253             : 
     254             :         /* use the swap entry with the highest priority */
     255           0 :         if (device)
     256           0 :                 *device = TAKE_PTR(selected_swap->device);
     257           0 :         if (type)
     258           0 :                 *type = TAKE_PTR(selected_swap->type);
     259           0 :         if (size)
     260           0 :                 *size = selected_swap->size;
     261           0 :         if (used)
     262           0 :                 *used = selected_swap->used;
     263             : 
     264           0 :         log_debug("Highest priority swap entry found %s: %i", selected_swap->device, selected_swap->priority);
     265             : 
     266           0 :         return 0;
     267             : }
     268             : 
     269           0 : static bool enough_swap_for_hibernation(void) {
     270           0 :         _cleanup_free_ char *active = NULL;
     271           0 :         unsigned long long act = 0;
     272           0 :         uint64_t size = 0, used = 0;
     273             :         int r;
     274             : 
     275           0 :         if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
     276           0 :                 return true;
     277             : 
     278           0 :         r = find_hibernate_location(NULL, NULL, &size, &used);
     279           0 :         if (r < 0)
     280           0 :                 return false;
     281             : 
     282           0 :         r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
     283           0 :         if (r < 0) {
     284           0 :                 log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
     285           0 :                 return false;
     286             :         }
     287             : 
     288           0 :         r = safe_atollu(active, &act);
     289           0 :         if (r < 0) {
     290           0 :                 log_debug_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", active);
     291           0 :                 return false;
     292             :         }
     293             : 
     294           0 :         r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
     295           0 :         log_debug("%s swap for hibernation, Active(anon)=%llu kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%",
     296             :                   r ? "Enough" : "Not enough", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
     297             : 
     298           0 :         return r;
     299             : }
     300             : 
     301           1 : int read_fiemap(int fd, struct fiemap **ret) {
     302           1 :         _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
     303             :         struct stat statinfo;
     304           1 :         uint32_t result_extents = 0;
     305           1 :         uint64_t fiemap_start = 0, fiemap_length;
     306           1 :         const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
     307           1 :         size_t fiemap_allocated = n_extra, result_fiemap_allocated = n_extra;
     308             : 
     309           1 :         if (fstat(fd, &statinfo) < 0)
     310           0 :                 return log_debug_errno(errno, "Cannot determine file size: %m");
     311           1 :         if (!S_ISREG(statinfo.st_mode))
     312           0 :                 return -ENOTTY;
     313           1 :         fiemap_length = statinfo.st_size;
     314             : 
     315             :         /* Zero this out in case we run on a file with no extents */
     316           1 :         fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
     317           1 :         if (!fiemap)
     318           0 :                 return -ENOMEM;
     319             : 
     320           1 :         result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
     321           1 :         if (!result_fiemap)
     322           0 :                 return -ENOMEM;
     323             : 
     324             :         /*  XFS filesystem has incorrect implementation of fiemap ioctl and
     325             :          *  returns extents for only one block-group at a time, so we need
     326             :          *  to handle it manually, starting the next fiemap call from the end
     327             :          *  of the last extent
     328             :          */
     329           1 :         while (fiemap_start < fiemap_length) {
     330           1 :                 *fiemap = (struct fiemap) {
     331             :                         .fm_start = fiemap_start,
     332             :                         .fm_length = fiemap_length,
     333             :                         .fm_flags = FIEMAP_FLAG_SYNC,
     334             :                 };
     335             : 
     336             :                 /* Find out how many extents there are */
     337           1 :                 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
     338           0 :                         return log_debug_errno(errno, "Failed to read extents: %m");
     339             : 
     340             :                 /* Nothing to process */
     341           1 :                 if (fiemap->fm_mapped_extents == 0)
     342           0 :                         break;
     343             : 
     344             :                 /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
     345             :                  * the extents for the whole file. Add space for the initial struct fiemap. */
     346           1 :                 if (!greedy_realloc0((void**) &fiemap, &fiemap_allocated,
     347           1 :                                      n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
     348           0 :                         return -ENOMEM;
     349             : 
     350           1 :                 fiemap->fm_extent_count = fiemap->fm_mapped_extents;
     351           1 :                 fiemap->fm_mapped_extents = 0;
     352             : 
     353           1 :                 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
     354           0 :                         return log_debug_errno(errno, "Failed to read extents: %m");
     355             : 
     356             :                 /* Resize result_fiemap to allow us to copy in the extents */
     357           1 :                 if (!greedy_realloc((void**) &result_fiemap, &result_fiemap_allocated,
     358           1 :                                     n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
     359           0 :                         return -ENOMEM;
     360             : 
     361           2 :                 memcpy(result_fiemap->fm_extents + result_extents,
     362           1 :                        fiemap->fm_extents,
     363           1 :                        sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
     364             : 
     365           1 :                 result_extents += fiemap->fm_mapped_extents;
     366             : 
     367             :                 /* Highly unlikely that it is zero */
     368           1 :                 if (_likely_(fiemap->fm_mapped_extents > 0)) {
     369           1 :                         uint32_t i = fiemap->fm_mapped_extents - 1;
     370             : 
     371           2 :                         fiemap_start = fiemap->fm_extents[i].fe_logical +
     372           1 :                                        fiemap->fm_extents[i].fe_length;
     373             : 
     374           1 :                         if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
     375           1 :                                 break;
     376             :                 }
     377             :         }
     378             : 
     379           1 :         memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
     380           1 :         result_fiemap->fm_mapped_extents = result_extents;
     381           1 :         *ret = TAKE_PTR(result_fiemap);
     382           1 :         return 0;
     383             : }
     384             : 
     385             : static int can_sleep_internal(const char *verb, bool check_allowed, const SleepConfig *sleep_config);
     386             : 
     387           1 : static bool can_s2h(const SleepConfig *sleep_config) {
     388             :         const char *p;
     389             :         int r;
     390             : 
     391           1 :         if (!clock_supported(CLOCK_BOOTTIME_ALARM)) {
     392           0 :                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
     393             :                          "CLOCK_BOOTTIME_ALARM is not supported");
     394           0 :                 return false;
     395             :         }
     396             : 
     397           1 :         FOREACH_STRING(p, "suspend", "hibernate") {
     398           1 :                 r = can_sleep_internal(p, false, sleep_config);
     399           1 :                 if (IN_SET(r, 0, -ENOSPC, -EADV)) {
     400           1 :                         log_debug("Unable to %s system.", p);
     401           1 :                         return false;
     402             :                 }
     403           0 :                 if (r < 0)
     404           0 :                         return log_debug_errno(r, "Failed to check if %s is possible: %m", p);
     405             :         }
     406             : 
     407           0 :         return true;
     408             : }
     409             : 
     410           5 : static int can_sleep_internal(const char *verb, bool check_allowed, const SleepConfig *sleep_config) {
     411             :         bool allow;
     412           5 :         char **modes = NULL, **states = NULL;
     413             :         int r;
     414             : 
     415           5 :         assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
     416             : 
     417           5 :         r = sleep_settings(verb, sleep_config, &allow, &modes, &states);
     418           5 :         if (r < 0)
     419           0 :                 return false;
     420             : 
     421           5 :         if (check_allowed && !allow) {
     422           0 :                 log_debug("Sleep mode \"%s\" is disabled by configuration.", verb);
     423           0 :                 return false;
     424             :         }
     425             : 
     426           5 :         if (streq(verb, "suspend-then-hibernate"))
     427           1 :                 return can_s2h(sleep_config);
     428             : 
     429           4 :         if (!can_sleep_state(states) || !can_sleep_disk(modes))
     430           4 :                 return false;
     431             : 
     432           0 :         if (streq(verb, "suspend"))
     433           0 :                 return true;
     434             : 
     435           0 :         if (!enough_swap_for_hibernation())
     436           0 :                 return -ENOSPC;
     437             : 
     438           0 :         return true;
     439             : }
     440             : 
     441           4 : int can_sleep(const char *verb) {
     442           4 :         _cleanup_(free_sleep_configp) SleepConfig *sleep_config = NULL;
     443             :         int r;
     444             : 
     445           4 :         r = parse_sleep_config(&sleep_config);
     446           4 :         if (r < 0)
     447           0 :                 return r;
     448             : 
     449           4 :         return can_sleep_internal(verb, true, sleep_config);
     450             : }
     451             : 
     452           5 : int sleep_settings(const char *verb, const SleepConfig *sleep_config, bool *ret_allow, char ***ret_modes, char ***ret_states) {
     453             : 
     454           5 :         assert(verb);
     455           5 :         assert(sleep_config);
     456           5 :         assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
     457             : 
     458           5 :         if (streq(verb, "suspend")) {
     459           2 :                 *ret_allow = sleep_config->allow_suspend;
     460           2 :                 *ret_modes = sleep_config->suspend_modes;
     461           2 :                 *ret_states = sleep_config->suspend_states;
     462           3 :         } else if (streq(verb, "hibernate")) {
     463           1 :                 *ret_allow = sleep_config->allow_hibernate;
     464           1 :                 *ret_modes = sleep_config->hibernate_modes;
     465           1 :                 *ret_states = sleep_config->hibernate_states;
     466           2 :         } else if (streq(verb, "hybrid-sleep")) {
     467           1 :                 *ret_allow = sleep_config->allow_hybrid_sleep;
     468           1 :                 *ret_modes = sleep_config->hybrid_modes;
     469           1 :                 *ret_states = sleep_config->hybrid_states;
     470           1 :         } else if (streq(verb, "suspend-then-hibernate")) {
     471           1 :                 *ret_allow = sleep_config->allow_s2h;
     472           1 :                 *ret_modes = *ret_states = NULL;
     473             :         }
     474             : 
     475             :         /* suspend modes empty by default */
     476           5 :         if ((!ret_modes && !streq(verb, "suspend")) || !ret_states)
     477           0 :                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No modes or states set for %s; Check sleep.conf", verb);
     478             : 
     479           5 :         return 0;
     480             : }
     481             : 
     482           5 : void free_sleep_config(SleepConfig *sc) {
     483           5 :         if (!sc)
     484           0 :                 return;
     485             : 
     486           5 :         strv_free(sc->suspend_modes);
     487           5 :         strv_free(sc->suspend_states);
     488             : 
     489           5 :         strv_free(sc->hibernate_modes);
     490           5 :         strv_free(sc->hibernate_states);
     491             : 
     492           5 :         strv_free(sc->hybrid_modes);
     493           5 :         strv_free(sc->hybrid_states);
     494             : 
     495           5 :         free(sc);
     496             : }

Generated by: LCOV version 1.14