From 75ab9ad4fe04539ffa13e9d744cce0354fe13d3d Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Mon, 26 Apr 2021 18:45:23 -0500 Subject: [PATCH] Update: Implement "execute" support and fix bugs. Provide new feature for executing into another program. This is provided via the new Item Action "execute". The function controller_perform_ready() is being called and the status is being checked but the status is not being assigned. There are a few cases where thread.enabled needs to be checked and a few cases it does not need to be checked. Several of the threads need to be aware of the normal/other status to properly determine the thread.eneabled situation. Without this, they make incorrect decisions that result in bugs. I did not want to implement a new structure to resolve this so instead provide custom wrapper functions to call that set the appropriate normal/other state. The functon controller_thread_process_cancel() now needs to be caller aware so that the caller does not get cancelled. --- level_3/controller/c/private-common.h | 45 ++- level_3/controller/c/private-controller.c | 81 ++++- level_3/controller/c/private-controller.h | 10 +- level_3/controller/c/private-entry.c | 13 + level_3/controller/c/private-rule.c | 7 +- level_3/controller/c/private-rule.h | 8 +- level_3/controller/c/private-thread.c | 332 ++++++++++++++------- level_3/controller/c/private-thread.h | 112 ++++++- .../settings/example/entries/htop-alternate.entry | 11 + .../data/settings/example/entries/htop.entry | 18 ++ .../settings/example/exits/htop-alternate.exit | 21 ++ level_3/controller/documents/entry.txt | 75 +++-- level_3/controller/specifications/entry.txt | 7 +- 13 files changed, 565 insertions(+), 175 deletions(-) create mode 100644 level_3/controller/data/settings/example/entries/htop-alternate.entry create mode 100644 level_3/controller/data/settings/example/entries/htop.entry create mode 100644 level_3/controller/data/settings/example/exits/htop-alternate.exit diff --git a/level_3/controller/c/private-common.h b/level_3/controller/c/private-common.h index a6656a3..70acb22 100644 --- a/level_3/controller/c/private-common.h +++ b/level_3/controller/c/private-common.h @@ -40,6 +40,7 @@ extern "C" { #define controller_string_entry "entry" #define controller_string_entries "entries" #define controller_string_environment "environment" + #define controller_string_execute "execute" #define controller_string_existing "existing" #define controller_string_exit "exit" #define controller_string_exits "exits" @@ -135,6 +136,7 @@ extern "C" { #define controller_string_entries_length 7 #define controller_string_environment_length 11 #define controller_string_existing_length 8 + #define controller_string_execute_length 7 #define controller_string_exit_length 4 #define controller_string_exits_length 5 #define controller_string_fail_length 4 @@ -228,6 +230,7 @@ extern "C" { const static f_string_t controller_string_entries_s = controller_string_entries; const static f_string_t controller_string_environment_s = controller_string_environment; const static f_string_t controller_string_existing_s = controller_string_existing; + const static f_string_t controller_string_execute_s = controller_string_execute; const static f_string_t controller_string_exit_s = controller_string_exit; const static f_string_t controller_string_exits_s = controller_string_exits; const static f_string_t controller_string_fail_s = controller_string_fail; @@ -1008,6 +1011,7 @@ extern "C" { * * controller_entry_action_type_*: * - consider: Designate a rule to be pre-loaded. + * - execute: Execute into another program. * - failsafe: Designate a failsafe "item". * - freeze: A Rule Action for freezing. * - item: A named set of Rules. @@ -1042,6 +1046,7 @@ extern "C" { #ifndef _di_controller_entry_action_t_ enum { controller_entry_action_type_consider = 1, + controller_entry_action_type_execute, controller_entry_action_type_failsafe, controller_entry_action_type_freeze, controller_entry_action_type_item, @@ -1321,16 +1326,36 @@ extern "C" { /** * States for enabled, designating how to stop the process. * - * controller_thread_enabled_not: the controller is no longer enabled, shut down and abort all work. - * controller_thread_enabled: the controller is operating normally. - * controller_thread_enabled_stop: the controller is shutting down, only process exit rules and stop actions. - * controller_thread_enabled_stop_ready: the controller is shutting down, only process exit rules and stop actions, and now ready to send termination signals. + * controller_thread_*: + * - enabled_not: The controller is no longer enabled, shut down and abort all work. + * - enabled: The controller is operating normally. + * - enabled_execute: The controller is executing another process, all running operations must terminate. + * - enabled_exit: The controller is shutting down, only process exit rules. + * - enabled_exit_execute: The controller is executing another process while in failsafe mode, all running operations must terminate. + * - enabled_exit_ready: The controller is shutting down, only process exit rules, and now ready to send termination signals. + * + * controller_thread_cancel_*: + * - signal: Cancellation is triggered by a signal. + * - call: Cancellation is explicitly called. + * - execute: Cancellation is explicitly called due to an "execute" Item Action, when not during Exit. + * - exit: Cancellation is explicitly called during Exit. + * - exit_execute: Cancellation is explicitly called due to an "execute" Item Action during Exit. */ enum { controller_thread_enabled_not = 0, controller_thread_enabled, - controller_thread_enabled_stop, - controller_thread_enabled_stop_ready, + controller_thread_enabled_execute, + controller_thread_enabled_exit, + controller_thread_enabled_exit_execute, + controller_thread_enabled_exit_ready, + }; + + enum { + controller_thread_cancel_signal = 0, + controller_thread_cancel_call, + controller_thread_cancel_execute, + controller_thread_cancel_exit, + controller_thread_cancel_exit_execute, }; typedef struct { @@ -1639,8 +1664,8 @@ extern "C" { * Given a r/w lock, periodically check to see if main thread is disabled while waiting. * * @param is_normal - * If TRUE, then process as if this is a normal operation (entry and control). - * If FALSE, then process as if this is an exit operation. + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. * @param thread * The thread data used to determine if the main thread is disabled or not. * @param lock @@ -1715,8 +1740,8 @@ extern "C" { * Given a r/w lock, periodically check to see if main thread is disabled while waiting. * * @param is_normal - * If TRUE, then process as if this is a normal operation (entry and control). - * If FALSE, then process as if this is an exit operation. + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. * @param thread * The thread data used to determine if the main thread is disabled or not. * @param lock diff --git a/level_3/controller/c/private-controller.c b/level_3/controller/c/private-controller.c index 3ae66d4..8adc276 100644 --- a/level_3/controller/c/private-controller.c +++ b/level_3/controller/c/private-controller.c @@ -933,7 +933,7 @@ extern "C" { } if (!simulate) { - controller_perform_ready(is_entry, *main, cache); + status = controller_perform_ready(is_entry, *main, cache); if (F_status_is_error(status)) return status; } @@ -1181,7 +1181,7 @@ extern "C" { status = controller_rule_process_begin(options_force, alias_rule, controller_entry_action_type_to_rule_action_type(entry_action->type), options_process, is_entry ? controller_process_type_entry : controller_process_type_exit, stack, *main, *cache); - if (F_status_set_fine(status) == F_memory_not || status == F_child || status == F_signal || !controller_thread_is_enabled(is_entry, main->thread)) { + if (F_status_set_fine(status) == F_memory_not || status == F_child || status == F_signal) { break; } @@ -1190,6 +1190,79 @@ extern "C" { } } } + else if (entry_action->type == controller_entry_action_type_execute) { + if (simulate || main->data->error.verbosity == f_console_verbosity_verbose) { + if (main->data->error.verbosity != f_console_verbosity_quiet) { + f_thread_mutex_lock(&main->thread->lock.print); + + fprintf(main->data->output.stream, "%c", f_string_eol_s[0]); + fprintf(main->data->output.stream, "%s is executing '", is_entry ? controller_string_entry_s : controller_string_exit_s); + + fprintf(main->data->output.stream, "%s", main->data->context.set.title.before->string); + for (f_array_length_t k = 0; k < entry_action->parameters.used; ++k) { + + fprintf(main->data->output.stream, "%s%s%s", main->data->context.set.title.before->string, entry_action->parameters.array[k].string, main->data->context.set.title.after->string); + + if (k + 1 < entry_action->parameters.used) { + fprintf(main->data->output.stream, " "); + } + } // for + fprintf(main->data->output.stream, "%s'.%c", main->data->context.set.title.after->string, f_string_eol_s[0]); + + controller_print_unlock_flush(main->data->output.stream, &main->thread->lock.print); + } + } + + if (simulate) { + return F_execute; + } + + controller_thread_process_cancel(is_entry, is_entry ? controller_thread_cancel_execute : controller_thread_cancel_exit_execute, main, process); + + int result = 0; + + status = fll_execute_into(0, entry_action->parameters, fl_execute_parameter_option_path, &result); + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_file_found_not) { + if (main->data->error.verbosity != f_console_verbosity_quiet) { + f_thread_mutex_lock(&main->thread->lock.print); + + fprintf(main->data->error.to.stream, "%c", f_string_eol_s[0]); + fprintf(main->data->error.to.stream, "%s%sExecution failed, unable to find program or script ", main->data->error.context.before->string, main->data->error.prefix ? main->data->error.prefix : f_string_empty_s, is_entry ? controller_string_entry_s : controller_string_exit_s); + fprintf(main->data->error.to.stream, "%s%s%s%s", main->data->error.context.after->string, main->data->error.notable.before->string, entry_action->parameters.array[0].string, main->data->error.notable.after->string); + fprintf(main->data->error.to.stream, "%s.%s%c", main->data->error.context.before->string, main->data->error.context.after->string, f_string_eol_s[0]); + + controller_entry_error_print_cache(is_entry, main->data->error, cache->action); + + controller_print_unlock_flush(main->data->error.to.stream, &main->thread->lock.print); + } + } + else { + controller_entry_error_print(is_entry, main->data->error, cache->action, F_status_set_fine(status), "fll_execute_into", F_true, main->thread); + } + + return F_status_set_error(F_execute); + } + else if (result != 0) { + if (main->data->error.verbosity != f_console_verbosity_quiet) { + f_thread_mutex_lock(&main->thread->lock.print); + + fprintf(main->data->error.to.stream, "%c", f_string_eol_s[0]); + fprintf(main->data->error.to.stream, "%s%sExecution failed with return value of ", main->data->error.context.before->string, main->data->error.prefix ? main->data->error.prefix : f_string_empty_s, is_entry ? controller_string_entry_s : controller_string_exit_s); + fprintf(main->data->error.to.stream, "%s%s%d%s", main->data->error.context.after->string, main->data->error.notable.before->string, result, main->data->error.notable.after->string); + fprintf(main->data->error.to.stream, "%s.%s%c", main->data->error.context.before->string, main->data->error.context.after->string, f_string_eol_s[0]); + + controller_entry_error_print_cache(is_entry, main->data->error, cache->action); + + controller_print_unlock_flush(main->data->error.to.stream, &main->thread->lock.print); + } + + return F_status_set_error(F_execute); + } + + return F_execute; + } else if (entry_action->type == controller_entry_action_type_timeout) { if (simulate || main->data->error.verbosity == f_console_verbosity_verbose) { @@ -1287,10 +1360,6 @@ extern "C" { } } // for - if (!controller_thread_is_enabled(is_entry, main->thread)) { - status = F_signal; - } - if (status == F_child || status == F_signal) break; cache->action.line_action = 0; diff --git a/level_3/controller/c/private-controller.h b/level_3/controller/c/private-controller.h index b15f320..fcc3f8b 100644 --- a/level_3/controller/c/private-controller.h +++ b/level_3/controller/c/private-controller.h @@ -329,7 +329,7 @@ extern "C" { #endif // _di_controller_preprocess_entry_ /** - * Process (execute) all items for the loaded entry or exit. + * Process (execute) all Items for the loaded Entry or Exit. * * @param failsafe * If TRUE, operate in failsafe mode (starts at designated failsafe Item). @@ -344,9 +344,11 @@ extern "C" { * * @return * F_none on success. + * F_execute on success and program exiting (scripts may result in this) or when execute would have been executed but is instead simulated. * - * F_require (with error bit) if a required entry item failed. + * F_require (with error bit) if a required Item failed. * F_critical (with error bit) on any critical error. + * F_execute (with error bit) if the "execute" Item Action failed. * * Errors (with error bit) from: f_macro_array_lengths_t_increase_by(). * Errors (with error bit) from: controller_perform_ready(). @@ -371,8 +373,8 @@ extern "C" { * This requires that a main.thread->lock.process lock be set on process->lock before being called. * * @param is_normal - * If TRUE, then process as if this is a normal operation (entry and control). - * If FALSE, then process as if this is an exit operation. + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. * @param action * The Rule Action to use. * @param alias diff --git a/level_3/controller/c/private-entry.c b/level_3/controller/c/private-entry.c index a584dcc..05be90f 100644 --- a/level_3/controller/c/private-entry.c +++ b/level_3/controller/c/private-entry.c @@ -57,6 +57,11 @@ extern "C" { buffer.used = controller_string_consider_length; break; + case controller_entry_action_type_execute: + buffer.string = controller_string_execute_s; + buffer.used = controller_string_execute_length; + break; + case controller_entry_action_type_failsafe: buffer.string = controller_string_failsafe_s; buffer.used = controller_string_failsafe_length; @@ -279,6 +284,9 @@ extern "C" { if (fl_string_dynamic_compare_string(controller_string_consider_s, cache->action.name_action, controller_string_consider_length) == F_equal_to) { actions->array[actions->used].type = controller_entry_action_type_consider; } + else if (fl_string_dynamic_compare_string(controller_string_execute_s, cache->action.name_action, controller_string_execute_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_execute; + } else if (fl_string_dynamic_compare_string(controller_string_failsafe_s, cache->action.name_action, controller_string_failsafe_length) == F_equal_to) { actions->array[actions->used].type = controller_entry_action_type_failsafe; } @@ -337,6 +345,11 @@ extern "C" { at_least = 2; at_most = allocate; } + else if (action->type == controller_entry_action_type_execute) { + allocate = cache->content_actions.array[i].used; + at_least = 1; + at_most = allocate; + } else if (action->type == controller_entry_action_type_failsafe || action->type == controller_entry_action_type_item) { allocate = 1; at_least = 1; diff --git a/level_3/controller/c/private-rule.c b/level_3/controller/c/private-rule.c index 890229c..c64fd1f 100644 --- a/level_3/controller/c/private-rule.c +++ b/level_3/controller/c/private-rule.c @@ -2560,7 +2560,12 @@ extern "C" { if (F_status_is_error_not(status)) { if (process->action && (options_force & controller_process_option_asynchronous)) { - status = f_thread_create(0, &process->id_thread, controller_thread_process, (void *) process); + if (process->type == controller_process_type_exit) { + status = f_thread_create(0, &process->id_thread, controller_thread_process_other, (void *) process); + } + else { + status = f_thread_create(0, &process->id_thread, controller_thread_process_normal, (void *) process); + } if (F_status_is_error(status)) { controller_error_print(main.data->error, F_status_set_fine(status), "f_thread_create", F_true, main.thread); diff --git a/level_3/controller/c/private-rule.h b/level_3/controller/c/private-rule.h index 62adf6f..abbe05b 100644 --- a/level_3/controller/c/private-rule.h +++ b/level_3/controller/c/private-rule.h @@ -730,8 +730,8 @@ extern "C" { * Read the rule file, extracting all valid items. * * @param is_normal - * If TRUE, then this operates as an entry or control. - * If FALSE, then this operates as an exit. + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. * @param alias * The string identifying the rule. * This is constructed from the path parts to the file without the file extension and without the settings directory prefix. @@ -832,8 +832,8 @@ extern "C" { * Wait until all currently running Rule processes are complete. * * @param is_normal - * If TRUE, then process as if this is a normal operation (entry and control). - * If FALSE, then process as if this is an exit operation. + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. * This is ignored when caller is not NULL. * @param main * The main data. diff --git a/level_3/controller/c/private-thread.c b/level_3/controller/c/private-thread.c index fbfb50d..2e16238 100644 --- a/level_3/controller/c/private-thread.c +++ b/level_3/controller/c/private-thread.c @@ -188,7 +188,7 @@ extern "C" { } if (F_status_is_error_not(status)) { - status = f_thread_create(0, &thread.id_signal, &controller_thread_signal, (void *) &main); + status = f_thread_create(0, &thread.id_signal, &controller_thread_signal_normal, (void *) &main); } if (F_status_is_error(status)) { @@ -308,7 +308,9 @@ extern "C" { } } - controller_thread_process_cancel(F_false, &main); + controller_thread_process_cancel(F_true, controller_thread_cancel_call, &main, 0); + + controller_thread_process_exit(&main); if (thread.id_signal) f_thread_join(thread.id_signal, 0); if (thread.id_cleanup) f_thread_join(thread.id_cleanup, 0); @@ -336,16 +338,12 @@ extern "C" { #endif // _di_controller_thread_main_ #ifndef _di_controller_thread_process_ - void * controller_thread_process(void *arguments) { - - f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); - - controller_process_t *process = (controller_process_t *) arguments; + void controller_thread_process(const bool is_normal, controller_process_t *process) { { controller_thread_t *thread = (controller_thread_t *) process->main_thread; - if (thread->enabled != controller_thread_enabled) return 0; + if (!controller_thread_is_enabled(is_normal, thread)) return; } const f_status_t status = controller_rule_process_do(controller_process_option_asynchronous, process); @@ -364,16 +362,36 @@ extern "C" { controller_setting_delete_simple(setting); controller_data_delete(data); } + } +#endif // _di_controller_thread_process_ + +#ifndef _di_controller_thread_process_normal_ + void * controller_thread_process_normal(void *arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + controller_thread_process(F_true, (controller_process_t *) arguments); return 0; } -#endif // _di_controller_thread_process_ +#endif // _di_controller_thread_process_normal_ + +#ifndef _di_controller_thread_process_other_ + void * controller_thread_process_other(void *arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + controller_thread_process(F_false, (controller_process_t *) arguments); + + return 0; + } +#endif // _di_controller_thread_process_other_ #ifndef _di_controller_thread_process_cancel_ - void controller_thread_process_cancel(const bool by_signal, controller_main_t *main) { + void controller_thread_process_cancel(const bool is_normal, const uint8_t by, controller_main_t *main, controller_process_t *caller) { // only cancel when enabled. - if (main->thread->enabled != controller_thread_enabled) { + if (!controller_thread_is_enabled(is_normal, main->thread)) { return; } @@ -384,7 +402,18 @@ extern "C" { main->thread->enabled = controller_thread_enabled_not; } else { - main->thread->enabled = controller_thread_enabled_stop; + if (by == controller_thread_cancel_execute) { + main->thread->enabled = controller_thread_enabled_execute; + } + else if (by == controller_thread_cancel_exit) { + main->thread->enabled = controller_thread_enabled_not; + } + else if (by == controller_thread_cancel_exit_execute) { + main->thread->enabled = controller_thread_enabled_exit_execute; + } + else { + main->thread->enabled = controller_thread_enabled_exit; + } f_thread_mutex_unlock(&main->thread->lock.alert); } @@ -402,95 +431,28 @@ extern "C" { // @todo consider switching to nanosleep() which may act better with interrupts and not require f_thread_cancel(). if (main->thread->id_cleanup) { f_thread_cancel(main->thread->id_cleanup); + f_thread_join(main->thread->id_cleanup, 0); + + main->thread->id_cleanup = 0; } // the sigtimedwait() function that is run inside of signal must be interrupted via the f_thread_cancel(). - if (!by_signal && main->thread->id_signal) { + if (by != controller_thread_cancel_signal && main->thread->id_signal) { f_thread_cancel(main->thread->id_signal); - } - - if (main->thread->enabled == controller_thread_enabled_stop) { - if (main->setting->ready == controller_setting_ready_done) { - - // the exit processing runs using the entry thread. - if (main->thread->id_entry) { - status = f_thread_join(main->thread->id_entry, 0); - - main->thread->id_entry = 0; - } - - const controller_main_entry_t entry = controller_macro_main_entry_t_initialize(main, main->setting); - - status = f_thread_create(0, &main->thread->id_entry, &controller_thread_exit, (void *) &entry); - - if (F_status_is_error(status)) { - if (main->data->error.verbosity != f_console_verbosity_quiet) { - controller_error_print(main->data->error, F_status_set_fine(status), "f_thread_create", F_true, main->thread); - } - - if (F_status_is_error_not(f_thread_mutex_lock(&main->thread->lock.alert))) { - main->thread->enabled = controller_thread_enabled_not; - - f_thread_mutex_unlock(&main->thread->lock.alert); - } - else { - main->thread->enabled = controller_thread_enabled_not; - } - } - else { - struct timespec time; + f_thread_join(main->thread->id_signal, 0); - do { - status = f_thread_mutex_lock(&main->thread->lock.alert); - - if (F_status_is_error(status)) { - main->thread->enabled = controller_thread_enabled_not; - - break; - } - else { - } - - controller_time(controller_thread_exit_ready_timeout_seconds, controller_thread_exit_ready_timeout_nanoseconds, &time); - - status = f_thread_condition_wait_timed(&time, &main->thread->lock.alert_condition, &main->thread->lock.alert); - - f_thread_mutex_unlock(&main->thread->lock.alert); - - } while (F_status_is_error_not(status) && main->thread->enabled == controller_thread_enabled_stop); - - if (F_status_is_error(status)) { - if (F_status_is_error_not(f_thread_mutex_lock(&main->thread->lock.alert))) { - main->thread->enabled = controller_thread_enabled_not; - - f_thread_mutex_unlock(&main->thread->lock.alert); - } - else { - main->thread->enabled = controller_thread_enabled_not; - } - } - } - } - else { - if (F_status_is_error_not(f_thread_mutex_lock(&main->thread->lock.alert))) { - main->thread->enabled = controller_thread_enabled_not; - - f_thread_mutex_unlock(&main->thread->lock.alert); - } - else { - main->thread->enabled = controller_thread_enabled_not; - } - } + main->thread->id_signal = 0; } for (; i < main->thread->processs.used; ++i) { if (!main->thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; process = main->thread->processs.array[i]; - // do not cancel exit processes. - if (process->type == controller_process_type_exit) continue; + // do not cancel exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit && main->thread->enabled != controller_thread_enabled_exit_execute) continue; if (process->child > 0) { f_signal_send(F_signal_termination, process->child); @@ -508,11 +470,12 @@ extern "C" { for (i = 0; i < main->thread->processs.used; ++i) { if (!main->thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; process = main->thread->processs.array[i]; - // do not cancel exit processes. - if (process->type == controller_process_type_exit) continue; + // do not cancel exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit && main->thread->enabled != controller_thread_enabled_exit_execute) continue; if (process->id_thread) { controller_time(0, controller_thread_exit_process_cancel_wait, &time); @@ -529,11 +492,12 @@ extern "C" { for (i = 0; i < main->thread->processs.size && spent < controller_thread_exit_process_cancel_total; ++i) { if (!main->thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; process = main->thread->processs.array[i]; - // do not cancel exit processes. - if (process->type == controller_process_type_exit) continue; + // do not cancel exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit && main->thread->enabled != controller_thread_enabled_exit_execute) continue; do { if (!process->id_thread) break; @@ -585,12 +549,14 @@ extern "C" { } // for for (i = 0; i < main->thread->processs.size; ++i) { + if (!main->thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; process = main->thread->processs.array[i]; - // do not kill exit processes. - if (process->type == controller_process_type_exit) continue; + // do not kill exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit && main->thread->enabled != controller_thread_enabled_exit_execute) continue; if (process->id_thread) { @@ -625,6 +591,99 @@ extern "C" { } #endif // _di_controller_thread_process_cancel_ +#ifndef _di_controller_thread_process_exit_ + void controller_thread_process_exit(controller_main_t *main) { + + if (main->thread->enabled != controller_thread_enabled_exit) return; + + if (main->setting->ready == controller_setting_ready_done) { + + // the exit processing runs using the entry thread. + if (main->thread->id_entry) { + f_thread_cancel(main->thread->id_entry); + f_thread_join(main->thread->id_entry, 0); + + main->thread->id_entry = 0; + } + + // restart the signal thread to allow for signals while operating the Exit. + if (!main->thread->id_signal) { + f_thread_create(0, &main->thread->id_signal, &controller_thread_signal_other, (void *) main); + } + + const controller_main_entry_t entry = controller_macro_main_entry_t_initialize(main, main->setting); + + f_status_t status = f_thread_create(0, &main->thread->id_entry, &controller_thread_exit, (void *) &entry); + + if (F_status_is_error(status)) { + if (main->data->error.verbosity != f_console_verbosity_quiet) { + controller_error_print(main->data->error, F_status_set_fine(status), "f_thread_create", F_true, main->thread); + } + + if (F_status_is_error_not(f_thread_mutex_lock(&main->thread->lock.alert))) { + main->thread->enabled = controller_thread_enabled_not; + + f_thread_mutex_unlock(&main->thread->lock.alert); + } + else { + main->thread->enabled = controller_thread_enabled_not; + } + } + else { + struct timespec time; + + do { + status = f_thread_mutex_lock(&main->thread->lock.alert); + + if (F_status_is_error(status)) { + main->thread->enabled = controller_thread_enabled_not; + + break; + } + + controller_time(controller_thread_exit_ready_timeout_seconds, controller_thread_exit_ready_timeout_nanoseconds, &time); + + status = f_thread_condition_wait_timed(&time, &main->thread->lock.alert_condition, &main->thread->lock.alert); + + f_thread_mutex_unlock(&main->thread->lock.alert); + + } while (F_status_is_error_not(status) && main->thread->enabled == controller_thread_enabled_exit); + + if (F_status_is_error(status)) { + if (F_status_is_error_not(f_thread_mutex_lock(&main->thread->lock.alert))) { + main->thread->enabled = controller_thread_enabled_not; + + f_thread_mutex_unlock(&main->thread->lock.alert); + } + else { + main->thread->enabled = controller_thread_enabled_not; + } + } + } + + // the sigtimedwait() function that is run inside of signal must be interrupted via the f_thread_cancel(). + if (main->thread->id_signal) { + f_thread_cancel(main->thread->id_signal); + f_thread_join(main->thread->id_signal, 0); + + main->thread->id_signal = 0; + } + + controller_thread_process_cancel(F_false, controller_thread_cancel_exit, main, 0); + } + else { + if (F_status_is_error_not(f_thread_mutex_lock(&main->thread->lock.alert))) { + main->thread->enabled = controller_thread_enabled_not; + + f_thread_mutex_unlock(&main->thread->lock.alert); + } + else { + main->thread->enabled = controller_thread_enabled_not; + } + } + } +#endif // _di_controller_thread_process_exit_ + #ifndef _di_controller_thread_entry_ void * controller_thread_entry(void *arguments) { @@ -671,7 +730,22 @@ extern "C" { if (F_status_is_error(*status)) { entry->setting->ready = controller_setting_ready_fail; - if (F_status_set_fine(*status) == F_require && entry->main->setting->failsafe_enabled) { + if ((F_status_set_fine(*status) == F_execute || F_status_set_fine(*status) == F_require) && entry->main->setting->failsafe_enabled) { + + // restore operating mode so that the failsafe can execute. + *status = f_thread_mutex_lock(&entry->main->thread->lock.alert); + + if (F_status_is_error_not(*status)) { + entry->main->thread->enabled = controller_thread_enabled; + + f_thread_mutex_unlock(&entry->main->thread->lock.alert); + } + + // restart the signal thread to allow for signals while operating the failsafe Items. + if (!entry->main->thread->id_signal) { + f_thread_create(0, &entry->main->thread->id_signal, &controller_thread_signal_normal, (void *) entry->main); + } + const f_status_t status_failsafe = controller_process_entry(F_true, F_true, entry->main, cache); if (F_status_is_error(status_failsafe)) { @@ -747,6 +821,40 @@ extern "C" { if (F_status_is_error(*status)) { entry->setting->ready = controller_setting_ready_fail; + + if ((F_status_set_fine(*status) == F_execute || F_status_set_fine(*status) == F_require) && entry->main->setting->failsafe_enabled) { + + // restore operating mode so that the failsafe can execute. + if (F_status_set_fine(*status) == F_execute) { + *status = f_thread_mutex_lock(&entry->main->thread->lock.alert); + + if (F_status_is_error_not(*status)) { + entry->main->thread->enabled = controller_thread_enabled_exit; + + f_thread_mutex_unlock(&entry->main->thread->lock.alert); + } + + // restart the signal thread to allow for signals while operating the failsafe Items. + if (!entry->main->thread->id_signal) { + f_thread_create(0, &entry->main->thread->id_signal, &controller_thread_signal_other, (void *) entry->main); + } + } + + const f_status_t status_failsafe = controller_process_entry(F_true, F_false, entry->main, cache); + + if (F_status_is_error(status_failsafe)) { + if (data->error.verbosity != f_console_verbosity_quiet) { + f_thread_mutex_lock(&entry->main->thread->lock.print); + + fprintf(data->error.to.stream, "%c", f_string_eol_s[0]); + fprintf(data->error.to.stream, "%s%sFailed while processing requested failsafe item '", data->error.context.before->string, data->error.prefix ? data->error.prefix : f_string_empty_s); + fprintf(data->error.to.stream, "%s%s%s%s", data->error.context.after->string, data->error.notable.before->string, entry->main->setting->entry.items.array[entry->main->setting->failsafe_enabled].name.string, data->error.notable.after->string); + fprintf(data->error.to.stream, "%s'.%s%c", data->error.context.before->string, data->error.context.after->string, f_string_eol_s[0]); + + controller_print_unlock_flush(data->error.to.stream, &entry->main->thread->lock.print); + } + } + } } else if (*status == F_signal) { entry->setting->ready = controller_setting_ready_abort; @@ -811,19 +919,15 @@ extern "C" { #endif // _di_controller_thread_rule_ #ifndef _di_controller_thread_signal_ - void * controller_thread_signal(void *arguments) { + void controller_thread_signal(const bool is_normal, controller_main_t *main) { - f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); - - controller_main_t *main = (controller_main_t *) arguments; - - if (!controller_thread_is_enabled(F_true, main->thread)) return 0; + if (!controller_thread_is_enabled(is_normal, main->thread)) return; siginfo_t information; struct timespec time; int error = 0; - while (controller_thread_is_enabled(F_true, main->thread)) { + while (controller_thread_is_enabled(is_normal, main->thread)) { controller_time(controller_thread_exit_ready_timeout_seconds, controller_thread_exit_ready_timeout_nanoseconds, &time); @@ -839,16 +943,36 @@ extern "C" { main->thread->signal = information.si_signo; - controller_thread_process_cancel(F_true, main); + controller_thread_process_cancel(is_normal, controller_thread_cancel_signal, main, 0); break; } } } // while + } +#endif // _di_controller_thread_signal_ + +#ifndef _di_controller_thread_signal_normal_ + void * controller_thread_signal_normal(void *arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + controller_thread_signal(F_true, (controller_main_t *) arguments); return 0; } -#endif // _di_controller_thread_signal_ +#endif // _di_controller_thread_signal_normal_ + +#ifndef _di_controller_thread_signal_other_ + void * controller_thread_signal_other(void *arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + controller_thread_signal(F_false, (controller_main_t *) arguments); + + return 0; + } +#endif // _di_controller_thread_signal_other_ #ifdef __cplusplus } // extern "C" diff --git a/level_3/controller/c/private-thread.h b/level_3/controller/c/private-thread.h index 6270fc5..93f6508 100644 --- a/level_3/controller/c/private-thread.h +++ b/level_3/controller/c/private-thread.h @@ -44,8 +44,8 @@ extern "C" { * Check to see if thread is enabled for the normal operations like entry and control or for exit operations. * * @param is_normal - * If TRUE, then process as if this is a normal operation (entry and control). - * If FALSE, then process as if this is an exit operation. + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. * @param thread * The thread data. * @@ -105,6 +105,7 @@ extern "C" { * F_none on success. * F_child on child process exiting. * F_signal on signal received. + * * F_failure (with error bit) on any failure. */ #ifndef _di_controller_thread_main_ @@ -114,6 +115,21 @@ extern "C" { /** * Asynchronously execute a Rule process. * + * @param is_normal + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. + * @param process + * The process data. + * + * @see controller_rule_process_do() + */ +#ifndef _di_controller_thread_process_ + extern void controller_thread_process(const bool is_normal, controller_process_t *process) f_gcc_attribute_visibility_internal; +#endif // _di_controller_thread_process_ + +/** + * Asynchronously execute a Rule process during normal operations. + * * @param arguments * The thread arguments. * Must be of type controller_process_t. @@ -121,26 +137,61 @@ extern "C" { * @return * 0, always. * - * @see controller_rule_process_do() + * @see controller_thread_process() */ -#ifndef _di_controller_thread_process_ - extern void * controller_thread_process(void *arguments) f_gcc_attribute_visibility_internal; -#endif // _di_controller_thread_process_ +#ifndef _di_controller_thread_process_normal_ + extern void * controller_thread_process_normal(void *arguments) f_gcc_attribute_visibility_internal; +#endif // _di_controller_thread_process_normal_ + +/** + * Asynchronously execute a Rule process during other operations. + * + * @param arguments + * The thread arguments. + * Must be of type controller_process_t. + * + * @return + * 0, always. + * + * @see controller_thread_process() + */ +#ifndef _di_controller_thread_process_other_ + extern void * controller_thread_process_other(void *arguments) f_gcc_attribute_visibility_internal; +#endif // _di_controller_thread_process_other_ /** * Cancel all process threads. * - * @param by_signal - * If TRUE, this was called from within the signal handling thread, so do not cancel the signal thread. - * If FALSE, then this was not called from within the signal handling thread, so cancel the signal thread. + * @param is_normal + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. + * @param by + * Designate the way in which the cancellation should operate. + * + * If controller_thread_cancel_signal, then this was called from within the signal handling thread, so do not cancel the signal thread. + * If controller_thread_cancel_call, then this was not called from within the signal handling thread, so cancel the signal thread. + * If controller_thread_cancel_execute, then this was called from within the Entry/Exit for executing a process, so cancel the signal thread but not the Entry thread. * @param main * The main thread data. + * @param caller + * (optional) The process that is calling the cancel so that this process itself does not get cancelled. + * Set to NULL to not use. */ #ifndef _di_controller_thread_process_cancel_ - void controller_thread_process_cancel(const bool by_signal, controller_main_t *main) f_gcc_attribute_visibility_internal; + extern void controller_thread_process_cancel(const bool is_normal, const uint8_t by, controller_main_t *main, controller_process_t *caller) f_gcc_attribute_visibility_internal; #endif // _di_controller_thread_process_cancel_ /** + * Process the Exit file, if applicable. + * + * @param main + * The main thread data. + */ +#ifndef _di_controller_thread_process_exit_ + extern void controller_thread_process_exit(controller_main_t *main) f_gcc_attribute_visibility_internal; +#endif // _di_controller_thread_process_exit_ + +/** * Thread for handling entry processing. * * This acts as the main rule thread during entry processing. @@ -223,16 +274,51 @@ extern "C" { * * Currently this only handles signals to exist, but may be updated to handle interrupt and hangup signals. * + * @param is_normal + * If TRUE, then process as if this operates during a normal operation (entry and control). + * If FALSE, then process as if this operates during a an exit operation. + * @param main + * The main data. + */ +#ifndef _di_controller_thread_signal_ + extern void controller_thread_signal(const bool is_normal, controller_main_t *main) f_gcc_attribute_visibility_internal; +#endif // _di_controller_thread_signal_ + +/** + * Thread for handling signals/interrupts during normal operations. + * + * Currently this only handles signals to exist, but may be updated to handle interrupt and hangup signals. + * * @param arguments * The thread arguments. * Must be of type controller_main_t. * * @return * 0, always. + * + * @see controller_thread_signal() */ -#ifndef _di_controller_thread_signal_ - extern void * controller_thread_signal(void *arguments) f_gcc_attribute_visibility_internal; -#endif // _di_controller_thread_signal_ +#ifndef _di_controller_thread_signal_normal_ + extern void * controller_thread_signal_normal(void *arguments) f_gcc_attribute_visibility_internal; +#endif // _di_controller_thread_signal_normal_ + +/** + * Thread for handling signals/interrupts during other operations. + * + * Currently this only handles signals to exist, but may be updated to handle interrupt and hangup signals. + * + * @param arguments + * The thread arguments. + * Must be of type controller_main_t. + * + * @return + * 0, always. + * + * @see controller_thread_signal() + */ +#ifndef _di_controller_thread_signal_other_ + extern void * controller_thread_signal_other(void *arguments) f_gcc_attribute_visibility_internal; +#endif // _di_controller_thread_signal_other_ #ifdef __cplusplus } // extern "C" diff --git a/level_3/controller/data/settings/example/entries/htop-alternate.entry b/level_3/controller/data/settings/example/entries/htop-alternate.entry new file mode 100644 index 0000000..86e7858 --- /dev/null +++ b/level_3/controller/data/settings/example/entries/htop-alternate.entry @@ -0,0 +1,11 @@ +# fss-0005 + +setting: + mode program + +main: + start serial s_1 asynchronous + start serial s_2 asynchronous + start serial s_3 asynchronous + + ready wait diff --git a/level_3/controller/data/settings/example/entries/htop.entry b/level_3/controller/data/settings/example/entries/htop.entry new file mode 100644 index 0000000..3f836a7 --- /dev/null +++ b/level_3/controller/data/settings/example/entries/htop.entry @@ -0,0 +1,18 @@ +# fss-0005 + +setting: + mode program + +main: + failsafe start_top + + start serial s_1 asynchronous + start serial s_2 asynchronous + start serial s_3 asynchronous + + ready wait + + execute htop + +start_top: + execute top diff --git a/level_3/controller/data/settings/example/exits/htop-alternate.exit b/level_3/controller/data/settings/example/exits/htop-alternate.exit new file mode 100644 index 0000000..c9dbc6e --- /dev/null +++ b/level_3/controller/data/settings/example/exits/htop-alternate.exit @@ -0,0 +1,21 @@ +# fss-0005 + +setting: + mode program + +main: + failsafe "start top" + + consider serial s_5 + consider serial s_6 + + stop serial s_4 asynchronous + stop serial s_5 asynchronous + stop serial s_6 asynchronous + + ready wait + + execute htop + +start top: + execute top diff --git a/level_3/controller/documents/entry.txt b/level_3/controller/documents/entry.txt index 660070e..429883c 100644 --- a/level_3/controller/documents/entry.txt +++ b/level_3/controller/documents/entry.txt @@ -15,46 +15,59 @@ Entry Documentation: The following settings are available: "mode". The "mode" setting represents the mode in which the Entry is operating in. - The "program" mode designates that the Entry operates as a program and exits when complete (does not execute an "exit" unless one is provided). - The "service" mode (the default mode) designates that the Entry operates as a service and will sit and wait for control commands when complete. - The "service" mode will call the "exit" with the same name as this Entry, but with the extension "exit", such as "default.exit". + The following modes are supported: "program" and "service". - Each item supports the following Action Names: "consider", "failsafe", "freeze", "item", "kill", "pause", "reload", "restart", "ready", "resume", "start", "stop", and "timeout". - Of those types, the following are considered a "rule" Action: "freeze", "kill", "pause", "reload", "restart", "resume", "start", and "stop". + The "program" mode\: + Designates that the Entry operates as a program and exits when complete. + Will call the "exit" with the same name as this Entry, but with the extension "exit", such as "default.exit". + Supports the Item Action "execute" to execute a program (switching the "controller" program entirely with the executed process). - The "consider" Action is a special case of a "rule" Action. + The "service" mode\: + Designates that the Entry operates as a service and will sit and wait for control commands when complete. + Will call the "exit" with the same name as this Entry, but with the extension "exit", such as "default.exit". + Does not support the Item Action "execute". + This is the default mode. + + Each "item" supports the following Action Names: "consider", "execute", "failsafe", "freeze", "item", "kill", "pause", "reload", "restart", "ready", "resume", "start", "stop", and "timeout". + Of those types, the following are considered a "rule" Action: "freeze", "kill", "pause", "reload", "restart", "resume", "start", "stop", and "thaw". + + The "consider" Item Action is a special case of a "rule" Action. All Action Parameters are the same as with the "rule" Action Parameters. The difference is that "consider" is only processed (instead of being processed and executed) and when some "rule" Action designates that this consideration is required (via "need"), wanted (via "want"), or wished for (via "wish") from the within the Rule file. If this is determined to be executed, then this is immediately executed when needed, wanted or wished for and applies all properties as appropriate (such as "asynchronous", for example). If this is determined not to be executed, then this "consider" is ignored as if it was never there in the first place. - The "failsafe" Action accepts only a valid Item Name in which will be executed when a failure is detected. - Only a single "failsafe" Action may exist at a time. - Each successive "failsafe" Action specified replaces the previously defined "failsafe" Action (in a top-down manner). - When operating in "failsafe", the "require" Action is ignored given that it is meaningless once operating in failsafe. + The "execute" Item Action well execute into the specified program. + On successfull execution, the controller program will no longer be running and will be replaced with the designated program. + This Item Action is only supported when operating in "program" mode. + + The "failsafe" Item Action accepts only a valid Item Name in which will be executed when a failure is detected. + Only a single "failsafe" Item Action may function at a time. + Each successive "failsafe" Item Action specified replaces the previously defined "failsafe" Item Action (in a top-down manner). + When operating in "failsafe", the "require" Item Action is ignored (given that it is meaningless once operating in "failsafe" mode). - The "freeze" is a "rule" Action for freezing some Control Group. - This "rule" Action will process the "freeze" inner Content of the named Rule. + The "freeze" Item Action is a "rule" Action for freezing some Control Group. + This Item Action will process the "freeze" inner Content of the named Rule. This is specific to Control Groups and is not yet fully implemented. Once implemented this documentation will need to be updated and clarified. - The "item" Action accepts only a valid Item Name in which will be immediately executed. + The "item" Item Action accepts only a valid Item Name in which will be immediately executed. Any valid Item Name, except for the reserved "main", may be used. - The "kill" is a "rule" Action for forcibly terminating some process. - This "rule" Action will process the "kill" inner Content of the named Rule. + The "kill" Item Action is a "rule" Action for forcibly terminating some process. + This Item Action will process the "kill" inner Content of the named Rule. - The "pause" is a "rule" Action for pausing some process. - This "rule" Action will process the "pause" inner Content of the named Rule. + The "pause" Item Action is a "rule" Action for pausing some process. + This Item Action will process the "pause" inner Content of the named Rule. - The "reload" is a "rule" Action for pausing some process. - This "rule" Action will process the "reload" inner Content of the named Rule. + The "reload" Item Action is a "rule" Action for pausing some process. + This Item Action will process the "reload" inner Content of the named Rule. - The "restart" is a "rule" Action for pausing some process. - This "rule" Action will process the "restart" inner Content of the named Rule. + The "restart" Item Action is a "rule" Action for pausing some process. + This Item Action will process the "restart" inner Content of the named Rule. - The "resume" is a "rule" Action for pausing some process. - This "rule" Action will process the "resume" inner Content of the named Rule. + The "resume" Item Action is a "rule" Action for pausing some process. + This Item Action will process the "resume" inner Content of the named Rule. The "ready" Action instructs the controller program when it is safe to perform normal tasks, such as creating the pid file. When not specified, the state is always assumed to be ready. @@ -64,20 +77,20 @@ Entry Documentation: Adding "ready" essentially specifies a point in time in the Entry in which things are expected to be safe for such basic operations. When the optional "wait" is provided, then "ready" will wait for all currently started asynchronous processes to complete before operating. - The "start" is a "rule" Action for pausing some process. - This "rule" Action will process the "start" inner Content of the named Rule. + The "start" Item Action is a "rule" Action for pausing some process. + This Item Action will process the "start" inner Content of the named Rule. - The "stop" is a "rule" Action for pausing some process. - This "rule" Action will process the "stop" inner Content of the named Rule. + The "stop" Item Action is a "rule" Action for pausing some process. + This Item Action will process the "stop" inner Content of the named Rule. - The "thaw" is a "rule" Action for unfreezing some Control Group. - This "rule" Action will process the "thaw" inner Content of the named Rule. + The "thaw" Item Action is a "rule" Action for unfreezing some Control Group. + This Item Action will process the "thaw" inner Content of the named Rule. This is specific to Control Groups and is not yet fully implemented. Once implemented this documentation will need to be updated and clarified. - The "timeout" Action provides default global settings for each of the three special situations: "start", "stop", and "kill". + The "timeout" Item Action provides default global settings for each of the three special situations: "start", "stop", and "kill". Each of these may only have a single one exist at a time (one "start", one "stop", and one "kill"). - Each successive "timeout" Action, specific to each Action Name (such as "start"), specified replaces the previously defined "timeout" Action (in a top-down manner). + Each successive "timeout" Item Action, specific to each Action Name (such as "start"), specified replaces the previously defined "timeout" Action (in a top-down manner). Each of these accepts a single Action Parameter that is a 0 or greater whole number representing the number of MegaTime (MT) (equivalent to milliseconds). For "start", this represents the number of MegaTime to wait after starting some rule before assuming something went wrong and the rule is returned as failed. For "stop", this represents the number of MegaTime to wait after stopping some rule before assuming something went wrong and the rule is returned as failed. diff --git a/level_3/controller/specifications/entry.txt b/level_3/controller/specifications/entry.txt index ce4ef2a..df3ee25 100644 --- a/level_3/controller/specifications/entry.txt +++ b/level_3/controller/specifications/entry.txt @@ -22,12 +22,15 @@ Entry Specification: The Actions\: "consider": One or more Content. - The first Content that is the relative file path (without any leading/trailing slashes and without file extension). - The second Content that is the basename for a rule file. + The first Content is the relative file path (without any leading/trailing slashes and without file extension). + The second Content is the basename for a rule file. The third and beyond may only be one of\: - "asynchronous" - "require" - "wait" + "execute": One or more Content. + The first Content is the program name or full path to the program (the program may be a script). + All remaining Content are passed as parameters to the program being executed. "failsafe": One Content that is a valid Object name, except for the reserved "main". "freeze": Two or more Content. The first Content that is the relative directory path (without any leading/trailing slashes). -- 1.8.3.1