507 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright 2023 Red Hat4 */5 6#include "admin-state.h"7 8#include "logger.h"9#include "memory-alloc.h"10#include "permassert.h"11 12#include "completion.h"13#include "types.h"14 15static const struct admin_state_code VDO_CODE_NORMAL_OPERATION = {16 .name = "VDO_ADMIN_STATE_NORMAL_OPERATION",17 .normal = true,18};19const struct admin_state_code *VDO_ADMIN_STATE_NORMAL_OPERATION = &VDO_CODE_NORMAL_OPERATION;20static const struct admin_state_code VDO_CODE_OPERATING = {21 .name = "VDO_ADMIN_STATE_OPERATING",22 .normal = true,23 .operating = true,24};25const struct admin_state_code *VDO_ADMIN_STATE_OPERATING = &VDO_CODE_OPERATING;26static const struct admin_state_code VDO_CODE_FORMATTING = {27 .name = "VDO_ADMIN_STATE_FORMATTING",28 .operating = true,29 .loading = true,30};31const struct admin_state_code *VDO_ADMIN_STATE_FORMATTING = &VDO_CODE_FORMATTING;32static const struct admin_state_code VDO_CODE_PRE_LOADING = {33 .name = "VDO_ADMIN_STATE_PRE_LOADING",34 .operating = true,35 .loading = true,36};37const struct admin_state_code *VDO_ADMIN_STATE_PRE_LOADING = &VDO_CODE_PRE_LOADING;38static const struct admin_state_code VDO_CODE_PRE_LOADED = {39 .name = "VDO_ADMIN_STATE_PRE_LOADED",40};41const struct admin_state_code *VDO_ADMIN_STATE_PRE_LOADED = &VDO_CODE_PRE_LOADED;42static const struct admin_state_code VDO_CODE_LOADING = {43 .name = "VDO_ADMIN_STATE_LOADING",44 .normal = true,45 .operating = true,46 .loading = true,47};48const struct admin_state_code *VDO_ADMIN_STATE_LOADING = &VDO_CODE_LOADING;49static const struct admin_state_code VDO_CODE_LOADING_FOR_RECOVERY = {50 .name = "VDO_ADMIN_STATE_LOADING_FOR_RECOVERY",51 .operating = true,52 .loading = true,53};54const struct admin_state_code *VDO_ADMIN_STATE_LOADING_FOR_RECOVERY =55 &VDO_CODE_LOADING_FOR_RECOVERY;56static const struct admin_state_code VDO_CODE_LOADING_FOR_REBUILD = {57 .name = "VDO_ADMIN_STATE_LOADING_FOR_REBUILD",58 .operating = true,59 .loading = true,60};61const struct admin_state_code *VDO_ADMIN_STATE_LOADING_FOR_REBUILD = &VDO_CODE_LOADING_FOR_REBUILD;62static const struct admin_state_code VDO_CODE_WAITING_FOR_RECOVERY = {63 .name = "VDO_ADMIN_STATE_WAITING_FOR_RECOVERY",64 .operating = true,65};66const struct admin_state_code *VDO_ADMIN_STATE_WAITING_FOR_RECOVERY =67 &VDO_CODE_WAITING_FOR_RECOVERY;68static const struct admin_state_code VDO_CODE_NEW = {69 .name = "VDO_ADMIN_STATE_NEW",70 .quiescent = true,71};72const struct admin_state_code *VDO_ADMIN_STATE_NEW = &VDO_CODE_NEW;73static const struct admin_state_code VDO_CODE_INITIALIZED = {74 .name = "VDO_ADMIN_STATE_INITIALIZED",75};76const struct admin_state_code *VDO_ADMIN_STATE_INITIALIZED = &VDO_CODE_INITIALIZED;77static const struct admin_state_code VDO_CODE_RECOVERING = {78 .name = "VDO_ADMIN_STATE_RECOVERING",79 .draining = true,80 .operating = true,81};82const struct admin_state_code *VDO_ADMIN_STATE_RECOVERING = &VDO_CODE_RECOVERING;83static const struct admin_state_code VDO_CODE_REBUILDING = {84 .name = "VDO_ADMIN_STATE_REBUILDING",85 .draining = true,86 .operating = true,87};88const struct admin_state_code *VDO_ADMIN_STATE_REBUILDING = &VDO_CODE_REBUILDING;89static const struct admin_state_code VDO_CODE_SAVING = {90 .name = "VDO_ADMIN_STATE_SAVING",91 .draining = true,92 .quiescing = true,93 .operating = true,94};95const struct admin_state_code *VDO_ADMIN_STATE_SAVING = &VDO_CODE_SAVING;96static const struct admin_state_code VDO_CODE_SAVED = {97 .name = "VDO_ADMIN_STATE_SAVED",98 .quiescent = true,99};100const struct admin_state_code *VDO_ADMIN_STATE_SAVED = &VDO_CODE_SAVED;101static const struct admin_state_code VDO_CODE_SCRUBBING = {102 .name = "VDO_ADMIN_STATE_SCRUBBING",103 .draining = true,104 .loading = true,105 .operating = true,106};107const struct admin_state_code *VDO_ADMIN_STATE_SCRUBBING = &VDO_CODE_SCRUBBING;108static const struct admin_state_code VDO_CODE_SAVE_FOR_SCRUBBING = {109 .name = "VDO_ADMIN_STATE_SAVE_FOR_SCRUBBING",110 .draining = true,111 .operating = true,112};113const struct admin_state_code *VDO_ADMIN_STATE_SAVE_FOR_SCRUBBING = &VDO_CODE_SAVE_FOR_SCRUBBING;114static const struct admin_state_code VDO_CODE_STOPPING = {115 .name = "VDO_ADMIN_STATE_STOPPING",116 .draining = true,117 .quiescing = true,118 .operating = true,119};120const struct admin_state_code *VDO_ADMIN_STATE_STOPPING = &VDO_CODE_STOPPING;121static const struct admin_state_code VDO_CODE_STOPPED = {122 .name = "VDO_ADMIN_STATE_STOPPED",123 .quiescent = true,124};125const struct admin_state_code *VDO_ADMIN_STATE_STOPPED = &VDO_CODE_STOPPED;126static const struct admin_state_code VDO_CODE_SUSPENDING = {127 .name = "VDO_ADMIN_STATE_SUSPENDING",128 .draining = true,129 .quiescing = true,130 .operating = true,131};132const struct admin_state_code *VDO_ADMIN_STATE_SUSPENDING = &VDO_CODE_SUSPENDING;133static const struct admin_state_code VDO_CODE_SUSPENDED = {134 .name = "VDO_ADMIN_STATE_SUSPENDED",135 .quiescent = true,136};137const struct admin_state_code *VDO_ADMIN_STATE_SUSPENDED = &VDO_CODE_SUSPENDED;138static const struct admin_state_code VDO_CODE_SUSPENDED_OPERATION = {139 .name = "VDO_ADMIN_STATE_SUSPENDED_OPERATION",140 .operating = true,141};142const struct admin_state_code *VDO_ADMIN_STATE_SUSPENDED_OPERATION = &VDO_CODE_SUSPENDED_OPERATION;143static const struct admin_state_code VDO_CODE_RESUMING = {144 .name = "VDO_ADMIN_STATE_RESUMING",145 .operating = true,146};147const struct admin_state_code *VDO_ADMIN_STATE_RESUMING = &VDO_CODE_RESUMING;148 149/**150 * get_next_state() - Determine the state which should be set after a given operation completes151 * based on the operation and the current state.152 * @operation The operation to be started.153 *154 * Return: The state to set when the operation completes or NULL if the operation can not be155 * started in the current state.156 */157static const struct admin_state_code *get_next_state(const struct admin_state *state,158 const struct admin_state_code *operation)159{160 const struct admin_state_code *code = vdo_get_admin_state_code(state);161 162 if (code->operating)163 return NULL;164 165 if (operation == VDO_ADMIN_STATE_SAVING)166 return (code == VDO_ADMIN_STATE_NORMAL_OPERATION ? VDO_ADMIN_STATE_SAVED : NULL);167 168 if (operation == VDO_ADMIN_STATE_SUSPENDING) {169 return (code == VDO_ADMIN_STATE_NORMAL_OPERATION170 ? VDO_ADMIN_STATE_SUSPENDED171 : NULL);172 }173 174 if (operation == VDO_ADMIN_STATE_STOPPING)175 return (code == VDO_ADMIN_STATE_NORMAL_OPERATION ? VDO_ADMIN_STATE_STOPPED : NULL);176 177 if (operation == VDO_ADMIN_STATE_PRE_LOADING)178 return (code == VDO_ADMIN_STATE_INITIALIZED ? VDO_ADMIN_STATE_PRE_LOADED : NULL);179 180 if (operation == VDO_ADMIN_STATE_SUSPENDED_OPERATION) {181 return (((code == VDO_ADMIN_STATE_SUSPENDED) ||182 (code == VDO_ADMIN_STATE_SAVED)) ? code : NULL);183 }184 185 return VDO_ADMIN_STATE_NORMAL_OPERATION;186}187 188/**189 * vdo_finish_operation() - Finish the current operation.190 *191 * Will notify the operation waiter if there is one. This method should be used for operations192 * started with vdo_start_operation(). For operations which were started with vdo_start_draining(),193 * use vdo_finish_draining() instead.194 *195 * Return: true if there was an operation to finish.196 */197bool vdo_finish_operation(struct admin_state *state, int result)198{199 if (!vdo_get_admin_state_code(state)->operating)200 return false;201 202 state->complete = state->starting;203 if (state->waiter != NULL)204 vdo_set_completion_result(state->waiter, result);205 206 if (!state->starting) {207 vdo_set_admin_state_code(state, state->next_state);208 if (state->waiter != NULL)209 vdo_launch_completion(vdo_forget(state->waiter));210 }211 212 return true;213}214 215/**216 * begin_operation() - Begin an operation if it may be started given the current state.217 * @waiter A completion to notify when the operation is complete; may be NULL.218 * @initiator The vdo_admin_initiator_fn to call if the operation may begin; may be NULL.219 *220 * Return: VDO_SUCCESS or an error.221 */222static int __must_check begin_operation(struct admin_state *state,223 const struct admin_state_code *operation,224 struct vdo_completion *waiter,225 vdo_admin_initiator_fn initiator)226{227 int result;228 const struct admin_state_code *next_state = get_next_state(state, operation);229 230 if (next_state == NULL) {231 result = vdo_log_error_strerror(VDO_INVALID_ADMIN_STATE,232 "Can't start %s from %s",233 operation->name,234 vdo_get_admin_state_code(state)->name);235 } else if (state->waiter != NULL) {236 result = vdo_log_error_strerror(VDO_COMPONENT_BUSY,237 "Can't start %s with extant waiter",238 operation->name);239 } else {240 state->waiter = waiter;241 state->next_state = next_state;242 vdo_set_admin_state_code(state, operation);243 if (initiator != NULL) {244 state->starting = true;245 initiator(state);246 state->starting = false;247 if (state->complete)248 vdo_finish_operation(state, VDO_SUCCESS);249 }250 251 return VDO_SUCCESS;252 }253 254 if (waiter != NULL)255 vdo_continue_completion(waiter, result);256 257 return result;258}259 260/**261 * start_operation() - Start an operation if it may be started given the current state.262 * @waiter A completion to notify when the operation is complete.263 * @initiator The vdo_admin_initiator_fn to call if the operation may begin; may be NULL.264 *265 * Return: true if the operation was started.266 */267static inline bool __must_check start_operation(struct admin_state *state,268 const struct admin_state_code *operation,269 struct vdo_completion *waiter,270 vdo_admin_initiator_fn initiator)271{272 return (begin_operation(state, operation, waiter, initiator) == VDO_SUCCESS);273}274 275/**276 * check_code() - Check the result of a state validation.277 * @valid true if the code is of an appropriate type.278 * @code The code which failed to be of the correct type.279 * @what What the code failed to be, for logging.280 * @waiter The completion to notify of the error; may be NULL.281 *282 * If the result failed, log an invalid state error and, if there is a waiter, notify it.283 *284 * Return: The result of the check.285 */286static bool check_code(bool valid, const struct admin_state_code *code, const char *what,287 struct vdo_completion *waiter)288{289 int result;290 291 if (valid)292 return true;293 294 result = vdo_log_error_strerror(VDO_INVALID_ADMIN_STATE,295 "%s is not a %s", code->name, what);296 if (waiter != NULL)297 vdo_continue_completion(waiter, result);298 299 return false;300}301 302/**303 * assert_vdo_drain_operation() - Check that an operation is a drain.304 * @waiter The completion to finish with an error if the operation is not a drain.305 *306 * Return: true if the specified operation is a drain.307 */308static bool __must_check assert_vdo_drain_operation(const struct admin_state_code *operation,309 struct vdo_completion *waiter)310{311 return check_code(operation->draining, operation, "drain operation", waiter);312}313 314/**315 * vdo_start_draining() - Initiate a drain operation if the current state permits it.316 * @operation The type of drain to initiate.317 * @waiter The completion to notify when the drain is complete.318 * @initiator The vdo_admin_initiator_fn to call if the operation may begin; may be NULL.319 *320 * Return: true if the drain was initiated, if not the waiter will be notified.321 */322bool vdo_start_draining(struct admin_state *state,323 const struct admin_state_code *operation,324 struct vdo_completion *waiter, vdo_admin_initiator_fn initiator)325{326 const struct admin_state_code *code = vdo_get_admin_state_code(state);327 328 if (!assert_vdo_drain_operation(operation, waiter))329 return false;330 331 if (code->quiescent) {332 vdo_launch_completion(waiter);333 return false;334 }335 336 if (!code->normal) {337 vdo_log_error_strerror(VDO_INVALID_ADMIN_STATE, "can't start %s from %s",338 operation->name, code->name);339 vdo_continue_completion(waiter, VDO_INVALID_ADMIN_STATE);340 return false;341 }342 343 return start_operation(state, operation, waiter, initiator);344}345 346/**347 * vdo_finish_draining() - Finish a drain operation if one was in progress.348 *349 * Return: true if the state was draining; will notify the waiter if so.350 */351bool vdo_finish_draining(struct admin_state *state)352{353 return vdo_finish_draining_with_result(state, VDO_SUCCESS);354}355 356/**357 * vdo_finish_draining_with_result() - Finish a drain operation with a status code.358 *359 * Return: true if the state was draining; will notify the waiter if so.360 */361bool vdo_finish_draining_with_result(struct admin_state *state, int result)362{363 return (vdo_is_state_draining(state) && vdo_finish_operation(state, result));364}365 366/**367 * vdo_assert_load_operation() - Check that an operation is a load.368 * @waiter The completion to finish with an error if the operation is not a load.369 *370 * Return: true if the specified operation is a load.371 */372bool vdo_assert_load_operation(const struct admin_state_code *operation,373 struct vdo_completion *waiter)374{375 return check_code(operation->loading, operation, "load operation", waiter);376}377 378/**379 * vdo_start_loading() - Initiate a load operation if the current state permits it.380 * @operation The type of load to initiate.381 * @waiter The completion to notify when the load is complete (may be NULL).382 * @initiator The vdo_admin_initiator_fn to call if the operation may begin; may be NULL.383 *384 * Return: true if the load was initiated, if not the waiter will be notified.385 */386bool vdo_start_loading(struct admin_state *state,387 const struct admin_state_code *operation,388 struct vdo_completion *waiter, vdo_admin_initiator_fn initiator)389{390 return (vdo_assert_load_operation(operation, waiter) &&391 start_operation(state, operation, waiter, initiator));392}393 394/**395 * vdo_finish_loading() - Finish a load operation if one was in progress.396 *397 * Return: true if the state was loading; will notify the waiter if so.398 */399bool vdo_finish_loading(struct admin_state *state)400{401 return vdo_finish_loading_with_result(state, VDO_SUCCESS);402}403 404/**405 * vdo_finish_loading_with_result() - Finish a load operation with a status code.406 * @result The result of the load operation.407 *408 * Return: true if the state was loading; will notify the waiter if so.409 */410bool vdo_finish_loading_with_result(struct admin_state *state, int result)411{412 return (vdo_is_state_loading(state) && vdo_finish_operation(state, result));413}414 415/**416 * assert_vdo_resume_operation() - Check whether an admin_state_code is a resume operation.417 * @waiter The completion to notify if the operation is not a resume operation; may be NULL.418 *419 * Return: true if the code is a resume operation.420 */421static bool __must_check assert_vdo_resume_operation(const struct admin_state_code *operation,422 struct vdo_completion *waiter)423{424 return check_code(operation == VDO_ADMIN_STATE_RESUMING, operation,425 "resume operation", waiter);426}427 428/**429 * vdo_start_resuming() - Initiate a resume operation if the current state permits it.430 * @operation The type of resume to start.431 * @waiter The completion to notify when the resume is complete (may be NULL).432 * @initiator The vdo_admin_initiator_fn to call if the operation may begin; may be NULL.433 *434 * Return: true if the resume was initiated, if not the waiter will be notified.435 */436bool vdo_start_resuming(struct admin_state *state,437 const struct admin_state_code *operation,438 struct vdo_completion *waiter, vdo_admin_initiator_fn initiator)439{440 return (assert_vdo_resume_operation(operation, waiter) &&441 start_operation(state, operation, waiter, initiator));442}443 444/**445 * vdo_finish_resuming() - Finish a resume operation if one was in progress.446 *447 * Return: true if the state was resuming; will notify the waiter if so.448 */449bool vdo_finish_resuming(struct admin_state *state)450{451 return vdo_finish_resuming_with_result(state, VDO_SUCCESS);452}453 454/**455 * vdo_finish_resuming_with_result() - Finish a resume operation with a status code.456 * @result The result of the resume operation.457 *458 * Return: true if the state was resuming; will notify the waiter if so.459 */460bool vdo_finish_resuming_with_result(struct admin_state *state, int result)461{462 return (vdo_is_state_resuming(state) && vdo_finish_operation(state, result));463}464 465/**466 * vdo_resume_if_quiescent() - Change the state to normal operation if the current state is467 * quiescent.468 *469 * Return: VDO_SUCCESS if the state resumed, VDO_INVALID_ADMIN_STATE otherwise.470 */471int vdo_resume_if_quiescent(struct admin_state *state)472{473 if (!vdo_is_state_quiescent(state))474 return VDO_INVALID_ADMIN_STATE;475 476 vdo_set_admin_state_code(state, VDO_ADMIN_STATE_NORMAL_OPERATION);477 return VDO_SUCCESS;478}479 480/**481 * vdo_start_operation() - Attempt to start an operation.482 *483 * Return: VDO_SUCCESS if the operation was started, VDO_INVALID_ADMIN_STATE if not484 */485int vdo_start_operation(struct admin_state *state,486 const struct admin_state_code *operation)487{488 return vdo_start_operation_with_waiter(state, operation, NULL, NULL);489}490 491/**492 * vdo_start_operation_with_waiter() - Attempt to start an operation.493 * @waiter the completion to notify when the operation completes or fails to start; may be NULL.494 * @initiator The vdo_admin_initiator_fn to call if the operation may begin; may be NULL.495 *496 * Return: VDO_SUCCESS if the operation was started, VDO_INVALID_ADMIN_STATE if not497 */498int vdo_start_operation_with_waiter(struct admin_state *state,499 const struct admin_state_code *operation,500 struct vdo_completion *waiter,501 vdo_admin_initiator_fn initiator)502{503 return (check_code(operation->operating, operation, "operation", waiter) ?504 begin_operation(state, operation, waiter, initiator) :505 VDO_INVALID_ADMIN_STATE);506}507