2020-11-09 05:23:58 +01:00
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2018-06-25 17:24:09 +02:00
# include <getopt.h>
# include <stdlib.h>
# include "alloc-util.h"
# include "bootspec.h"
2019-08-01 16:28:29 +02:00
# include "efi-loader.h"
2018-06-25 17:24:09 +02:00
# include "efivars.h"
# include "fd-util.h"
# include "fs-util.h"
# include "log.h"
2018-11-19 20:26:37 +01:00
# include "main-func.h"
2018-06-25 17:24:09 +02:00
# include "parse-util.h"
# include "path-util.h"
2019-11-15 18:38:44 +01:00
# include "pretty-print.h"
# include "terminal-util.h"
2018-06-25 17:24:09 +02:00
# include "util.h"
# include "verbs.h"
# include "virt.h"
2019-01-23 17:05:15 +01:00
static char * * arg_path = NULL ;
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
STATIC_DESTRUCTOR_REGISTER ( arg_path , strv_freep ) ;
2018-11-20 09:39:25 +01:00
2018-06-25 17:24:09 +02:00
static int help ( int argc , char * argv [ ] , void * userdata ) {
2019-11-15 18:38:44 +01:00
_cleanup_free_ char * link = NULL ;
int r ;
2018-06-25 17:24:09 +02:00
2019-11-15 18:38:44 +01:00
r = terminal_urlify_man ( " systemd-bless-boot.service " , " 8 " , & link ) ;
if ( r < 0 )
return log_oom ( ) ;
printf ( " %s [OPTIONS...] COMMAND \n "
" \n %sMark the boot process as good or bad.%s \n "
" \n Commands: \n "
2020-08-05 12:16:03 +02:00
" status Show status of current boot loader entry \n "
2019-11-15 18:38:44 +01:00
" good Mark this boot as good \n "
" bad Mark this boot as bad \n "
" indeterminate Undo any marking as good or bad \n "
" \n Options: \n "
2018-06-25 17:24:09 +02:00
" -h --help Show this help \n "
" --version Print version \n "
2019-01-23 17:05:15 +01:00
" --path=PATH Path to the $BOOT partition (may be used multiple times) \n "
2019-11-15 18:38:44 +01:00
" \n See the %s for details. \n "
, program_invocation_short_name
, ansi_highlight ( )
, ansi_normal ( )
, link
) ;
2018-06-25 17:24:09 +02:00
return 0 ;
}
static int parse_argv ( int argc , char * argv [ ] ) {
enum {
ARG_PATH = 0x100 ,
ARG_VERSION ,
} ;
static const struct option options [ ] = {
{ " help " , no_argument , NULL , ' h ' } ,
{ " version " , no_argument , NULL , ARG_VERSION } ,
{ " path " , required_argument , NULL , ARG_PATH } ,
{ }
} ;
int c , r ;
assert ( argc > = 0 ) ;
assert ( argv ) ;
while ( ( c = getopt_long ( argc , argv , " h " , options , NULL ) ) > = 0 )
switch ( c ) {
case ' h ' :
help ( 0 , NULL , NULL ) ;
return 0 ;
case ARG_VERSION :
return version ( ) ;
case ARG_PATH :
2019-01-23 17:05:15 +01:00
r = strv_extend ( & arg_path , optarg ) ;
2018-06-25 17:24:09 +02:00
if ( r < 0 )
return log_oom ( ) ;
break ;
case ' ? ' :
return - EINVAL ;
default :
assert_not_reached ( " Unknown option " ) ;
}
return 1 ;
}
2019-01-23 17:05:15 +01:00
static int acquire_path ( void ) {
_cleanup_free_ char * esp_path = NULL , * xbootldr_path = NULL ;
char * * a ;
2018-06-25 17:24:09 +02:00
int r ;
2019-01-23 17:05:15 +01:00
if ( ! strv_isempty ( arg_path ) )
return 0 ;
r = find_esp_and_warn ( NULL , false , & esp_path , NULL , NULL , NULL , NULL ) ;
if ( r < 0 & & r ! = - ENOKEY ) /* ENOKEY means not found, and is the only error the function won't log about on its own */
return r ;
r = find_xbootldr_and_warn ( NULL , false , & xbootldr_path , NULL ) ;
if ( r < 0 & & r ! = - ENOKEY )
2018-06-25 17:24:09 +02:00
return r ;
2019-01-23 17:05:15 +01:00
if ( ! esp_path & & ! xbootldr_path )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOENT ) ,
" Couldn't find $BOOT partition. It is recommended to mount it to /boot. \n "
" Alternatively, use --path= to specify path to mount point. " ) ;
if ( esp_path )
a = strv_new ( esp_path , xbootldr_path ) ;
else
a = strv_new ( xbootldr_path ) ;
if ( ! a )
return log_oom ( ) ;
strv_free_and_replace ( arg_path , a ) ;
if ( DEBUG_LOGGING ) {
_cleanup_free_ char * j ;
j = strv_join ( arg_path , " : " ) ;
log_debug ( " Using %s as boot loader drop-in search path. " , j ) ;
}
2018-06-25 17:24:09 +02:00
return 0 ;
}
static int parse_counter (
const char * path ,
const char * * p ,
uint64_t * ret_left ,
uint64_t * ret_done ) {
uint64_t left , done ;
const char * z , * e ;
size_t k ;
int r ;
assert ( path ) ;
assert ( p ) ;
e = * p ;
assert ( e ) ;
assert ( * e = = ' + ' ) ;
e + + ;
k = strspn ( e , DIGITS ) ;
2018-11-20 23:40:44 +01:00
if ( k = = 0 )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) ,
" Can't parse empty 'tries left' counter from LoaderBootCountPath: %s " ,
path ) ;
2018-06-25 17:24:09 +02:00
z = strndupa ( e , k ) ;
r = safe_atou64 ( z , & left ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to parse 'tries left' counter from LoaderBootCountPath: %s " , path ) ;
e + = k ;
if ( * e = = ' - ' ) {
e + + ;
k = strspn ( e , DIGITS ) ;
2018-11-20 23:40:44 +01:00
if ( k = = 0 ) /* If there's a "-" there also needs to be at least one digit */
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) ,
" Can't parse empty 'tries done' counter from LoaderBootCountPath: %s " ,
path ) ;
2018-06-25 17:24:09 +02:00
z = strndupa ( e , k ) ;
r = safe_atou64 ( z , & done ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to parse 'tries done' counter from LoaderBootCountPath: %s " , path ) ;
e + = k ;
} else
done = 0 ;
if ( done = = 0 )
log_warning ( " The 'tries done' counter is currently at zero. This can't really be, after all we are running, and this boot must hence count as one. Proceeding anyway. " ) ;
* p = e ;
if ( ret_left )
* ret_left = left ;
if ( ret_done )
* ret_done = done ;
return 0 ;
}
static int acquire_boot_count_path (
char * * ret_path ,
char * * ret_prefix ,
uint64_t * ret_left ,
uint64_t * ret_done ,
char * * ret_suffix ) {
_cleanup_free_ char * path = NULL , * prefix = NULL , * suffix = NULL ;
const char * last , * e ;
uint64_t left , done ;
int r ;
r = efi_get_variable_string ( EFI_VENDOR_LOADER , " LoaderBootCountPath " , & path ) ;
if ( r = = - ENOENT )
return - EUNATCH ; /* in this case, let the caller print a message */
if ( r < 0 )
return log_error_errno ( r , " Failed to read LoaderBootCountPath EFI variable: %m " ) ;
efi_tilt_backslashes ( path ) ;
2018-11-20 23:40:44 +01:00
if ( ! path_is_normalized ( path ) )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) ,
" Path read from LoaderBootCountPath is not normalized, refusing: %s " ,
path ) ;
2018-06-25 17:24:09 +02:00
2018-11-20 23:40:44 +01:00
if ( ! path_is_absolute ( path ) )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) ,
" Path read from LoaderBootCountPath is not absolute, refusing: %s " ,
path ) ;
2018-06-25 17:24:09 +02:00
last = last_path_component ( path ) ;
e = strrchr ( last , ' + ' ) ;
2018-11-20 23:40:44 +01:00
if ( ! e )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) ,
" Path read from LoaderBootCountPath does not contain a counter, refusing: %s " ,
path ) ;
2018-06-25 17:24:09 +02:00
if ( ret_prefix ) {
prefix = strndup ( path , e - path ) ;
if ( ! prefix )
return log_oom ( ) ;
}
r = parse_counter ( path , & e , & left , & done ) ;
if ( r < 0 )
return r ;
if ( ret_suffix ) {
suffix = strdup ( e ) ;
if ( ! suffix )
return log_oom ( ) ;
* ret_suffix = TAKE_PTR ( suffix ) ;
}
if ( ret_path )
* ret_path = TAKE_PTR ( path ) ;
if ( ret_prefix )
* ret_prefix = TAKE_PTR ( prefix ) ;
if ( ret_left )
* ret_left = left ;
if ( ret_done )
* ret_done = done ;
return 0 ;
}
static int make_good ( const char * prefix , const char * suffix , char * * ret ) {
_cleanup_free_ char * good = NULL ;
assert ( prefix ) ;
assert ( suffix ) ;
assert ( ret ) ;
/* Generate the path we'd use on good boots. This one is easy. If we are successful, we simple drop the counter
* pair entirely from the name . After all , we know all is good , and the logs will contain information about the
* tries we needed to come here , hence it ' s safe to drop the counters from the name . */
good = strjoin ( prefix , suffix ) ;
if ( ! good )
return - ENOMEM ;
* ret = TAKE_PTR ( good ) ;
return 0 ;
}
static int make_bad ( const char * prefix , uint64_t done , const char * suffix , char * * ret ) {
_cleanup_free_ char * bad = NULL ;
assert ( prefix ) ;
assert ( suffix ) ;
assert ( ret ) ;
/* Generate the path we'd use on bad boots. Let's simply set the 'left' counter to zero, and keep the 'done'
* counter . The information might be interesting to boot loaders , after all . */
if ( done = = 0 ) {
bad = strjoin ( prefix , " +0 " , suffix ) ;
if ( ! bad )
return - ENOMEM ;
} else {
if ( asprintf ( & bad , " %s+0-% " PRIu64 " %s " , prefix , done , suffix ) < 0 )
return - ENOMEM ;
}
* ret = TAKE_PTR ( bad ) ;
return 0 ;
}
static const char * skip_slash ( const char * path ) {
assert ( path ) ;
assert ( path [ 0 ] = = ' / ' ) ;
return path + 1 ;
}
static int verb_status ( int argc , char * argv [ ] , void * userdata ) {
_cleanup_free_ char * path = NULL , * prefix = NULL , * suffix = NULL , * good = NULL , * bad = NULL ;
uint64_t left , done ;
2019-01-23 17:05:15 +01:00
char * * p ;
2018-06-25 17:24:09 +02:00
int r ;
r = acquire_boot_count_path ( & path , & prefix , & left , & done , & suffix ) ;
if ( r = = - EUNATCH ) { /* No boot count in place, then let's consider this a "clean" boot, as "good", "bad" or "indeterminate" don't apply. */
puts ( " clean " ) ;
return 0 ;
}
if ( r < 0 )
return r ;
2019-01-23 17:05:15 +01:00
r = acquire_path ( ) ;
2018-06-25 17:24:09 +02:00
if ( r < 0 )
return r ;
r = make_good ( prefix , suffix , & good ) ;
if ( r < 0 )
return log_oom ( ) ;
r = make_bad ( prefix , done , suffix , & bad ) ;
if ( r < 0 )
return log_oom ( ) ;
2019-01-23 17:05:15 +01:00
log_debug ( " Booted file: %s \n "
" The same modified for 'good': %s \n "
" The same modified for 'bad': %s \n " ,
path ,
good ,
bad ) ;
2018-06-25 17:24:09 +02:00
log_debug ( " Tries left: % " PRIu64 " \n "
" Tries done: % " PRIu64 " \n " ,
left , done ) ;
2019-01-23 17:05:15 +01:00
STRV_FOREACH ( p , arg_path ) {
_cleanup_close_ int fd = - 1 ;
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
fd = open ( * p , O_DIRECTORY | O_CLOEXEC | O_RDONLY ) ;
if ( fd < 0 ) {
if ( errno = = ENOENT )
continue ;
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
return log_error_errno ( errno , " Failed to open $BOOT partition '%s': %m " , * p ) ;
}
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
if ( faccessat ( fd , skip_slash ( path ) , F_OK , 0 ) > = 0 ) {
puts ( " indeterminate " ) ;
return 0 ;
}
if ( errno ! = ENOENT )
return log_error_errno ( errno , " Failed to check if '%s' exists: %m " , path ) ;
if ( faccessat ( fd , skip_slash ( good ) , F_OK , 0 ) > = 0 ) {
puts ( " good " ) ;
return 0 ;
}
if ( errno ! = ENOENT )
return log_error_errno ( errno , " Failed to check if '%s' exists: %m " , good ) ;
if ( faccessat ( fd , skip_slash ( bad ) , F_OK , 0 ) > = 0 ) {
puts ( " bad " ) ;
return 0 ;
}
if ( errno ! = ENOENT )
return log_error_errno ( errno , " Failed to check if '%s' exists: %m " , bad ) ;
/* We didn't find any of the three? If so, let's try the next directory, before we give up. */
2018-06-25 17:24:09 +02:00
}
2019-01-23 17:05:15 +01:00
return log_error_errno ( SYNTHETIC_ERRNO ( EBUSY ) , " Couldn't determine boot state: %m " ) ;
2018-06-25 17:24:09 +02:00
}
static int verb_set ( int argc , char * argv [ ] , void * userdata ) {
_cleanup_free_ char * path = NULL , * prefix = NULL , * suffix = NULL , * good = NULL , * bad = NULL , * parent = NULL ;
const char * target , * source1 , * source2 ;
uint64_t done ;
2019-01-23 17:05:15 +01:00
char * * p ;
2018-06-25 17:24:09 +02:00
int r ;
r = acquire_boot_count_path ( & path , & prefix , NULL , & done , & suffix ) ;
if ( r = = - EUNATCH ) /* acquire_boot_count_path() won't log on its own for this specific error */
return log_error_errno ( r , " Not booted with boot counting in effect. " ) ;
if ( r < 0 )
return r ;
2019-01-23 17:05:15 +01:00
r = acquire_path ( ) ;
2018-06-25 17:24:09 +02:00
if ( r < 0 )
return r ;
r = make_good ( prefix , suffix , & good ) ;
if ( r < 0 )
return log_oom ( ) ;
r = make_bad ( prefix , done , suffix , & bad ) ;
if ( r < 0 )
return log_oom ( ) ;
/* Figure out what rename to what */
if ( streq ( argv [ 0 ] , " good " ) ) {
target = good ;
source1 = path ;
source2 = bad ; /* Maybe this boot was previously marked as 'bad'? */
} else if ( streq ( argv [ 0 ] , " bad " ) ) {
target = bad ;
source1 = path ;
source2 = good ; /* Maybe this boot was previously marked as 'good'? */
} else {
assert ( streq ( argv [ 0 ] , " indeterminate " ) ) ;
target = path ;
source1 = good ;
source2 = bad ;
}
2019-01-23 17:05:15 +01:00
STRV_FOREACH ( p , arg_path ) {
_cleanup_close_ int fd = - 1 ;
fd = open ( * p , O_DIRECTORY | O_CLOEXEC | O_RDONLY ) ;
if ( fd < 0 )
return log_error_errno ( errno , " Failed to open $BOOT partition '%s': %m " , * p ) ;
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
r = rename_noreplace ( fd , skip_slash ( source1 ) , fd , skip_slash ( target ) ) ;
2018-06-25 17:24:09 +02:00
if ( r = = - EEXIST )
goto exists ;
else if ( r = = - ENOENT ) {
2019-01-23 17:05:15 +01:00
r = rename_noreplace ( fd , skip_slash ( source2 ) , fd , skip_slash ( target ) ) ;
if ( r = = - EEXIST )
2018-06-25 17:24:09 +02:00
goto exists ;
2019-01-23 17:05:15 +01:00
else if ( r = = - ENOENT ) {
if ( faccessat ( fd , skip_slash ( target ) , F_OK , 0 ) > = 0 ) /* Hmm, if we can't find either source file, maybe the destination already exists? */
goto exists ;
if ( errno ! = ENOENT )
return log_error_errno ( errno , " Failed to determine if %s already exists: %m " , target ) ;
/* We found none of the snippets here, try the next directory */
continue ;
} else if ( r < 0 )
return log_error_errno ( r , " Failed to rename '%s' to '%s': %m " , source2 , target ) ;
else
log_debug ( " Successfully renamed '%s' to '%s'. " , source2 , target ) ;
2018-06-25 17:24:09 +02:00
} else if ( r < 0 )
2019-01-23 17:05:15 +01:00
return log_error_errno ( r , " Failed to rename '%s' to '%s': %m " , source1 , target ) ;
2018-06-25 17:24:09 +02:00
else
2019-01-23 17:05:15 +01:00
log_debug ( " Successfully renamed '%s' to '%s'. " , source1 , target ) ;
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
/* First, fsync() the directory these files are located in */
parent = dirname_malloc ( target ) ;
if ( ! parent )
return log_oom ( ) ;
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
r = fsync_path_at ( fd , skip_slash ( parent ) ) ;
if ( r < 0 )
log_debug_errno ( errno , " Failed to synchronize image directory, ignoring: %m " ) ;
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
/* Secondly, syncfs() the whole file system these files are located in */
if ( syncfs ( fd ) < 0 )
log_debug_errno ( errno , " Failed to synchronize $BOOT partition, ignoring: %m " ) ;
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
log_info ( " Marked boot as '%s'. (Boot attempt counter is at % " PRIu64 " .) " , argv [ 0 ] , done ) ;
}
2018-06-25 17:24:09 +02:00
2019-01-23 17:05:15 +01:00
log_error_errno ( SYNTHETIC_ERRNO ( EBUSY ) , " Can't find boot counter source file for '%s': %m " , target ) ;
2018-06-25 17:24:09 +02:00
return 1 ;
exists :
log_debug ( " Operation already executed before, not doing anything. " ) ;
return 0 ;
}
2018-11-20 09:39:25 +01:00
static int run ( int argc , char * argv [ ] ) {
2018-06-25 17:24:09 +02:00
static const Verb verbs [ ] = {
2019-01-18 16:46:37 +01:00
{ " help " , VERB_ANY , VERB_ANY , 0 , help } ,
{ " status " , VERB_ANY , 1 , VERB_DEFAULT , verb_status } ,
{ " good " , VERB_ANY , 1 , 0 , verb_set } ,
{ " bad " , VERB_ANY , 1 , 0 , verb_set } ,
{ " indeterminate " , VERB_ANY , 1 , 0 , verb_set } ,
2018-06-25 17:24:09 +02:00
{ }
} ;
int r ;
log_parse_environment ( ) ;
log_open ( ) ;
r = parse_argv ( argc , argv ) ;
if ( r < = 0 )
2018-11-20 09:39:25 +01:00
return r ;
2018-06-25 17:24:09 +02:00
2018-11-20 23:40:44 +01:00
if ( detect_container ( ) > 0 )
return log_error_errno ( SYNTHETIC_ERRNO ( EOPNOTSUPP ) ,
" Marking a boot is not supported in containers. " ) ;
2018-06-25 17:24:09 +02:00
2018-11-20 23:40:44 +01:00
if ( ! is_efi_boot ( ) )
return log_error_errno ( SYNTHETIC_ERRNO ( EOPNOTSUPP ) ,
" Marking a boot is only supported on EFI systems. " ) ;
2018-06-25 17:24:09 +02:00
2018-11-20 09:39:25 +01:00
return dispatch_verb ( argc , argv , verbs , NULL ) ;
2018-06-25 17:24:09 +02:00
}
2018-11-20 09:39:25 +01:00
DEFINE_MAIN_FUNCTION ( run ) ;