2017-11-18 19:09:20 +03:00
/* SPDX-License-Identifier: LGPL-2.1+ */
2013-05-04 20:31:28 +04:00
/***
2018-06-12 20:00:24 +03:00
Copyright © 2018 Dell Inc .
2013-05-04 20:31:28 +04:00
* * */
2015-12-03 23:13:37 +03:00
# include <errno.h>
2018-03-08 11:41:50 +03:00
# include <linux/fs.h>
2015-12-03 23:13:37 +03:00
# include <stdbool.h>
# include <stddef.h>
2013-05-04 20:31:28 +04:00
# include <stdio.h>
2015-12-03 23:13:37 +03:00
# include <string.h>
2018-07-25 23:19:44 +03:00
# include <sys/utsname.h>
2015-12-03 23:13:37 +03:00
# include <syslog.h>
# include <unistd.h>
2013-05-04 20:31:28 +04:00
2018-07-25 23:19:44 +03:00
# include "sd-id128.h"
2015-10-27 05:01:06 +03:00
# include "alloc-util.h"
2013-05-04 20:31:28 +04:00
# include "conf-parser.h"
2015-11-03 14:26:12 +03:00
# include "def.h"
2016-04-19 12:18:18 +03:00
# include "env-util.h"
2015-10-25 15:14:12 +03:00
# include "fd-util.h"
2013-05-04 20:31:28 +04:00
# include "fileio.h"
# include "log.h"
2015-12-03 23:13:37 +03:00
# include "macro.h"
2015-11-03 14:26:12 +03:00
# include "parse-util.h"
2018-07-25 18:06:57 +03:00
# include "path-util.h"
2015-10-25 15:14:12 +03:00
# include "sleep-config.h"
2015-10-24 23:58:24 +03:00
# include "string-util.h"
2013-05-04 20:31:28 +04:00
# include "strv.h"
2018-03-08 16:17:33 +03:00
int parse_sleep_config ( const char * verb , char * * * _modes , char * * * _states , usec_t * _delay ) {
2014-06-19 00:02:18 +04:00
2013-05-04 20:31:28 +04:00
_cleanup_strv_free_ char
* * suspend_mode = NULL , * * suspend_state = NULL ,
* * hibernate_mode = NULL , * * hibernate_state = NULL ,
* * hybrid_mode = NULL , * * hybrid_state = NULL ;
2018-05-10 02:04:53 +03:00
_cleanup_strv_free_ char * * modes , * * states ; /* always initialized below */
2018-03-11 11:13:03 +03:00
usec_t delay = 180 * USEC_PER_MINUTE ;
2013-05-04 20:31:28 +04:00
const ConfigTableItem items [ ] = {
{ " Sleep " , " SuspendMode " , config_parse_strv , 0 , & suspend_mode } ,
{ " Sleep " , " SuspendState " , config_parse_strv , 0 , & suspend_state } ,
{ " Sleep " , " HibernateMode " , config_parse_strv , 0 , & hibernate_mode } ,
{ " Sleep " , " HibernateState " , config_parse_strv , 0 , & hibernate_state } ,
{ " Sleep " , " HybridSleepMode " , config_parse_strv , 0 , & hybrid_mode } ,
{ " Sleep " , " HybridSleepState " , config_parse_strv , 0 , & hybrid_state } ,
2018-03-08 16:17:33 +03:00
{ " Sleep " , " HibernateDelaySec " , config_parse_sec , 0 , & delay } ,
2014-06-19 00:02:18 +04:00
{ }
} ;
2013-05-04 20:31:28 +04:00
2017-11-09 02:26:11 +03:00
( void ) config_parse_many_nulstr ( PKGSYSCONFDIR " /sleep.conf " ,
CONF_PATHS_NULSTR ( " systemd/sleep.conf.d " ) ,
" Sleep \0 " , config_item_table_lookup , items ,
CONFIG_PARSE_WARN , NULL ) ;
2013-05-04 20:31:28 +04:00
if ( streq ( verb , " suspend " ) ) {
/* empty by default */
2018-03-22 18:59:46 +03:00
modes = TAKE_PTR ( suspend_mode ) ;
2013-05-04 20:31:28 +04:00
if ( suspend_state )
2018-03-22 18:59:46 +03:00
states = TAKE_PTR ( suspend_state ) ;
2013-05-04 20:31:28 +04:00
else
2013-12-31 20:23:58 +04:00
states = strv_new ( " mem " , " standby " , " freeze " , NULL ) ;
2013-05-04 20:31:28 +04:00
} else if ( streq ( verb , " hibernate " ) ) {
if ( hibernate_mode )
2018-03-22 18:59:46 +03:00
modes = TAKE_PTR ( hibernate_mode ) ;
2013-05-04 20:31:28 +04:00
else
2013-12-31 20:23:58 +04:00
modes = strv_new ( " platform " , " shutdown " , NULL ) ;
2013-05-04 20:31:28 +04:00
if ( hibernate_state )
2018-03-22 18:59:46 +03:00
states = TAKE_PTR ( hibernate_state ) ;
2013-05-04 20:31:28 +04:00
else
2013-12-31 20:23:58 +04:00
states = strv_new ( " disk " , NULL ) ;
2013-05-04 20:31:28 +04:00
} else if ( streq ( verb , " hybrid-sleep " ) ) {
if ( hybrid_mode )
2018-03-22 18:59:46 +03:00
modes = TAKE_PTR ( hybrid_mode ) ;
2013-05-04 20:31:28 +04:00
else
2013-12-31 20:23:58 +04:00
modes = strv_new ( " suspend " , " platform " , " shutdown " , NULL ) ;
2013-05-04 20:31:28 +04:00
if ( hybrid_state )
2018-03-22 18:59:46 +03:00
states = TAKE_PTR ( hybrid_state ) ;
2013-05-04 20:31:28 +04:00
else
2013-12-31 20:23:58 +04:00
states = strv_new ( " disk " , NULL ) ;
2018-03-11 11:13:03 +03:00
2018-03-28 19:00:06 +03:00
} else if ( streq ( verb , " suspend-then-hibernate " ) )
2018-03-11 11:13:03 +03:00
modes = states = NULL ;
else
2013-05-04 20:31:28 +04:00
assert_not_reached ( " what verb " ) ;
2018-03-11 11:13:03 +03:00
if ( ( ! modes & & STR_IN_SET ( verb , " hibernate " , " hybrid-sleep " ) ) | |
2018-05-10 02:04:53 +03:00
( ! states & & ! streq ( verb , " suspend-then-hibernate " ) ) )
2013-05-04 20:31:28 +04:00
return log_oom ( ) ;
2018-03-08 16:17:33 +03:00
if ( _modes )
2018-05-10 02:04:53 +03:00
* _modes = TAKE_PTR ( modes ) ;
2018-03-08 16:17:33 +03:00
if ( _states )
2018-05-10 02:04:53 +03:00
* _states = TAKE_PTR ( states ) ;
2018-03-08 16:17:33 +03:00
if ( _delay )
* _delay = delay ;
2013-05-04 20:31:28 +04:00
return 0 ;
}
int can_sleep_state ( char * * types ) {
2014-07-30 06:01:36 +04:00
char * * type ;
2013-05-04 20:31:28 +04:00
int r ;
_cleanup_free_ char * p = NULL ;
if ( strv_isempty ( types ) )
return true ;
/* If /sys is read-only we cannot sleep */
if ( access ( " /sys/power/state " , W_OK ) < 0 )
return false ;
r = read_one_line_file ( " /sys/power/state " , & p ) ;
if ( r < 0 )
return false ;
STRV_FOREACH ( type , types ) {
2014-07-30 06:01:36 +04:00
const char * word , * state ;
2013-05-04 20:31:28 +04:00
size_t l , k ;
k = strlen ( * type ) ;
2014-07-30 06:01:36 +04:00
FOREACH_WORD_SEPARATOR ( word , l , p , WHITESPACE , state )
if ( l = = k & & memcmp ( word , * type , l ) = = 0 )
2013-05-04 20:31:28 +04:00
return true ;
}
return false ;
}
int can_sleep_disk ( char * * types ) {
2014-07-30 06:01:36 +04:00
char * * type ;
2013-05-04 20:31:28 +04:00
int r ;
_cleanup_free_ char * p = NULL ;
if ( strv_isempty ( types ) )
return true ;
/* If /sys is read-only we cannot sleep */
2018-07-25 22:43:09 +03:00
if ( access ( " /sys/power/disk " , W_OK ) < 0 ) {
log_debug_errno ( errno , " /sys/power/disk is not writable: %m " ) ;
2013-05-04 20:31:28 +04:00
return false ;
2018-07-25 22:43:09 +03:00
}
2013-05-04 20:31:28 +04:00
r = read_one_line_file ( " /sys/power/disk " , & p ) ;
2018-07-25 22:43:09 +03:00
if ( r < 0 ) {
log_debug_errno ( r , " Couldn't read /sys/power/disk: %m " ) ;
2013-05-04 20:31:28 +04:00
return false ;
2018-07-25 22:43:09 +03:00
}
2013-05-04 20:31:28 +04:00
STRV_FOREACH ( type , types ) {
2014-07-30 06:01:36 +04:00
const char * word , * state ;
2013-05-04 20:31:28 +04:00
size_t l , k ;
k = strlen ( * type ) ;
2014-07-30 06:01:36 +04:00
FOREACH_WORD_SEPARATOR ( word , l , p , WHITESPACE , state ) {
if ( l = = k & & memcmp ( word , * type , l ) = = 0 )
2013-05-04 20:31:28 +04:00
return true ;
2014-07-30 06:01:36 +04:00
if ( l = = k + 2 & &
word [ 0 ] = = ' [ ' & &
memcmp ( word + 1 , * type , l - 2 ) = = 0 & &
word [ l - 1 ] = = ' ] ' )
2013-05-04 20:31:28 +04:00
return true ;
}
}
return false ;
}
2013-09-14 03:41:52 +04:00
# define HIBERNATION_SWAP_THRESHOLD 0.98
2018-03-08 11:41:50 +03:00
int find_hibernate_location ( char * * device , char * * type , size_t * size , size_t * used ) {
2013-09-18 00:12:16 +04:00
_cleanup_fclose_ FILE * f ;
2015-01-21 06:22:15 +03:00
unsigned i ;
2013-09-18 00:12:16 +04:00
2014-02-13 17:59:56 +04:00
f = fopen ( " /proc/swaps " , " re " ) ;
2013-09-18 00:12:16 +04:00
if ( ! f ) {
log_full ( errno = = ENOENT ? LOG_DEBUG : LOG_WARNING ,
" Failed to retrieve open /proc/swaps: %m " ) ;
assert ( errno > 0 ) ;
return - errno ;
}
2013-09-14 03:41:52 +04:00
2013-09-18 00:12:16 +04:00
( void ) fscanf ( f , " %*s %*s %*s %*s %*s \n " ) ;
for ( i = 1 ; ; i + + ) {
2018-03-08 11:41:50 +03:00
_cleanup_free_ char * dev_field = NULL , * type_field = NULL ;
2013-09-18 00:12:16 +04:00
size_t size_field , used_field ;
int k ;
k = fscanf ( f ,
" %ms " /* device/file */
" %ms " /* type of swap */
2015-01-21 06:22:15 +03:00
" %zu " /* swap size */
" %zu " /* used */
2013-09-18 00:12:16 +04:00
" %*i \n " , /* priority */
2018-03-08 11:41:50 +03:00
& dev_field , & type_field , & size_field , & used_field ) ;
2018-07-25 22:41:58 +03:00
if ( k = = EOF )
break ;
2013-09-18 00:12:16 +04:00
if ( k ! = 4 ) {
log_warning ( " Failed to parse /proc/swaps:%u " , i ) ;
continue ;
}
2018-07-25 22:41:58 +03:00
if ( streq ( type_field , " file " ) ) {
2018-07-25 18:06:57 +03:00
if ( endswith ( dev_field , " \\ 040(deleted) " ) ) {
2018-07-25 22:41:58 +03:00
log_warning ( " Ignoring deleted swap file '%s'. " , dev_field ) ;
2018-07-25 18:06:57 +03:00
continue ;
}
2018-07-25 22:41:58 +03:00
} else if ( streq ( type_field , " partition " ) ) {
2018-07-25 18:06:57 +03:00
const char * fn ;
2018-07-25 22:41:58 +03:00
2018-07-25 18:06:57 +03:00
fn = path_startswith ( dev_field , " /dev/ " ) ;
if ( fn & & startswith ( fn , " zram " ) ) {
2018-07-25 22:41:58 +03:00
log_debug ( " Ignoring compressed RAM swap device '%s'. " , dev_field ) ;
2018-07-25 18:06:57 +03:00
continue ;
}
2013-09-18 00:12:16 +04:00
}
2018-07-25 22:41:58 +03:00
2018-03-08 11:41:50 +03:00
if ( device )
* device = TAKE_PTR ( dev_field ) ;
if ( type )
* type = TAKE_PTR ( type_field ) ;
if ( size )
* size = size_field ;
if ( used )
* used = used_field ;
2013-09-18 00:12:16 +04:00
return 0 ;
2013-09-14 03:41:52 +04:00
}
2013-09-18 00:12:16 +04:00
log_debug ( " No swap partitions were found. " ) ;
return - ENOSYS ;
}
2018-04-11 10:27:32 +03:00
static bool enough_swap_for_hibernation ( void ) {
2013-09-18 00:12:16 +04:00
_cleanup_free_ char * active = NULL ;
2014-02-19 20:47:11 +04:00
unsigned long long act = 0 ;
size_t size = 0 , used = 0 ;
2013-09-18 00:12:16 +04:00
int r ;
2016-04-19 12:18:18 +03:00
if ( getenv_bool ( " SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK " ) > 0 )
return true ;
2018-03-08 11:41:50 +03:00
r = find_hibernate_location ( NULL , NULL , & size , & used ) ;
2013-09-18 00:12:16 +04:00
if ( r < 0 )
2013-09-14 03:41:52 +04:00
return false ;
2015-09-30 15:57:55 +03:00
r = get_proc_field ( " /proc/meminfo " , " Active(anon) " , WHITESPACE , & active ) ;
2013-09-14 03:41:52 +04:00
if ( r < 0 ) {
2018-07-25 23:22:37 +03:00
log_debug_errno ( r , " Failed to retrieve Active(anon) from /proc/meminfo: %m " ) ;
2013-09-14 03:41:52 +04:00
return false ;
}
r = safe_atollu ( active , & act ) ;
if ( r < 0 ) {
2018-07-25 23:22:37 +03:00
log_debug_errno ( r , " Failed to parse Active(anon) from /proc/meminfo: %s: %m " , active ) ;
2013-09-14 03:41:52 +04:00
return false ;
}
2013-09-18 00:12:16 +04:00
r = act < = ( size - used ) * HIBERNATION_SWAP_THRESHOLD ;
log_debug ( " Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%% " ,
r ? " " : " im " , act , size , used , 100 * HIBERNATION_SWAP_THRESHOLD ) ;
2013-09-14 03:41:52 +04:00
return r ;
}
2018-07-25 23:19:44 +03:00
static int kernel_exists ( void ) {
struct utsname u ;
sd_id128_t m ;
int i , r ;
/* Do some superficial checks whether the kernel we are currently running is still around. If it isn't we
* shouldn ' t offer hibernation as we couldn ' t possible resume from hibernation again . Of course , this check is
* very superficial , as the kernel ' s mere existance is hardly enough to know whether the hibernate / resume cycle
* will succeed . However , the common case of kernel updates can be caught this way , and it ' s definitely worth
* covering that . */
for ( i = 0 ; ; i + + ) {
_cleanup_free_ char * path = NULL ;
switch ( i ) {
case 0 :
/* First, let's look in /lib/modules/`uname -r`/vmlinuz. This is where current Fedora places
* its RPM - managed kernels . It ' s a good place , as it means compiled vendor code is monopolized
* in / usr , and then the kernel image is stored along with its modules in the same
* hierarchy . It ' s also what our ' kernel - install ' script is written for . */
if ( uname ( & u ) < 0 )
return log_debug_errno ( errno , " Failed to acquire kernel release: %m " ) ;
path = strjoin ( " /lib/modules/ " , u . release , " /vmlinuz " ) ;
break ;
case 1 :
/* Secondly, let's look in /boot/vmlinuz-`uname -r`. This is where older Fedora and other
* distributions tend to place the kernel . */
path = strjoin ( " /boot/vmlinuz- " , u . release ) ;
break ;
case 2 :
/* For the other cases, we look in the EFI/boot partition, at the place where our
* " kernel-install " script copies the kernel on install by default . */
r = sd_id128_get_machine ( & m ) ;
if ( r < 0 )
return log_debug_errno ( r , " Failed to read machine ID: %m " ) ;
( void ) asprintf ( & path , " /efi/ " SD_ID128_FORMAT_STR " /%s/linux " , SD_ID128_FORMAT_VAL ( m ) , u . release ) ;
break ;
case 3 :
( void ) asprintf ( & path , " /boot/ " SD_ID128_FORMAT_STR " /%s/linux " , SD_ID128_FORMAT_VAL ( m ) , u . release ) ;
break ;
case 4 :
( void ) asprintf ( & path , " /boot/efi/ " SD_ID128_FORMAT_STR " /%s/linux " , SD_ID128_FORMAT_VAL ( m ) , u . release ) ;
break ;
default :
return false ;
}
if ( ! path )
return - ENOMEM ;
log_debug ( " Testing whether %s exists. " , path ) ;
if ( access ( path , F_OK ) > = 0 )
return true ;
if ( errno ! = ENOENT )
log_debug_errno ( errno , " Failed to determine whether '%s' exists, ignoring: %m " , path ) ;
}
}
2018-03-08 11:41:50 +03:00
int read_fiemap ( int fd , struct fiemap * * ret ) {
_cleanup_free_ struct fiemap * fiemap = NULL , * result_fiemap = NULL ;
struct stat statinfo ;
uint32_t result_extents = 0 ;
uint64_t fiemap_start = 0 , fiemap_length ;
2018-04-10 12:23:46 +03:00
const size_t n_extra = DIV_ROUND_UP ( sizeof ( struct fiemap ) , sizeof ( struct fiemap_extent ) ) ;
size_t fiemap_allocated = n_extra , result_fiemap_allocated = n_extra ;
2018-03-08 11:41:50 +03:00
if ( fstat ( fd , & statinfo ) < 0 )
return log_debug_errno ( errno , " Cannot determine file size: %m " ) ;
if ( ! S_ISREG ( statinfo . st_mode ) )
return - ENOTTY ;
fiemap_length = statinfo . st_size ;
2018-04-10 12:23:46 +03:00
/* Zero this out in case we run on a file with no extents */
fiemap = calloc ( n_extra , sizeof ( struct fiemap_extent ) ) ;
2018-03-08 11:41:50 +03:00
if ( ! fiemap )
return - ENOMEM ;
2018-04-10 12:23:46 +03:00
result_fiemap = malloc_multiply ( n_extra , sizeof ( struct fiemap_extent ) ) ;
2018-03-08 11:41:50 +03:00
if ( ! result_fiemap )
return - ENOMEM ;
/* XFS filesystem has incorrect implementation of fiemap ioctl and
* returns extents for only one block - group at a time , so we need
* to handle it manually , starting the next fiemap call from the end
* of the last extent
*/
while ( fiemap_start < fiemap_length ) {
* fiemap = ( struct fiemap ) {
. fm_start = fiemap_start ,
. fm_length = fiemap_length ,
. fm_flags = FIEMAP_FLAG_SYNC ,
} ;
/* Find out how many extents there are */
if ( ioctl ( fd , FS_IOC_FIEMAP , fiemap ) < 0 )
return log_debug_errno ( errno , " Failed to read extents: %m " ) ;
/* Nothing to process */
if ( fiemap - > fm_mapped_extents = = 0 )
break ;
2018-04-10 12:23:46 +03:00
/* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
* the extents for the whole file . Add space for the initial struct fiemap . */
if ( ! greedy_realloc0 ( ( void * * ) & fiemap , & fiemap_allocated ,
n_extra + fiemap - > fm_mapped_extents , sizeof ( struct fiemap_extent ) ) )
2018-03-08 11:41:50 +03:00
return - ENOMEM ;
fiemap - > fm_extent_count = fiemap - > fm_mapped_extents ;
fiemap - > fm_mapped_extents = 0 ;
if ( ioctl ( fd , FS_IOC_FIEMAP , fiemap ) < 0 )
return log_debug_errno ( errno , " Failed to read extents: %m " ) ;
2018-04-10 12:23:46 +03:00
/* Resize result_fiemap to allow us to copy in the extents */
if ( ! greedy_realloc ( ( void * * ) & result_fiemap , & result_fiemap_allocated ,
n_extra + result_extents + fiemap - > fm_mapped_extents , sizeof ( struct fiemap_extent ) ) )
2018-03-08 11:41:50 +03:00
return - ENOMEM ;
memcpy ( result_fiemap - > fm_extents + result_extents ,
fiemap - > fm_extents ,
sizeof ( struct fiemap_extent ) * fiemap - > fm_mapped_extents ) ;
result_extents + = fiemap - > fm_mapped_extents ;
/* Highly unlikely that it is zero */
2018-04-10 12:23:46 +03:00
if ( _likely_ ( fiemap - > fm_mapped_extents > 0 ) ) {
2018-03-08 11:41:50 +03:00
uint32_t i = fiemap - > fm_mapped_extents - 1 ;
fiemap_start = fiemap - > fm_extents [ i ] . fe_logical +
fiemap - > fm_extents [ i ] . fe_length ;
if ( fiemap - > fm_extents [ i ] . fe_flags & FIEMAP_EXTENT_LAST )
break ;
}
}
memcpy ( result_fiemap , fiemap , sizeof ( struct fiemap ) ) ;
result_fiemap - > fm_mapped_extents = result_extents ;
* ret = TAKE_PTR ( result_fiemap ) ;
return 0 ;
}
2018-03-08 16:17:33 +03:00
static bool can_s2h ( void ) {
2018-04-10 12:39:14 +03:00
const char * p ;
2018-03-08 16:17:33 +03:00
int r ;
r = access ( " /sys/class/rtc/rtc0/wakealarm " , W_OK ) ;
if ( r < 0 ) {
log_full ( errno = = ENOENT ? LOG_DEBUG : LOG_WARNING ,
" /sys/class/rct/rct0/wakealarm is not writable %m " ) ;
return false ;
}
2018-04-10 12:39:14 +03:00
FOREACH_STRING ( p , " suspend " , " hibernate " ) {
r = can_sleep ( p ) ;
2018-07-25 23:19:44 +03:00
if ( IN_SET ( r , 0 , - ENOSPC , - ENOMEDIUM ) ) {
2018-04-10 12:39:14 +03:00
log_debug ( " Unable to %s system. " , p ) ;
return false ;
}
2018-04-11 09:51:06 +03:00
if ( r < 0 )
return log_debug_errno ( r , " Failed to check if %s is possible: %m " , p ) ;
2018-03-08 16:17:33 +03:00
}
return true ;
}
2013-05-04 20:31:28 +04:00
int can_sleep ( const char * verb ) {
_cleanup_strv_free_ char * * modes = NULL , * * states = NULL ;
int r ;
2018-03-28 19:00:06 +03:00
assert ( STR_IN_SET ( verb , " suspend " , " hibernate " , " hybrid-sleep " , " suspend-then-hibernate " ) ) ;
2018-03-08 16:17:33 +03:00
2018-03-28 19:00:06 +03:00
if ( streq ( verb , " suspend-then-hibernate " ) )
2018-03-08 16:17:33 +03:00
return can_s2h ( ) ;
2013-05-04 20:31:28 +04:00
2018-03-08 16:17:33 +03:00
r = parse_sleep_config ( verb , & modes , & states , NULL ) ;
2013-05-04 20:31:28 +04:00
if ( r < 0 )
return false ;
2013-09-14 03:41:52 +04:00
if ( ! can_sleep_state ( states ) | | ! can_sleep_disk ( modes ) )
return false ;
2018-04-11 09:51:06 +03:00
if ( streq ( verb , " suspend " ) )
return true ;
2018-07-25 23:19:44 +03:00
if ( kernel_exists ( ) < = 0 ) {
log_debug_errno ( errno , " Couldn't find kernel, not offering hibernation. " ) ;
return - ENOMEDIUM ;
}
2018-04-11 10:27:32 +03:00
if ( ! enough_swap_for_hibernation ( ) )
2018-04-11 09:51:06 +03:00
return - ENOSPC ;
return true ;
2013-05-04 20:31:28 +04:00
}