2023-11-16 19:58:24 -05:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
# include "admin-state.h"
# include "logger.h"
# include "memory-alloc.h"
# include "permassert.h"
# include "completion.h"
# include "types.h"
static const struct admin_state_code VDO_CODE_NORMAL_OPERATION = {
. name = " VDO_ADMIN_STATE_NORMAL_OPERATION " ,
. normal = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_NORMAL_OPERATION = & VDO_CODE_NORMAL_OPERATION ;
static const struct admin_state_code VDO_CODE_OPERATING = {
. name = " VDO_ADMIN_STATE_OPERATING " ,
. normal = true ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_OPERATING = & VDO_CODE_OPERATING ;
static const struct admin_state_code VDO_CODE_FORMATTING = {
. name = " VDO_ADMIN_STATE_FORMATTING " ,
. operating = true ,
. loading = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_FORMATTING = & VDO_CODE_FORMATTING ;
static const struct admin_state_code VDO_CODE_PRE_LOADING = {
. name = " VDO_ADMIN_STATE_PRE_LOADING " ,
. operating = true ,
. loading = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_PRE_LOADING = & VDO_CODE_PRE_LOADING ;
static const struct admin_state_code VDO_CODE_PRE_LOADED = {
. name = " VDO_ADMIN_STATE_PRE_LOADED " ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_PRE_LOADED = & VDO_CODE_PRE_LOADED ;
static const struct admin_state_code VDO_CODE_LOADING = {
. name = " VDO_ADMIN_STATE_LOADING " ,
. normal = true ,
. operating = true ,
. loading = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_LOADING = & VDO_CODE_LOADING ;
static const struct admin_state_code VDO_CODE_LOADING_FOR_RECOVERY = {
. name = " VDO_ADMIN_STATE_LOADING_FOR_RECOVERY " ,
. operating = true ,
. loading = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_LOADING_FOR_RECOVERY =
& VDO_CODE_LOADING_FOR_RECOVERY ;
static const struct admin_state_code VDO_CODE_LOADING_FOR_REBUILD = {
. name = " VDO_ADMIN_STATE_LOADING_FOR_REBUILD " ,
. operating = true ,
. loading = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_LOADING_FOR_REBUILD = & VDO_CODE_LOADING_FOR_REBUILD ;
static const struct admin_state_code VDO_CODE_WAITING_FOR_RECOVERY = {
. name = " VDO_ADMIN_STATE_WAITING_FOR_RECOVERY " ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_WAITING_FOR_RECOVERY =
& VDO_CODE_WAITING_FOR_RECOVERY ;
static const struct admin_state_code VDO_CODE_NEW = {
. name = " VDO_ADMIN_STATE_NEW " ,
. quiescent = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_NEW = & VDO_CODE_NEW ;
static const struct admin_state_code VDO_CODE_INITIALIZED = {
. name = " VDO_ADMIN_STATE_INITIALIZED " ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_INITIALIZED = & VDO_CODE_INITIALIZED ;
static const struct admin_state_code VDO_CODE_RECOVERING = {
. name = " VDO_ADMIN_STATE_RECOVERING " ,
. draining = true ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_RECOVERING = & VDO_CODE_RECOVERING ;
static const struct admin_state_code VDO_CODE_REBUILDING = {
. name = " VDO_ADMIN_STATE_REBUILDING " ,
. draining = true ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_REBUILDING = & VDO_CODE_REBUILDING ;
static const struct admin_state_code VDO_CODE_SAVING = {
. name = " VDO_ADMIN_STATE_SAVING " ,
. draining = true ,
. quiescing = true ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_SAVING = & VDO_CODE_SAVING ;
static const struct admin_state_code VDO_CODE_SAVED = {
. name = " VDO_ADMIN_STATE_SAVED " ,
. quiescent = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_SAVED = & VDO_CODE_SAVED ;
static const struct admin_state_code VDO_CODE_SCRUBBING = {
. name = " VDO_ADMIN_STATE_SCRUBBING " ,
. draining = true ,
. loading = true ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_SCRUBBING = & VDO_CODE_SCRUBBING ;
static const struct admin_state_code VDO_CODE_SAVE_FOR_SCRUBBING = {
. name = " VDO_ADMIN_STATE_SAVE_FOR_SCRUBBING " ,
. draining = true ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_SAVE_FOR_SCRUBBING = & VDO_CODE_SAVE_FOR_SCRUBBING ;
static const struct admin_state_code VDO_CODE_STOPPING = {
. name = " VDO_ADMIN_STATE_STOPPING " ,
. draining = true ,
. quiescing = true ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_STOPPING = & VDO_CODE_STOPPING ;
static const struct admin_state_code VDO_CODE_STOPPED = {
. name = " VDO_ADMIN_STATE_STOPPED " ,
. quiescent = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_STOPPED = & VDO_CODE_STOPPED ;
static const struct admin_state_code VDO_CODE_SUSPENDING = {
. name = " VDO_ADMIN_STATE_SUSPENDING " ,
. draining = true ,
. quiescing = true ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_SUSPENDING = & VDO_CODE_SUSPENDING ;
static const struct admin_state_code VDO_CODE_SUSPENDED = {
. name = " VDO_ADMIN_STATE_SUSPENDED " ,
. quiescent = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_SUSPENDED = & VDO_CODE_SUSPENDED ;
static const struct admin_state_code VDO_CODE_SUSPENDED_OPERATION = {
. name = " VDO_ADMIN_STATE_SUSPENDED_OPERATION " ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_SUSPENDED_OPERATION = & VDO_CODE_SUSPENDED_OPERATION ;
static const struct admin_state_code VDO_CODE_RESUMING = {
. name = " VDO_ADMIN_STATE_RESUMING " ,
. operating = true ,
} ;
const struct admin_state_code * VDO_ADMIN_STATE_RESUMING = & VDO_CODE_RESUMING ;
/**
* get_next_state ( ) - Determine the state which should be set after a given operation completes
* based on the operation and the current state .
* @ operation The operation to be started .
*
* Return : The state to set when the operation completes or NULL if the operation can not be
* started in the current state .
*/
static const struct admin_state_code * get_next_state ( const struct admin_state * state ,
const struct admin_state_code * operation )
{
const struct admin_state_code * code = vdo_get_admin_state_code ( state ) ;
if ( code - > operating )
return NULL ;
if ( operation = = VDO_ADMIN_STATE_SAVING )
return ( code = = VDO_ADMIN_STATE_NORMAL_OPERATION ? VDO_ADMIN_STATE_SAVED : NULL ) ;
if ( operation = = VDO_ADMIN_STATE_SUSPENDING ) {
return ( code = = VDO_ADMIN_STATE_NORMAL_OPERATION
? VDO_ADMIN_STATE_SUSPENDED
: NULL ) ;
}
if ( operation = = VDO_ADMIN_STATE_STOPPING )
return ( code = = VDO_ADMIN_STATE_NORMAL_OPERATION ? VDO_ADMIN_STATE_STOPPED : NULL ) ;
if ( operation = = VDO_ADMIN_STATE_PRE_LOADING )
return ( code = = VDO_ADMIN_STATE_INITIALIZED ? VDO_ADMIN_STATE_PRE_LOADED : NULL ) ;
if ( operation = = VDO_ADMIN_STATE_SUSPENDED_OPERATION ) {
return ( ( ( code = = VDO_ADMIN_STATE_SUSPENDED ) | |
( code = = VDO_ADMIN_STATE_SAVED ) ) ? code : NULL ) ;
}
return VDO_ADMIN_STATE_NORMAL_OPERATION ;
}
/**
* vdo_finish_operation ( ) - Finish the current operation .
*
* Will notify the operation waiter if there is one . This method should be used for operations
* started with vdo_start_operation ( ) . For operations which were started with vdo_start_draining ( ) ,
* use vdo_finish_draining ( ) instead .
*
* Return : true if there was an operation to finish .
*/
bool vdo_finish_operation ( struct admin_state * state , int result )
{
if ( ! vdo_get_admin_state_code ( state ) - > operating )
return false ;
state - > complete = state - > starting ;
if ( state - > waiter ! = NULL )
vdo_set_completion_result ( state - > waiter , result ) ;
if ( ! state - > starting ) {
vdo_set_admin_state_code ( state , state - > next_state ) ;
if ( state - > waiter ! = NULL )
2024-02-13 10:55:50 -06:00
vdo_launch_completion ( vdo_forget ( state - > waiter ) ) ;
2023-11-16 19:58:24 -05:00
}
return true ;
}
/**
* begin_operation ( ) - Begin an operation if it may be started given the current state .
* @ waiter A completion to notify when the operation is complete ; may be NULL .
* @ initiator The vdo_admin_initiator_fn to call if the operation may begin ; may be NULL .
*
* Return : VDO_SUCCESS or an error .
*/
static int __must_check begin_operation ( struct admin_state * state ,
const struct admin_state_code * operation ,
struct vdo_completion * waiter ,
vdo_admin_initiator_fn initiator )
{
int result ;
const struct admin_state_code * next_state = get_next_state ( state , operation ) ;
if ( next_state = = NULL ) {
2024-02-14 09:22:04 -06:00
result = vdo_log_error_strerror ( VDO_INVALID_ADMIN_STATE ,
2023-11-16 19:58:24 -05:00
" Can't start %s from %s " ,
operation - > name ,
vdo_get_admin_state_code ( state ) - > name ) ;
} else if ( state - > waiter ! = NULL ) {
2024-02-14 09:22:04 -06:00
result = vdo_log_error_strerror ( VDO_COMPONENT_BUSY ,
2023-11-16 19:58:24 -05:00
" Can't start %s with extant waiter " ,
operation - > name ) ;
} else {
state - > waiter = waiter ;
state - > next_state = next_state ;
vdo_set_admin_state_code ( state , operation ) ;
if ( initiator ! = NULL ) {
state - > starting = true ;
initiator ( state ) ;
state - > starting = false ;
if ( state - > complete )
vdo_finish_operation ( state , VDO_SUCCESS ) ;
}
return VDO_SUCCESS ;
}
if ( waiter ! = NULL )
vdo_continue_completion ( waiter , result ) ;
return result ;
}
/**
* start_operation ( ) - Start an operation if it may be started given the current state .
* @ waiter A completion to notify when the operation is complete .
* @ initiator The vdo_admin_initiator_fn to call if the operation may begin ; may be NULL .
*
* Return : true if the operation was started .
*/
static inline bool __must_check start_operation ( struct admin_state * state ,
const struct admin_state_code * operation ,
struct vdo_completion * waiter ,
vdo_admin_initiator_fn initiator )
{
return ( begin_operation ( state , operation , waiter , initiator ) = = VDO_SUCCESS ) ;
}
/**
* check_code ( ) - Check the result of a state validation .
* @ valid true if the code is of an appropriate type .
* @ code The code which failed to be of the correct type .
* @ what What the code failed to be , for logging .
* @ waiter The completion to notify of the error ; may be NULL .
*
* If the result failed , log an invalid state error and , if there is a waiter , notify it .
*
* Return : The result of the check .
*/
static bool check_code ( bool valid , const struct admin_state_code * code , const char * what ,
struct vdo_completion * waiter )
{
int result ;
if ( valid )
return true ;
2024-02-14 09:22:04 -06:00
result = vdo_log_error_strerror ( VDO_INVALID_ADMIN_STATE ,
2023-11-16 19:58:24 -05:00
" %s is not a %s " , code - > name , what ) ;
if ( waiter ! = NULL )
vdo_continue_completion ( waiter , result ) ;
return false ;
}
/**
2024-02-21 17:17:31 +08:00
* assert_vdo_drain_operation ( ) - Check that an operation is a drain .
2023-11-16 19:58:24 -05:00
* @ waiter The completion to finish with an error if the operation is not a drain .
*
* Return : true if the specified operation is a drain .
*/
static bool __must_check assert_vdo_drain_operation ( const struct admin_state_code * operation ,
struct vdo_completion * waiter )
{
return check_code ( operation - > draining , operation , " drain operation " , waiter ) ;
}
/**
* vdo_start_draining ( ) - Initiate a drain operation if the current state permits it .
* @ operation The type of drain to initiate .
* @ waiter The completion to notify when the drain is complete .
* @ initiator The vdo_admin_initiator_fn to call if the operation may begin ; may be NULL .
*
* Return : true if the drain was initiated , if not the waiter will be notified .
*/
bool vdo_start_draining ( struct admin_state * state ,
const struct admin_state_code * operation ,
struct vdo_completion * waiter , vdo_admin_initiator_fn initiator )
{
const struct admin_state_code * code = vdo_get_admin_state_code ( state ) ;
if ( ! assert_vdo_drain_operation ( operation , waiter ) )
return false ;
if ( code - > quiescent ) {
vdo_launch_completion ( waiter ) ;
return false ;
}
if ( ! code - > normal ) {
2024-02-14 09:22:04 -06:00
vdo_log_error_strerror ( VDO_INVALID_ADMIN_STATE , " can't start %s from %s " ,
2023-11-16 19:58:24 -05:00
operation - > name , code - > name ) ;
vdo_continue_completion ( waiter , VDO_INVALID_ADMIN_STATE ) ;
return false ;
}
return start_operation ( state , operation , waiter , initiator ) ;
}
/**
* vdo_finish_draining ( ) - Finish a drain operation if one was in progress .
*
* Return : true if the state was draining ; will notify the waiter if so .
*/
bool vdo_finish_draining ( struct admin_state * state )
{
return vdo_finish_draining_with_result ( state , VDO_SUCCESS ) ;
}
/**
* vdo_finish_draining_with_result ( ) - Finish a drain operation with a status code .
*
* Return : true if the state was draining ; will notify the waiter if so .
*/
bool vdo_finish_draining_with_result ( struct admin_state * state , int result )
{
return ( vdo_is_state_draining ( state ) & & vdo_finish_operation ( state , result ) ) ;
}
/**
* vdo_assert_load_operation ( ) - Check that an operation is a load .
* @ waiter The completion to finish with an error if the operation is not a load .
*
* Return : true if the specified operation is a load .
*/
bool vdo_assert_load_operation ( const struct admin_state_code * operation ,
struct vdo_completion * waiter )
{
return check_code ( operation - > loading , operation , " load operation " , waiter ) ;
}
/**
* vdo_start_loading ( ) - Initiate a load operation if the current state permits it .
* @ operation The type of load to initiate .
* @ waiter The completion to notify when the load is complete ( may be NULL ) .
* @ initiator The vdo_admin_initiator_fn to call if the operation may begin ; may be NULL .
*
* Return : true if the load was initiated , if not the waiter will be notified .
*/
bool vdo_start_loading ( struct admin_state * state ,
const struct admin_state_code * operation ,
struct vdo_completion * waiter , vdo_admin_initiator_fn initiator )
{
return ( vdo_assert_load_operation ( operation , waiter ) & &
start_operation ( state , operation , waiter , initiator ) ) ;
}
/**
* vdo_finish_loading ( ) - Finish a load operation if one was in progress .
*
* Return : true if the state was loading ; will notify the waiter if so .
*/
bool vdo_finish_loading ( struct admin_state * state )
{
return vdo_finish_loading_with_result ( state , VDO_SUCCESS ) ;
}
/**
* vdo_finish_loading_with_result ( ) - Finish a load operation with a status code .
* @ result The result of the load operation .
*
* Return : true if the state was loading ; will notify the waiter if so .
*/
bool vdo_finish_loading_with_result ( struct admin_state * state , int result )
{
return ( vdo_is_state_loading ( state ) & & vdo_finish_operation ( state , result ) ) ;
}
/**
* assert_vdo_resume_operation ( ) - Check whether an admin_state_code is a resume operation .
* @ waiter The completion to notify if the operation is not a resume operation ; may be NULL .
*
* Return : true if the code is a resume operation .
*/
static bool __must_check assert_vdo_resume_operation ( const struct admin_state_code * operation ,
struct vdo_completion * waiter )
{
return check_code ( operation = = VDO_ADMIN_STATE_RESUMING , operation ,
" resume operation " , waiter ) ;
}
/**
* vdo_start_resuming ( ) - Initiate a resume operation if the current state permits it .
* @ operation The type of resume to start .
* @ waiter The completion to notify when the resume is complete ( may be NULL ) .
* @ initiator The vdo_admin_initiator_fn to call if the operation may begin ; may be NULL .
*
* Return : true if the resume was initiated , if not the waiter will be notified .
*/
bool vdo_start_resuming ( struct admin_state * state ,
const struct admin_state_code * operation ,
struct vdo_completion * waiter , vdo_admin_initiator_fn initiator )
{
return ( assert_vdo_resume_operation ( operation , waiter ) & &
start_operation ( state , operation , waiter , initiator ) ) ;
}
/**
* vdo_finish_resuming ( ) - Finish a resume operation if one was in progress .
*
* Return : true if the state was resuming ; will notify the waiter if so .
*/
bool vdo_finish_resuming ( struct admin_state * state )
{
return vdo_finish_resuming_with_result ( state , VDO_SUCCESS ) ;
}
/**
* vdo_finish_resuming_with_result ( ) - Finish a resume operation with a status code .
* @ result The result of the resume operation .
*
* Return : true if the state was resuming ; will notify the waiter if so .
*/
bool vdo_finish_resuming_with_result ( struct admin_state * state , int result )
{
return ( vdo_is_state_resuming ( state ) & & vdo_finish_operation ( state , result ) ) ;
}
/**
* vdo_resume_if_quiescent ( ) - Change the state to normal operation if the current state is
* quiescent .
*
* Return : VDO_SUCCESS if the state resumed , VDO_INVALID_ADMIN_STATE otherwise .
*/
int vdo_resume_if_quiescent ( struct admin_state * state )
{
if ( ! vdo_is_state_quiescent ( state ) )
return VDO_INVALID_ADMIN_STATE ;
vdo_set_admin_state_code ( state , VDO_ADMIN_STATE_NORMAL_OPERATION ) ;
return VDO_SUCCESS ;
}
/**
* vdo_start_operation ( ) - Attempt to start an operation .
*
* Return : VDO_SUCCESS if the operation was started , VDO_INVALID_ADMIN_STATE if not
*/
int vdo_start_operation ( struct admin_state * state ,
const struct admin_state_code * operation )
{
return vdo_start_operation_with_waiter ( state , operation , NULL , NULL ) ;
}
/**
* vdo_start_operation_with_waiter ( ) - Attempt to start an operation .
* @ waiter the completion to notify when the operation completes or fails to start ; may be NULL .
* @ initiator The vdo_admin_initiator_fn to call if the operation may begin ; may be NULL .
*
* Return : VDO_SUCCESS if the operation was started , VDO_INVALID_ADMIN_STATE if not
*/
int vdo_start_operation_with_waiter ( struct admin_state * state ,
const struct admin_state_code * operation ,
struct vdo_completion * waiter ,
vdo_admin_initiator_fn initiator )
{
return ( check_code ( operation - > operating , operation , " operation " , waiter ) ?
begin_operation ( state , operation , waiter , initiator ) :
VDO_INVALID_ADMIN_STATE ) ;
}