| // 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. | |
| * @state: The current admin 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. | |
| * @state: The current admin state. | |
| * @result: The result of the 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) | |
| vdo_launch_completion(vdo_forget(state->waiter)); | |
| } | |
| return true; | |
| } | |
| /** | |
| * begin_operation() - Begin an operation if it may be started given the current state. | |
| * @state: The current admin state. | |
| * @operation: The operation to be started. | |
| * @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) { | |
| result = vdo_log_error_strerror(VDO_INVALID_ADMIN_STATE, | |
| "Can't start %s from %s", | |
| operation->name, | |
| vdo_get_admin_state_code(state)->name); | |
| } else if (state->waiter != NULL) { | |
| result = vdo_log_error_strerror(VDO_COMPONENT_BUSY, | |
| "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. | |
| * @state: The current admin state. | |
| * @operation: The operation to be started. | |
| * @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: 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; | |
| result = vdo_log_error_strerror(VDO_INVALID_ADMIN_STATE, | |
| "%s is not a %s", code->name, what); | |
| if (waiter != NULL) | |
| vdo_continue_completion(waiter, result); | |
| return false; | |
| } | |
| /** | |
| * assert_vdo_drain_operation() - Check that an operation is a drain. | |
| * @operation: The operation to check. | |
| * @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. | |
| * @state: The current admin state. | |
| * @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) { | |
| vdo_log_error_strerror(VDO_INVALID_ADMIN_STATE, "can't start %s from %s", | |
| 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. | |
| * @state: The current admin state. | |
| * | |
| * 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. | |
| * @state: The current admin state. | |
| * @result: The result of the drain operation. | |
| * | |
| * 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. | |
| * @operation: The operation to check. | |
| * @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. | |
| * @state: The current admin state. | |
| * @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. | |
| * @state: The current admin state. | |
| * | |
| * 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. | |
| * @state: The current admin state. | |
| * @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. | |
| * @operation: The operation to check. | |
| * @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. | |
| * @state: The current admin state. | |
| * @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. | |
| * @state: The current admin state. | |
| * | |
| * 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. | |
| * @state: The current admin state. | |
| * @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. | |
| * @state: The current admin state. | |
| * | |
| * 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. | |
| * @state: The current admin state. | |
| * @operation: The operation to attempt to start. | |
| * | |
| * 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. | |
| * @state: The current admin state. | |
| * @operation: The operation to attempt to start. | |
| * @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); | |
| } |