2017-11-18 19:09:20 +03:00
/* SPDX-License-Identifier: LGPL-2.1+ */
2013-05-04 20:31:28 +04:00
/***
This file is part of systemd .
Copyright 2013 Zbigniew Jędrzejewski - Szmek
2018-03-08 16:17:33 +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>
# 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"
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"
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 ;
2013-12-31 20:23:58 +04:00
char * * modes , * * states ;
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-03-28 19:00:06 +03:00
( ! states & & ! streq ( verb , " suspend-then-hibernate " ) ) ) {
2013-12-31 20:23:58 +04:00
strv_free ( modes ) ;
strv_free ( states ) ;
2013-05-04 20:31:28 +04:00
return log_oom ( ) ;
}
2018-03-08 16:17:33 +03:00
if ( _modes )
* _modes = modes ;
if ( _states )
* _states = states ;
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 */
if ( access ( " /sys/power/disk " , W_OK ) < 0 )
return false ;
r = read_one_line_file ( " /sys/power/disk " , & 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 ;
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 ) ;
2013-09-18 00:12:16 +04:00
if ( k ! = 4 ) {
if ( k = = EOF )
break ;
log_warning ( " Failed to parse /proc/swaps:%u " , i ) ;
continue ;
}
2018-03-08 11:41:50 +03:00
if ( streq ( type_field , " partition " ) & & endswith ( dev_field , " \\ 040(deleted) " ) ) {
log_warning ( " Ignoring deleted swapfile '%s'. " , dev_field ) ;
2013-09-18 00:12:16 +04:00
continue ;
}
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 ) {
2014-11-28 15:19:16 +03:00
log_error_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 ) {
2014-11-28 19:09:20 +03:00
log_error_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-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-04-11 09:51:06 +03:00
if ( IN_SET ( r , 0 , - ENOSPC ) ) {
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-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
}