| // 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) |
| vdo_launch_completion(vdo_forget(state->waiter)); |
| } |
| |
| 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) { |
| 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. |
| * @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; |
| |
| 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. |
| * @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) { |
| 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. |
| * |
| * 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); |
| } |