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
systemd is free software ; you can redistribute it and / or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
( at your option ) any later version .
systemd is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
2015-12-03 23:13:37 +03:00
# include <errno.h>
# 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
2013-09-18 00:12:16 +04:00
static int hibernation_partition_size ( size_t * size , size_t * used ) {
_cleanup_fclose_ FILE * f ;
2015-01-21 06:22:15 +03:00
unsigned i ;
2013-09-18 00:12:16 +04:00
assert ( size ) ;
assert ( used ) ;
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 + + ) {
2013-12-25 01:42:06 +04:00
_cleanup_free_ char * dev = NULL , * type = 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 */
& dev , & type , & size_field , & used_field ) ;
if ( k ! = 4 ) {
if ( k = = EOF )
break ;
log_warning ( " Failed to parse /proc/swaps:%u " , i ) ;
continue ;
}
2013-12-25 01:42:06 +04:00
if ( streq ( type , " partition " ) & & endswith ( dev , " \\ 040(deleted) " ) ) {
log_warning ( " Ignoring deleted swapfile '%s'. " , dev ) ;
2013-09-18 00:12:16 +04:00
continue ;
}
* size = size_field ;
* used = used_field ;
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 ;
}
static bool enough_memory_for_hibernation ( void ) {
_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 ;
2013-09-18 00:12:16 +04:00
r = hibernation_partition_size ( & size , & used ) ;
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 16:17:33 +03:00
static bool can_s2h ( void ) {
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 ;
}
r = can_sleep ( " suspend " ) ;
if ( r < 0 ) {
log_debug_errno ( r , " Unable to suspend system. " ) ;
return false ;
}
r = can_sleep ( " hibernate " ) ;
if ( r < 0 ) {
log_debug_errno ( r , " Unable to hibernate system. " ) ;
return false ;
}
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 ;
return streq ( verb , " suspend " ) | | enough_memory_for_hibernation ( ) ;
2013-05-04 20:31:28 +04:00
}