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>
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"
2019-09-27 07:02:28 +03:00
# include "btrfs-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"
2019-09-27 07:02:28 +03:00
# include "stdio-util.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
2019-05-20 08:43:29 +03:00
int parse_sleep_config ( SleepConfig * * ret_sleep_config ) {
_cleanup_ ( free_sleep_configp ) SleepConfig * sc ;
2018-09-26 12:17:36 +03:00
int allow_suspend = - 1 , allow_hibernate = - 1 ,
allow_s2h = - 1 , allow_hybrid_sleep = - 1 ;
2019-05-20 08:43:29 +03:00
sc = new0 ( SleepConfig , 1 ) ;
if ( ! sc )
return log_oom ( ) ;
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 } ,
2019-05-20 08:43:29 +03:00
{ " Sleep " , " SuspendMode " , config_parse_strv , 0 , & sc - > suspend_modes } ,
{ " Sleep " , " SuspendState " , config_parse_strv , 0 , & sc - > suspend_states } ,
{ " Sleep " , " HibernateMode " , config_parse_strv , 0 , & sc - > hibernate_modes } ,
{ " Sleep " , " HibernateState " , config_parse_strv , 0 , & sc - > hibernate_states } ,
{ " Sleep " , " HybridSleepMode " , config_parse_strv , 0 , & sc - > hybrid_modes } ,
{ " Sleep " , " HybridSleepState " , config_parse_strv , 0 , & sc - > hybrid_states } ,
2018-09-26 12:17:36 +03:00
2019-05-20 08:43:29 +03:00
{ " Sleep " , " HibernateDelaySec " , config_parse_sec , 0 , & sc - > hibernate_delay_sec } ,
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
2019-05-20 08:43:29 +03:00
/* use default values unless set */
sc - > allow_suspend = allow_suspend ! = 0 ;
sc - > allow_hibernate = allow_hibernate ! = 0 ;
2019-05-30 16:06:51 +03:00
sc - > allow_hybrid_sleep = allow_hybrid_sleep > = 0 ? allow_hybrid_sleep
: ( allow_suspend ! = 0 & & allow_hibernate ! = 0 ) ;
sc - > allow_s2h = allow_s2h > = 0 ? allow_s2h
: ( allow_suspend ! = 0 & & allow_hibernate ! = 0 ) ;
2019-05-20 08:43:29 +03:00
if ( ! sc - > suspend_states )
sc - > suspend_states = strv_new ( " mem " , " standby " , " freeze " ) ;
if ( ! sc - > hibernate_modes )
sc - > hibernate_modes = strv_new ( " platform " , " shutdown " ) ;
if ( ! sc - > hibernate_states )
sc - > hibernate_states = strv_new ( " disk " ) ;
if ( ! sc - > hybrid_modes )
sc - > hybrid_modes = strv_new ( " suspend " , " platform " , " shutdown " ) ;
if ( ! sc - > hybrid_states )
sc - > hybrid_states = strv_new ( " disk " ) ;
if ( sc - > hibernate_delay_sec = = 0 )
2019-07-29 13:49:38 +03:00
sc - > hibernate_delay_sec = 2 * USEC_PER_HOUR ;
2019-05-20 08:43:29 +03:00
/* ensure values set for all required fields */
if ( ! sc - > suspend_states | | ! sc - > hibernate_modes
| | ! sc - > hibernate_states | | ! sc - > hybrid_modes | | ! sc - > hybrid_states )
2013-05-04 20:31:28 +04:00
return log_oom ( ) ;
2019-05-20 08:43:29 +03:00
* ret_sleep_config = TAKE_PTR ( sc ) ;
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
2019-09-27 07:02:28 +03:00
SwapEntry * swap_entry_free ( SwapEntry * se ) {
2019-07-15 06:01:20 +03:00
if ( ! se )
return NULL ;
free ( se - > device ) ;
free ( se - > type ) ;
return mfree ( se ) ;
}
2019-09-27 07:02:28 +03:00
HibernateLocation * hibernate_location_free ( HibernateLocation * hl ) {
if ( ! hl )
return NULL ;
swap_entry_free ( hl - > swap ) ;
free ( hl - > resume ) ;
return mfree ( hl ) ;
}
static int swap_device_to_major_minor ( const SwapEntry * swap , char * * ret ) {
_cleanup_free_ char * major_minor = NULL ;
_cleanup_close_ int fd = - 1 ;
struct stat sb ;
dev_t swap_dev ;
int r ;
assert ( swap ) ;
assert ( swap - > device ) ;
assert ( swap - > type ) ;
fd = open ( swap - > device , O_RDONLY | O_CLOEXEC | O_NONBLOCK ) ;
if ( fd < 0 )
return log_debug_errno ( errno , " Unable to open '%s': %m " , swap - > device ) ;
r = fstat ( fd , & sb ) ;
if ( r < 0 )
return log_debug_errno ( errno , " Unable to stat %s: %m " , swap - > device ) ;
swap_dev = streq ( swap - > type , " partition " ) ? sb . st_rdev : sb . st_dev ;
if ( asprintf ( & major_minor , " %u:%u " , major ( swap_dev ) , minor ( swap_dev ) ) < 0 )
return log_oom ( ) ;
* ret = TAKE_PTR ( major_minor ) ;
return 0 ;
}
static int calculate_swap_file_offset ( const SwapEntry * swap , uint64_t * ret_offset ) {
_cleanup_close_ int fd = - 1 ;
_cleanup_free_ struct fiemap * fiemap = NULL ;
struct stat sb ;
int r , btrfs ;
assert ( swap ) ;
assert ( swap - > device ) ;
assert ( streq ( swap - > type , " file " ) ) ;
fd = open ( swap - > device , O_RDONLY | O_CLOEXEC | O_NOCTTY ) ;
2019-10-25 18:02:58 +03:00
if ( fd < 0 )
2019-09-27 07:02:28 +03:00
return log_error_errno ( errno , " Failed to open %s: %m " , swap - > device ) ;
2019-10-25 18:02:58 +03:00
if ( fstat ( fd , & sb ) < 0 )
2019-09-27 07:02:28 +03:00
return log_error_errno ( errno , " Failed to stat %s: %m " , swap - > device ) ;
btrfs = btrfs_is_filesystem ( fd ) ;
if ( btrfs < 0 )
2019-10-25 18:10:47 +03:00
return log_error_errno ( btrfs , " Error checking %s for Btrfs filesystem: %m " , swap - > device ) ;
2019-09-27 07:02:28 +03:00
else if ( btrfs > 0 ) {
log_debug ( " Detection of swap file offset on Btrfs is not supported: %s; skipping " , swap - > device ) ;
* ret_offset = 0 ;
return 0 ;
}
r = read_fiemap ( fd , & fiemap ) ;
if ( r < 0 )
return log_debug_errno ( r , " Unable to read extent map for '%s': %m " , swap - > device ) ;
* ret_offset = fiemap - > fm_extents [ 0 ] . fe_physical / page_size ( ) ;
return 0 ;
}
2019-07-15 06:01:20 +03:00
2019-09-27 07:02:28 +03:00
static int read_resume_files ( char * * ret_resume , uint64_t * ret_resume_offset ) {
_cleanup_free_ char * resume , * resume_offset_str = NULL ;
uint64_t resume_offset = 0 ;
int r ;
r = read_one_line_file ( " /sys/power/resume " , & resume ) ;
if ( r < 0 )
return log_debug_errno ( r , " Error reading from /sys/power/resume: %m " ) ;
r = read_one_line_file ( " /sys/power/resume_offset " , & resume_offset_str ) ;
2019-10-25 18:10:47 +03:00
if ( r = = - ENOENT )
log_debug ( " Kernel does not support resume_offset; swap file offset detection will be skipped. " ) ;
else if ( r < 0 )
return log_debug_errno ( r , " Error reading from /sys/power/resume_offset: %m " ) ;
else {
2019-09-27 07:02:28 +03:00
r = safe_atou64 ( resume_offset_str , & resume_offset ) ;
if ( r < 0 )
2019-10-25 18:10:47 +03:00
return log_error_errno ( r , " Failed to parse value in /sys/power/resume_offset \" %s \" : %m " , resume_offset_str ) ;
2019-09-27 07:02:28 +03:00
}
if ( resume_offset > 0 & & streq ( * ret_resume , " 0:0 " ) ) {
log_debug ( " Found offset in /sys/power/resume_offset: % " PRIu64 " ; no device id found in /sys/power/resume; ignoring resume_offset " , resume_offset ) ;
resume_offset = 0 ;
}
* ret_resume = TAKE_PTR ( resume ) ;
* ret_resume_offset = resume_offset ;
return 0 ;
}
static bool location_is_resume_device ( const HibernateLocation * location , const char * sys_resume , const uint64_t sys_offset ) {
assert ( location ) ;
assert ( location - > resume ) ;
assert ( sys_resume ) ;
return streq ( sys_resume , location - > resume ) & & sys_offset = = location - > resume_offset ;
}
/*
* Attempt to find the hibernation location by parsing / proc / swaps , / sys / power / resume , and
* / sys / power / resume_offset .
*
* Returns :
* 1 - HibernateLocation matches values found in / sys / power / resume & / sys / power / resume_offset
* 0 - HibernateLocation is highest priority swap with most remaining space ; no valid values exist in / sys / power / resume & / sys / power / resume_offset
* negative value in the case of error
*/
int find_hibernate_location ( HibernateLocation * * ret_hibernate_location ) {
2019-10-25 18:10:47 +03:00
_cleanup_fclose_ FILE * f = NULL ;
2019-09-27 07:02:28 +03:00
_cleanup_ ( hibernate_location_freep ) HibernateLocation * hibernate_location = NULL ;
_cleanup_free_ char * sys_resume = NULL ;
uint64_t sys_offset = 0 ;
2015-01-21 06:22:15 +03:00
unsigned i ;
2019-09-27 07:02:28 +03:00
int r ;
/* read the /sys/power/resume & /sys/power/resume_offset values */
r = read_resume_files ( & sys_resume , & sys_offset ) ;
if ( r < 0 )
return r ;
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 ,
2019-10-22 07:36:27 +03:00
" Failed to 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 + + ) {
2019-07-15 06:01:20 +03:00
_cleanup_ ( swap_entry_freep ) SwapEntry * swap = NULL ;
2019-09-27 07:02:28 +03:00
uint64_t swap_offset = 0 ;
2013-09-18 00:12:16 +04:00
int k ;
2019-07-15 06:01:20 +03:00
swap = new0 ( SwapEntry , 1 ) ;
if ( ! swap )
return log_oom ( ) ;
2013-09-18 00:12:16 +04:00
k = fscanf ( f ,
2019-07-15 06:01:20 +03:00
" %ms " /* device/file */
" %ms " /* type of swap */
" % " PRIu64 /* swap size */
" % " PRIu64 /* used */
" %i \n " , /* priority */
& swap - > device , & swap - > type , & swap - > size , & swap - > used , & swap - > priority ) ;
2018-07-25 22:41:58 +03:00
if ( k = = EOF )
break ;
2019-07-15 06:01:20 +03:00
if ( k ! = 5 ) {
2013-09-18 00:12:16 +04:00
log_warning ( " Failed to parse /proc/swaps:%u " , i ) ;
continue ;
}
2019-07-15 06:01:20 +03:00
if ( streq ( swap - > type , " file " ) ) {
if ( endswith ( swap - > device , " \\ 040(deleted) " ) ) {
log_warning ( " Ignoring deleted swap file '%s'. " , swap - > device ) ;
2018-07-25 18:06:57 +03:00
continue ;
}
2019-09-27 07:02:28 +03:00
r = calculate_swap_file_offset ( swap , & swap_offset ) ;
if ( r < 0 )
return r ;
2019-07-15 06:01:20 +03:00
} else if ( streq ( swap - > type , " partition " ) ) {
2018-07-25 18:06:57 +03:00
const char * fn ;
2018-07-25 22:41:58 +03:00
2019-07-15 06:01:20 +03:00
fn = path_startswith ( swap - > device , " /dev/ " ) ;
2018-07-25 18:06:57 +03:00
if ( fn & & startswith ( fn , " zram " ) ) {
2019-07-15 06:01:20 +03:00
log_debug ( " Ignoring compressed RAM swap device '%s'. " , swap - > device ) ;
2018-07-25 18:06:57 +03:00
continue ;
}
2019-09-27 07:02:28 +03:00
} else {
log_debug ( " Swap type %s is unsupported for hibernation: %s; skipping " , swap - > type , swap - > device ) ;
continue ;
2013-09-18 00:12:16 +04:00
}
2018-07-25 22:41:58 +03:00
2019-09-27 07:02:28 +03:00
/* prefer resume device or highest priority swap with most remaining space */
if ( ! hibernate_location | | swap - > priority > hibernate_location - > swap - > priority
| | ( ( swap - > priority = = hibernate_location - > swap - > priority )
& & ( swap - > size - swap - > used ) > ( hibernate_location - > swap - > size - hibernate_location - > swap - > used ) ) ) {
_cleanup_free_ char * swap_device_id = NULL ;
r = swap_device_to_major_minor ( swap , & swap_device_id ) ;
if ( r < 0 )
return r ;
hibernate_location = hibernate_location_free ( hibernate_location ) ;
2019-10-25 18:10:47 +03:00
hibernate_location = new ( HibernateLocation , 1 ) ;
2019-09-27 07:02:28 +03:00
if ( ! hibernate_location )
return log_oom ( ) ;
2019-10-25 18:10:47 +03:00
* hibernate_location = ( HibernateLocation ) {
. resume = TAKE_PTR ( swap_device_id ) ,
. resume_offset = swap_offset ,
. swap = TAKE_PTR ( swap ) ,
} ;
2019-09-27 07:02:28 +03:00
/* if the swap is the resume device, stop looping swaps */
if ( location_is_resume_device ( hibernate_location , sys_resume , sys_offset ) )
break ;
2019-07-15 06:01:20 +03:00
}
2013-09-14 03:41:52 +04:00
}
2019-09-27 07:02:28 +03:00
if ( ! hibernate_location )
return log_debug_errno ( SYNTHETIC_ERRNO ( ENOSYS ) , " No swap partitions or files were found " ) ;
if ( ! streq ( sys_resume , " 0:0 " ) & & ! location_is_resume_device ( hibernate_location , sys_resume , sys_offset ) )
return log_warning_errno ( SYNTHETIC_ERRNO ( ENOSYS ) , " /sys/power/resume and /sys/power/resume_offset has no matching entry in /proc/swaps ; Hibernation will fail : resume = % s , resume_offset = % " PRIu64,
sys_resume , sys_offset ) ;
log_debug ( " Hibernation will attempt to use swap entry with path: %s, device: %s, offset: % " PRIu64 " , priority: %i " ,
hibernate_location - > swap - > device , hibernate_location - > resume , hibernate_location - > resume_offset , hibernate_location - > swap - > priority ) ;
2019-07-15 06:01:20 +03:00
2019-09-27 07:02:28 +03:00
* ret_hibernate_location = TAKE_PTR ( hibernate_location ) ;
2019-07-15 06:01:20 +03:00
2019-09-27 07:02:28 +03:00
if ( location_is_resume_device ( * ret_hibernate_location , sys_resume , sys_offset ) )
return 1 ;
2019-07-15 06:01:20 +03:00
return 0 ;
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 ;
2019-09-27 07:02:28 +03:00
_cleanup_ ( hibernate_location_freep ) HibernateLocation * hibernate_location = NULL ;
2014-02-19 20:47:11 +04:00
unsigned long long act = 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 ;
2019-09-27 07:02:28 +03:00
r = find_hibernate_location ( & hibernate_location ) ;
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 ;
}
2019-09-27 07:02:28 +03:00
r = act < = ( hibernate_location - > swap - > size - hibernate_location - > swap - > used ) * HIBERNATION_SWAP_THRESHOLD ;
2019-07-15 06:01:20 +03:00
log_debug ( " %s swap for hibernation, Active(anon)=%llu kB, size=% " PRIu64 " kB, used=% " PRIu64 " kB, threshold=%.2g%% " ,
2019-09-27 07:02:28 +03:00
r ? " Enough " : " Not enough " , act , hibernate_location - > swap - > size , hibernate_location - > swap - > 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 ;
}
2019-05-20 08:43:29 +03:00
static int can_sleep_internal ( const char * verb , bool check_allowed , const SleepConfig * sleep_config ) ;
2018-09-26 12:17:36 +03:00
2019-05-20 08:43:29 +03:00
static bool can_s2h ( const SleepConfig * sleep_config ) {
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 " ) {
2019-05-20 08:43:29 +03:00
r = can_sleep_internal ( p , false , sleep_config ) ;
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 ;
}
2019-05-20 08:43:29 +03:00
static int can_sleep_internal ( const char * verb , bool check_allowed , const SleepConfig * sleep_config ) {
2018-09-26 12:17:36 +03:00
bool allow ;
2019-05-20 08:43:29 +03:00
char * * modes = NULL , * * states = NULL ;
2013-05-04 20:31:28 +04:00
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
2019-05-20 08:43:29 +03:00
r = sleep_settings ( verb , sleep_config , & allow , & modes , & states ) ;
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 " ) )
2019-05-20 08:43:29 +03:00
return can_s2h ( sleep_config ) ;
2018-09-26 12:17:36 +03:00
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 ) {
2019-05-20 08:43:29 +03:00
_cleanup_ ( free_sleep_configp ) SleepConfig * sleep_config = NULL ;
int r ;
r = parse_sleep_config ( & sleep_config ) ;
if ( r < 0 )
return r ;
return can_sleep_internal ( verb , true , sleep_config ) ;
}
int sleep_settings ( const char * verb , const SleepConfig * sleep_config , bool * ret_allow , char * * * ret_modes , char * * * ret_states ) {
assert ( verb ) ;
assert ( sleep_config ) ;
assert ( STR_IN_SET ( verb , " suspend " , " hibernate " , " hybrid-sleep " , " suspend-then-hibernate " ) ) ;
if ( streq ( verb , " suspend " ) ) {
* ret_allow = sleep_config - > allow_suspend ;
* ret_modes = sleep_config - > suspend_modes ;
* ret_states = sleep_config - > suspend_states ;
} else if ( streq ( verb , " hibernate " ) ) {
* ret_allow = sleep_config - > allow_hibernate ;
* ret_modes = sleep_config - > hibernate_modes ;
* ret_states = sleep_config - > hibernate_states ;
} else if ( streq ( verb , " hybrid-sleep " ) ) {
* ret_allow = sleep_config - > allow_hybrid_sleep ;
* ret_modes = sleep_config - > hybrid_modes ;
* ret_states = sleep_config - > hybrid_states ;
} else if ( streq ( verb , " suspend-then-hibernate " ) ) {
* ret_allow = sleep_config - > allow_s2h ;
* ret_modes = * ret_states = NULL ;
}
/* suspend modes empty by default */
if ( ( ! ret_modes & & ! streq ( verb , " suspend " ) ) | | ! ret_states )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " No modes or states set for %s ; Check sleep . conf " , verb);
return 0 ;
}
void free_sleep_config ( SleepConfig * sc ) {
if ( ! sc )
return ;
strv_free ( sc - > suspend_modes ) ;
strv_free ( sc - > suspend_states ) ;
strv_free ( sc - > hibernate_modes ) ;
strv_free ( sc - > hibernate_states ) ;
strv_free ( sc - > hybrid_modes ) ;
strv_free ( sc - > hybrid_states ) ;
free ( sc ) ;
2018-09-26 12:17:36 +03:00
}