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>
2019-03-27 13:32:41 +03:00
# include <fcntl.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-12-04 10:26:09 +03:00
# include <sys/ioctl.h>
2019-03-27 13:32:41 +03:00
# include <sys/stat.h>
# include <sys/types.h>
2015-12-03 23:13:37 +03:00
# include <syslog.h>
# include <unistd.h>
2013-05-04 20:31:28 +04:00
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"
2019-03-26 11:57:53 +03:00
# include "errno-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"
2019-05-16 19:12:41 +03:00
# include "time-util.h"
2013-05-04 20:31:28 +04:00
2018-09-26 12:17:36 +03:00
int parse_sleep_config ( const char * verb , bool * ret_allow , char * * * ret_modes , char * * * ret_states , usec_t * ret_delay ) {
int allow_suspend = - 1 , allow_hibernate = - 1 ,
allow_s2h = - 1 , allow_hybrid_sleep = - 1 ;
bool allow ;
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 [ ] = {
2018-09-26 12:17:36 +03:00
{ " Sleep " , " AllowSuspend " , config_parse_tristate , 0 , & allow_suspend } ,
{ " Sleep " , " AllowHibernation " , config_parse_tristate , 0 , & allow_hibernate } ,
{ " Sleep " , " AllowSuspendThenHibernate " , config_parse_tristate , 0 , & allow_s2h } ,
{ " Sleep " , " AllowHybridSleep " , config_parse_tristate , 0 , & allow_hybrid_sleep } ,
{ " 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 } ,
{ " 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 " ) ) {
2018-09-26 12:17:36 +03:00
allow = allow_suspend ! = 0 ;
2013-05-04 20:31:28 +04:00
/* 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
2018-10-31 19:03:50 +03:00
states = strv_new ( " mem " , " standby " , " freeze " ) ;
2013-05-04 20:31:28 +04:00
} else if ( streq ( verb , " hibernate " ) ) {
2018-09-26 12:17:36 +03:00
allow = allow_hibernate ! = 0 ;
2013-05-04 20:31:28 +04:00
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
2018-10-31 19:03:50 +03:00
modes = strv_new ( " platform " , " shutdown " ) ;
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
2018-10-31 19:03:50 +03:00
states = strv_new ( " disk " ) ;
2013-05-04 20:31:28 +04:00
} else if ( streq ( verb , " hybrid-sleep " ) ) {
2018-09-26 12:17:36 +03:00
allow = allow_hybrid_sleep > 0 | |
( allow_suspend ! = 0 & & allow_hibernate ! = 0 ) ;
2013-05-04 20:31:28 +04:00
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
2018-10-31 19:03:50 +03:00
modes = strv_new ( " suspend " , " platform " , " shutdown " ) ;
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
2018-10-31 19:03:50 +03:00
states = strv_new ( " disk " ) ;
2018-03-11 11:13:03 +03:00
2018-09-26 12:17:36 +03:00
} else if ( streq ( verb , " suspend-then-hibernate " ) ) {
allow = allow_s2h > 0 | |
( allow_suspend ! = 0 & & allow_hibernate ! = 0 ) ;
2018-03-11 11:13:03 +03:00
modes = states = NULL ;
2018-09-26 12:17:36 +03:00
} 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-09-26 12:17:36 +03:00
if ( ret_allow )
* ret_allow = allow ;
if ( ret_modes )
* ret_modes = TAKE_PTR ( modes ) ;
if ( ret_states )
* ret_states = TAKE_PTR ( states ) ;
if ( ret_delay )
* ret_delay = delay ;
2018-03-08 16:17:33 +03:00
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 " ) ;
2019-03-26 11:57:53 +03:00
return negative_errno ( ) ;
2013-09-18 00:12:16 +04:00
}
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
}
2018-11-21 01:40:44 +03:00
return log_debug_errno ( SYNTHETIC_ERRNO ( ENOSYS ) ,
" No swap partitions were found. " ) ;
2013-09-18 00:12:16 +04:00
}
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 ;
2018-09-26 11:15:46 +03:00
log_debug ( " %s swap for hibernation, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%% " ,
r ? " Enough " : " Not enough " , act , size , used , 100 * HIBERNATION_SWAP_THRESHOLD ) ;
2013-09-14 03:41:52 +04:00
return r ;
}
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-09-26 12:17:36 +03:00
static int can_sleep_internal ( const char * verb , bool check_allowed ) ;
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 ;
2019-05-16 19:12:41 +03:00
if ( ! clock_supported ( CLOCK_BOOTTIME_ALARM ) ) {
2018-03-08 16:17:33 +03:00
log_full ( errno = = ENOENT ? LOG_DEBUG : LOG_WARNING ,
2019-05-16 19:12:41 +03:00
" CLOCK_BOOTTIME_ALARM is not supported " ) ;
2018-03-08 16:17:33 +03:00
return false ;
}
2018-04-10 12:39:14 +03:00
FOREACH_STRING ( p , " suspend " , " hibernate " ) {
2018-09-26 12:17:36 +03:00
r = can_sleep_internal ( p , false ) ;
2018-12-21 13:01:34 +03:00
if ( IN_SET ( r , 0 , - ENOSPC , - EADV ) ) {
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 ;
}
2018-09-26 12:17:36 +03:00
static int can_sleep_internal ( const char * verb , bool check_allowed ) {
bool allow ;
2013-05-04 20:31:28 +04:00
_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-09-26 12:17:36 +03:00
r = parse_sleep_config ( verb , & allow , & modes , & states , NULL ) ;
2013-05-04 20:31:28 +04:00
if ( r < 0 )
return false ;
2018-09-26 12:17:36 +03:00
if ( check_allowed & & ! allow ) {
log_debug ( " Sleep mode \" %s \" is disabled by configuration. " , verb ) ;
return false ;
}
if ( streq ( verb , " suspend-then-hibernate " ) )
return can_s2h ( ) ;
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-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
}
2018-09-26 12:17:36 +03:00
int can_sleep ( const char * verb ) {
return can_sleep_internal ( verb , true ) ;
}