From: Kevin Day Date: Sun, 16 Jan 2022 01:04:34 +0000 (-0600) Subject: Progress: Begin designing contoller to control socket communication. X-Git-Tag: 0.5.8~113 X-Git-Url: https://git.kevux.org/?a=commitdiff_plain;h=002bf17595459e65173be16f983977ead99593b6;p=fll Progress: Begin designing contoller to control socket communication. This includes some significant structural re-organization of the controller project. --- diff --git a/level_3/controller/c/common/private-cache.c b/level_3/controller/c/common/private-cache.c index 883f43b..d57dd4b 100644 --- a/level_3/controller/c/common/private-cache.c +++ b/level_3/controller/c/common/private-cache.c @@ -1,5 +1,5 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { diff --git a/level_3/controller/c/common/private-common.c b/level_3/controller/c/common/private-common.c new file mode 100644 index 0000000..31b1e76 --- /dev/null +++ b/level_3/controller/c/common/private-common.c @@ -0,0 +1,131 @@ +#include "../controller/controller.h" +#include "private-common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_string_s_ + const f_string_t controller_action_s = CONTROLLER_action_s; + const f_string_t controller_actions_s = CONTROLLER_actions_s; + const f_string_t controller_affinity_s = CONTROLLER_affinity_s; + const f_string_t controller_as_s = CONTROLLER_as_s; + const f_string_t controller_asynchronous_s = CONTROLLER_asynchronous_s; + const f_string_t controller_bash_s = CONTROLLER_bash_s; + const f_string_t controller_batch_s = CONTROLLER_batch_s; + const f_string_t controller_capability_s = CONTROLLER_capability_s; + const f_string_t controller_cgroup_s = CONTROLLER_cgroup_s; + const f_string_t controller_create_s = CONTROLLER_create_s; + const f_string_t controller_command_s = CONTROLLER_command_s; + const f_string_t controller_consider_s = CONTROLLER_consider_s; + const f_string_t controller_control_s = CONTROLLER_control_s; + const f_string_t controller_control_group_s = CONTROLLER_control_group_s; + const f_string_t controller_control_mode_s = CONTROLLER_control_mode_s; + const f_string_t controller_control_user_s = CONTROLLER_control_user_s; + const f_string_t controller_cpu_s = CONTROLLER_cpu_s; + const f_string_t controller_core_s = CONTROLLER_core_s; + const f_string_t controller_data_s = CONTROLLER_data_s; + const f_string_t controller_deadline_s = CONTROLLER_deadline_s; + const f_string_t controller_default_s = CONTROLLER_default_s; + const f_string_t controller_define_s = CONTROLLER_define_s; + const f_string_t controller_delay_s = CONTROLLER_delay_s; + const f_string_t controller_disable_s = CONTROLLER_disable_s; + const f_string_t controller_entry_s = CONTROLLER_entry_s; + const f_string_t controller_entries_s = CONTROLLER_entries_s; + const f_string_t controller_environment_s = CONTROLLER_environment_s; + const f_string_t controller_error_s = CONTROLLER_error_s; + const f_string_t controller_execute_s = CONTROLLER_execute_s; + const f_string_t controller_existing_s = CONTROLLER_existing_s; + const f_string_t controller_exit_s = CONTROLLER_exit_s; + const f_string_t controller_exits_s = CONTROLLER_exits_s; + const f_string_t controller_fail_s = CONTROLLER_fail_s; + const f_string_t controller_failsafe_s = CONTROLLER_failsafe_s; + const f_string_t controller_failure_s = CONTROLLER_failure_s; + const f_string_t controller_fifo_s = CONTROLLER_fifo_s; + const f_string_t controller_freeze_s = CONTROLLER_freeze_s; + const f_string_t controller_fsize_s = CONTROLLER_fsize_s; + const f_string_t controller_full_path_s = CONTROLLER_full_path_s; + const f_string_t controller_group_s = CONTROLLER_group_s; + const f_string_t controller_groups_s = CONTROLLER_groups_s; + const f_string_t controller_how_s = CONTROLLER_how_s; + const f_string_t controller_idle_s = CONTROLLER_idle_s; + const f_string_t controller_item_s = CONTROLLER_item_s; + const f_string_t controller_init_s = CONTROLLER_init_s; + const f_string_t controller_kill_s = CONTROLLER_kill_s; + const f_string_t controller_length_s = CONTROLLER_length_s; + const f_string_t controller_limit_s = CONTROLLER_limit_s; + const f_string_t controller_locks_s = CONTROLLER_locks_s; + const f_string_t controller_main_s = CONTROLLER_main_s; + const f_string_t controller_max_s = CONTROLLER_max_s; + const f_string_t controller_memlock_s = CONTROLLER_memlock_s; + const f_string_t controller_method_s = CONTROLLER_method_s; + const f_string_t controller_mode_s = CONTROLLER_mode_s; + const f_string_t controller_msgqueue_s = CONTROLLER_msgqueue_s; + const f_string_t controller_name_s = CONTROLLER_name_s; + const f_string_t controller_need_s = CONTROLLER_need_s; + const f_string_t controller_new_s = CONTROLLER_new_s; + const f_string_t controller_nice_s = CONTROLLER_nice_s; + const f_string_t controller_no_s = CONTROLLER_no_s; + const f_string_t controller_nofile_s = CONTROLLER_nofile_s; + const f_string_t controller_normal_s = CONTROLLER_normal_s; + const f_string_t controller_nproc_s = CONTROLLER_nproc_s; + const f_string_t controller_on_s = CONTROLLER_on_s; + const f_string_t controller_optional_s = CONTROLLER_optional_s; + const f_string_t controller_other_s = CONTROLLER_other_s; + const f_string_t controller_parameter_s = CONTROLLER_parameter_s; + const f_string_t controller_parameters_s = CONTROLLER_parameters_s; + const f_string_t controller_path_s = CONTROLLER_path_s; + const f_string_t controller_pause_s = CONTROLLER_pause_s; + const f_string_t controller_payload_type_s = CONTROLLER_payload_type_s; + const f_string_t controller_pid_s = CONTROLLER_pid_s; + const f_string_t controller_pid_file_s = CONTROLLER_pid_file_s; + const f_string_t controller_processor_s = CONTROLLER_processor_s; + const f_string_t controller_program_s = CONTROLLER_program_s; + const f_string_t controller_ready_s = CONTROLLER_ready_s; + const f_string_t controller_reload_s = CONTROLLER_reload_s; + const f_string_t controller_require_s = CONTROLLER_require_s; + const f_string_t controller_required_s = CONTROLLER_required_s; + const f_string_t controller_rerun_s = CONTROLLER_rerun_s; + const f_string_t controller_reset_s = CONTROLLER_reset_s; + const f_string_t controller_restart_s = CONTROLLER_restart_s; + const f_string_t controller_resume_s = CONTROLLER_resume_s; + const f_string_t controller_round_robin_s = CONTROLLER_round_robin_s; + const f_string_t controller_rss_s = CONTROLLER_rss_s; + const f_string_t controller_rtprio_s = CONTROLLER_rtprio_s; + const f_string_t controller_rttime_s = CONTROLLER_rttime_s; + const f_string_t controller_rule_s = CONTROLLER_rule_s; + const f_string_t controller_rules_s = CONTROLLER_rules_s; + const f_string_t controller_same_s = CONTROLLER_same_s; + const f_string_t controller_scheduler_s = CONTROLLER_scheduler_s; + const f_string_t controller_script_s = CONTROLLER_script_s; + const f_string_t controller_service_s = CONTROLLER_service_s; + const f_string_t controller_setting_s = CONTROLLER_setting_s; + const f_string_t controller_session_s = CONTROLLER_session_s; + const f_string_t controller_session_new_s = CONTROLLER_session_new_s; + const f_string_t controller_session_same_s = CONTROLLER_session_same_s; + const f_string_t controller_show_s = CONTROLLER_show_s; + const f_string_t controller_sigpending_s = CONTROLLER_sigpending_s; + const f_string_t controller_stack_s = CONTROLLER_stack_s; + const f_string_t controller_start_s = CONTROLLER_start_s; + const f_string_t controller_status_s = CONTROLLER_status_s; + const f_string_t controller_stop_s = CONTROLLER_stop_s; + const f_string_t controller_succeed_s = CONTROLLER_succeed_s; + const f_string_t controller_success_s = CONTROLLER_success_s; + const f_string_t controller_synchronous_s = CONTROLLER_synchronous_s; + const f_string_t controller_thaw_s = CONTROLLER_thaw_s; + const f_string_t controller_timeout_s = CONTROLLER_timeout_s; + const f_string_t controller_type_s = CONTROLLER_type_s; + const f_string_t controller_use_s = CONTROLLER_use_s; + const f_string_t controller_user_s = CONTROLLER_user_s; + const f_string_t controller_utility_s = CONTROLLER_utility_s; + const f_string_t controller_value_s = CONTROLLER_value_s; + const f_string_t controller_wait_s = CONTROLLER_wait_s; + const f_string_t controller_want_s = CONTROLLER_want_s; + const f_string_t controller_wish_s = CONTROLLER_wish_s; + const f_string_t controller_with_s = CONTROLLER_with_s; + const f_string_t controller_yes_s = CONTROLLER_yes_s; +#endif // _di_controller_string_s_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/common/private-common.h b/level_3/controller/c/common/private-common.h new file mode 100644 index 0000000..21fb146 --- /dev/null +++ b/level_3/controller/c/common/private-common.h @@ -0,0 +1,488 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_common_h +#define _PRIVATE_common_h + +#ifdef __cplusplus +extern "C" { +#endif + +// These are included in the order of their dependencies. +#include "private-cache.h" +#include "private-execute_set.h" +#include "private-lock.h" +#include "private-rule.h" +#include "private-task.h" +#include "private-process.h" +#include "private-entry.h" +#include "private-setting.h" +#include "private-thread.h" +#include "private-state.h" +#include "private-control.h" + +/** + * All special strings used within this program. + * + * These are generally the names to match, representing some action or setting. + */ +#ifndef _di_controller_string_s_ + #define CONTROLLER_action_s "action" + #define CONTROLLER_actions_s "actions" + #define CONTROLLER_affinity_s "affinity" + #define CONTROLLER_as_s "as" + #define CONTROLLER_asynchronous_s "asynchronous" + #define CONTROLLER_bash_s "bash" + #define CONTROLLER_batch_s "batch" + #define CONTROLLER_capability_s "capability" + #define CONTROLLER_cgroup_s "cgroup" + #define CONTROLLER_create_s "create" + #define CONTROLLER_command_s "command" + #define CONTROLLER_consider_s "consider" + #define CONTROLLER_control_s "control" + #define CONTROLLER_control_group_s "control_group" + #define CONTROLLER_control_mode_s "control_mode" + #define CONTROLLER_control_user_s "control_user" + #define CONTROLLER_cpu_s "cpu" + #define CONTROLLER_core_s "core" + #define CONTROLLER_data_s "data" + #define CONTROLLER_deadline_s "deadline" + #define CONTROLLER_default_s "default" + #define CONTROLLER_define_s "define" + #define CONTROLLER_delay_s "delay" + #define CONTROLLER_disable_s "disable" + #define CONTROLLER_entry_s "entry" + #define CONTROLLER_entries_s "entries" + #define CONTROLLER_environment_s "environment" + #define CONTROLLER_error_s "error" + #define CONTROLLER_execute_s "execute" + #define CONTROLLER_existing_s "existing" + #define CONTROLLER_exit_s "exit" + #define CONTROLLER_exits_s "exits" + #define CONTROLLER_fail_s "fail" + #define CONTROLLER_failsafe_s "failsafe" + #define CONTROLLER_failure_s "failure" + #define CONTROLLER_fifo_s "fifo" + #define CONTROLLER_freeze_s "freeze" + #define CONTROLLER_fsize_s "fsize" + #define CONTROLLER_full_path_s "full_path" + #define CONTROLLER_group_s "group" + #define CONTROLLER_groups_s "groups" + #define CONTROLLER_how_s "how" + #define CONTROLLER_idle_s "idle" + #define CONTROLLER_item_s "item" + #define CONTROLLER_init_s "init" + #define CONTROLLER_kill_s "kill" + #define CONTROLLER_length_s "length" + #define CONTROLLER_limit_s "limit" + #define CONTROLLER_locks_s "locks" + #define CONTROLLER_main_s "main" + #define CONTROLLER_max_s "max" + #define CONTROLLER_memlock_s "memlock" + #define CONTROLLER_method_s "method" + #define CONTROLLER_mode_s "mode" + #define CONTROLLER_msgqueue_s "msgqueue" + #define CONTROLLER_name_s "name" + #define CONTROLLER_need_s "need" + #define CONTROLLER_new_s "new" + #define CONTROLLER_nice_s "nice" + #define CONTROLLER_no_s "no" + #define CONTROLLER_nofile_s "nofile" + #define CONTROLLER_normal_s "normal" + #define CONTROLLER_nproc_s "nproc" + #define CONTROLLER_on_s "on" + #define CONTROLLER_optional_s "optional" + #define CONTROLLER_other_s "other" + #define CONTROLLER_parameter_s "parameter" + #define CONTROLLER_parameters_s "parameters" + #define CONTROLLER_path_s "path" + #define CONTROLLER_pause_s "pause" + #define CONTROLLER_payload_type_s "# fss-000e\n" + #define CONTROLLER_pid_s "pid" + #define CONTROLLER_pid_file_s "pid_file" + #define CONTROLLER_processor_s "processor" + #define CONTROLLER_program_s "program" + #define CONTROLLER_ready_s "ready" + #define CONTROLLER_reload_s "reload" + #define CONTROLLER_require_s "require" + #define CONTROLLER_required_s "required" + #define CONTROLLER_rerun_s "rerun" + #define CONTROLLER_reset_s "reset" + #define CONTROLLER_restart_s "restart" + #define CONTROLLER_resume_s "resume" + #define CONTROLLER_round_robin_s "round_robin" + #define CONTROLLER_rss_s "rss" + #define CONTROLLER_rtprio_s "rtprio" + #define CONTROLLER_rttime_s "rttime" + #define CONTROLLER_rule_s "rule" + #define CONTROLLER_rules_s "rules" + #define CONTROLLER_same_s "same" + #define CONTROLLER_scheduler_s "scheduler" + #define CONTROLLER_script_s "script" + #define CONTROLLER_service_s "service" + #define CONTROLLER_session_s "session" + #define CONTROLLER_session_new_s "session_new" + #define CONTROLLER_session_same_s "session_same" + #define CONTROLLER_setting_s "setting" + #define CONTROLLER_sigpending_s "sigpending" + #define CONTROLLER_show_s "show" + #define CONTROLLER_stack_s "stack" + #define CONTROLLER_start_s "start" + #define CONTROLLER_status_s "status" + #define CONTROLLER_stop_s "stop" + #define CONTROLLER_succeed_s "succeed" + #define CONTROLLER_success_s "success" + #define CONTROLLER_synchronous_s "synchronous" + #define CONTROLLER_thaw_s "thaw" + #define CONTROLLER_timeout_s "timeout" + #define CONTROLLER_type_s "type" + #define CONTROLLER_use_s "use" + #define CONTROLLER_user_s "user" + #define CONTROLLER_utility_s "utility" + #define CONTROLLER_value_s "value" + #define CONTROLLER_wait_s "wait" + #define CONTROLLER_want_s "want" + #define CONTROLLER_wish_s "wish" + #define CONTROLLER_with_s "with" + #define CONTROLLER_yes_s "yes" + + #define controller_action_s_length 6 + #define controller_actions_s_length 7 + #define controller_affinity_s_length 8 + #define controller_as_s_length 2 + #define controller_asynchronous_s_length 12 + #define controller_bash_s_length 4 + #define controller_batch_s_length 5 + #define controller_capability_s_length 10 + #define controller_cgroup_s_length 6 + #define controller_create_s_length 6 + #define controller_command_s_length 7 + #define controller_consider_s_length 8 + #define controller_control_s_length 7 + #define controller_control_group_s_length 13 + #define controller_control_mode_s_length 12 + #define controller_control_user_s_length 12 + #define controller_core_s_length 4 + #define controller_cpu_s_length 3 + #define controller_data_s_length 4 + #define controller_deadline_s_length 8 + #define controller_default_s_length 7 + #define controller_define_s_length 6 + #define controller_delay_s_length 5 + #define controller_disable_s_length 7 + #define controller_entry_s_length 5 + #define controller_entries_s_length 7 + #define controller_environment_s_length 11 + #define controller_error_s_length 5 + #define controller_existing_s_length 8 + #define controller_execute_s_length 7 + #define controller_exit_s_length 4 + #define controller_exits_s_length 5 + #define controller_fail_s_length 4 + #define controller_failure_s_length 7 + #define controller_failsafe_s_length 8 + #define controller_fifo_s_length 4 + #define controller_freeze_s_length 6 + #define controller_fsize_s_length 5 + #define controller_full_path_s_length 9 + #define controller_group_s_length 5 + #define controller_groups_s_length 6 + #define controller_how_s_length 3 + #define controller_idle_s_length 4 + #define controller_init_s_length 4 + #define controller_item_s_length 4 + #define controller_kill_s_length 4 + #define controller_length_s_length 6 + #define controller_limit_s_length 5 + #define controller_locks_s_length 5 + #define controller_main_s_length 4 + #define controller_max_s_length 3 + #define controller_memlock_s_length 7 + #define controller_method_s_length 6 + #define controller_mode_s_length 4 + #define controller_msgqueue_s_length 8 + #define controller_name_s_length 4 + #define controller_need_s_length 4 + #define controller_new_s_length 3 + #define controller_nice_s_length 4 + #define controller_no_s_length 2 + #define controller_nofile_s_length 6 + #define controller_normal_s_length 6 + #define controller_nproc_s_length 5 + #define controller_on_s_length 2 + #define controller_optional_s_length 8 + #define controller_other_s_length 5 + #define controller_parameter_s_length 9 + #define controller_parameters_s_length 10 + #define controller_path_s_length 4 + #define controller_pause_s_length 5 + #define controller_payload_type_s_length 11 + #define controller_pid_s_length 3 + #define controller_pid_file_s_length 8 + #define controller_processor_s_length 9 + #define controller_program_s_length 7 + #define controller_ready_s_length 5 + #define controller_reload_s_length 6 + #define controller_require_s_length 7 + #define controller_required_s_length 8 + #define controller_rerun_s_length 5 + #define controller_reset_s_length 5 + #define controller_restart_s_length 7 + #define controller_resume_s_length 6 + #define controller_round_robin_s_length 11 + #define controller_rss_s_length 3 + #define controller_rtprio_s_length 6 + #define controller_rttime_s_length 6 + #define controller_rule_s_length 4 + #define controller_rules_s_length 5 + #define controller_same_s_length 4 + #define controller_scheduler_s_length 9 + #define controller_script_s_length 6 + #define controller_service_s_length 7 + #define controller_session_s_length 7 + #define controller_session_new_s_length 11 + #define controller_session_same_s_length 12 + #define controller_setting_s_length 7 + #define controller_show_s_length 4 + #define controller_sigpending_s_length 10 + #define controller_stack_s_length 5 + #define controller_start_s_length 5 + #define controller_status_s_length 6 + #define controller_stop_s_length 4 + #define controller_succeed_s_length 7 + #define controller_success_s_length 7 + #define controller_synchronous_s_length 11 + #define controller_thaw_s_length 4 + #define controller_timeout_s_length 7 + #define controller_type_s_length 4 + #define controller_use_s_length 3 + #define controller_user_s_length 4 + #define controller_utility_s_length 7 + #define controller_value_s_length 5 + #define controller_wait_s_length 4 + #define controller_want_s_length 4 + #define controller_wish_s_length 4 + #define controller_with_s_length 4 + #define controller_yes_s_length 3 + + extern const f_string_t controller_action_s; + extern const f_string_t controller_actions_s; + extern const f_string_t controller_affinity_s; + extern const f_string_t controller_as_s; + extern const f_string_t controller_asynchronous_s; + extern const f_string_t controller_bash_s; + extern const f_string_t controller_batch_s; + extern const f_string_t controller_capability_s; + extern const f_string_t controller_cgroup_s; + extern const f_string_t controller_create_s; + extern const f_string_t controller_command_s; + extern const f_string_t controller_consider_s; + extern const f_string_t controller_control_s; + extern const f_string_t controller_control_group_s; + extern const f_string_t controller_control_mode_s; + extern const f_string_t controller_control_user_s; + extern const f_string_t controller_core_s; + extern const f_string_t controller_cpu_s; + extern const f_string_t controller_data_s; + extern const f_string_t controller_deadline_s; + extern const f_string_t controller_default_s; + extern const f_string_t controller_define_s; + extern const f_string_t controller_delay_s; + extern const f_string_t controller_disable_s; + extern const f_string_t controller_entry_s; + extern const f_string_t controller_entries_s; + extern const f_string_t controller_environment_s; + extern const f_string_t controller_error_s; + extern const f_string_t controller_existing_s; + extern const f_string_t controller_execute_s; + extern const f_string_t controller_exit_s; + extern const f_string_t controller_exits_s; + extern const f_string_t controller_fail_s; + extern const f_string_t controller_failsafe_s; + extern const f_string_t controller_failure_s; + extern const f_string_t controller_fifo_s; + extern const f_string_t controller_freeze_s; + extern const f_string_t controller_fsize_s; + extern const f_string_t controller_full_path_s; + extern const f_string_t controller_group_s; + extern const f_string_t controller_groups_s; + extern const f_string_t controller_how_s; + extern const f_string_t controller_idle_s; + extern const f_string_t controller_init_s; + extern const f_string_t controller_item_s; + extern const f_string_t controller_kill_s; + extern const f_string_t controller_length_s; + extern const f_string_t controller_limit_s; + extern const f_string_t controller_locks_s; + extern const f_string_t controller_main_s; + extern const f_string_t controller_max_s; + extern const f_string_t controller_memlock_s; + extern const f_string_t controller_method_s; + extern const f_string_t controller_mode_s; + extern const f_string_t controller_msgqueue_s; + extern const f_string_t controller_name_s; + extern const f_string_t controller_need_s; + extern const f_string_t controller_new_s; + extern const f_string_t controller_nice_s; + extern const f_string_t controller_no_s; + extern const f_string_t controller_nofile_s; + extern const f_string_t controller_normal_s; + extern const f_string_t controller_nproc_s; + extern const f_string_t controller_on_s; + extern const f_string_t controller_optional_s; + extern const f_string_t controller_other_s; + extern const f_string_t controller_parameter_s; + extern const f_string_t controller_parameters_s; + extern const f_string_t controller_path_s; + extern const f_string_t controller_pause_s; + extern const f_string_t controller_payload_type_s; + extern const f_string_t controller_pid_s; + extern const f_string_t controller_pid_file_s; + extern const f_string_t controller_processor_s; + extern const f_string_t controller_program_s; + extern const f_string_t controller_ready_s; + extern const f_string_t controller_reload_s; + extern const f_string_t controller_require_s; + extern const f_string_t controller_required_s; + extern const f_string_t controller_rerun_s; + extern const f_string_t controller_reset_s; + extern const f_string_t controller_restart_s; + extern const f_string_t controller_resume_s; + extern const f_string_t controller_round_robin_s; + extern const f_string_t controller_rss_s; + extern const f_string_t controller_rtprio_s; + extern const f_string_t controller_rttime_s; + extern const f_string_t controller_rule_s; + extern const f_string_t controller_rules_s; + extern const f_string_t controller_same_s; + extern const f_string_t controller_scheduler_s; + extern const f_string_t controller_script_s; + extern const f_string_t controller_service_s; + extern const f_string_t controller_session_s; + extern const f_string_t controller_session_new_s; + extern const f_string_t controller_session_same_s; + extern const f_string_t controller_setting_s; + extern const f_string_t controller_show_s; + extern const f_string_t controller_sigpending_s; + extern const f_string_t controller_stack_s; + extern const f_string_t controller_start_s; + extern const f_string_t controller_status_s; + extern const f_string_t controller_stop_s; + extern const f_string_t controller_succeed_s; + extern const f_string_t controller_success_s; + extern const f_string_t controller_synchronous_s; + extern const f_string_t controller_thaw_s; + extern const f_string_t controller_timeout_s; + extern const f_string_t controller_type_s; + extern const f_string_t controller_use_s; + extern const f_string_t controller_user_s; + extern const f_string_t controller_utility_s; + extern const f_string_t controller_value_s; + extern const f_string_t controller_wait_s; + extern const f_string_t controller_want_s; + extern const f_string_t controller_wish_s; + extern const f_string_t controller_with_s; + extern const f_string_t controller_yes_s; +#endif // _di_controller_string_s_ + +/** + * A set of codes for resource limitations. + * + * This essentally converts the POSIX standard names into a more verbose format. + */ +#ifndef _di_controller_resource_limit_t_ + enum { + controller_resource_limit_type_as_e = RLIMIT_AS, + controller_resource_limit_type_core_e = RLIMIT_CORE, + controller_resource_limit_type_cpu_e = RLIMIT_CPU, + controller_resource_limit_type_data_e = RLIMIT_DATA, + controller_resource_limit_type_fsize_e = RLIMIT_FSIZE, + controller_resource_limit_type_locks_e = RLIMIT_LOCKS, + controller_resource_limit_type_memlock_e = RLIMIT_MEMLOCK, + controller_resource_limit_type_msgqueue_e = RLIMIT_MSGQUEUE, + controller_resource_limit_type_nice_e = RLIMIT_NICE, + controller_resource_limit_type_nofile_e = RLIMIT_NOFILE, + controller_resource_limit_type_nproc_e = RLIMIT_NPROC, + controller_resource_limit_type_rss_e = RLIMIT_RSS, + controller_resource_limit_type_rtprio_e = RLIMIT_RTPRIO, + controller_resource_limit_type_rttime_e = RLIMIT_RTTIME, + controller_resource_limit_type_sigpending_e = RLIMIT_SIGPENDING, + controller_resource_limit_type_stack_e = RLIMIT_STACK, + }; +#endif // _di_controller_resource_limit_t_ + +/** + * Provide common/generic definitions. + * + * The controller_common_allocation_large_d or controller_common_allocation_small_d must be at least 2 for this project. + * + * controller_common_allocation_*: + * - large: An allocation step used for buffers that are anticipated to have large buffers. + * - small: An allocation step used for buffers that are anticipated to have small buffers. + */ +#ifndef _di_controller_common_ + #define controller_common_allocation_large_d 256 + #define controller_common_allocation_small_d 16 +#endif // _di_controller_common_ + +/** + * A set of codes representing different with flags. + */ +#ifndef _di_controller_with_defines_ + #define controller_with_full_path_d 0x1 + #define controller_with_session_new_d 0x2 + #define controller_with_session_same_d 0x4 +#endif // _di_controller_with_defines_ + +/** + * A wrapper used for passing a common set of all data, particularly for sharing between threads. + * + * main: The main program data. + * setting: All loaded settings. + * thread: All thread related data. + */ +#ifndef _di_controller_main_t_ + typedef struct { + controller_main_t *main; + controller_setting_t *setting; + controller_thread_t *thread; + } controller_global_t; + + #define controller_global_t_initialize { 0, 0, 0 } + + #define macro_controller_global_t_initialize(main, setting, thread) { \ + main, \ + setting, \ + thread, \ + } +#endif // _di_controller_main_t_ + +/** + * A wrapper used for passing a set of entry processing and execution related data. + * + * global: All data globally shared. + * setting: The setting data. + */ +#ifndef _di_controller_main_entry_t_ + typedef struct { + controller_global_t *global; + controller_setting_t *setting; + } controller_main_entry_t; + + #define controller_main_entry_t_initialize { 0, 0 } + + #define macro_controller_main_entry_t_initialize(global, setting) { \ + global, \ + setting, \ + } +#endif // _di_controller_main_entry_t_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_common_h diff --git a/level_3/controller/c/common/private-control.c b/level_3/controller/c/common/private-control.c index 5c6f562..dd5a69c 100644 --- a/level_3/controller/c/common/private-control.c +++ b/level_3/controller/c/common/private-control.c @@ -1,16 +1,21 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { #endif -#ifndef _di_controller_packet_delete_simple_ - void controller_packet_delete_simple(controller_packet_t * const packet) { +#ifndef _di_controller_control_delete_simple_ + void controller_control_delete_simple(controller_control_t * const control) { - f_string_dynamic_resize(0, &packet->payload); + f_string_dynamic_resize(0, &control->cache_1); + f_string_dynamic_resize(0, &control->cache_2); + f_string_dynamic_resize(0, &control->cache_3); + + f_string_dynamic_resize(0, &control->input); + f_string_dynamic_resize(0, &control->output); } -#endif // _di_controller_packet_delete_simple_ +#endif // _di_controller_control_delete_simple_ #ifdef __cplusplus } // extern "C" diff --git a/level_3/controller/c/common/private-control.h b/level_3/controller/c/common/private-control.h index 3f5da51..e08883e 100644 --- a/level_3/controller/c/common/private-control.h +++ b/level_3/controller/c/common/private-control.h @@ -16,51 +16,107 @@ extern "C" { * Provide default control settings. * * controller_control_default: - * - socket_backlog: The amount of waiting client connections to support while handling a socket connection. - * - socket_buffer: The size of the buffer in bytes when processing packets (must not be set smaller the packet headers). - * - socket_header: The minimum size of the packet header to read to be able to process the size information. - * - socket_linger: The number of seconds to linger the connection before closing. - * - socket_timeout: The number of microseconds to wait. + * - socket_backlog: The amount of waiting client connections to support while handling a socket connection. + * - socket_buffer: The preferred max size of the buffer such that if the buffer exceeds this then it is reallocated to this size at the end of processing. + * - socket_buffer_max: The max size allowed in the buffer (this value must not be set smaller than the packet headers). + * - socket_cache: The preferred max size of the contol cache such that if the cache exceeds this then it is reallocated to this size at the end of processing. + * - socket_header: The minimum size in bytes of the packet header to read to be able to process the size information. + * - socket_linger: The number of seconds to linger the connection before closing. + * - socket_timeout: The number of microseconds to wait. + * - socket_payload_max: The max size allowed for the "payload" part of a packet and must be smaller than socket_buffer_max (this is smaller than socket_buffer_max to allow for enough room to afford a header). */ #ifndef _di_controller_control_defaults_ - #define controller_control_default_socket_backlog_d 64 - #define controller_control_default_socket_buffer_d 16384 - #define controller_control_default_socket_header_d 33 - #define controller_control_default_socket_linger_d 1 - #define controller_control_default_socket_timeout_d 10000 // 0.01 seconds. + #define controller_control_default_socket_backlog_d 64 + #define controller_control_default_socket_buffer_d 2048 + #define controller_control_default_socket_buffer_max_d 4294967296 + #define controller_control_default_socket_cache_d 128 + #define controller_control_default_socket_header_d 5 + #define controller_control_default_socket_linger_d 2 + #define controller_control_default_socket_timeout_d 10000 // 0.01 seconds. + #define controller_control_default_socket_payload_max_d 4294965248 #endif // _di_controller_defaults_ /** - * A structure representing the packet payload. + * A structure for control processing. * - * Payload structure: [uint32_t][char X]. - * - * bytes: The header repesenting the number of bytes in the character payload. - * payload: The character payload whose size is represented by the header bytes. + * server: The server socket connection. + * client: The client socket connection. + * cache_1: A generic buffer used for caching control related data. + * cache_2: A generic buffer used for caching control related data. + * input: A buffer used for receiving data from the client. + * output: A buffer used for transmitting data to the client. */ -#ifndef _di_controller_packet_t_ +#ifndef _di_controller_control_t_ typedef struct { - uint32_t bytes; - f_string_dynamic_t payload; - } controller_packet_t; + f_socket_t *server; + f_socket_t *client; + + f_string_dynamic_t cache_1; + f_string_dynamic_t cache_2; + f_string_dynamic_t cache_3; - #define controller_packet_t_initialize { 0, f_string_dynamic_t_initialize } + f_string_dynamic_t input; + f_string_dynamic_t output; + } controller_control_t; - #define macro_controller_packet_t_initialize(bytes, payload) { \ - bytes, \ - payload, \ + #define controller_control_t_initialize { 0, 0, f_string_dynamic_t_initialize, f_string_dynamic_t_initialize, f_string_dynamic_t_initialize, f_string_dynamic_t_initialize, f_string_dynamic_t_initialize } + + #define macro_controller_control_t_initialize(server, client) { \ + 0, \ + 0, \ + f_string_dynamic_t_initialize, \ + f_string_dynamic_t_initialize, \ + f_string_dynamic_t_initialize, \ + f_string_dynamic_t_initialize, \ + f_string_dynamic_t_initialize, \ } -#endif // _di_controller_main_t_ +#endif // _di_controller_control_t_ + +/** + * A codes repesent different flags associated with a packet. + * + * controller_control_packet_flag_*: + * - binary: Designate that the packet is in binary mode (when not set then packet is in string mode). + * - endian_big: Designate that the packet is in big endian order (when not set then packet is in little endian order). + */ +#ifndef _di_controller_control_packet_flag_ + #define controller_control_packet_flag_binary_d 0x1 + #define controller_control_packet_flag_endian_big_d 0x2 +#endif // _di_controller_control_packet_flag_ + +/** + * A 34-bit long little-endian structure representing the packet header. + * + * This represents the packet header which is different from the header inside the packet. + * + * Generally, the string-based packet format is FSS-000E (Payload). + * This format is stored within packet and has it's own header and payload parts. + * Example pseudo-structure: + * [0][0][4294967296][# fss-000e + * header: + * type message + * length 4294965248 + * payload: + * ... + * ] + * + * This means that string format is "technical" binary because of the header, but after the header it is entirely a string. + * Unlike other strings, this string is not NULL terminated and is instead end of tranmsission or end of file terminated (as appropriate). + * + * type: A boolean that when TRUE designates this as a binary and when FALSE designates this as a string packet. + * endian: A boolean designating that when TRUE thath the length digit is big endian and when FALSE designates that the length is little endian. + * length: A size representing how large the entire packet is (including the header that is 34 bits). + */ /** - * Fully deallocate all memory for the given packet without caring about return status. + * Fully deallocate all memory for the given control data without caring about return status. * - * @param packet - * The packet to deallocate. + * @param control + * The structure to deallocate. */ -#ifndef _di_controller_packet_delete_simple_ - extern void controller_packet_delete_simple(controller_packet_t * const packet) F_attribute_visibility_internal_d; -#endif // _di_controller_packet_delete_simple_ +#ifndef _di_controller_control_delete_simple_ + extern void controller_control_delete_simple(controller_control_t * const control) F_attribute_visibility_internal_d; +#endif // _di_controller_control_delete_simple_ #ifdef __cplusplus } // extern "C" diff --git a/level_3/controller/c/common/private-entry.c b/level_3/controller/c/common/private-entry.c index 9d79605..dea74a2 100644 --- a/level_3/controller/c/common/private-entry.c +++ b/level_3/controller/c/common/private-entry.c @@ -1,5 +1,5 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { diff --git a/level_3/controller/c/common/private-lock.c b/level_3/controller/c/common/private-lock.c index 9917891..af3260d 100644 --- a/level_3/controller/c/common/private-lock.c +++ b/level_3/controller/c/common/private-lock.c @@ -1,5 +1,5 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { diff --git a/level_3/controller/c/common/private-lock.h b/level_3/controller/c/common/private-lock.h index d5b9dbb..98af3d6 100644 --- a/level_3/controller/c/common/private-lock.h +++ b/level_3/controller/c/common/private-lock.h @@ -15,21 +15,21 @@ extern "C" { /** * A structure for sharing mutexes globally between different threads. * - * The print lock is intended to lock any activity printing to stdout/stderr. * The alert lock is intended for a generic waiting on alerts operations. + * The print lock is intended to lock any activity printing to stdout/stderr. * The process lock is intended to lock any activity on the processs structure. * The rule lock is intended to lock any activity on the rules structure. * - * print: The print mutex lock. * alert: The alert mutex lock for waking up on alerts. + * print: The print mutex lock. * process: The process r/w lock. * rule: The rule r/w lock. * alert_condition: The condition used to trigger alerts. */ #ifndef _di_controller_lock_t_ typedef struct { - f_thread_mutex_t print; f_thread_mutex_t alert; + f_thread_mutex_t print; f_thread_lock_t process; f_thread_lock_t rule; diff --git a/level_3/controller/c/common/private-process.c b/level_3/controller/c/common/private-process.c index d01207f..0923db2 100644 --- a/level_3/controller/c/common/private-process.c +++ b/level_3/controller/c/common/private-process.c @@ -1,5 +1,5 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { diff --git a/level_3/controller/c/common/private-rule.c b/level_3/controller/c/common/private-rule.c index 6199fb5..baf3dd4 100644 --- a/level_3/controller/c/common/private-rule.c +++ b/level_3/controller/c/common/private-rule.c @@ -1,5 +1,5 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { diff --git a/level_3/controller/c/common/private-setting.c b/level_3/controller/c/common/private-setting.c index 19f25e8..1662322 100644 --- a/level_3/controller/c/common/private-setting.c +++ b/level_3/controller/c/common/private-setting.c @@ -1,5 +1,5 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { diff --git a/level_3/controller/c/common/private-task.c b/level_3/controller/c/common/private-task.c index 0c4aea2..98fccd7 100644 --- a/level_3/controller/c/common/private-task.c +++ b/level_3/controller/c/common/private-task.c @@ -1,5 +1,5 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { diff --git a/level_3/controller/c/common/private-thread.c b/level_3/controller/c/common/private-thread.c index 62613b1..131b426 100644 --- a/level_3/controller/c/common/private-thread.c +++ b/level_3/controller/c/common/private-thread.c @@ -1,5 +1,5 @@ -#include "../controller.h" -#include "../private-common.h" +#include "../controller/controller.h" +#include "private-common.h" #ifdef __cplusplus extern "C" { diff --git a/level_3/controller/c/control/private-control.c b/level_3/controller/c/control/private-control.c new file mode 100644 index 0000000..fcdeca2 --- /dev/null +++ b/level_3/controller/c/control/private-control.c @@ -0,0 +1,333 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "private-control.h" +#include "../controller/private-controller_print.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_control_accept_ + f_status_t controller_control_accept(const controller_global_t *global, controller_control_t * const control) { + + f_socket_t client = f_socket_t_initialize; + + control->client = &client; + + f_status_t status = f_socket_accept(&client, control->server->id); + + if (F_status_is_error(status)) { + f_socket_disconnect(&client, f_socket_close_fast_e); + + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_socket_accept", F_true); + + return status; + } + + controller_control_configure_client(global, &client); + + control->input.used = 0; + control->output.used = 0; + + char buffer[controller_control_default_socket_buffer_d + 1]; + size_t length = 0; + + memset(buffer, 0, controller_control_default_socket_buffer_d + 1); + + // Pre-process the packet header. + client.size_read = controller_control_default_socket_header_d; + status = f_socket_read(&client, f_socket_flag_peek_d, buffer, &length); + + if (F_status_is_error(status)) { + f_socket_disconnect(&client, f_socket_close_fast_e); + + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_socket_read", F_true); + + return status; + } + + if (!length) { + status = controller_control_respond_error_string(global, control, F_empty, "Received packet is empty."); + + f_socket_disconnect(&client, f_socket_close_fast_e); + + if (F_status_is_error(status)) return status; + + return F_valid_not; + } + + if (length < controller_control_default_socket_header_d) { + status = controller_control_respond_error_string(global, control, F_too_large, "Received packet is too small."); + + f_socket_disconnect(&client, f_socket_close_fast_e); + + if (F_status_is_error(status)) return status; + + return F_valid_not; + } + + if (length > controller_control_default_socket_buffer_max_d) { + status = controller_control_respond_error_string(global, control, F_too_large, "Received packet is too large."); + + f_socket_disconnect(&client, f_socket_close_fast_e); + + if (F_status_is_error(status)) return status; + + return F_valid_not; + } + + const uint8_t packet_flag = controller_control_packet_header_flag(buffer); + const uint32_t packet_length = controller_control_packet_header_length(packet_flag & controller_control_packet_flag_endian_big_d, buffer); + + if (packet_flag & controller_control_packet_flag_binary_d) { + status = controller_control_respond_error_string(global, control, F_supported_not, "Binary is not a currently supported packet mode."); + + f_socket_disconnect(&client, f_socket_close_fast_e); + + if (F_status_is_error(status)) return status; + + return F_supported_not; + } + + client.size_read = controller_control_default_socket_buffer_d; + + // Pre-allocate the input buffer. + status = f_string_dynamic_increase_by(packet_length, &control->input); + + if (F_status_is_error(status)) { + controller_control_respond_error_string(global, control, F_memory_not, "Failure allocating memory."); + + f_socket_disconnect(&client, f_socket_close_fast_e); + + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_string_dynamic_increase_by", F_true); + + return status; + } + + { + size_t total = 0; + + do { + status = f_socket_read(&client, 0, &control->input, &total); + + if (F_status_is_error(status)) { + controller_control_respond_error_string(global, control, F_status_set_fine(status), "Failure while reading from client socket."); + + f_socket_disconnect(&client, f_socket_close_fast_e); + + return F_status_set_fine(status); + } + + } while (total == client.size_read); + } + + if (control->input.used != length) { + controller_control_respond_error_string(global, control, F_valid_not, "Received packet header length did not match actual received packet length."); + + f_socket_disconnect(&client, f_socket_close_fast_e); + + return F_valid_not; + } + + // @todo process the data. + + // @todo send any responses. + + f_socket_disconnect(&client, f_socket_close_fast_e); + + if (control->input.size > controller_control_default_socket_buffer_d) { + status = f_string_dynamic_resize(controller_control_default_socket_buffer_d, &control->input); + + if (F_status_is_error(status)) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_string_dynamic_resize", F_true); + + return status; + } + } + + if (control->output.size > controller_control_default_socket_buffer_d) { + status = f_string_dynamic_resize(controller_control_default_socket_buffer_d, &control->output); + + if (F_status_is_error(status)) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_string_dynamic_resize", F_true); + + return status; + } + } + + return F_none; + } +#endif // _di_controller_control_accept_ + +#ifndef _di_controller_control_configure_client_ + f_status_t controller_control_configure_client(const controller_global_t *global, f_socket_t * const client) { + + struct timeval time_out; + time_out.tv_sec = 0; + time_out.tv_usec = controller_control_default_socket_timeout_d; + + f_status_t status = f_socket_option_set(client, 1, f_socket_option_time_out_receive_d, (void *) &time_out, sizeof(struct timeval)); + + if (F_status_is_error_not(status)) { + status = f_socket_option_set(client, 1, f_socket_option_time_out_send_d, (void *) &time_out, sizeof(struct timeval)); + } + + if (F_status_is_error(status)) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_socket_option_set", F_true); + } + + return status; + } +#endif // _di_controller_control_configure_client_ + +#ifndef _di_controller_control_configure_server_ + f_status_t controller_control_configure_server(const controller_global_t *global, f_socket_t * const server) { + + const struct linger value = { 1, controller_control_default_socket_linger_d }; + + const f_status_t status = f_socket_option_set(server, 1, f_socket_option_linger_d, (void *) &value, sizeof(struct linger)); + + if (F_status_is_error(status)) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_socket_option_set", F_true); + } + + return status; + } +#endif // _di_controller_control_configure_server_ + +#ifndef _di_controller_control_packet_header_flag_ + uint8_t controller_control_packet_header_flag(const char buffer[]) { + return (uint8_t) ((buffer[0] & 0x8) ? controller_control_packet_flag_binary_d : 0) | ((buffer[0] & 0x4) ? controller_control_packet_flag_endian_big_d : 0); + } +#endif // _di_controller_control_packet_header_flag_ + +#ifndef _di_controller_control_packet_header_length_ + uint32_t controller_control_packet_header_length(const bool is_big, const char buffer[]) { + + register uint32_t length = (((buffer[0] & 0x3f) << 26) | (buffer[1] << 18) | (buffer[2] << 10) | (buffer[3] << 2) | ((buffer[4] & 0xc0) >> 6)); + + #ifdef _is_F_endian_big + if (is_big) { + return length; + } + #else + if (!is_big) { + return length; + } + #endif // _is_F_endian_big + + length = (length & 0x55555555 << 1) | (length & 0x55555555 >> 1); + length = (length & 0x33333333 << 2) | (length & 0xcccccccc >> 2); + length = (length & 0x0f0f0f0f << 4) | (length & 0xf0f0f0f0 >> 4); + length = (length & 0x00ff00ff << 8) | (length & 0xff00ff00 >> 8); + + return (length & 0x0000ffff << 16) | (length & 0xffff0000 >> 16); + } +#endif // _di_controller_control_packet_header_length_ + +#ifndef _di_controller_control_respond_build_header_ + f_status_t controller_control_respond_build_header(const controller_global_t *global, controller_control_t * const control, const f_string_static_t type, const f_string_static_t status, const f_array_length_t length) { + + f_status_t status2 = F_none; + + const f_string_static_t object_header = macro_f_string_static_t_initialize(f_fss_string_header_s, F_fss_string_header_s_length); + const f_string_static_t object_type = macro_f_string_static_t_initialize(controller_type_s, controller_type_s_length); + const f_string_static_t object_status = macro_f_string_static_t_initialize(controller_status_s, controller_status_s_length); + const f_string_static_t object_length = macro_f_string_static_t_initialize(controller_length_s, controller_length_s_length); + const f_state_t state = f_state_t_initialize; + const f_conversion_data_t data_conversion = macro_f_conversion_data_t_initialize(10, 0, 1); + + f_string_statics_t content = f_string_statics_t_initialize; + f_string_static_t contents[1]; + content.array = contents; + content.used = 1; + content.size = 1; + + control->cache_1.used = 0; + control->cache_2.used = 0; + + // Header: type. + if (type.used) { + contents[0] = type; + + status2 = fll_fss_extended_write_string(object_type, content, 0, state, &control->cache_1); + if (F_status_is_error(status2)) return status2; + } + + // Header: status. + if (status.used) { + contents[0] = status; + + status2 = fll_fss_extended_write_string(object_status, content, 0, state, &control->cache_1); + if (F_status_is_error(status2)) return status2; + + control->cache_2.used = 0; + } + + // Header: length. + status2 = f_conversion_number_unsigned_to_string(length, data_conversion, &control->cache_2); + if (F_status_is_error(status2)) return status2; + + contents[0] = control->cache_2; + + status2 = fll_fss_extended_write_string(object_length, content, 0, state, &control->cache_1); + if (F_status_is_error(status2)) return status2; + + // Prepend the identifier comment to the output. + status2 = f_string_append(controller_payload_type_s, controller_payload_type_s_length, &control->output); + if (F_status_is_error(status2)) return status2; + + // Append entire header block to the output. + status2 = fll_fss_payload_write_string(object_header, control->cache_1, F_false, 0, state, &control->output); + if (F_status_is_error(status2)) return status2; + + return F_none; + } +#endif // _di_controller_control_respond_build_header_ + +#ifndef _di_controller_control_respond_error_ + f_status_t controller_control_respond_error(const controller_global_t *global, controller_control_t * const control, const f_status_t status, const f_string_static_t message) { + + f_status_t status2 = F_none; + const f_state_t state = f_state_t_initialize; + + control->output.used = 0; + control->cache_3.used = 0; + + { + const f_conversion_data_t data_conversion = macro_f_conversion_data_t_initialize(10, 0, 1); + + status2 = f_conversion_number_unsigned_to_string(F_status_set_fine(status), data_conversion, &control->cache_3); + if (F_status_is_error(status2)) return status2; + } + + { + const f_string_static_t content_error = macro_f_string_static_t_initialize(controller_error_s, controller_error_s_length); + + status2 = controller_control_respond_build_header(global, control, content_error, control->cache_3, message.used); + if (F_status_is_error(status2)) return status2; + } + + { + const f_string_static_t object_payload = macro_f_string_static_t_initialize(f_fss_string_payload_s, F_fss_string_payload_s_length); + + status2 = fll_fss_payload_write_string(object_payload, message, F_false, 0, state, &control->output); + if (F_status_is_error(status2)) return status2; + } + + return f_socket_write(control->client, 0, control->output.string, 0); + } +#endif // _di_controller_control_respond_error_ + +#ifndef _di_controller_control_respond_error_string_ + f_status_t controller_control_respond_error_string(const controller_global_t *global, controller_control_t * const control, const f_status_t status, const f_string_t message) { + + const f_string_static_t string = macro_f_string_static_t_initialize(message, strlen(message)); + + return controller_control_respond_error(global, control, status, string); + } +#endif // _di_controller_control_respond_error_string_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/control/private-control.h b/level_3/controller/c/control/private-control.h new file mode 100644 index 0000000..5d38cd7 --- /dev/null +++ b/level_3/controller/c/control/private-control.h @@ -0,0 +1,237 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_control_h +#define _PRIVATE_control_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Accept connections from a control socket server. + * + * Connectons are processed and actions are performed. + * + * @param global + * The global data. + * @param control + * The control data structure. + * + * @return + * F_none on success. + * F_valid_not on invalid packet from client (this is not an error with the function itself and so no error bit is set). + * + * Errors (with error bit) from: f_socket_accept(). + * + * @see f_socket_accept() + */ +#ifndef _di_controller_control_accept_ + extern f_status_t controller_control_accept(const controller_global_t *global, controller_control_t * const control) F_attribute_visibility_internal_d; +#endif // _di_controller_control_accept_ + +/** + * Configure client socket settings. + * + * @param global + * The global data. + * @param client + * The client socket structure. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_socket_option_set(). + * + * @see f_socket_option_set() + */ +#ifndef _di_controller_control_configure_client_ + extern f_status_t controller_control_configure_client(const controller_global_t *global, f_socket_t * const client) F_attribute_visibility_internal_d; +#endif // _di_controller_control_configure_client_ + +/** + * Configure server socket settings. + * + * @param global + * The global data. + * @param server + * The server socket structure. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_socket_option_set(). + * + * @see f_socket_option_set() + */ +#ifndef _di_controller_control_configure_server_ + extern f_status_t controller_control_configure_server(const controller_global_t *global, f_socket_t * const server) F_attribute_visibility_internal_d; +#endif // _di_controller_control_configure_server_ + +/** + * Given the header buffer, get the flag bits. + * + * @param buffer + * The buffer to read the length of and get the + * + * @return + * The 8-bit number representing the flags. + */ +#ifndef _di_controller_control_packet_header_flag_ + extern uint8_t controller_control_packet_header_flag(const char buffer[]) F_attribute_visibility_internal_d; +#endif // _di_controller_control_packet_header_flag_ + +/** + * Given the header buffer, get the length bits. + * + * The endianness is automatically detected and swapped by this function to guarantee host order bytes. + * + * @param is_big + * If TRUE, then the length in the buffer is in big endian format. + * If FALSE, then the length in the buffer is in little endian format. + * @param buffer + * The buffer to read the length of and get the + * + * @return + * The 32-bit number representing the length. + */ +#ifndef _di_controller_control_packet_header_length_ + extern uint32_t controller_control_packet_header_length(const bool is_big, const char buffer[]) F_attribute_visibility_internal_d; +#endif // _di_controller_control_packet_header_length_ + +/** + * Accept connections from a control socket server. + * + * Connectons are processed and actions are performed. + * + * @param global + * The global data. + * @param server + * The server socket structure. + * @param packet + * The control packet data structure. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_socket_accept(). + * Errors (with error bit) from: f_socket_read(). + * + * @see f_socket_accept() + * @see f_socket_read() + */ +#ifndef _di_controller_control_respond_ + extern f_status_t controller_control_respond(const controller_global_t *global, controller_control_t * const control) F_attribute_visibility_internal_d; +#endif // _di_controller_control_respond_ + +/** + * Construct the header portion of the payload. + * + * This also prepends the FSS identifier comment. + * + * This resets and uses control.cache_1 and control.cache_2. + * Do not use either of these for passing strings to this function. + * + * The results of this function are appended to control.output. + * + * @param global + * The global data. + * @param control + * The control structure. + * @param type + * The packet type. + * Set type.used to 0 to not add to the header. + * @param status + * The status code. + * Set status.used to 0 to not add to the header. + * @param length + * The length of the payload Content. + * This is always added to the header. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_conversion_number_unsigned_to_string(). + * Errors (with error bit) from: f_string_append(). + * Errors (with error bit) from: fll_fss_extended_write_string(). + * Errors (with error bit) from: fll_fss_payload_write_string(). + * + * @see f_conversion_number_unsigned_to_string() + * @see f_string_append() + * @see fll_fss_extended_write_string() + * @see fll_fss_payload_write_string() + */ +#ifndef _di_controller_control_respond_build_header_ + extern f_status_t controller_control_respond_build_header(const controller_global_t *global, controller_control_t * const control, const f_string_static_t type, const f_string_static_t status, const f_array_length_t length) F_attribute_visibility_internal_d; +#endif // _di_controller_control_respond_build_header_ + +/** + * Accept connections from a control socket server. + * + * Connectons are processed and actions are performed. + * + * This resets and uses control.cache_3 and control.output. + * This calls functions that resets and uses control.cache_1, control.cache_2. + * + * Common response error codes: + * - F_empty: Packet is empty (does not even have headers). + * - F_packet_not: Packet is not a valid packet. + * - F_supported_not: Format or action is not supported. + * - F_too_large: Packet is too large (exceeds max packet length). + * - F_too_small: Packet is too small (smaller than minimum header length). + * + * @param global + * The global data. + * @param control + * The control structure. + * @param status + * The status code. + * @param message + * A status message. + * + * @return + * Result of f_socket_write() on success. + * + * Errors (with error bit) from: f_socket_write(). + * + * @see f_socket_write() + */ +#ifndef _di_controller_control_respond_error_ + extern f_status_t controller_control_respond_error(const controller_global_t *global, controller_control_t * const control, const f_status_t status, const f_string_static_t message) F_attribute_visibility_internal_d; +#endif // _di_controller_control_respond_error_ + +/** + * Alternate version of controller_control_respond_error() that accepts f_string_t for message. + * + * This calls functions that resets and uses control.cache_1, control.cache_2, control.cache_3, and control.output. + * + * @param global + * The global data. + * @param control + * The control structure. + * @param status + * The status code. + * @param message + * A status message. + * This must be a NULL terminated string so that functions like strlen() may get the length of this string. + * + * @return + * Result of controller_control_respond_error() on success. + * + * Errors (with error bit) from: controller_control_respond_error(). + * + * @see controller_control_respond_error() + */ +#ifndef _di_controller_control_respond_error_string_ + extern f_status_t controller_control_respond_error_string(const controller_global_t *global, controller_control_t * const control, const f_status_t status, const f_string_t message) F_attribute_visibility_internal_d; +#endif // _di_controller_control_respond_error_string_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_control_h diff --git a/level_3/controller/c/control/private-control_print.c b/level_3/controller/c/control/private-control_print.c new file mode 100644 index 0000000..b27a99f --- /dev/null +++ b/level_3/controller/c/control/private-control_print.c @@ -0,0 +1,12 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "private-control.h" +#include "../lock/private-lock_print.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/control/private-control_print.h b/level_3/controller/c/control/private-control_print.h new file mode 100644 index 0000000..a87f0c5 --- /dev/null +++ b/level_3/controller/c/control/private-control_print.h @@ -0,0 +1,19 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_control_print_h +#define _PRIVATE_control_print_h + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_control_print_h diff --git a/level_3/controller/c/controller/controller.c b/level_3/controller/c/controller/controller.c new file mode 100644 index 0000000..a03d3ee --- /dev/null +++ b/level_3/controller/c/controller/controller.c @@ -0,0 +1,501 @@ +#include "controller.h" +#include "../common/private-common.h" +#include "../control/private-control.h" +#include "private-controller.h" +#include "private-controller_print.h" +#include "../entry/private-entry.h" +#include "../lock/private-lock_print.h" +#include "../rule/private-rule.h" +#include "../thread/private-thread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_print_help_ + f_status_t controller_print_help(controller_main_t * const main) { + + controller_lock_print(main->output.to, 0); + + fll_program_print_help_header(main->output.to, main->context, main->program_name_long, controller_program_version_s); + + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_help_s, f_console_standard_long_help_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " Print this help message."); + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_dark_s, f_console_standard_long_dark_s, f_console_symbol_short_disable_s, f_console_symbol_long_disable_s, " Output using colors that show up better on dark backgrounds."); + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_light_s, f_console_standard_long_light_s, f_console_symbol_short_disable_s, f_console_symbol_long_disable_s, " Output using colors that show up better on light backgrounds."); + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_no_color_s, f_console_standard_long_no_color_s, f_console_symbol_short_disable_s, f_console_symbol_long_disable_s, "Do not main->output.to in color."); + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_quiet_s, f_console_standard_long_quiet_s, f_console_symbol_short_disable_s, f_console_symbol_long_disable_s, " Decrease verbosity beyond normal main->output.to."); + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_normal_s, f_console_standard_long_normal_s, f_console_symbol_short_disable_s, f_console_symbol_long_disable_s, " Set verbosity to normal main->output.to."); + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_verbose_s, f_console_standard_long_verbose_s, f_console_symbol_short_disable_s, f_console_symbol_long_disable_s, " Increase verbosity beyond normal main->output.to."); + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_debug_s, f_console_standard_long_debug_s, f_console_symbol_short_disable_s, f_console_symbol_long_disable_s, " Enable debugging, inceasing verbosity beyond normal main->output.to."); + fll_program_print_help_option(main->output.to, main->context, f_console_standard_short_version_s, f_console_standard_long_version_s, f_console_symbol_short_disable_s, f_console_symbol_long_disable_s, " Print only the version number."); + + f_print_character(f_string_eol_s[0], main->output.to.stream); + + fll_program_print_help_option(main->output.to, main->context, controller_short_cgroup_s, controller_long_cgroup_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " Specify a custom control group file path, such as '" F_control_group_path_system_prefix_s F_control_group_path_system_default_s "'."); + fll_program_print_help_option(main->output.to, main->context, controller_short_daemon_s, controller_long_daemon_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " Run in daemon only mode (do not process the entry)."); + fll_program_print_help_option(main->output.to, main->context, controller_short_init_s, controller_long_init_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " The program will run as an init replacement."); + fll_program_print_help_option(main->output.to, main->context, controller_short_interruptible_s, controller_long_interruptible_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " Designate that this program can be interrupted by a signal."); + fll_program_print_help_option(main->output.to, main->context, controller_short_pid_s, controller_long_pid_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " Specify a custom pid file path, such as '" controller_path_pid_s CONTROLLER_default_s controller_path_suffix_s "'."); + fll_program_print_help_option(main->output.to, main->context, controller_short_settings_s, controller_long_settings_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " Specify a custom settings path, such as '" controller_path_settings_s "'."); + fll_program_print_help_option(main->output.to, main->context, controller_short_simulate_s, controller_long_simulate_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " Run as a simulation."); + fll_program_print_help_option(main->output.to, main->context, controller_short_uninterruptible_s, controller_long_uninterruptible_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, "Designate that this program cannot be interrupted by a signal."); + fll_program_print_help_option(main->output.to, main->context, controller_short_validate_s, controller_long_validate_s, f_console_symbol_short_enable_s, f_console_symbol_long_enable_s, " Validate the settings (entry and rules) without running (does not simulate)."); + + fll_program_print_help_usage(main->output.to, main->context, main->program_name, "entry"); + + fl_print_format(" When both the %[%s%s%] parameter and the", main->output.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, controller_long_simulate_s, main->context.set.notable); + fl_print_format(" %[%s%s%] parameter are specified, then additional information on each would be executed rule is printed but no simulation is performed.%c%c", main->output.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, controller_long_validate_s, main->context.set.notable, f_string_eol_s[0], f_string_eol_s[0]); + + fl_print_format(" The default interrupt behavior is to operate as if the %[%s%s%] parameter is passed.%c%c", main->output.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, main->setting_default.used ? controller_long_uninterruptible_s : controller_long_interruptible_s, main->context.set.notable, f_string_eol_s[0], f_string_eol_s[0]); + + fl_print_format(" Specify an empty string for the %[%s%s%] parameter to disable pid file creation for this program.%c%c", main->output.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, controller_long_pid_s, main->context.set.notable, f_string_eol_s[0], f_string_eol_s[0]); + + controller_unlock_print_flush(main->output.to, 0); + + return F_none; + } +#endif // _di_controller_print_help_ + +#ifndef _di_controller_main_ + f_status_t controller_main(controller_main_t * const main, const f_console_arguments_t *arguments) { + + f_status_t status = F_none; + + { + const f_console_parameters_t parameters = macro_f_console_parameters_t_initialize(main->parameters, controller_total_parameters_d); + + { + f_console_parameter_id_t ids[3] = { controller_parameter_no_color_e, controller_parameter_light_e, controller_parameter_dark_e }; + const f_console_parameter_ids_t choices = macro_f_console_parameter_ids_t_initialize(ids, 3); + + status = fll_program_parameter_process(*arguments, parameters, choices, F_true, &main->remaining, &main->context); + + main->output.set = &main->context.set; + main->error.set = &main->context.set; + main->warning.set = &main->context.set; + + if (main->context.set.error.before) { + main->output.context = f_color_set_empty_s; + main->output.notable = main->context.set.notable; + + main->error.context = main->context.set.error; + main->error.notable = main->context.set.notable; + + main->warning.context = main->context.set.warning; + main->warning.notable = main->context.set.notable; + } + else { + f_color_set_t *sets[] = { &main->output.context, &main->output.notable, &main->error.context, &main->error.notable, &main->warning.context, &main->warning.notable, 0 }; + + fll_program_parameter_process_empty(&main->context, sets); + } + + if (F_status_is_error(status)) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + fll_error_print(main->error, F_status_set_fine(status), "fll_program_parameter_process", F_true); + fll_print_terminated(f_string_eol_s, main->error.to.stream); + } + + controller_main_delete(main); + + return F_status_set_error(status); + } + } + + // Identify priority of verbosity related parameters. + { + f_console_parameter_id_t ids[4] = { controller_parameter_verbosity_quiet_e, controller_parameter_verbosity_normal_e, controller_parameter_verbosity_verbose_e, controller_parameter_verbosity_debug_e }; + f_console_parameter_id_t choice = 0; + const f_console_parameter_ids_t choices = macro_f_console_parameter_ids_t_initialize(ids, 4); + + status = f_console_parameter_prioritize_right(parameters, choices, &choice); + + if (F_status_is_error(status)) { + controller_main_delete(main); + + return status; + } + + if (choice == controller_parameter_verbosity_quiet_e) { + main->output.verbosity = f_console_verbosity_quiet_e; + main->error.verbosity = f_console_verbosity_quiet_e; + main->warning.verbosity = f_console_verbosity_quiet_e; + } + else if (choice == controller_parameter_verbosity_normal_e) { + main->output.verbosity = f_console_verbosity_normal_e; + main->error.verbosity = f_console_verbosity_normal_e; + main->warning.verbosity = f_console_verbosity_normal_e; + } + else if (choice == controller_parameter_verbosity_verbose_e) { + main->output.verbosity = f_console_verbosity_verbose_e; + main->error.verbosity = f_console_verbosity_verbose_e; + main->warning.verbosity = f_console_verbosity_verbose_e; + } + else if (choice == controller_parameter_verbosity_debug_e) { + main->output.verbosity = f_console_verbosity_debug_e; + main->error.verbosity = f_console_verbosity_debug_e; + main->warning.verbosity = f_console_verbosity_debug_e; + } + } + + status = F_none; + } + + if (main->parameters[controller_parameter_help_e].result == f_console_result_found_e) { + controller_print_help(main); + + controller_main_delete(main); + + return F_none; + } + + if (main->parameters[controller_parameter_version_e].result == f_console_result_found_e) { + controller_lock_print(main->output.to, 0); + + fll_program_print_version(main->output.to, controller_program_version_s); + + controller_unlock_print_flush(main->output.to, 0); + + controller_main_delete(main); + + return F_none; + } + + controller_setting_t setting = controller_setting_t_initialize; + + struct sockaddr_un address; + setting.control_socket.address = (struct sockaddr *) &address; + setting.control_socket.domain = f_socket_domain_file_d; + setting.control_socket.type = f_socket_type_stream_d; + setting.control_socket.length = sizeof(struct sockaddr_un); + + memset(&address, 0, setting.control_socket.length); + + if (main->remaining.used) { + status = f_string_append_nulless(arguments->argv[main->remaining.array[0]], strnlen(arguments->argv[main->remaining.array[0]], f_console_parameter_size), &setting.name_entry); + } + else { + status = f_string_append_nulless(controller_default_s, controller_default_s_length, &setting.name_entry); + } + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_append_nulless", F_true); + + controller_main_delete(main); + + return status; + } + + status = f_string_dynamic_terminate_after(&setting.name_entry); + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + + controller_main_delete(main); + + return status; + } + + if (main->parameters[controller_parameter_init_e].result == f_console_result_found_e) { + main->as_init = F_true; + } + + if (main->as_init) { + setting.mode = controller_setting_mode_service_e; + } + + if (main->parameters[controller_parameter_settings_e].result == f_console_result_found_e) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, 0); + + fl_print_format("%c%[%SThe parameter '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[%s%s%]", main->error.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, controller_long_settings_s, main->context.set.notable); + fl_print_format("%[' is specified, but no value is given.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, 0); + } + + status = F_status_set_error(F_parameter); + } + else if (main->parameters[controller_parameter_settings_e].locations.used) { + const f_array_length_t location = main->parameters[controller_parameter_settings_e].values.array[main->parameters[controller_parameter_settings_e].values.used - 1]; + + status = fll_path_canonical(arguments->argv[location], &setting.path_setting); + + if (F_status_is_error(status)) { + fll_error_file_print(main->error, F_status_set_fine(status), "fll_path_canonical", F_true, arguments->argv[location], "verify", fll_error_file_type_path_e); + } + } + else { + if (main->parameters[controller_parameter_init_e].result == f_console_result_found_e && !main->as_init) { + status = f_string_append(controller_path_settings_init_s, controller_path_settings_init_s_length, &setting.path_setting); + } + else if (main->setting_default.used) { + status = f_string_append(main->setting_default.string, main->setting_default.used, &setting.path_setting); + } + else { + status = f_string_append(controller_path_settings_s, controller_path_settings_s_length, &setting.path_setting); + } + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_append", F_true); + } + } + + if (F_status_is_error_not(status)) { + if (main->parameters[controller_parameter_pid_e].result == f_console_result_found_e) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, 0); + + fl_print_format("%c%[%SThe parameter '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[%s%s%]", main->error.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, controller_long_pid_s, main->context.set.notable); + fl_print_format("%[' is specified, but no value is given.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, 0); + } + + status = F_status_set_error(F_parameter); + } + else if (main->parameters[controller_parameter_pid_e].locations.used) { + const f_array_length_t location = main->parameters[controller_parameter_pid_e].values.array[main->parameters[controller_parameter_pid_e].values.used - 1]; + + if (strnlen(arguments->argv[location], f_console_parameter_size)) { + status = fll_path_canonical(arguments->argv[location], &setting.path_pid); + + if (F_status_is_error(status)) { + fll_error_file_print(main->error, F_status_set_fine(status), "fll_path_canonical", F_true, arguments->argv[location], "verify", fll_error_file_type_path_e); + } + } + else { + setting.path_pid.used = 0; + } + } + } + + if (F_status_is_error_not(status) && !setting.path_pid.used && !main->parameters[controller_parameter_pid_e].locations.used) { + if (main->parameters[controller_parameter_init_e].result == f_console_result_found_e) { + status = f_string_append(controller_path_pid_init_s, controller_path_pid_init_s_length, &setting.path_pid); + } + else { + status = f_string_append(main->path_pid.string, main->path_pid.used, &setting.path_pid); + } + + if (F_status_is_error_not(status)) { + status = f_string_append(setting.name_entry.string, setting.name_entry.used, &setting.path_pid); + } + + if (F_status_is_error_not(status)) { + status = f_string_append(controller_path_suffix_s, controller_path_suffix_s_length, &setting.path_pid); + } + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_append", F_true); + } + } + + if (F_status_is_error_not(status)) { + if (main->parameters[controller_parameter_cgroup_e].result == f_console_result_found_e) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, 0); + + fl_print_format("%c%[%SThe parameter '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[%s%s%]", main->error.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, controller_long_cgroup_s, main->context.set.notable); + fl_print_format("%[' is specified, but no value is given.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, 0); + } + + status = F_status_set_error(F_parameter); + } + else if (main->parameters[controller_parameter_cgroup_e].locations.used) { + const f_array_length_t location = main->parameters[controller_parameter_cgroup_e].values.array[main->parameters[controller_parameter_cgroup_e].values.used - 1]; + + if (strnlen(arguments->argv[location], f_console_parameter_size)) { + status = fll_path_canonical(arguments->argv[location], &setting.path_cgroup); + + if (F_status_is_error(status)) { + fll_error_file_print(main->error, F_status_set_fine(status), "fll_path_canonical", F_true, arguments->argv[location], "verify", fll_error_file_type_path_e); + } + else { + status = f_string_append_assure(F_path_separator_s, 1, &setting.path_cgroup); + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_append_assure", F_true); + } + else { + status = f_string_dynamic_terminate_after(&setting.path_cgroup); + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + } + } + } + } + else { + if (main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(main->warning.to, 0); + + fl_print_format("%c%[%SThe parameter '%]", main->warning.to.stream, f_string_eol_s[0], main->warning.context, main->warning.prefix ? main->warning.prefix : f_string_empty_s, main->warning.context); + fl_print_format("%[%s%s%]", main->warning.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, controller_long_cgroup_s, main->context.set.notable); + fl_print_format("%[' must be a file directory path but instead is an empty string, falling back to the default.%]%c", main->warning.to.stream, main->warning.context, main->warning.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->warning.to, 0); + } + } + } + } + + if (F_status_is_error_not(status) && main->parameters[controller_parameter_daemon_e].result == f_console_result_found_e) { + if (main->parameters[controller_parameter_validate_e].result == f_console_result_found_e) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, 0); + + fl_print_format("%c%[%SThe parameter '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[' must not be specified with the parameter '%]", main->error.to.stream, main->error.context, main->error.context); + fl_print_format("%[%s%s%]", main->error.to.stream, main->context.set.notable, f_console_symbol_long_enable_s, controller_long_daemon_s, main->context.set.notable); + fl_print_format("%['.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, 0); + } + + status = F_status_set_error(F_parameter); + } + } + + // Handle defaults dependent on the "as init" execution state. + if (main->as_init) { + setting.entry.pid = controller_entry_pid_disable_e; + setting.entry.show = controller_entry_show_init_e; + + if (main->parameters[controller_parameter_interruptible_e].result == f_console_result_found_e) { + setting.interruptible = F_true; + } + else { + setting.interruptible = F_false; + } + } + else { + if (main->parameters[controller_parameter_uninterruptible_e].result == f_console_result_found_e) { + setting.interruptible = F_false; + } + else { + setting.interruptible = F_true; + } + } + + if (F_status_is_error_not(status)) { + f_signal_set_fill(&main->signal.set); + + status = f_thread_signal_mask(SIG_BLOCK, &main->signal.set, 0); + + if (F_status_is_error_not(status)) { + status = f_signal_open(&main->signal); + } + + // If there is an error opening a signal descriptor, then do not handle signals. + if (F_status_is_error(status)) { + f_signal_mask(SIG_UNBLOCK, &main->signal.set, 0); + f_signal_close(&main->signal); + } + + // A control file path is required. + if (!setting.path_cgroup.used) { + status = f_string_append_nulless(F_control_group_path_system_prefix_s, F_control_group_path_system_prefix_s_length, &setting.path_cgroup); + + if (F_status_is_error_not(status)) { + status = f_string_append_nulless(F_control_group_path_system_default_s, F_control_group_path_system_default_s_length, &setting.path_cgroup); + } + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_append_nulless", F_true); + } + else { + status = f_string_append_assure(F_path_separator_s, 1, &setting.path_cgroup); + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_append_assure", F_true); + } + else { + status = f_string_dynamic_terminate_after(&setting.path_cgroup); + + if (F_status_is_error(status)) { + fll_error_print(main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + } + } + } + } + } + + if (F_status_is_error_not(status)) { + status = controller_thread_main(main, &setting); + } + + // Ensure a newline is always put at the end of the program execution, unless in quiet mode. + if (F_status_is_error(status) && main->output.verbosity != f_console_verbosity_quiet_e) { + if (F_status_set_fine(status) == F_interrupt) { + fflush(main->output.to.stream); + } + + fll_print_terminated(f_string_eol_s, main->output.to.stream); + } + + if (status != F_child && setting.pid_created) { + const f_status_t status_delete = controller_file_pid_delete(main->pid, setting.path_pid); + + if (F_status_is_error(status_delete) && main->warning.verbosity == f_console_verbosity_debug_e) { + if (F_status_set_fine(status_delete) == F_number_not) { + controller_lock_print(main->warning.to, 0); + + fl_print_format("%c%[%SThe pid file '%]", main->warning.to.stream, f_string_eol_s[0], main->warning.context, main->warning.prefix ? main->warning.prefix : f_string_empty_s, main->warning.context); + fl_print_format("%[%Q%]", main->warning.to.stream, main->warning.notable, setting.path_pid, main->warning.notable); + fl_print_format("%[' must not be specified with the parameter '%]", main->warning.to.stream, main->warning.context, main->warning.context); + fl_print_format("%[%i%]", main->warning.to.stream, main->warning.notable, main->pid, main->warning.notable); + fl_print_format("%[' doesn't contain the expected number, not deleting file.%]%c", main->warning.to.stream, main->warning.context, main->warning.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->warning.to, 0); + } + else if (F_status_set_fine(status_delete) != F_interrupt) { + fll_error_file_print(main->warning, F_status_set_fine(status_delete), "controller_file_pid_delete", F_true, setting.path_pid.string, "delete", fll_error_file_type_file_e); + } + } + } + + if (status != F_child && setting.path_control.used) { + f_socket_disconnect(&setting.control_socket, f_socket_close_read_write_e); + + if (!setting.control_readonly) { + f_file_remove(setting.path_control.string); + } + } + + controller_setting_delete_simple(&setting); + controller_main_delete(main); + + if (status == F_child) { + return status; + } + + return status; + } +#endif // _di_controller_main_ + +#ifndef _di_controller_main_delete_ + f_status_t controller_main_delete(controller_main_t * const main) { + + for (f_array_length_t i = 0; i < controller_total_parameters_d; ++i) { + + macro_f_array_lengths_t_delete_simple(main->parameters[i].locations); + macro_f_array_lengths_t_delete_simple(main->parameters[i].locations_sub); + macro_f_array_lengths_t_delete_simple(main->parameters[i].values); + } // for + + macro_f_array_lengths_t_delete_simple(main->remaining); + macro_f_color_context_t_delete_simple(main->context); + + return F_none; + } +#endif // _di_controller_main_delete_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/controller/controller.h b/level_3/controller/c/controller/controller.h new file mode 100644 index 0000000..ff580ed --- /dev/null +++ b/level_3/controller/c/controller/controller.h @@ -0,0 +1,343 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + * + * This is the Controller program. + * + * This program utilizes the Featureless Linux Library. + * This program provides system service management, much like sysvcontroller and controllerng. + * This program can be controlled from user-space via the "control" program. + * This program can be used in an initrd and should be capable of pivot root operations. + * + * @todo Implement "exit" files that are the opposite of "entry" files whereas rules specified within are all called via the "stop" action type. + * This would then allow for switching modes. + * The "exit" would be specified in the "entry", by name and would be found under "exits" directory alongside the "entries" directory. + * + * @todo check the return status of unlocks. + * + * @todo the read/write locks (and unlocks) needs to be more robust in that they need to attempt to keep going even on failure or need to wait until resolvable. + * this is done to help ensure that the controller program always continues onward. + * + * @todo just like with the read/write locks, the out of memory cases need to be handled to keep going instead of bailing. + * likely these will need to be sleeps on the premise that eventually memory will clear itself up. + */ +#ifndef _controller_h + +// include pre-requirements +#define _GNU_SOURCE + +// libc includes +#include +#include +#include +#include +#include +#include +#include + +// fll-0 includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// fll-1 includes +#include +#include +#include +#include +#include +#include + +// fll-2 includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_program_version_ + #define controller_program_version_major_s F_string_ascii_0_s + #define controller_program_version_minor_s F_string_ascii_5_s + #define controller_program_version_micro_s F_string_ascii_8_s + + #ifndef controller_program_version_nano_prefix_s + #define controller_program_version_nano_prefix_s + #endif + + #ifndef controller_program_version_nano_s + #define controller_program_version_nano_s + #endif + + #define controller_program_version_s controller_program_version_major_s F_string_ascii_period_s controller_program_version_minor_s F_string_ascii_period_s controller_program_version_micro_s controller_program_version_nano_prefix_s controller_program_version_nano_s +#endif // _di_controller_program_version_ + +#ifndef _di_controller_program_name_ + #define controller_program_name_s "controller" + #define controller_program_name_long_s "Controller Program" + + #define controller_program_name_init_s "init" + #define controller_program_name_init_long_s "Init Program" +#endif // _di_controller_program_name_ + +#ifndef _di_controller_defines_ + + // The init pid path is a system-specific path and needs to be more easily contolled at compile time. + #if defined(_override_controller_path_pid_init_) && defined(_override_controller_path_pid_init_length_) + #define controller_path_pid_init_s _override_controller_path_pid_init_ + #define controller_path_pid_init_s_length _override_controller_path_pid_init_length_ + #elif defined(_controller_as_init_) + #define controller_path_pid_init_s "/var/run/init/init-" + #define controller_path_pid_init_s_length 19 + #else + #define controller_path_pid_init_s "/var/run/controller/controller-" + #define controller_path_pid_init_s_length 31 + #endif /* defined(_override_controller_path_pid_init_) && defined(_override_controller_path_pid_init_length_) */ + + // The settings path is a system-specific path and needs to be more easily contolled at compile time. + #if defined(_override_controller_path_settings_init_) && defined(_override_controller_path_settings_init_length_) + #define controller_path_settings_init_s _override_controller_path_settings_init_ + #define controller_path_settings_init_s_length _override_controller_path_settings_init_length_ + #elif defined(_controller_as_init_) + #define controller_path_settings_init_s "/etc/init" + #define controller_path_settings_init_s_length 9 + #else + #define controller_path_settings_init_s "/etc/controller" + #define controller_path_settings_init_s_length 15 + #endif /* defined(_override_controller_path_settings_init_) && defined(_override_controller_path_settings_init_length_) */ + + #ifdef _override_controller_default_program_script_ + #define controller_default_program_script_s _override_controller_default_program_script_ + #else + #define controller_default_program_script_s "bash" + #endif // _override_controller_default_program_script_ + + #define controller_path_pid_s "controller/run/controller-" + #define controller_path_settings_s "controller" + #define controller_path_suffix_s ".pid" + + #define controller_path_pid_s_length 26 + #define controller_path_settings_s_length 10 + #define controller_path_suffix_s_length 4 + + #define controller_short_cgroup_s "c" + #define controller_short_daemon_s "d" + #define controller_short_init_s "I" + #define controller_short_interruptible_s "i" + #define controller_short_pid_s "p" + #define controller_short_settings_s "s" + #define controller_short_simulate_s "S" + #define controller_short_uninterruptible_s "U" + #define controller_short_validate_s "v" + + #define controller_long_cgroup_s "cgroup" + #define controller_long_daemon_s "daemon" + #define controller_long_init_s "init" + #define controller_long_interruptible_s "interruptible" + #define controller_long_pid_s "pid" + #define controller_long_settings_s "settings" + #define controller_long_simulate_s "simulate" + #define controller_long_uninterruptible_s "uninterruptible" + #define controller_long_validate_s "validate" + + enum { + controller_parameter_help_e, + controller_parameter_light_e, + controller_parameter_dark_e, + controller_parameter_no_color_e, + controller_parameter_verbosity_quiet_e, + controller_parameter_verbosity_normal_e, + controller_parameter_verbosity_verbose_e, + controller_parameter_verbosity_debug_e, + controller_parameter_version_e, + + controller_parameter_cgroup_e, + controller_parameter_daemon_e, + controller_parameter_init_e, + controller_parameter_interruptible_e, + controller_parameter_pid_e, + controller_parameter_settings_e, + controller_parameter_simulate_e, + controller_parameter_uninterruptible_e, + controller_parameter_validate_e, + }; + + #define controller_console_parameter_t_initialize \ + { \ + f_console_parameter_t_initialize(f_console_standard_short_help_s, f_console_standard_long_help_s, 0, 0, f_console_type_normal_e), \ + f_console_parameter_t_initialize(f_console_standard_short_light_s, f_console_standard_long_light_s, 0, 0, f_console_type_inverse_e), \ + f_console_parameter_t_initialize(f_console_standard_short_dark_s, f_console_standard_long_dark_s, 0, 0, f_console_type_inverse_e), \ + f_console_parameter_t_initialize(f_console_standard_short_no_color_s, f_console_standard_long_no_color_s, 0, 0, f_console_type_inverse_e), \ + f_console_parameter_t_initialize(f_console_standard_short_quiet_s, f_console_standard_long_quiet_s, 0, 0, f_console_type_inverse_e), \ + f_console_parameter_t_initialize(f_console_standard_short_normal_s, f_console_standard_long_normal_s, 0, 0, f_console_type_inverse_e), \ + f_console_parameter_t_initialize(f_console_standard_short_verbose_s, f_console_standard_long_verbose_s, 0, 0, f_console_type_inverse_e), \ + f_console_parameter_t_initialize(f_console_standard_short_debug_s, f_console_standard_long_debug_s, 0, 0, f_console_type_inverse_e), \ + f_console_parameter_t_initialize(f_console_standard_short_version_s, f_console_standard_long_version_s, 0, 0, f_console_type_inverse_e), \ + f_console_parameter_t_initialize(controller_short_cgroup_s, controller_long_cgroup_s, 0, 1, f_console_type_normal_e), \ + f_console_parameter_t_initialize(controller_short_daemon_s, controller_long_daemon_s, 0, 0, f_console_type_normal_e), \ + f_console_parameter_t_initialize(controller_short_init_s, controller_long_init_s, 0, 0, f_console_type_normal_e), \ + f_console_parameter_t_initialize(controller_short_interruptible_s, controller_long_interruptible_s, 0, 0, f_console_type_normal_e), \ + f_console_parameter_t_initialize(controller_short_pid_s, controller_long_pid_s, 0, 1, f_console_type_normal_e), \ + f_console_parameter_t_initialize(controller_short_settings_s, controller_long_settings_s, 0, 1, f_console_type_normal_e), \ + f_console_parameter_t_initialize(controller_short_simulate_s, controller_long_simulate_s, 0, 0, f_console_type_normal_e), \ + f_console_parameter_t_initialize(controller_short_uninterruptible_s, controller_long_uninterruptible_s, 0, 0, f_console_type_normal_e), \ + f_console_parameter_t_initialize(controller_short_validate_s, controller_long_validate_s, 0, 0, f_console_type_normal_e), \ + } + + #define controller_total_parameters_d 18 +#endif // _di_controller_defines_ + +#ifndef _di_controller_main_t_ + typedef struct { + f_console_parameter_t parameters[controller_total_parameters_d]; + + f_array_lengths_t remaining; + bool process_pipe; + bool as_init; + + fl_print_t output; + fl_print_t error; + fl_print_t warning; + + f_signal_t signal; + + pid_t pid; + mode_t umask; + int child; + + f_string_t program_name; + f_string_t program_name_long; + f_string_static_t setting_default; + f_string_static_t path_pid; + + f_color_context_t context; + } controller_main_t; + + #define controller_main_t_initialize \ + { \ + controller_console_parameter_t_initialize, \ + f_array_lengths_t_initialize, \ + F_false, \ + F_false, \ + fl_print_t_initialize, \ + macro_fl_print_t_initialize_error(), \ + macro_fl_print_t_initialize_warning(), \ + f_signal_t_initialize, \ + 0, \ + 0, \ + 0, \ + f_string_t_initialize, \ + f_string_t_initialize, \ + f_string_static_t_initialize, \ + f_string_static_t_initialize, \ + f_color_context_t_initialize, \ + } +#endif // _di_controller_main_t_ + +/** + * Print help. + * + * @param main + * The main program data. + * + * @return + * F_none on success. + */ +#ifndef _di_controller_print_help_ + extern f_status_t controller_print_help(controller_main_t * const main); +#endif // _di_controller_print_help_ + +/** + * Execute main program. + * + * Be sure to call controller_main_delete() after executing this. + * + * If main.signal is non-zero, then this blocks and handles the following signals: + * - F_signal_abort + * - F_signal_broken_pipe + * - F_signal_hangup + * - F_signal_interrupt + * - F_signal_quit + * - F_signal_termination + * + * @param main + * The main program data. + * @param arguments + * The parameters passed to the process. + * + * @return + * F_none on success. + * F_child if this is a child process returning. + * + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * + * Status codes (with error bit) are returned on any problem. + * + * @see controller_main_delete() + */ +#ifndef _di_controller_main_ + extern f_status_t controller_main(controller_main_t * const main, const f_console_arguments_t *arguments); +#endif // _di_controller_main_ + +/** + * Deallocate main. + * + * Be sure to call this after executing controller_main(). + * + * If main.signal is non-zero, then this blocks and handles the following signals: + * - F_signal_abort + * - F_signal_broken_pipe + * - F_signal_hangup + * - F_signal_interrupt + * - F_signal_quit + * - F_signal_termination + * + * @param main + * The main program data. + * + * @return + * F_none on success. + * + * Status codes (with error bit) are returned on any problem. + * + * @see controller_main() + */ +#ifndef _di_controller_main_delete_ + extern f_status_t controller_main_delete(controller_main_t * const main); +#endif // _di_controller_main_delete_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _controller_h diff --git a/level_3/controller/c/controller/private-controller.c b/level_3/controller/c/controller/private-controller.c new file mode 100644 index 0000000..4febf96 --- /dev/null +++ b/level_3/controller/c/controller/private-controller.c @@ -0,0 +1,789 @@ +#include "controller.h" +#include "../common/private-common.h" +#include "private-controller.h" +#include "private-controller_print.h" +#include "../entry/private-entry_print.h" +#include "../lock/private-lock_print.h" +#include "../thread/private-thread_control.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_range_after_number_sign_ + f_string_range_t controller_range_after_number_sign(const f_string_static_t buffer, const f_string_range_t range) { + + f_string_range_t result = range; + + for (; result.start <= result.stop; ++result.start) { + + if (!buffer.string[result.start]) continue; + + if (buffer.string[result.start] == '-' || buffer.string[result.start] == '+') { + ++result.start; + } + + break; + } // while + + return result; + } +#endif // _di_controller_range_after_number_sign_ + +#ifndef _di_controller_string_dynamic_rip_nulless_terminated_ + f_status_t controller_dynamic_rip_nulless_terminated(const f_string_static_t source, const f_string_range_t range, f_string_dynamic_t *destination) { + + f_status_t status = fl_string_dynamic_rip_nulless(source, range, destination); + if (F_status_is_error(status)) return status; + + return f_string_dynamic_terminate_after(destination); + } +#endif // _di_controller_string_dynamic_rip_nulless_terminated_ + +#ifndef _di_controller_string_dynamic_append_terminated_ + f_status_t controller_dynamic_append_terminated(const f_string_static_t source, f_string_dynamic_t *destination) { + + f_status_t status = f_string_dynamic_append_nulless(source, destination); + if (F_status_is_error(status)) return status; + + return f_string_dynamic_terminate_after(destination); + } +#endif // _di_controller_string_dynamic_append_terminated_ + +#ifndef _di_controller_string_dynamic_partial_append_terminated_ + f_status_t controller_dynamic_partial_append_terminated(const f_string_static_t source, const f_string_range_t range, f_string_dynamic_t *destination) { + + f_status_t status = f_string_dynamic_partial_append(source, range, destination); + if (F_status_is_error(status)) return status; + + return f_string_dynamic_terminate_after(destination); + } +#endif // _di_controller_string_dynamic_partial_append_terminated_ + +#ifndef _di_controller_file_load_ + f_status_t controller_file_load(const controller_global_t global, const bool required, const f_string_t path_prefix, const f_string_static_t path_name, const f_string_t path_suffix, const f_array_length_t path_prefix_length, const f_array_length_t path_suffix_length, controller_cache_t * const cache) { + + f_status_t status = F_none; + f_file_t file = f_file_t_initialize; + + cache->action.name_file.used = 0; + cache->buffer_file.used = 0; + + macro_f_time_spec_t_clear(cache->timestamp); + + status = f_string_append(path_prefix, path_prefix_length, &cache->action.name_file); + + if (F_status_is_error_not(status)) { + status = f_string_append(f_path_separator_s, F_path_separator_s_length, &cache->action.name_file); + } + + if (F_status_is_error_not(status)) { + status = f_string_append(path_name.string, path_name.used, &cache->action.name_file); + } + + if (F_status_is_error_not(status)) { + status = f_string_append(F_path_extension_separator_s, F_path_extension_separator_s_length, &cache->action.name_file); + } + + if (F_status_is_error_not(status)) { + status = f_string_append(path_suffix, path_suffix_length, &cache->action.name_file); + } + + if (F_status_is_error(status)) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_append", F_true); + } + + return status; + } + + status = f_string_dynamic_terminate_after(&cache->action.name_file); + + if (F_status_is_error(status)) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + } + + return status; + } + + const f_array_length_t path_length = global.setting->path_setting.used ? global.setting->path_setting.used + F_path_separator_s_length + cache->action.name_file.used : cache->action.name_file.used; + char path[path_length + 1]; + + if (global.setting->path_setting.used) { + memcpy(path, global.setting->path_setting.string, global.setting->path_setting.used); + memcpy(path + global.setting->path_setting.used + F_path_separator_s_length, cache->action.name_file.string, cache->action.name_file.used); + + path[global.setting->path_setting.used] = f_path_separator_s[0]; + } + + path[path_length] = 0; + + status = f_file_stream_open(path, 0, &file); + + if (F_status_is_error(status)) { + if (!required && F_status_set_fine(status) == F_file_found_not) { + f_file_stream_close(F_true, &file); + + return F_file_found_not; + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error_file(global.thread, global.main->error, F_status_set_fine(status), "f_file_stream_open", F_true, path, "open", fll_error_file_type_file_e); + } + } + else { + status = f_file_stream_read(file, &cache->buffer_file); + + if (F_status_is_error(status)) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error_file(global.thread, global.main->error, F_status_set_fine(status), "f_file_stream_read", F_true, path, "read", fll_error_file_type_file_e); + } + } + } + + f_file_stream_close(F_true, &file); + + if (F_status_is_error_not(status)) { + struct stat stat_file; + + status = f_file_stat(path, F_true, &stat_file); + + if (F_status_is_error(status)) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error_file(global.thread, global.main->error, F_status_set_fine(status), "f_file_stat", F_true, path, "stat", fll_error_file_type_file_e); + } + } + else { + cache->timestamp.seconds = stat_file.st_ctim.tv_sec; + cache->timestamp.nanoseconds = stat_file.st_ctim.tv_nsec; + } + } + + if (F_status_is_error(status)) return status; + + return F_none; + } +#endif // _di_controller_file_load_ + +#ifndef _di_controller_file_pid_create_ + f_status_t controller_file_pid_create(const pid_t pid, const f_string_static_t path) { + + f_status_t status = F_none; + + // the file exists, do not attempt to overwrite. + if (f_file_exists(path.string) == F_true) { + return F_status_set_error(F_file_found); + } + + { + f_string_dynamic_t path_directory = f_string_dynamic_t_initialize; + + status = f_file_name_directory(path.string, path.used, &path_directory); + + if (F_status_is_error_not(status)) { + status = f_directory_exists(path_directory.string); + } + + macro_f_string_dynamic_t_delete_simple(path_directory) + + if (F_status_is_error(status)) return status; + + // the directory does not exist so do not bother attempting to create a pid file. + if (status == F_false) { + return F_status_set_error(F_directory_not); + } + } + + f_file_t file = f_file_t_initialize; + + file.flag = F_file_flag_write_only_d; + + status = f_file_stream_open(path.string, f_file_open_mode_truncate_s, &file); + if (F_status_is_error(status)) return status; + + fll_print_format("%i%c", file.stream, pid, f_string_eol_s[0]); + + f_file_stream_close(F_true, &file); + + if (F_status_is_error(status)) return status; + + return F_none; + } +#endif // _di_controller_file_pid_create_ + +#ifndef _di_controller_file_pid_delete_ + f_status_t controller_file_pid_delete(const pid_t pid, const f_string_static_t path) { + + // only delete if the file exists and there is no error while checking. + if (f_file_exists(path.string) != F_true) { + return F_none; + } + + f_status_t status = F_none; + f_file_t pid_file = f_file_t_initialize; + + status = f_file_stream_open(path.string, f_file_open_mode_read_s, &pid_file); + if (F_status_is_error(status)) return status; + + f_string_dynamic_t pid_buffer = f_string_dynamic_t_initialize; + + status = f_file_stream_read(pid_file, &pid_buffer); + + if (F_status_is_error_not(status)) { + status = f_file_stream_close(F_true, &pid_file); + } + + if (F_status_is_error_not(status)) { + f_number_unsigned_t number = 0; + f_string_range_t range = macro_f_string_range_t_initialize(pid_buffer.used); + + for (; range.start < pid_buffer.used; ++range.start) { + if (!isspace(pid_buffer.string[range.start])) break; + } // for + + for (; range.stop > 0; --range.stop) { + if (!isspace(pid_buffer.string[range.stop])) break; + } // for + + status = fl_conversion_string_to_decimal_unsigned(pid_buffer.string, range, &number); + + if (F_status_is_error_not(status) && number == pid) { + status = f_file_remove(path.string); + } + else { + status = F_status_set_error(F_number_not); + } + } + + macro_f_string_dynamic_t_delete_simple(pid_buffer); + + return status; + } +#endif // _di_controller_file_pid_delete_ + +#ifndef _di_controller_file_pid_read_ + f_status_t controller_file_pid_read(const f_string_static_t path, pid_t * const pid) { + + *pid = 0; + + f_status_t status = f_file_exists(path.string); + if (F_status_is_error(status)) return status; + + if (status != F_true) { + return F_data_not; + } + + f_file_t pid_file = f_file_t_initialize; + + status = f_file_stream_open(path.string, f_file_open_mode_read_s, &pid_file); + if (F_status_is_error(status)) return status; + + f_string_dynamic_t pid_buffer = f_string_dynamic_t_initialize; + + status = f_file_stream_read(pid_file, &pid_buffer); + + if (F_status_is_error_not(status)) { + status = f_file_stream_close(F_true, &pid_file); + } + + if (F_status_is_error_not(status)) { + f_number_unsigned_t number = 0; + f_string_range_t range = macro_f_string_range_t_initialize(pid_buffer.used); + + for (; range.start < pid_buffer.used; ++range.start) { + if (!isspace(pid_buffer.string[range.start])) break; + } // for + + for (; range.stop > 0; --range.stop) { + if (!isspace(pid_buffer.string[range.stop])) break; + } // for + + status = fl_conversion_string_to_decimal_unsigned(pid_buffer.string, range, &number); + + if (F_status_is_error_not(status)) { + *pid = (pid_t) number; + } + } + + macro_f_string_dynamic_t_delete_simple(pid_buffer); + + return status; + } +#endif // _di_controller_file_pid_read_ + +#ifndef _di_controller_get_id_user_ + f_status_t controller_get_id_user(const f_string_static_t buffer, const f_string_range_t range, controller_cache_t * const cache, uid_t * const id) { + + f_number_unsigned_t number = 0; + + f_status_t status = fl_conversion_string_to_number_unsigned(buffer.string, range, &number); + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_number) { + cache->action.generic.used = 0; + + status = controller_dynamic_rip_nulless_terminated(buffer, range, &cache->action.generic); + if (F_status_is_error(status)) return status; + + status = f_account_id_user_by_name(cache->action.generic.string, id); + if (F_status_is_error(status)) return status; + + if (status == F_exist_not) { + return F_status_set_error(F_exist_not); + } + + return F_none; + } + + return status; + } + else if (number > F_type_size_32_unsigned_d) { + return F_status_set_error(F_number_too_large); + } + + return status; + } +#endif // _di_controller_get_id_user_ + +#ifndef _di_controller_get_id_group_ + f_status_t controller_get_id_group(const f_string_static_t buffer, const f_string_range_t range, controller_cache_t * const cache, gid_t * const id) { + + f_number_unsigned_t number = 0; + + f_status_t status = fl_conversion_string_to_number_unsigned(buffer.string, range, &number); + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_number) { + cache->action.generic.used = 0; + + status = controller_dynamic_rip_nulless_terminated(buffer, range, &cache->action.generic); + if (F_status_is_error(status)) return status; + + status = f_account_id_group_by_name(cache->action.generic.string, id); + if (F_status_is_error(status)) return status; + + if (status == F_exist_not) { + return F_status_set_error(F_exist_not); + } + + return F_none; + } + + return status; + } + else if (number > F_type_size_32_unsigned_d) { + return F_status_set_error(F_number_too_large); + } + + return status; + } +#endif // _di_controller_get_id_group_ + +#ifndef _di_controller_perform_ready_ + f_status_t controller_perform_ready(const controller_global_t *global, controller_cache_t * const cache, const bool is_entry) { + + if (!is_entry) { + return F_none; + } + + f_status_t status = F_none; + + if (global->setting->entry.pid != controller_entry_pid_disable_e && !global->setting->path_pid.used) { + if (global->main->parameters[controller_parameter_validate_e].result == f_console_result_additional_e) { + status = controller_file_pid_create(global->main->pid, global->setting->path_pid); + } + + // Report pid file error but because this could be an "init" program, consider the pid file as optional and continue on. + if (F_status_is_error(status)) { + + // Always return immediately on memory errors. + if (F_status_set_fine(status) == F_memory_not) { + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->error.to, global->thread); + + controller_print_error_file(0, global->main->error, F_status_set_fine(status), "controller_file_pid_create", F_true, global->setting->path_pid.string, "create", fll_error_file_type_file_e); + + flockfile(global->main->error.to.stream); + + controller_entry_print_error_cache(is_entry, global->main->error, cache->action); + + controller_unlock_print_flush(global->main->error.to, global->thread); + } + + return status; + } + + if (global->main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->warning.to, global->thread); + + if (F_status_set_fine(status) == F_read_only) { + fl_print_format("%c%[%SThe pid file '%]", global->main->warning.to.stream, f_string_eol_s[0], global->main->warning.context, global->main->warning.prefix, global->main->warning.context); + fl_print_format("%[%Q%]", global->main->warning.to.stream, global->main->warning.notable, global->setting->path_pid, global->main->warning.notable); + fl_print_format("%[' could not be written because the destination is read only.%]%c", global->main->warning.to.stream, global->main->warning.context, global->main->warning.context, f_string_eol_s[0]); + } + else { + controller_print_error_file(0, global->main->warning, F_status_set_fine(status), "controller_file_pid_create", F_true, global->setting->path_pid.string, "create", fll_error_file_type_file_e); + } + + controller_entry_print_error_cache(is_entry, global->main->warning, cache->action); + + controller_unlock_print_flush(global->main->warning.to, global->thread); + } + + status = F_none; + } + else { + global->setting->pid_created = F_true; + + if (global->main->output.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%cPID file '", global->main->output.to.stream, f_string_eol_s[0]); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.notable, global->setting->path_pid, global->main->context.set.notable); + + if (global->main->parameters[controller_parameter_validate_e].result == f_console_result_none_e) { + fl_print_format("' created.%c", global->main->output.to.stream, f_string_eol_s[0]); + } + else { + fl_print_format("'.%c", global->main->output.to.stream, f_string_eol_s[0]); + } + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + } + + if (global->setting->path_control.used) { + if (global->setting->control_readonly) { + if (f_file_exists(global->setting->path_control.string) != F_true) { + if (global->main->output.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%c%[%SControl socket '%]", global->main->warning.to.stream, f_string_eol_s[0], global->main->warning.context, global->main->warning.prefix, global->main->warning.context); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.notable, global->setting->path_control, global->main->context.set.notable); + fl_print_format("' .%c", global->main->output.to.stream, f_string_eol_s[0]); + fl_print_format("%[' cannot be found while read only mode is enabled and so the Control socket is unavailable.%]%c", global->main->output.to.stream, global->main->warning.context, global->main->warning.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + + return status; + } + } + else { + status = f_socket_create(&global->setting->control_socket); + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_memory_not) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_socket_create", F_true); + + return status; + } + + if (global->main->output.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%c%[%SControl socket '%]", global->main->warning.to.stream, f_string_eol_s[0], global->main->warning.context, global->main->warning.prefix, global->main->warning.context); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.notable, global->setting->path_control, global->main->context.set.notable); + fl_print_format("%[' could not be created, code %]", global->main->output.to.stream, global->main->warning.context, global->main->warning.context); + fl_print_format("%[%ui%]", global->main->output.to.stream, global->main->context.set.notable, F_status_set_fine(status), global->main->context.set.notable); + fl_print_format("%[.%]%c", global->main->output.to.stream, global->main->warning.context, global->main->warning.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + else { + status = f_file_remove(global->setting->path_control.string); + + if (F_status_set_fine(status) == F_memory_not) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_file_remove", F_true); + + return status; + } + + global->setting->control_socket.name = global->setting->path_control.string; + + status = f_socket_bind_file(global->setting->control_socket); + + if (F_status_is_error(status)) { + f_socket_disconnect(&global->setting->control_socket, f_socket_close_fast_e); + + if (F_status_set_fine(status) == F_memory_not) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_socket_bind_file", F_true); + + return status; + } + + if (global->main->output.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%c%[%SControl socket '%]", global->main->warning.to.stream, f_string_eol_s[0], global->main->warning.context, global->main->warning.prefix, global->main->warning.context); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.notable, global->setting->path_control, global->main->context.set.notable); + fl_print_format("%[' could not be bound, code %]", global->main->output.to.stream, global->main->warning.context, global->main->warning.context); + fl_print_format("%[%ui%]", global->main->output.to.stream, global->main->context.set.notable, F_status_set_fine(status), global->main->context.set.notable); + fl_print_format("%[.%]%c", global->main->output.to.stream, global->main->warning.context, global->main->warning.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + else { + status = f_file_role_change(global->setting->path_control.string, global->setting->control_user, global->setting->control_group, F_true); + + if (F_status_is_error(status)) { + f_socket_disconnect(&global->setting->control_socket, f_socket_close_fast_e); + + if (F_status_set_fine(status) == F_memory_not) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_file_role_change", F_true); + + return status; + } + + if (global->main->output.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%c%[%SControl socket '%]", global->main->warning.to.stream, f_string_eol_s[0], global->main->warning.context, global->main->warning.prefix, global->main->warning.context); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.notable, global->setting->path_control, global->main->context.set.notable); + fl_print_format("%[' failed to set file roles, code %]", global->main->output.to.stream, global->main->warning.context, global->main->warning.context); + fl_print_format("%[%ui%]", global->main->output.to.stream, global->main->context.set.notable, F_status_set_fine(status), global->main->context.set.notable); + fl_print_format("%[.%]%c", global->main->output.to.stream, global->main->warning.context, global->main->warning.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + else { + status = f_file_mode_set(global->setting->path_control.string, global->setting->control_mode); + + if (F_status_is_error(status)) { + f_socket_disconnect(&global->setting->control_socket, f_socket_close_fast_e); + + if (F_status_set_fine(status) == F_memory_not) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_file_role_change", F_true); + + return status; + } + + if (global->main->output.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%c%[%SControl socket '%]", global->main->warning.to.stream, f_string_eol_s[0], global->main->warning.context, global->main->warning.prefix, global->main->warning.context); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.notable, global->setting->path_control, global->main->context.set.notable); + fl_print_format("%[' failed to set file mode, code %]", global->main->output.to.stream, global->main->warning.context, global->main->warning.context); + fl_print_format("%[%ui%]", global->main->output.to.stream, global->main->context.set.notable, F_status_set_fine(status), global->main->context.set.notable); + fl_print_format("%[.%]%c", global->main->output.to.stream, global->main->warning.context, global->main->warning.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + else { + if (global->main->output.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%cControl socket '", global->main->output.to.stream, f_string_eol_s[0]); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.notable, global->setting->path_control, global->main->context.set.notable); + + if (global->main->parameters[controller_parameter_validate_e].result == f_console_result_none_e) { + fl_print_format("' created.%c", global->main->output.to.stream, f_string_eol_s[0]); + } + else { + fl_print_format("'.%c", global->main->output.to.stream, f_string_eol_s[0]); + } + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + + status = f_thread_create(0, &global->thread->id_listen, &controller_thread_control_listen, (void *) global); + + if (status == F_child) { + return status; + } + + if (F_status_is_error_not(status)) { + status = f_thread_create(0, &global->thread->id_control, &controller_thread_control, (void *) global); + + if (status == F_child) { + return status; + } + } + + if (F_status_is_error(status)) { + if (global->thread->id_listen) { + f_thread_cancel(global->thread->id_listen); + f_thread_join(global->thread->id_listen, 0); + + global->thread->id_listen = 0; + } + + global->thread->id_control = 0; + + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_thread_create", F_true); + } + } + } + } + } + } + + // Don't fail if unable to create socket file. + status = F_none; + } + } + + return status; + } +#endif // _di_controller_perform_ready_ + +#ifndef _di_controller_status_simplify_error_ + f_status_t controller_status_simplify_error(const f_status_t status) { + + if (status == F_memory_not) { + return F_status_set_error(F_memory); + } + + if (status == F_file_open_max || status == F_space_not || status == F_busy) { + return F_status_set_error(F_resource); + } + + if (status == F_access_denied || status == F_filesystem_quota_block || status == F_prohibited || status == F_input_output) { + return F_status_set_error(F_access); + } + + if (status == F_complete_not_utf || status == F_complete_not_utf_block || status == F_complete_not_utf_eof || status == F_complete_not_utf_eol || status == F_complete_not_utf_eos || status == F_complete_not_utf_stop) { + return F_status_set_error(F_encoding); + } + + if (status == F_number || status == F_number_negative || status == F_number_positive || status == F_number_overflow) { + return F_status_set_error(F_number); + } + + if (status == F_parameter || status == F_found_not || status == F_interrupt || status == F_supported_not || status == F_critical) { + return F_status_set_error(status); + } + + if (status == F_valid_not) { + return F_status_set_error(F_valid_not); + } + + return F_status_set_error(F_failure); + } +#endif // _di_controller_status_simplify_error_ + +#ifndef _di_controller_time_ + void controller_time(const time_t seconds, const long nanoseconds, struct timespec *time) { + + struct timeval now; + + gettimeofday(&now, 0); + + time->tv_sec = now.tv_sec + seconds; + time->tv_nsec = (now.tv_usec * 1000) + nanoseconds; + + // If tv_nsec is 1 second or greater, then increment seconds. + if (time->tv_nsec >= 1000000000) { + ++(time->tv_sec); + + time->tv_nsec -= 1000000000; + } + } +#endif // _di_controller_time_ + +#ifndef _di_controller_time_milliseconds_ + struct timespec controller_time_milliseconds(const f_number_unsigned_t milliseconds) { + + struct timespec time; + time.tv_sec = milliseconds > 1000 ? milliseconds / 1000 : 0; + time.tv_nsec = (time.tv_sec ? milliseconds - time.tv_sec : milliseconds) * 1000; + + return time; + } +#endif // _di_controller_time_milliseconds_ + +#ifndef _di_controller_time_seconds_ + struct timespec controller_time_seconds(const f_number_unsigned_t seconds) { + + struct timespec time; + time.tv_sec = seconds; + time.tv_nsec = 0; + + return time; + } +#endif // _di_controller_time_seconds_ + +#ifndef _di_controller_time_sleep_nanoseconds_ + int controller_time_sleep_nanoseconds(controller_main_t * const main, controller_setting_t * const setting, struct timespec time) { + + // When sleep is a second or more, instead wait for terminating signals if interruptible. + if (setting->interruptible && time.tv_sec) { + siginfo_t information; + f_signal_t signal = f_signal_t_initialize; + + memset(&information, 0, sizeof(siginfo_t)); + + f_signal_set_empty(&signal.set); + f_signal_set_add(F_signal_abort, &signal.set); + f_signal_set_add(F_signal_interrupt, &signal.set); + f_signal_set_add(F_signal_quit, &signal.set); + f_signal_set_add(F_signal_termination, &signal.set); + + if (F_status_is_error(f_signal_wait_until(&signal.set, &time, &information))) { + return -1; + } + + return 0; + } + + return nanosleep(&time, 0); + } +#endif // _di_controller_time_sleep_nanoseconds_ + +#ifndef _di_controller_validate_define_name_ + f_status_t controller_validate_environment_name(const f_string_static_t name) { + + if (!name.used) return F_none; + + f_status_t status = F_none; + + if (name.string[0] != '_') { + status = f_utf_is_alpha(name.string, name.used); + + if (F_status_is_error(status)) return status; + if (status == F_false) return F_false; + } + + for (f_array_length_t i = macro_f_utf_byte_width(name.string[0]); i < name.used; i += macro_f_utf_byte_width(name.string[i])) { + + if (name.string[i] == '_') continue; + + status = f_utf_is_alpha_digit(name.string, name.used); + + if (F_status_is_error(status)) return status; + if (status == F_false) return F_false; + } // for + + return F_true; + } +#endif // _di_controller_validate_define_name_ + +#ifndef _di_controller_validate_has_graph_ + f_status_t controller_validate_has_graph(const f_string_static_t name) { + + if (!name.used) return F_none; + + f_status_t status = F_none; + + for (f_array_length_t i = 0; i < name.used; i += macro_f_utf_byte_width(name.string[i])) { + + status = f_utf_is_graph(name.string, name.used); + + if (F_status_is_error(status)) return status; + if (status == F_true) return F_true; + } // for + + return F_false; + } +#endif // _di_controller_validate_has_graph_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/controller/private-controller.h b/level_3/controller/c/controller/private-controller.h new file mode 100644 index 0000000..a0fd07b --- /dev/null +++ b/level_3/controller/c/controller/private-controller.h @@ -0,0 +1,420 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_controller_h +#define _PRIVATE_controller_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Given a string whose range represents a number, seek past the first positive or negative sign. + * + * This will stop at the first non-NULL, non-'+' and non-'-' characters. + * + * Only the first '+' or '-' are processed. + * + * @param buffer + * The string referenced by the range. + * @param range + * The range within the buffer to process. + * + * @return + * The string range. + * The start range will be past the stop range on overflow or on any failure. + */ +#ifndef _di_controller_range_after_number_sign_ + extern f_string_range_t controller_range_after_number_sign(const f_string_static_t buffer, const f_string_range_t range) F_attribute_visibility_internal_d; +#endif // _di_controller_range_after_number_sign_ + +/** + * Rip a string from the source and then add a NULL after the end of the string. + * + * @param source + * The string to copy from. + * @param destination + * The string to copy to. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * Errors (with error bit) from: fl_string_dynamic_rip_nulless(). + * + * @see f_string_dynamic_terminate_after() + * @see fl_string_dynamic_rip_nulless() + */ +#ifndef _di_controller_string_dynamic_rip_nulless_terminated_ + extern f_status_t controller_dynamic_rip_nulless_terminated(const f_string_static_t source, const f_string_range_t range, f_string_dynamic_t *destination) F_attribute_visibility_internal_d; +#endif // _di_controller_string_dynamic_rip_nulless_terminated_ + +/** + * Append a string and then add a NULL after the end of the string. + * + * @param source + * The string to copy from. + * @param destination + * The string to copy to. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_string_dynamic_append_nulless(). + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * + * @see f_string_dynamic_append_nulless() + * @see f_string_dynamic_terminate_after() + */ +#ifndef _di_controller_string_dynamic_append_terminated_ + extern f_status_t controller_dynamic_append_terminated(const f_string_static_t from, f_string_dynamic_t *destination) F_attribute_visibility_internal_d; +#endif // _di_controller_string_dynamic_append_terminated_ + +/** + * Append given range from within a string and then add a NULL after the end of the string. + * + * @param from + * The string to copy from. + * @param range + * The range within the from string to copy. + * @param destination + * The string to copy to. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_string_dynamic_append(). + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * + * @see f_string_dynamic_append() + * @see f_string_dynamic_terminate_after() + */ +#ifndef _di_controller_string_dynamic_partial_append_terminated_ + extern f_status_t controller_dynamic_partial_append_terminated(const f_string_static_t from, const f_string_range_t range, f_string_dynamic_t *destination) F_attribute_visibility_internal_d; +#endif // _di_controller_string_dynamic_partial_append_terminated_ + +/** + * Load a file from the controller settings directory. + * + * @param global + * The global data. + * @param required + * If TRUE, the file is required to exist and will throw an error if not found. + * If FALSE, the file is not required to exist and will return without error if not found. + * @param path_prefix + * The path prefix, such as 'entries' from '/etc/controller/entries/default.entry'. + * @param path_name + * The path name, such as 'default' from '/etc/controller/entries/default.entry'. + * @param path_suffix + * The path suffix, such as 'entry' from '/etc/controller/entries/default.entry'. + * @param path_prefix_length + * The length of the prefix path. + * @param path_suffix_length + * The length of the suffix path. + * @param cache + * The following within the cache is updated: + * - name_file: The partial path of the file is inserted. + * - buffer_file: The contents of the file is inserted. + * - timestamp: This is updated to reflect the last changed timestamp. + * + * @return + * F_none on success. + * F_file_found_not if required is FALSE and the file is not found. + * + * Errors (with error bit) from: f_file_stat(). + * Errors (with error bit) from: f_file_stream_open(). + * Errors (with error bit) from: f_file_stream_read(). + * Errors (with error bit) from: f_string_append(). + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * + * @see f_file_stat() + * @see f_file_stream_open() + * @see f_file_stream_read() + * @see f_string_append() + * @see f_string_dynamic_terminate_after() + */ +#ifndef _di_controller_file_load_ + extern f_status_t controller_file_load(const controller_global_t global, const bool required, const f_string_t path_prefix, const f_string_static_t path_name, const f_string_t path_suffix, const f_array_length_t path_prefix_length, const f_array_length_t path_suffix_length, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_file_load_ + +/** + * Create the pid file, if possible. + * + * @param pid + * The PID (process id). + * @param path + * The file path to the pid file to create. + * + * @return + * F_none on success. + * F_access_denied if pid file is not created due to access denied errors. + * F_directory_not if pid file is not created due to a parent directory is unavailable or invalid. + * + * Errors (with error bit) from: f_directory_exists(). + * Errors (with error bit) from: f_file_name_directory(). + * Errors (with error bit) from: f_file_stream_open(). + * + * @see f_directory_exists() + * @see f_file_name_directory() + * @see f_file_stream_open() + */ +#ifndef _di_controller_file_pid_create_ + f_status_t controller_file_pid_create(const pid_t pid, const f_string_static_t path) F_attribute_visibility_internal_d; +#endif // _di_controller_file_pid_create_ + +/** + * Delete the pid file, if exists and is valid. + * + * This is meant to be called on exit and avoids checking status codes, returning void. + * + * @param pid + * The PID (process id). + * @param path + * The file path to the pid file to create. + * + * @return + * F_none on success. + * + * F_number_not (with error bit) if the number from the pid file doesn't match the expected pid. + * + * Errors (with error bit) from: f_file_stream_close(). + * Errors (with error bit) from: f_file_stream_open(). + * Errors (with error bit) from: f_file_stream_read(). + * Errors (with error bit) from: fl_conversion_string_to_decimal_unsigned() + */ +#ifndef _di_controller_file_pid_delete_ + f_status_t controller_file_pid_delete(const pid_t pid, const f_string_static_t path) F_attribute_visibility_internal_d; +#endif // _di_controller_file_pid_delete_ + +/** + * Read the PID from a PID file. + * + * @param path + * The file path to the pid file to create. + * @param pid + * The PID to be read. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_file_stream_close(). + * Errors (with error bit) from: f_file_stream_open(). + * Errors (with error bit) from: f_file_stream_read(). + * Errors (with error bit) from: fl_conversion_string_to_decimal_unsigned() + */ +#ifndef _di_controller_file_pid_read_ + f_status_t controller_file_pid_read(const f_string_static_t path, pid_t * const pid) F_attribute_visibility_internal_d; +#endif // _di_controller_file_pid_read_ + +/** + * Convert the string from a string representation of an ID or a user name into the numeric representation of that ID or user name. + * + * @param buffer + * A string containing user name or ID. + * @param range + * The range within the buffer specifically containing the name or ID. + * @param cache + * The cache. + * @param id + * The determined user ID. + * + * @return + * F_none on success. + * F_exist_not (with error bit) if failed to match the name to an ID. + * F_number_too_large (with error bit) if the given ID is too large. + * + * Errors (with error bit) from: f_account_id_user_by_name(). + * Errors (with error bit) from: fl_conversion_string_to_number_unsigned(). + * + * @see f_account_id_user_by_name() + * @see fl_conversion_string_to_number_unsigned() + */ +#ifndef _di_controller_get_id_user_ + f_status_t controller_get_id_user(const f_string_static_t buffer, const f_string_range_t range, controller_cache_t * const cache, uid_t * const id) F_attribute_visibility_internal_d; +#endif // _di_controller_get_id_user_ + +/** + * Convert the string from a string representation of an ID or a group name into the numeric representation of that ID or group name. + * + * @param buffer + * A string containing group name or ID. + * @param range + * The range within the buffer specifically containing the name or ID. + * @param cache + * The cache. + * @param id + * The determined group ID. + * + * @return + * F_none on success. + * F_exist_not (with error bit) if failed to match the name to an ID. + * F_number_too_large (with error bit) if the given ID is too large. + * + * Errors (with error bit) from: f_account_id_group_by_name(). + * Errors (with error bit) from: fl_conversion_string_to_number_unsigned(). + * + * @see f_account_id_group_by_name() + * @see fl_conversion_string_to_number_unsigned() + */ +#ifndef _di_controller_get_id_group_ + f_status_t controller_get_id_group(const f_string_static_t buffer, const f_string_range_t range, controller_cache_t * const cache, gid_t * const id) F_attribute_visibility_internal_d; +#endif // _di_controller_get_id_group_ + +/** + * Perform all activities requiring the state to be "ready". + * + * This prints messages on errors. + * + * This does not do any locking or unlocking for the setting data, be sure to lock appropriately before and after calling this. + * + * @param global + * The global data. + * @param cache + * The cache. + * @param is_entry + * If TRUE, then this operate as an entry. + * If FALSE, then this operate as an exit. + * + * @return + * F_none on success. + * + * Errors from controller_file_pid_create() are not returned, unless it is a memory error. + * + * @see controller_file_pid_create() + */ +#ifndef _di_controller_perform_ready_ + extern f_status_t controller_perform_ready(const controller_global_t *global, controller_cache_t * const cache, const bool is_entry) F_attribute_visibility_internal_d; +#endif // _di_controller_perform_ready_ + +/** + * Given a wide range of status codes (that are errors), simplify them down to a small subset. + * + * @param status + * The status code (without the error bit set) to simplify. + * + * @return + * A subset of status codes with error bit. + */ +#ifndef _di_controller_status_simplify_error_ + extern f_status_t controller_status_simplify_error(const f_status_t status) F_attribute_visibility_internal_d; +#endif // _di_controller_status_simplify_error_ + +/** + * Get the current time, plus the given offset. + * + * @todo this is basic enough that there needs to be an f_time class with this function f_time_now(), f_time_future(), f_time_past(). + * "struct timespec" -> f_time_nano_t, "struct timeval" -> f_time_micro_t. + * + * @param seconds + * The seconds to add to current time. + * @param nanoseconds + * The nanoseconds to add to current time. + * @param time + * The resulting current time. + */ +#ifndef _di_controller_time_ + void controller_time(const time_t seconds, const long nanoseconds, struct timespec *time) F_attribute_visibility_internal_d; +#endif // _di_controller_time_ + +/** + * Convert milliseconds to nanoseconds. + * + * @param milliseconds + * The number of milliseconds. + * + * @return + * A time structure suitable for passing to nanosleep() and similar functions. + * + * @see nanosleep() + */ +#ifndef _di_controller_time_milliseconds_ + extern struct timespec controller_time_milliseconds(const f_number_unsigned_t milliseconds) F_attribute_visibility_internal_d; +#endif // _di_controller_time_milliseconds_ + +/** + * Convert seconds to nanoseconds. + * + * @param seconds + * The number of seconds. + * + * @return + * A time structure suitable for passing to nanosleep() and similar functions. + * + * @see nanosleep() + */ +#ifndef _di_controller_time_seconds_ + extern struct timespec controller_time_seconds(const f_number_unsigned_t seconds) F_attribute_visibility_internal_d; +#endif // _di_controller_time_seconds_ + +/** + * Sleep for a given number of nanoseconds. + * + * @param main + * The main program data. + * @param setting + * The settings. + * @param time + * The number of nanoseconds to sleep. + * + * @return + * The result of nanosleep(). + * + * @see nanosleep() + */ +#ifndef _di_controller_time_sleep_nanoseconds_ + extern int controller_time_sleep_nanoseconds(controller_main_t * const main, controller_setting_t * const setting, struct timespec time) F_attribute_visibility_internal_d; +#endif // _di_controller_time_sleep_nanoseconds_ + +/** + * Validate that the given string is a valid environment variable name. + * + * A valid environment variable name must begin with an alpha-character or an underscore. + * Every character after that may be alphanumeric or underscore. + * All other characters, including Unicode characters, are invalid. + * + * @param name + * The string to validate. + * + * @return + * F_true on valid. + * F_false on invalid. + * F_none if there is no string to validate (used = 0). + * + * Errors (with error bit) from: f_utf_is_alpha(). + * Errors (with error bit) from: f_utf_is_alpha_digit(). + * + * @see f_utf_is_alpha() + * @see f_utf_is_alpha_digit() + */ +#ifndef _di_controller_validate_define_name_ + extern f_status_t controller_validate_environment_name(const f_string_static_t name) F_attribute_visibility_internal_d; +#endif // _di_controller_validate_define_name_ + +/** + * Validate that the given string has at least one graph character. + * + * @param name + * The string to validate. + * + * @return + * F_true on valid. + * F_false on invalid. + * F_none if there is no string to validate (used = 0). + * + * Errors (with error bit) from: f_utf_is_graph(). + * + * @see f_utf_is_graph() + */ +#ifndef _di_controller_validate_has_graph_ + extern f_status_t controller_validate_has_graph(const f_string_static_t name) F_attribute_visibility_internal_d; +#endif // _di_controller_validate_has_graph_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_controller_h diff --git a/level_3/controller/c/controller/private-controller_print.c b/level_3/controller/c/controller/private-controller_print.c new file mode 100644 index 0000000..7646f03 --- /dev/null +++ b/level_3/controller/c/controller/private-controller_print.c @@ -0,0 +1,68 @@ +#include "controller.h" +#include "../common/private-common.h" +#include "private-controller_print.h" +#include "../lock/private-lock_print.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_print_error_ + void controller_print_error(controller_thread_t * const thread, const fl_print_t print, const f_status_t status, const f_string_t function, const bool fallback) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + if (status == F_interrupt) return; + + // fll_error_print() automatically locks, so manually handle only the mutex locking and flushing rather than calling controller_lock_print(). + if (thread) { + f_thread_mutex_lock(&thread->lock.print); + } + + fll_error_print(print, status, function, fallback); + + if (thread) { + f_thread_mutex_unlock(&thread->lock.print); + } + } +#endif // _di_controller_print_error_ + +#ifndef _di_controller_print_error_file_ + void controller_print_error_file(controller_thread_t * const thread, const fl_print_t print, const f_status_t status, const f_string_t function, const bool fallback, const f_string_t name, const f_string_t operation, const uint8_t type) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + if (status == F_interrupt) return; + + // fll_error_print() automatically locks, so manually handle only the mutex locking and flushing rather than calling controller_lock_print(). + if (thread) { + f_thread_mutex_lock(&thread->lock.print); + } + + fll_error_file_print(print, status, function, fallback, name, operation, type); + + if (thread) { + f_thread_mutex_unlock(&thread->lock.print); + } + } +#endif // _di_controller_print_error_file_ + +#ifndef _di_controller_print_signal_received_ + void controller_print_signal_received(controller_main_t * const main, const f_status_t signal) { + + if (main->warning.verbosity != f_console_verbosity_verbose_e) return; + + // Must flush and reset color because the interrupt may have interrupted the middle of a print function. + fflush(main->warning.to.stream); + + flockfile(main->warning.to.stream); + + fl_print_format("%]%c%c%[Received signal code %]", main->warning.to.stream, main->context.set.reset, f_string_eol_s[0], f_string_eol_s[0], main->context.set.warning, main->context.set.warning); + fl_print_format("%[%i%]", main->warning.to.stream, main->context.set.notable, signal, main->context.set.notable); + fl_print_format("%[.%]%c", main->warning.to.stream, main->context.set.warning, main->context.set.warning, f_string_eol_s[0]); + + funlockfile(main->warning.to.stream); + } +#endif // _di_controller_print_signal_received_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/controller/private-controller_print.h b/level_3/controller/c/controller/private-controller_print.h new file mode 100644 index 0000000..bbfd80e --- /dev/null +++ b/level_3/controller/c/controller/private-controller_print.h @@ -0,0 +1,83 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_controller_print_h +#define _PRIVATE_controller_print_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Print the error, locking the print mutex during the print. + * + * @param thread + * (optional) The thread data. + * Set to NULL to disable locking on the thread (this should be done only if the lock is already in place). + * @param print + * Designates how printing is to be performed. + * @param status + * The status code to process. + * Make sure this has F_status_set_fine() called if the status code has any error or warning bits. + * @param function + * The name of the function where the error happened. + * Set to 0 to disable. + * @param fallback + * Set to F_true to print the fallback error message for unknown errors. + * + * @see fll_error_print() + */ +#ifndef _di_controller_print_error_ + extern void controller_print_error(controller_thread_t * const thread, const fl_print_t print, const f_status_t status, const f_string_t function, const bool fallback) F_attribute_visibility_internal_d; +#endif // _di_controller_print_error_ + +/** + * Print the file error, locking the print mutex during the print. + * + * @param thread + * (optional) The thread data. + * Set to NULL to disable locking on the thread (this should be done only if the lock is already in place). + * @param print + * Designates how printing is to be performed. + * @param status + * The status code to process. + * Make sure this has F_status_set_fine() called if the status code has any error or warning bits. + * @param function + * The name of the function where the error happened. + * Set to 0 to disable. + * @param fallback + * Set to F_true to print the fallback error message for unknown errors. + * @param name + * The name of the file or directory. + * @param operation + * The operation that fails, such as 'create' or 'access'. + * @param type + * A valid file type code from the fll_error_file_type enum. + * + * @see fll_error_file_print() + */ +#ifndef _di_controller_print_error_file_ + extern void controller_print_error_file(controller_thread_t * const thread, const fl_print_t print, const f_status_t status, const f_string_t function, const bool fallback, const f_string_t name, const f_string_t operation, const uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_print_error_file_ + +/** + * Print a message about a process signal being recieved, such as an interrupt signal. + * + * @param main + * The main program data. + * @param signal + * The signal received. + */ +#ifndef _di_controller_print_signal_received_ + extern void controller_print_signal_received(controller_main_t * const main, const f_status_t signal) F_attribute_visibility_internal_d; +#endif // _di_controller_print_signal_received_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_controller_print_h diff --git a/level_3/controller/c/entry/private-entry.c b/level_3/controller/c/entry/private-entry.c new file mode 100644 index 0000000..4b16437 --- /dev/null +++ b/level_3/controller/c/entry/private-entry.c @@ -0,0 +1,2279 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../controller/private-controller.h" +#include "../controller/private-controller_print.h" +#include "../entry/private-entry.h" +#include "../entry/private-entry_print.h" +#include "../lock/private-lock.h" +#include "../lock/private-lock_print.h" +#include "../rule/private-rule.h" +#include "../thread/private-thread.h" +#include "../thread/private-thread_process.h" +#include "../thread/private-thread_signal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_entry_action_type_is_rule_ + f_status_t controller_entry_action_type_is_rule(uint8_t type) { + + switch (type) { + case controller_entry_action_type_freeze_e: + case controller_entry_action_type_kill_e: + case controller_entry_action_type_pause_e: + case controller_entry_action_type_reload_e: + case controller_entry_action_type_restart_e: + case controller_entry_action_type_resume_e: + case controller_entry_action_type_start_e: + case controller_entry_action_type_stop_e: + case controller_entry_action_type_thaw_e: + return F_true; + } + + return F_false; + } +#endif // _di_controller_entry_action_type_is_rule_ + +#ifndef _di_controller_entry_action_type_name_ + f_string_static_t controller_entry_action_type_name(const uint8_t type) { + + f_string_static_t buffer = f_string_static_t_initialize; + + switch (type) { + case controller_entry_action_type_consider_e: + buffer.string = controller_consider_s; + buffer.used = controller_consider_s_length; + break; + + case controller_entry_action_type_execute_e: + buffer.string = controller_execute_s; + buffer.used = controller_execute_s_length; + break; + + case controller_entry_action_type_failsafe_e: + buffer.string = controller_failsafe_s; + buffer.used = controller_failsafe_s_length; + break; + + case controller_entry_action_type_freeze_e: + buffer.string = controller_freeze_s; + buffer.used = controller_freeze_s_length; + break; + + case controller_entry_action_type_item_e: + buffer.string = controller_item_s; + buffer.used = controller_item_s_length; + break; + + case controller_entry_action_type_kill_e: + buffer.string = controller_kill_s; + buffer.used = controller_kill_s_length; + break; + + case controller_entry_action_type_pause_e: + buffer.string = controller_pause_s; + buffer.used = controller_pause_s_length; + break; + + case controller_entry_action_type_ready_e: + buffer.string = controller_ready_s; + buffer.used = controller_ready_s_length; + break; + + case controller_entry_action_type_reload_e: + buffer.string = controller_reload_s; + buffer.used = controller_reload_s_length; + break; + + case controller_entry_action_type_restart_e: + buffer.string = controller_restart_s; + buffer.used = controller_restart_s_length; + break; + + case controller_entry_action_type_resume_e: + buffer.string = controller_resume_s; + buffer.used = controller_resume_s_length; + break; + + case controller_entry_action_type_start_e: + buffer.string = controller_start_s; + buffer.used = controller_start_s_length; + break; + + case controller_entry_action_type_stop_e: + buffer.string = controller_stop_s; + buffer.used = controller_stop_s_length; + break; + + case controller_entry_action_type_thaw_e: + buffer.string = controller_thaw_s; + buffer.used = controller_thaw_s_length; + break; + + case controller_entry_action_type_timeout_e: + buffer.string = controller_timeout_s; + buffer.used = controller_timeout_s_length; + break; + } + + buffer.size = buffer.used; + + return buffer; + } +#endif // _di_controller_entry_action_type_name_ + +#ifndef _di_controller_entry_action_type_to_rule_action_type_ + uint8_t controller_entry_action_type_to_rule_action_type(uint8_t type) { + + switch (type) { + case controller_entry_action_type_freeze_e: + return controller_rule_action_type_freeze_e; + + case controller_entry_action_type_kill_e: + return controller_rule_action_type_kill_e; + + case controller_entry_action_type_pause_e: + return controller_rule_action_type_pause_e; + + case controller_entry_action_type_reload_e: + return controller_rule_action_type_reload_e; + + case controller_entry_action_type_restart_e: + return controller_rule_action_type_restart_e; + + case controller_entry_action_type_resume_e: + return controller_rule_action_type_resume_e; + + case controller_entry_action_type_start_e: + return controller_rule_action_type_start_e; + + case controller_entry_action_type_stop_e: + return controller_rule_action_type_stop_e; + + case controller_entry_action_type_thaw_e: + return controller_rule_action_type_thaw_e; + } + + return 0; + } +#endif // _di_controller_entry_action_type_to_rule_action_type_ + +#ifndef _di_controller_entry_actions_read_ + f_status_t controller_entry_actions_read(const controller_global_t global, const bool is_entry, const f_string_range_t content_range, controller_cache_t * const cache, controller_entry_actions_t *actions) { + + f_status_t status = F_none; + f_status_t status_action = F_none; + + actions->used = 0; + + cache->object_actions.used = cache->object_actions.size; + + while (cache->object_actions.used) { + + cache->object_actions.array[--cache->object_actions.used].start = 1; + cache->object_actions.array[cache->object_actions.used].stop = 0; + } // while + + cache->content_actions.used = cache->content_actions.size; + + while (cache->content_actions.used) { + cache->content_actions.array[--cache->content_actions.used].used = 0; + } // while + + { + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_entry, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + f_string_range_t range = content_range; + + status = fll_fss_extended_read(cache->buffer_file, state, &range, &cache->object_actions, &cache->content_actions, 0, 0, &cache->delimits, 0); + } + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fll_fss_extended_read", F_true, global.thread); + + return status; + } + + status = fl_fss_apply_delimit(cache->delimits, &cache->buffer_file); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fl_fss_apply_delimit", F_true, global.thread); + + return status; + } + + cache->delimits.used = 0; + + status = controller_entry_actions_increase_by(cache->object_actions.used, actions); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_entry_actions_increase_by", F_true, global.thread); + + return status; + } + + controller_entry_action_t *action = 0; + + f_array_length_t allocate = 0; + f_array_length_t at_least = 0; + f_array_length_t at_most = 0; + + f_array_length_t i = 0; + f_array_length_t j = 0; + + for (; i < cache->object_actions.used; ++i) { + + cache->action.line_action = 0; + cache->action.name_action.used = 0; + + action = &actions->array[actions->used]; + action->type = 0; + action->code = 0; + action->line = 0; + action->number = 0; + action->status = F_known_not; + action->parameters.used = 0; + + status = f_fss_count_lines(cache->buffer_file, cache->object_actions.array[i].start, &cache->action.line_action); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_fss_count_lines", F_true, global.thread); + break; + } + + action->line = ++cache->action.line_action; + + status = controller_dynamic_rip_nulless_terminated(cache->buffer_file, cache->object_actions.array[i], &cache->action.name_action); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_dynamic_rip_nulless_terminated", F_true, global.thread); + break; + } + + if (fl_string_dynamic_compare_string(controller_consider_s, cache->action.name_action, controller_consider_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_consider_e; + } + else if (fl_string_dynamic_compare_string(controller_execute_s, cache->action.name_action, controller_execute_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_execute_e; + } + else if (fl_string_dynamic_compare_string(controller_failsafe_s, cache->action.name_action, controller_failsafe_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_failsafe_e; + } + else if (fl_string_dynamic_compare_string(controller_freeze_s, cache->action.name_action, controller_freeze_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_freeze_e; + } + else if (fl_string_dynamic_compare_string(controller_item_s, cache->action.name_action, controller_item_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_item_e; + } + else if (fl_string_dynamic_compare_string(controller_kill_s, cache->action.name_action, controller_kill_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_kill_e; + } + else if (fl_string_dynamic_compare_string(controller_pause_s, cache->action.name_action, controller_pause_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_pause_e; + } + else if (fl_string_dynamic_compare_string(controller_ready_s, cache->action.name_action, controller_ready_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_ready_e; + } + else if (fl_string_dynamic_compare_string(controller_reload_s, cache->action.name_action, controller_reload_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_reload_e; + } + else if (fl_string_dynamic_compare_string(controller_restart_s, cache->action.name_action, controller_restart_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_restart_e; + } + else if (fl_string_dynamic_compare_string(controller_resume_s, cache->action.name_action, controller_resume_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_resume_e; + } + else if (fl_string_dynamic_compare_string(controller_start_s, cache->action.name_action, controller_start_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_start_e; + } + else if (fl_string_dynamic_compare_string(controller_stop_s, cache->action.name_action, controller_stop_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_stop_e; + } + else if (fl_string_dynamic_compare_string(controller_thaw_s, cache->action.name_action, controller_thaw_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_thaw_e; + } + else if (fl_string_dynamic_compare_string(controller_timeout_s, cache->action.name_action, controller_timeout_s_length) == F_equal_to) { + actions->array[actions->used].type = controller_entry_action_type_timeout_e; + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SUnknown %s item action '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->warning.context); + fl_print_format("%[%S%]", global.main->warning.to.stream, global.main->warning.notable, cache->action.name_action, global.main->warning.notable); + fl_print_format("%['.%]%c", global.main->warning.to.stream, global.main->warning.context, global.main->warning.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->warning, cache->action); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + + continue; + } + + if (action->type == controller_entry_action_type_consider_e || controller_entry_action_type_is_rule(action->type)) { + allocate = cache->content_actions.array[i].used; + at_least = 2; + at_most = allocate; + } + else if (action->type == controller_entry_action_type_execute_e) { + allocate = cache->content_actions.array[i].used; + at_least = 1; + at_most = allocate; + } + else if (action->type == controller_entry_action_type_failsafe_e || action->type == controller_entry_action_type_item_e) { + allocate = 1; + at_least = 1; + at_most = 1; + } + else if (action->type == controller_entry_action_type_timeout_e) { + allocate = 2; + at_least = 2; + at_most = 2; + } + else if (action->type == controller_entry_action_type_ready_e) { + allocate = 1; + at_least = 0; + at_most = 1; + } + + if (cache->content_actions.array[i].used < at_least || cache->content_actions.array[i].used > at_most) { + action->status = F_status_set_error(F_parameter); + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + f_thread_mutex_lock(&global.thread->lock.print); + + flockfile(global.main->error.to.stream); + + fl_print_format("%c%[%SThe %s item action '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, cache->action.name_action, global.main->error.notable); + fl_print_format("%[' requires ", global.main->error.to.stream, global.main->error.context); + + if (at_least == at_most) { + f_print_terminated("exactly ", global.main->error.to.stream); + } + + fl_print_format("%]%[%un%]", global.main->error.to.stream, global.main->error.context, global.main->error.notable, at_least, global.main->error.notable); + + if (action->type == controller_entry_action_type_consider_e || controller_entry_action_type_is_rule(action->type)) { + fl_print_format("%[ or more parameters.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + } + else { + if (at_least == at_most) { + fl_print_format("%[ parameters.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + } + else { + fl_print_format("%[ to %]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%un%]", global.main->error.to.stream, global.main->error.notable, at_most, global.main->error.notable); + fl_print_format("%[ parameters.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + } + } + + funlockfile(global.main->error.to.stream); + + f_thread_mutex_unlock(&global.thread->lock.print); + } + } + else { + action->status = F_none; + } + + if (F_status_is_error(action->status)) { + if (F_status_is_error_not(status_action)) { + status_action = action->status; + } + + continue; + } + + if (allocate) { + status = f_string_dynamics_increase_by(allocate, &action->parameters); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamics_increase_by", F_true, global.thread); + + action->status = status; + + if (F_status_is_error_not(status_action)) { + status_action = status; + } + + break; + } + + for (j = 0; j < allocate && j < cache->content_actions.array[i].used; ++j) { + + action->parameters.array[j].used = 0; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_file, cache->content_actions.array[i].array[j], &action->parameters.array[j]); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, global.thread); + + action->status = status; + + if (F_status_is_error_not(status_action)) { + status_action = status; + } + + break; + } + + ++action->parameters.used; + } // for + + if (F_status_is_error_not(action->status)) { + if (action->type == controller_entry_action_type_consider_e || controller_entry_action_type_is_rule(action->type)) { + if (action->parameters.array[0].used) { + + // Force the path to be canonical (removing all '../' parts). + status = fll_path_canonical(action->parameters.array[0].string, &cache->buffer_path); + + if (F_status_is_error(status)) { + // @todo instead call: fll_error_file_print(). + // fll_error_file_print(main->error, F_status_set_fine(status), "fll_path_canonical", F_true, arguments->argv[location], "verify", fll_error_file_type_path_e); + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fll_path_canonical", F_true, global.thread); + + action->status = status; + + if (F_status_set_fine(status) == F_memory_not) { + status_action = status; + break; + } + + if (F_status_is_error_not(status_action)) { + status_action = status; + } + } + } + else { + action->status = F_status_set_error(F_parameter); + + if (F_status_is_error_not(status_action)) { + status_action = action->status; + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + fll_print_format("%c%[%SThe %s item action must not have an empty string for a path (the first parameter).%]%c", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context, f_string_eol_s[0]); + } + } + + if (action->parameters.array[1].used) { + cache->buffer_path.used = 0; + + status = f_file_name_base(action->parameters.array[1].string, action->parameters.array[1].used, &cache->buffer_path); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_file_name_base", F_true, global.thread); + + if (F_status_set_fine(status) == F_memory_not) { + status_action = status; + break; + } + + action->status = status; + + if (F_status_is_error_not(status_action)) { + status_action = status; + } + } + else { + if (fl_string_dynamic_compare(action->parameters.array[1], cache->buffer_path) == F_equal_to_not) { + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + status = f_string_dynamic_terminate_after(&cache->buffer_path); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, global.thread); + + action->status = status; + + if (F_status_set_fine(status) == F_memory_not) { + status_action = status; + } + + break; + } + + flockfile(global.main->error.to.stream); + + fl_print_format("%c%[%SThe %s item action second parameter '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, action->parameters.array[1], global.main->error.notable); + fl_print_format("%[' must be a base path name, such as '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_path, global.main->error.notable); + fl_print_format("%['.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + funlockfile(global.main->error.to.stream); + } + + action->status = F_status_set_error(F_parameter); + + if (F_status_is_error_not(status_action)) { + status_action = action->status; + } + } + } + } + else { + action->status = F_status_set_error(F_parameter); + + if (F_status_is_error_not(status_action)) { + status_action = action->status; + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + fll_print_format("%c%[%SThe %s item action must not have an empty string for a rule name (the second parameter).%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context, f_string_eol_s[0]); + } + } + + for (j = 2; j < action->parameters.used; ++j) { + + if (fl_string_dynamic_compare_string(controller_asynchronous_s, action->parameters.array[j], controller_asynchronous_s_length) == F_equal_to) { + action->code |= controller_entry_rule_code_asynchronous_d; + } + else if (fl_string_dynamic_compare_string(controller_require_s, action->parameters.array[j], controller_require_s_length) == F_equal_to) { + action->code |= controller_entry_rule_code_require_d; + } + else if (fl_string_dynamic_compare_string(controller_wait_s, action->parameters.array[j], controller_wait_s_length) == F_equal_to) { + action->code |= controller_entry_rule_code_wait_d; + } + else { + if (action->status == F_none) { + action->status = F_status_set_error(F_supported_not); + + if (F_status_is_error_not(status_action)) { + status_action = action->status; + } + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + flockfile(global.main->error.to.stream); + + fl_print_format("%c%[%SThe %s item action third parameter (and beyond) must be one of '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_asynchronous_s, global.main->error.notable); + fl_print_format("%[', '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_require_s, global.main->error.notable); + fl_print_format("%[', or '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_wait_s, global.main->error.notable); + fl_print_format("%[' but instead has '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, action->parameters.array[j], global.main->error.notable); + fl_print_format("%['.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + funlockfile(global.main->error.to.stream); + } + } + } // for + } + else if (action->type == controller_entry_action_type_failsafe_e || action->type == controller_entry_action_type_item_e) { + if (fl_string_dynamic_compare_string(controller_main_s, action->parameters.array[0], controller_main_s_length) == F_equal_to) { + action->status = F_status_set_error(F_supported_not); + + if (F_status_is_error_not(status_action)) { + status_action = action->status; + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + flockfile(global.main->error.to.stream); + + fl_print_format("%c%[%SThe %s item action may not specify the reserved item '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_main_s, global.main->error.notable); + fl_print_format("%['.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + funlockfile(global.main->error.to.stream); + } + } + } + else if (action->type == controller_entry_action_type_timeout_e) { + + if (fl_string_dynamic_compare_string(controller_kill_s, action->parameters.array[0], controller_kill_s_length) == F_equal_to) { + action->code = controller_entry_timeout_code_kill_d; + } + else if (fl_string_dynamic_compare_string(controller_start_s, action->parameters.array[0], controller_start_s_length) == F_equal_to) { + action->code = controller_entry_timeout_code_start_d; + } + else if (fl_string_dynamic_compare_string(controller_stop_s, action->parameters.array[0], controller_stop_s_length) == F_equal_to) { + action->code = controller_entry_timeout_code_stop_d; + } + else { + action->status = F_status_set_error(F_supported_not); + + if (F_status_is_error_not(status_action)) { + status_action = action->status; + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + flockfile(global.main->error.to.stream); + + fl_print_format("%c%[%SThe %s item action must have one of '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_kill_s, global.main->error.notable); + fl_print_format("%[', '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_start_s, global.main->error.notable); + fl_print_format("%[', or '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_stop_s, global.main->error.notable); + fl_print_format("%[' but instead has '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, action->parameters.array[0], global.main->error.notable); + fl_print_format("%['.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + funlockfile(global.main->error.to.stream); + } + } + + if (action->status == F_none) { + const f_string_range_t range = macro_f_string_range_t_initialize(action->parameters.array[1].used); + + status = fl_conversion_string_to_number_unsigned(action->parameters.array[1].string, range, &action->number); + + if (F_status_is_error(status) || status == F_data_not) { + action->number = 0; + + if (status == F_data_not) { + action->status = F_status_set_error(F_number); + } + else { + action->status = controller_status_simplify_error(F_status_set_fine(status)); + } + + if (F_status_set_fine(status) == F_memory_not) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fl_conversion_string_to_number_unsigned", F_true, global.thread); + + status_action = status; + break; + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + flockfile(global.main->error.to.stream); + + fl_print_format("%c%[%SThe %s item action parameter '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, action->parameters.array[1], global.main->error.notable); + fl_print_format("%[' is not a valid supported number.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + funlockfile(global.main->error.to.stream); + } + } + } + } + else if (action->type == controller_entry_action_type_ready_e) { + if (action->parameters.used) { + if (fl_string_dynamic_compare_string(controller_wait_s, action->parameters.array[0], controller_wait_s_length) == F_equal_to) { + action->code |= controller_entry_rule_code_wait_d; + } + else { + action->status = F_status_set_error(F_supported_not); + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + flockfile(global.main->error.to.stream); + + fl_print_format("%c%[%SThe %s item action may only have '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_wait_s, global.main->error.notable); + fl_print_format("%[' but instead has '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, action->parameters.array[0], global.main->error.notable); + fl_print_format("%['.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + funlockfile(global.main->error.to.stream); + } + } + } + } + } + } + + ++actions->used; + } // for + + if (F_status_is_error(status_action)) { + return status_action; + } + + return status; + } +#endif // _di_controller_entry_actions_read_ + +#ifndef _di_controller_entry_preprocess_ + f_status_t controller_entry_preprocess(const controller_global_t global, const bool is_entry, controller_cache_t * const cache) { + + f_status_t status = F_none; + f_status_t status2 = F_none; + + f_array_length_t i = 0; + f_array_length_t j = 0; + + f_array_length_t at_i = 0; + f_array_length_t at_j = 1; + + controller_entry_t *entry = is_entry ? &global.setting->entry : &global.setting->exit; + controller_entry_actions_t *actions = 0; + + uint8_t error_has = F_false; + + // This effectively sets the read for an entry and resets the ready for an exit. + // @todo should there be a ready_exit instead? + // @todo the global.setting->ready in this function may need mutex lock protection. + // @todo disconnect the socket file if applicable. + global.setting->ready = controller_setting_ready_no_e; + + cache->ats.used = 0; + + cache->action.line_action = 0; + cache->action.line_item = 0; + cache->action.name_action.used = 0; + cache->action.name_item.used = 0; + + macro_f_array_lengths_t_increase_by(status, cache->ats, controller_common_allocation_small_d) + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "macro_f_array_lengths_t_increase_by", F_true, global.thread); + + return status; + } + + // Utilize the ats cache as an item execution stack (at_i is for item index, and at_j (at_i + 1) is for action index). + cache->ats.array[0] = 0; + cache->ats.array[1] = 0; + cache->ats.used = 2; + + cache->action.line_item = entry->items.array[0].line; + cache->action.name_item.used = 0; + + status = controller_dynamic_append_terminated(entry->items.array[0].name, &cache->action.name_item); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_dynamic_append_terminated", F_true, global.thread); + + return status; + } + + while (controller_thread_is_enabled(is_entry, global.thread)) { + + actions = &entry->items.array[cache->ats.array[at_i]].actions; + + for (; cache->ats.array[at_j] < actions->used && controller_thread_is_enabled(is_entry, global.thread); ++cache->ats.array[at_j]) { + + cache->action.line_action = actions->array[cache->ats.array[at_j]].line; + cache->action.name_action.used = 0; + + status2 = controller_dynamic_append_terminated(controller_entry_action_type_name(actions->array[cache->ats.array[at_j]].type), &cache->action.name_action); + + if (F_status_is_error(status2)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status2), "controller_dynamic_append_terminated", F_true, global.thread); + + return status2; + } + + if (actions->array[cache->ats.array[at_j]].type == controller_entry_action_type_ready_e) { + + if (global.setting->ready == controller_setting_ready_wait_e) { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SMultiple '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, global.main->warning.context); + fl_print_format("%[%s%]", global.main->warning.to.stream, global.main->warning.notable, controller_ready_s, global.main->warning.notable); + fl_print_format("%[' %s item actions detected; only the first will be used.%]%c", global.main->warning.to.stream, global.main->warning.context, is_entry ? controller_entry_s : controller_exit_s, global.main->warning.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->warning, cache->action); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + } + else { + global.setting->ready = controller_setting_ready_wait_e; + } + } + else if (actions->array[cache->ats.array[at_j]].type == controller_entry_action_type_item_e) { + error_has = F_false; + + // "main" is not allowed to be used for an "item" and "setting" is not an executable "item". + if (fl_string_dynamic_compare_string(controller_main_s, actions->array[cache->ats.array[at_j]].parameters.array[0], controller_main_s_length) == F_equal_to) { + continue; + } + else if (fl_string_dynamic_compare_string(controller_setting_s, actions->array[cache->ats.array[at_j]].parameters.array[0], controller_setting_s_length) == F_equal_to) { + continue; + } + + // Walk though each items and check to see if the item actually exists. + for (i = 1; i < entry->items.used && controller_thread_is_enabled(is_entry, global.thread); ++i) { + + if (fl_string_dynamic_compare(entry->items.array[i].name, actions->array[cache->ats.array[at_j]].parameters.array[0]) == F_equal_to) { + + // Check to see if "i" is already in the stack (to prevent recursion) (skipping main). + for (j = 2; j < cache->ats.used; j += 2) { + + if (cache->ats.array[j] == i) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SThe %s item named '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, is_entry ? controller_entry_s : controller_exit_s, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, entry->items.array[i].name, global.main->error.notable); + fl_print_format("%[' cannot be executed because recursion is not allowed.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->error, cache->action); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status)) { + status = F_status_set_error(F_recurse); + } + + error_has = F_true; + break; + } + } // for + + if (error_has) break; + + macro_f_array_lengths_t_increase_by(status2, cache->ats, controller_common_allocation_small_d) + + if (F_status_is_error(status2)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status2), "macro_f_array_lengths_t_increase_by", F_true, global.thread); + + return status2; + } + + // Save the value so to avoid string comparison during normal operation. + actions->array[cache->ats.array[at_j]].number = i; + + // Continue into the requested item. + at_i = cache->ats.used; + at_j = cache->ats.used + 1; + + cache->ats.array[at_i] = i; + cache->ats.array[at_j] = 0; + cache->ats.used += 2; + + cache->action.name_action.used = 0; + cache->action.line_action = 0; + + cache->action.name_item.used = 0; + cache->action.line_item = entry->items.array[i].line; + + status2 = controller_dynamic_append_terminated(entry->items.array[i].name, &cache->action.name_item); + + if (F_status_is_error(status2)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status2), "controller_dynamic_append_terminated", F_true, global.thread); + + return status2; + } + + break; + } + } // for + + if (error_has || i >= entry->items.used) { + if (i >= entry->items.used) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SThe %s item named '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, is_entry ? controller_entry_s : controller_exit_s, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, actions->array[cache->ats.array[at_j]].parameters.array[0], global.main->error.notable); + fl_print_format("%[' does not exist.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->error, cache->action); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status)) { + status = F_status_set_error(F_valid_not); + } + } + } + else { + break; + } + } + } // for + + cache->action.line_action = 0; + cache->action.name_action.used = 0; + + // End of actions found, so drop to previous loop in stack. + if (cache->ats.array[at_j] == actions->used) { + + // All actions for "main" are processed so there is nothing left to do. + if (at_i == 0) break; + + at_i -= 2; + at_j -= 2; + + cache->ats.used -= 2; + ++cache->ats.array[at_j]; + + cache->action.line_item = entry->items.array[cache->ats.array[at_i]].line; + cache->action.name_item.used = 0; + + status2 = controller_dynamic_append_terminated(entry->items.array[cache->ats.array[at_i]].name, &cache->action.name_item); + + if (F_status_is_error(status2)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status2), "controller_dynamic_append_terminated", F_true, global.thread); + + return status2; + } + } + } // while + + if (!controller_thread_is_enabled(is_entry, global.thread)) { + return F_status_set_error(F_interrupt); + } + + // If ready was never found in the entry, then default to always ready. + if (global.setting->ready == controller_setting_ready_no_e) { + global.setting->ready = controller_setting_ready_yes_e; + } + + return status; + } +#endif // _di_controller_entry_preprocess_ + +#ifndef _di_controller_entry_process_ + f_status_t controller_entry_process(const controller_global_t *global, controller_cache_t * const cache, const bool failsafe, const bool is_entry) { + + f_status_t status = F_none; + f_status_t status_lock = F_none; + + f_array_length_t i = 0; + f_array_length_t j = 0; + + f_array_length_t at_i = 0; + f_array_length_t at_j = 1; + + uint8_t options_force = 0; + uint8_t options_process = 0; + + controller_entry_t *entry = is_entry ? &global->setting->entry : &global->setting->exit; + controller_entry_action_t *entry_action = 0; + controller_entry_actions_t *entry_actions = 0; + controller_process_t *process = 0; + + // an empty stack is used here because each rule here is the first rule run in the rule's scope. + const f_array_lengths_t stack = f_array_lengths_t_initialize; + + cache->ats.used = 0; + cache->stack.used = 0; + + cache->action.line_action = 0; + cache->action.line_item = 0; + cache->action.name_action.used = 0; + cache->action.name_item.used = 0; + + macro_f_array_lengths_t_increase_by(status, cache->ats, controller_common_allocation_small_d) + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global->main->error, cache->action, F_status_set_fine(status), "macro_f_array_lengths_t_increase_by", F_true, global->thread); + + return status; + } + + // utilize the ats cache as an item execution stack (at_i is for item index, and at_j (at_i + 1) is for action index). + cache->ats.array[0] = failsafe ? global->setting->failsafe_item_id : 0; + cache->ats.array[1] = 0; + cache->ats.used = 2; + + cache->action.line_item = entry->items.array[cache->ats.array[0]].line; + cache->action.name_item.used = 0; + + status = controller_dynamic_append_terminated(entry->items.array[cache->ats.array[0]].name, &cache->action.name_item); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global->main->error, cache->action, F_status_set_fine(status), "controller_dynamic_append_terminated", F_true, global->thread); + + return status; + } + + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e || global->main->error.verbosity == f_console_verbosity_verbose_e || global->main->error.verbosity == f_console_verbosity_debug_e) { + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%cProcessing %s%s item '", global->main->output.to.stream, f_string_eol_s[0], failsafe ? "failsafe " : "", is_entry ? controller_entry_s : controller_exit_s); + fl_print_format("%[%Q%]'.%c", global->main->output.to.stream, global->main->context.set.notable, cache->action.name_item, global->main->context.set.notable, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + + // The pre-process determines if ready is explicitly specified within the entry file and if it is not start as ready. + if (global->setting->ready == controller_setting_ready_yes_e) { + status = controller_perform_ready(global, cache, is_entry); + if (F_status_is_error(status)) return status; + } + + while (controller_thread_is_enabled(is_entry, global->thread)) { + + entry_actions = &entry->items.array[cache->ats.array[at_i]].actions; + + for (; cache->ats.array[at_j] < entry_actions->used && controller_thread_is_enabled(is_entry, global->thread); ++cache->ats.array[at_j]) { + + entry_action = &entry_actions->array[cache->ats.array[at_j]]; + + cache->action.line_action = entry_action->line; + cache->action.name_action.used = 0; + + status = controller_dynamic_append_terminated(controller_entry_action_type_name(entry_action->type), &cache->action.name_action); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global->main->error, cache->action, F_status_set_fine(status), "controller_dynamic_append_terminated", F_true, global->thread); + + return status; + } + + if (F_status_is_error(entry_action->status)) { + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e) { + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%cThe %s item action '", global->main->output.to.stream, f_string_eol_s[0], is_entry ? controller_entry_s : controller_exit_s); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.title, cache->action.name_action, global->main->context.set.title); + + if (entry_action->parameters.used) { + fl_print_format(" %[", global->main->output.to.stream, global->main->context.set.notable); + + controller_entry_action_parameters_print(global->main->output.to.stream, *entry_action); + + fl_print_format("%]", global->main->output.to.stream, global->main->context.set.notable); + } + + fl_print_format("' is %[%s%] and is in a ", global->main->output.to.stream, global->main->context.set.notable, entry_action->code & controller_entry_rule_code_require_d ? "required" : "optional", global->main->context.set.notable); + + fl_print_format("%[failed%] state, skipping.%c", global->main->output.to.stream, global->main->context.set.notable, global->main->context.set.notable, global->main->context.set.notable, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + else { + if ((entry_action->code & controller_entry_rule_code_require_d) && global->main->error.verbosity != f_console_verbosity_quiet_e || !(entry_action->code & controller_entry_rule_code_require_d) && (global->main->warning.verbosity == f_console_verbosity_verbose_e || global->main->warning.verbosity == f_console_verbosity_debug_e)) { + fl_print_t *output = 0; + + if (entry_action->code & controller_entry_rule_code_require_d) { + output = &global->main->error; + } + else { + output = &global->main->warning; + } + + controller_lock_print(output->to, global->thread); + + fl_print_format("%c%[%SThe %s item action '%]", output->to.stream, f_string_eol_s[0], output->context, output->prefix ? output->prefix : f_string_empty_s, is_entry ? controller_entry_s : controller_exit_s, output->context); + fl_print_format("%[%Q%]", output->to.stream, output->notable, cache->action.name_action, output->notable); + + + if (entry_action->parameters.used) { + fl_print_format(" %[", output->to.stream, global->main->context.set.notable); + + controller_entry_action_parameters_print(output->to.stream, *entry_action); + + fl_print_format("%]", output->to.stream, global->main->context.set.notable); + } + + if (entry_action->code & controller_entry_rule_code_require_d) { + fl_print_format("%[' is%] %[required%]", output->to.stream, output->context, output->context, output->notable, output->notable); + } + else { + fl_print_format("%[' is%] %[optional%]", output->to.stream, output->context, output->context, output->notable, output->notable); + } + + fl_print_format(" %[and is in a%] %[failed%]", output->to.stream, output->context, output->context, output->notable, output->notable); + + if (entry_action->code & controller_entry_rule_code_require_d) { + fl_print_format(" %[state, aborting.%]%c", output->to.stream, output->context, output->context, f_string_eol_s[0]); + } + else { + fl_print_format(" %[state, skipping.%]%c", output->to.stream, output->context, output->context, f_string_eol_s[0]); + } + + controller_entry_print_error_cache(is_entry, *output, cache->action); + + controller_unlock_print_flush(output->to, global->thread); + } + + if (controller_entry_action_type_is_rule(entry_action->type) && entry_action->code & controller_entry_rule_code_require_d) { + return F_status_is_error(F_require); + } + } + + continue; + } + + if (entry_action->type == controller_entry_action_type_ready_e) { + if ((entry_action->code & controller_entry_rule_code_wait_d) || global->setting->ready == controller_setting_ready_wait_e) { + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e || global->main->error.verbosity == f_console_verbosity_verbose_e || global->main->error.verbosity == f_console_verbosity_debug_e || entry->show == controller_entry_show_init_e) { + if (global->main->output.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%cWaiting before processing %s item action '", global->main->output.to.stream, f_string_eol_s[0], is_entry ? controller_entry_s : controller_exit_s); + fl_print_format("%[%s%]", global->main->output.to.stream, global->main->context.set.title, controller_ready_s, global->main->context.set.title); + fl_print_format("'.%c", global->main->output.to.stream, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + + if (global->main->parameters[controller_parameter_validate_e].result == f_console_result_none_e) { + status = controller_rule_wait_all(*global, is_entry, F_false, process); + if (F_status_is_error(status)) return status; + } + } + + if (global->setting->ready == controller_setting_ready_yes_e) { + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e || global->main->error.verbosity == f_console_verbosity_verbose_e || global->main->error.verbosity == f_console_verbosity_debug_e) { + if (global->main->output.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%cIgnoring %s item action '", global->main->output.to.stream, f_string_eol_s[0], is_entry ? controller_entry_s : controller_exit_s); + fl_print_format("%[%s%]", global->main->output.to.stream, global->main->context.set.title, controller_ready_s, global->main->context.set.title); + fl_print_format("', state already is ready.%c", global->main->output.to.stream, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + } + else { + if (!failsafe && (global->main->error.verbosity == f_console_verbosity_verbose_e || entry->show == controller_entry_show_init_e) && global->main->parameters[controller_parameter_simulate_e].result == f_console_result_none_e) { + fl_print_format("%cState is now '%[%s%]'.%c", global->main->output.to.stream, f_string_eol_s[0], global->main->context.set.notable, controller_ready_s, global->main->context.set.notable, f_string_eol_s[0]); + } + + status = controller_perform_ready(global, cache, is_entry); + if (F_status_is_error(status)) return status; + } + } + else if (entry_action->type == controller_entry_action_type_item_e) { + if (entry_action->number == 0 || entry_action->number >= entry->items.used || failsafe && entry_action->number == global->setting->failsafe_item_id) { + + // This should not happen if the pre-process is working as intended, but in case it doesn't, return a critical error to prevent infinite recursion and similar errors. + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->error.to, global->thread); + + fl_print_format("%c%[Invalid %s item index '%]", global->main->error.to.stream, f_string_eol_s[0], global->main->error.context, is_entry ? controller_entry_s : controller_exit_s, global->main->error.context); + fl_print_format("%[%un%]", global->main->error.to.stream, global->main->error.notable, entry_action->number, global->main->error.notable); + fl_print_format("%[' detected.%]%c", global->main->error.to.stream, global->main->error.context, global->main->error.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global->main->error, cache->action); + + controller_unlock_print_flush(global->main->error.to, global->thread); + } + + return F_status_is_error(F_critical); + } + + macro_f_array_lengths_t_increase_by(status, cache->ats, controller_common_allocation_small_d) + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global->main->error, cache->action, F_status_set_fine(status), "macro_f_array_lengths_t_increase_by", F_true, global->thread); + + return status; + } + + // continue into the requested item. + cache->ats.array[cache->ats.used] = entry_action->number; + cache->ats.array[cache->ats.used + 1] = 0; + + at_i = cache->ats.used; + at_j = cache->ats.used + 1; + + cache->ats.used += 2; + + cache->action.name_action.used = 0; + cache->action.line_action = 0; + + cache->action.name_item.used = 0; + cache->action.line_item = entry->items.array[cache->ats.array[at_i]].line; + + status = controller_dynamic_append_terminated(entry->items.array[cache->ats.array[at_i]].name, &cache->action.name_item); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global->main->error, cache->action, F_status_set_fine(status), "controller_dynamic_append_terminated", F_true, global->thread); + + return status; + } + + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e || global->main->error.verbosity == f_console_verbosity_verbose_e || global->main->error.verbosity == f_console_verbosity_debug_e) { + if (global->main->output.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%cProcessing %s item '", global->main->output.to.stream, f_string_eol_s[0], is_entry ? controller_entry_s : controller_exit_s); + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.title, cache->action.name_item, global->main->context.set.title); + fl_print_format("'.%c", global->main->output.to.stream, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + + // Exit inner loop to force restarting and start processing the requested item. + break; + } + else if (entry_action->type == controller_entry_action_type_consider_e || controller_entry_action_type_is_rule(entry_action->type)) { + status_lock = controller_lock_write(is_entry, global->thread, &global->thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global->main->error, F_status_set_fine(status_lock), F_false, global->thread); + + break; + } + + status = controller_rules_increase(&global->setting->rules); + + f_thread_unlock(&global->thread->lock.rule); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global->main->error, cache->action, F_status_set_fine(status), "controller_rules_increase", F_true, global->thread); + + return status; + } + + const f_array_length_t id_rule_length = entry_action->parameters.array[0].used + entry_action->parameters.array[1].used + 1; + char id_rule_name[id_rule_length + 1]; + const f_string_static_t alias_rule = macro_f_string_static_t_initialize(id_rule_name, id_rule_length); + + memcpy(id_rule_name, entry_action->parameters.array[0].string, entry_action->parameters.array[0].used); + memcpy(id_rule_name + entry_action->parameters.array[0].used + 1, entry_action->parameters.array[1].string, entry_action->parameters.array[1].used); + + id_rule_name[entry_action->parameters.array[0].used] = f_path_separator_s[0]; + id_rule_name[id_rule_length] = 0; + + status_lock = controller_lock_read(is_entry, global->thread, &global->thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global->main->error, F_status_set_fine(status_lock), F_true, global->thread); + + break; + } + + status = controller_rule_find(alias_rule, global->setting->rules, 0); + + f_thread_unlock(&global->thread->lock.rule); + + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e || global->main->error.verbosity == f_console_verbosity_verbose_e || global->main->error.verbosity == f_console_verbosity_debug_e || (entry->show == controller_entry_show_init_e && entry_action->type != controller_entry_action_type_consider_e)) { + if (global->main->output.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%c%s %s item rule ", global->main->output.to.stream, f_string_eol_s[0], entry_action->type == controller_entry_action_type_consider_e ? "Considering" : "Processing", is_entry ? controller_entry_s : controller_exit_s); + fl_print_format("'%[%Q%]'", global->main->output.to.stream, global->main->context.set.title, alias_rule, global->main->context.set.title); + + if (entry->show == controller_entry_show_init_e && global->main->parameters[controller_parameter_simulate_e].result == f_console_result_none_e) { + fl_print_format(" [%[%s%]]", global->main->output.to.stream, global->main->context.set.notable, entry_action->code == controller_entry_rule_code_asynchronous_d ? controller_asynchronous_s : controller_synchronous_s, global->main->context.set.notable); + + if (entry_action->code == controller_entry_rule_code_wait_d) { + fl_print_format(" [%[%s%]]", global->main->output.to.stream, global->main->context.set.notable, controller_wait_s, global->main->context.set.notable); + } + + if (entry_action->code == controller_entry_rule_code_require_d) { + fl_print_format(" [%[%s%]]", global->main->output.to.stream, global->main->context.set.notable, controller_required_s, global->main->context.set.notable); + } + } + + fl_print_format(".%c", global->main->output.to.stream, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + + if (!controller_thread_is_enabled(is_entry, global->thread)) break; + + // The rule is not yet loaded, ensure that it is loaded. + if (status != F_true) { + + // Rule execution will re-use the existing cache, so save the current cache. + const f_array_length_t cache_line_action = cache->action.line_action; + const f_array_length_t cache_line_item = cache->action.line_item; + + const f_array_length_t cache_name_action_used = cache->action.name_action.used; + const f_array_length_t cache_name_item_used = cache->action.name_item.used; + const f_array_length_t cache_name_file_used = cache->action.name_file.used; + + char cache_name_action[cache_name_action_used]; + char cache_name_item[cache_name_item_used]; + char cache_name_file[cache_name_file_used]; + + memcpy(cache_name_action, cache->action.name_action.string, cache->action.name_action.used); + memcpy(cache_name_item, cache->action.name_item.string, cache->action.name_item.used); + memcpy(cache_name_file, cache->action.name_file.string, cache->action.name_file.used); + + status_lock = controller_lock_write(is_entry, global->thread, &global->thread->lock.rule); + + if (F_status_is_fine(status_lock)) { + status = controller_rule_read(*global, is_entry, alias_rule, cache, entry, &global->setting->rules.array[global->setting->rules.used]); + } + + // Restore cache. + memcpy(cache->action.name_action.string, cache_name_action, cache_name_action_used); + memcpy(cache->action.name_item.string, cache_name_item, cache_name_item_used); + memcpy(cache->action.name_file.string, cache_name_file, cache_name_file_used); + + cache->action.name_action.string[cache_name_action_used] = 0; + cache->action.name_item.string[cache_name_item_used] = 0; + cache->action.name_file.string[cache_name_file_used] = 0; + + cache->action.name_action.used = cache_name_action_used; + cache->action.name_item.used = cache_name_item_used; + cache->action.name_file.used = cache_name_file_used; + + cache->action.line_action = cache_line_action; + cache->action.line_item = cache_line_item; + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global->main->error, F_status_set_fine(status_lock), F_false, global->thread); + break; + } + + if (F_status_set_fine(status) == F_interrupt || !controller_thread_is_enabled(is_entry, global->thread)) { + f_thread_unlock(&global->thread->lock.rule); + + break; + } + + if (F_status_is_error(status)) { + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->error.to, global->thread); + + controller_entry_print_error_cache(is_entry, global->main->error, cache->action); + + controller_unlock_print_flush(global->main->error.to, global->thread); + } + + // Designate the action as failed. + entry_action->status = F_status_set_error(F_failure); + + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_none_e) { + f_thread_unlock(&global->thread->lock.rule); + + if (entry_action->code & controller_entry_rule_code_require_d) { + return F_status_set_error(F_require); + } + + ++cache->ats.array[at_j]; + break; + } + } + else { + ++global->setting->rules.used; + } + + f_thread_unlock(&global->thread->lock.rule); + } + + if (F_status_is_error_not(status)) { + options_force = 0; + options_process = 0; + + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e) { + options_process |= controller_process_option_simulate_d; + } + + if (entry_action->code & controller_entry_rule_code_require_d) { + options_process |= controller_process_option_require_d; + } + + if (entry_action->code & controller_entry_rule_code_wait_d) { + options_process |= controller_process_option_wait_d; + } + + if (global->main->parameters[controller_parameter_validate_e].result == f_console_result_found_e) { + options_process |= controller_process_option_validate_d; + } + + if (entry_action->code & controller_entry_rule_code_asynchronous_d) { + if (global->main->parameters[controller_parameter_validate_e].result == f_console_result_none_e) { + options_force |= controller_process_option_asynchronous_d; + } + + options_process |= controller_process_option_asynchronous_d; + } + + status = controller_rule_process_begin(*global, options_force, alias_rule, controller_entry_action_type_to_rule_action_type(entry_action->type), options_process, is_entry ? controller_process_type_entry_e : controller_process_type_exit_e, stack, *cache); + + if (F_status_set_fine(status) == F_memory_not || status == F_child || F_status_set_fine(status) == F_interrupt) { + break; + } + + if (F_status_is_error(status) && global->main->parameters[controller_parameter_simulate_e].result == f_console_result_none_e && (entry_action->code & controller_entry_rule_code_require_d)) { + return F_status_set_error(F_require); + } + } + } + else if (entry_action->type == controller_entry_action_type_execute_e) { + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e || global->main->error.verbosity == f_console_verbosity_verbose_e || global->main->error.verbosity == f_console_verbosity_debug_e || entry->show == controller_entry_show_init_e) { + if (global->main->output.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%c%s is executing '", global->main->output.to.stream, f_string_eol_s[0], is_entry ? controller_entry_s : controller_exit_s); + + for (f_array_length_t k = 0; k < entry_action->parameters.used; ++k) { + + fl_print_format("%[%Q%]", global->main->output.to.stream, global->main->context.set.title, entry_action->parameters.array[k], global->main->context.set.title); + + if (k + 1 < entry_action->parameters.used) { + f_print_character(f_string_space_s[0], global->main->output.to.stream); + } + } // for + + fl_print_format("'.%c", global->main->output.to.stream, f_string_eol_s[0]); + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + } + + if (global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e) { + return F_execute; + } + + controller_thread_process_cancel(*global, is_entry, is_entry ? controller_thread_cancel_execute_e : controller_thread_cancel_exit_execute_e, process); + + int result = 0; + int option = FL_execute_parameter_option_path_d; + + if (entry->session == controller_entry_session_new_e) { + option |= FL_execute_parameter_option_session_d; + } + + status = fll_execute_into(0, entry_action->parameters, option, 0, (void *) &result); + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_file_found_not) { + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->error.to, global->thread); + + fl_print_format("%c%[%SExecution failed, unable to find program or script '%]", global->main->error.to.stream, f_string_eol_s[0], global->main->error.context, global->main->error.prefix ? global->main->error.prefix : f_string_empty_s, global->main->error.context); + fl_print_format("%[%Q%]", global->main->error.to.stream, global->main->error.notable, entry_action->parameters.array[0], global->main->error.notable); + fl_print_format("%['.%]%c", global->main->error.to.stream, global->main->error.context, global->main->error.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global->main->error, cache->action); + + controller_unlock_print_flush(global->main->error.to, global->thread); + } + } + else { + controller_entry_print_error(is_entry, global->main->error, cache->action, F_status_set_fine(status), "fll_execute_into", F_true, global->thread); + } + + return F_status_set_error(F_execute); + } + else if (result != 0) { + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->error.to, global->thread); + + fl_print_format("%c%[%SExecution failed with return value of '%]", global->main->error.to.stream, f_string_eol_s[0], global->main->error.context, global->main->error.prefix ? global->main->error.prefix : f_string_empty_s, global->main->error.context); + fl_print_format("%[%i%]", global->main->error.to.stream, global->main->error.notable, result, global->main->error.notable); + fl_print_format("$['.%]%c", global->main->error.to.stream, global->main->error.context, global->main->error.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global->main->error, cache->action); + + controller_unlock_print_flush(global->main->error.to, global->thread); + } + + return F_status_set_error(F_execute); + } + + return F_execute; + } + else if (entry_action->type == controller_entry_action_type_timeout_e) { + const f_string_t suffix = " MegaTime (milliseconds)"; + + if (entry_action->code == controller_entry_timeout_code_kill_d) { + entry->timeout_kill = entry_action->number; + + controller_entry_preprocess_print_simulate_setting_value(*global, is_entry, controller_timeout_s, controller_kill_s, entry->items.array[global->setting->failsafe_item_id].name, suffix); + } + else if (entry_action->code == controller_entry_timeout_code_start_d) { + entry->timeout_start = entry_action->number; + + controller_entry_preprocess_print_simulate_setting_value(*global, is_entry, controller_timeout_s, controller_start_s, entry->items.array[global->setting->failsafe_item_id].name, suffix); + } + else if (entry_action->code == controller_entry_timeout_code_stop_d) { + entry->timeout_stop = entry_action->number; + + controller_entry_preprocess_print_simulate_setting_value(*global, is_entry, controller_timeout_s, controller_stop_s, entry->items.array[global->setting->failsafe_item_id].name, suffix); + } + } + else if (entry_action->type == controller_entry_action_type_failsafe_e) { + + if (failsafe) { + if (global->main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global->main->warning.to, global->thread); + + fl_print_format("%c%[%SFailsafe may not be specified when running in failsafe, ignoring.%]%c", global->main->warning.to.stream, f_string_eol_s[0], global->main->warning.context, global->main->warning.prefix, global->main->warning.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global->main->warning, cache->action); + + controller_unlock_print_flush(global->main->warning.to, global->thread); + } + } + else { + if (entry_action->number == 0 || entry_action->number >= entry->items.used) { + + // This should not happen if the pre-process is working as designed, but in case it doesn't, return a critical error to prevent infinite recursion and similar errors. + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global->main->error.to, global->thread); + + fl_print_format("%c%[%SInvalid %s item index '%]", global->main->error.to.stream, f_string_eol_s[0], global->main->error.context, global->main->error.prefix ? global->main->error.prefix : f_string_empty_s, is_entry ? controller_entry_s : controller_exit_s, global->main->error.context); + fl_print_format("%[%un%]", global->main->error.to.stream, global->main->error.notable, entry_action->number, global->main->error.notable); + fl_print_format("%[' detected.%]%c", global->main->error.to.stream, global->main->error.context, global->main->error.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global->main->error, cache->action); + + controller_unlock_print_flush(global->main->error.to, global->thread); + } + + return F_status_is_error(F_critical); + } + else { + global->setting->failsafe_enabled = F_true; + global->setting->failsafe_item_id = entry_action->number; + + controller_entry_preprocess_print_simulate_setting_value(*global, is_entry, controller_failsafe_s, 0, entry->items.array[global->setting->failsafe_item_id].name, 0); + } + } + } + } // for + + if (status == F_child || F_status_set_fine(status) == F_interrupt) break; + + cache->action.line_action = 0; + cache->action.name_action.used = 0; + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_memory_not || F_status_set_fine(status) == F_require) { + break; + } + } + + // End of actions found, so drop to previous loop in stack. + if (cache->ats.array[at_j] == entry_actions->used) { + + // All actions for "main" are processed so there is nothing left to do. + if (at_i == 0) break; + + at_i -= 2; + at_j -= 2; + + cache->ats.used -= 2; + ++cache->ats.array[at_j]; + + cache->action.line_item = entry->items.array[cache->ats.array[at_i]].line; + cache->action.name_item.used = 0; + + status = controller_dynamic_append_terminated(entry->items.array[cache->ats.array[at_i]].name, &cache->action.name_item); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global->main->error, cache->action, F_status_set_fine(status), "controller_dynamic_append_terminated", F_true, global->thread); + + break; + } + } + } // while + + if (!controller_thread_is_enabled(is_entry, global->thread)) { + return F_status_set_error(F_interrupt); + } + + if (status == F_child) { + return status; + } + + if (F_status_is_error(status_lock)) { + return status_lock; + } + + // Check to see if any required processes failed, but do not do this if already operating in failsafe. + if (F_status_is_error_not(status) && !failsafe && global->main->parameters[controller_parameter_validate_e].result == f_console_result_none_e) { + const f_status_t status_wait = controller_rule_wait_all(*global, is_entry, F_true, 0); + + if (F_status_is_error(status_wait)) { + return status_wait; + } + + if (status_wait == F_require) { + return F_status_set_error(F_require); + } + } + + if ((global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e && global->main->error.verbosity != f_console_verbosity_quiet_e) || global->main->error.verbosity == f_console_verbosity_verbose_e) { + controller_lock_print(global->main->output.to, global->thread); + + fl_print_format("%cDone processing %s item '", global->main->output.to.stream, f_string_eol_s[0], is_entry ? controller_entry_s : controller_exit_s); + fl_print_format("%[%s%]", global->main->output.to.stream, global->main->context.set.title, controller_main_s, global->main->context.set.title); + fl_print_format("'.%c", global->main->output.to.stream, f_string_eol_s[0]); + + // failsafe should not print the extra newline because the failure exit from controller_main should handle this. + if (!failsafe) { + f_print_terminated(f_string_eol_s, global->main->output.to.stream); + } + + controller_unlock_print_flush(global->main->output.to, global->thread); + } + + return status; + } +#endif // _di_controller_entry_process_ + +#ifndef _di_controller_entry_read_ + f_status_t controller_entry_read(const controller_global_t global, const bool is_entry, controller_cache_t * const cache) { + + f_status_t status = F_none; + + controller_entry_t *entry = is_entry ? &global.setting->entry : &global.setting->exit; + + entry->status = F_known_not; + entry->items.used = 0; + + if (global.main->as_init) { + entry->session = controller_entry_session_new_e; + } + else { + entry->session = controller_entry_session_same_e; + } + + cache->action.line_action = 0; + cache->action.line_item = 0; + + macro_f_time_spec_t_clear(cache->timestamp); + + cache->comments.used = 0; + cache->delimits.used = 0; + + cache->content_action.used = 0; + + { + f_array_length_t i = 0; + + for (; i < cache->content_actions.used; ++i) { + cache->content_actions.array[i].used = 0; + } // for + + for (i = 0; i < cache->content_items.used; ++i) { + cache->content_items.array[i].used = 0; + } // for + } + + cache->content_actions.used = 0; + cache->content_items.used = 0; + + cache->object_actions.used = 0; + cache->object_items.used = 0; + + cache->buffer_file.used = 0; + cache->buffer_path.used = 0; + + cache->action.name_file.used = 0; + cache->action.name_action.used = 0; + cache->action.name_item.used = 0; + + if (is_entry) { + status = controller_file_load(global, F_true, controller_entries_s, global.setting->name_entry, controller_entry_s, controller_entries_s_length, controller_entry_s_length, cache); + } + else { + status = controller_file_load(global, F_false, controller_exits_s, global.setting->name_entry, controller_exit_s, controller_exits_s_length, controller_exit_s_length, cache); + + if (status == F_file_found_not) { + return F_file_found_not; + } + } + + if (F_status_is_error_not(status)) { + if (cache->buffer_file.used) { + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_entry, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + f_string_range_t range = macro_f_string_range_t_initialize(cache->buffer_file.used); + + status = fll_fss_basic_list_read(cache->buffer_file, state, &range, &cache->object_items, &cache->content_items, &cache->delimits, 0, &cache->comments); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fll_fss_basic_list_read", F_true); + } + else { + status = fl_fss_apply_delimit(cache->delimits, &cache->buffer_file); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fl_fss_apply_delimit", F_true, global.thread); + } + } + } + else { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fll_print_format("%c%[%SThe %s file is empty.%]%c", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : is_entry ? controller_entry_s : controller_exit_s, global.main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + status = F_status_set_error(F_data_not); + } + } + + if (F_status_is_error_not(status) && cache->object_items.used) { + status = controller_entry_items_increase_by(cache->object_items.used, &entry->items); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_entry_items_increase_by", F_true, global.thread); + } + else { + + // 0x1 = main found, 0x2 = found existing. + uint8_t code = 0; + + f_string_range_t *range = 0; + + f_array_length_t at = 0; + f_array_length_t i = 0; + f_array_length_t j = 0; + + for (; i < cache->object_items.used && controller_thread_is_enabled(is_entry, global.thread); ++i) { + + if (code & 0x2) { + code -= 0x2; + } + + at = 0; + range = 0; + + cache->action.line_action = 0; + cache->action.line_item = 0; + + cache->comments.used = 0; + cache->delimits.used = 0; + + cache->content_action.used = 0; + cache->content_actions.used = 0; + + cache->object_actions.used = 0; + + cache->buffer_path.used = 0; + + cache->action.name_action.used = 0; + cache->action.name_item.used = 0; + + status = controller_entry_items_increase_by(controller_common_allocation_small_d, &entry->items); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_entry_items_increase_by", F_true, global.thread); + break; + } + + status = controller_dynamic_partial_append_terminated(cache->buffer_file, cache->object_items.array[i], &cache->action.name_item); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_dynamic_partial_append_terminated", F_true, global.thread); + break; + } + + status = f_fss_count_lines(cache->buffer_file, cache->object_items.array[i].start, &cache->action.line_item); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_fss_count_lines", F_true, global.thread); + break; + } + + ++cache->action.line_item; + + for (j = (code & 0x1) ? 1 : 0; j < entry->items.used; ++j) { + + if (fl_string_dynamic_compare(entry->items.array[j].name, cache->action.name_item) == F_equal_to) { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SIgnoring duplicate %s item '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->warning.context); + fl_print_format("%[%Q%]", global.main->warning.to.stream, global.main->warning.notable, cache->action.name_file, global.main->warning.notable); + fl_print_format("%['.%]%c", global.main->warning.to.stream, global.main->warning.context, global.main->warning.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->warning, cache->action); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + + code |= 0x2; + break; + } + } // for + + if (code & 0x2) continue; + + range = &cache->content_items.array[i].array[0]; + + if (fl_string_dynamic_compare_string(controller_main_s, cache->action.name_item, controller_main_s_length) == F_equal_to) { + code |= 0x1; + + at = 0; + + if (!entry->items.used) { + entry->items.used = 1; + } + } + else if (fl_string_dynamic_compare_string(controller_setting_s, cache->action.name_item, controller_setting_s_length) == F_equal_to) { + status = controller_entry_settings_read(global, is_entry, *range, cache); + + continue; + } + else if (entry->items.used) { + at = entry->items.used++; + } + else { + + // skip position 0, which is reserved for "main". + entry->items.array[0].name.used = 0; + + at = 1; + entry->items.used = 2; + } + + entry->items.array[at].line = cache->action.line_item; + + status = controller_dynamic_append_terminated(cache->action.name_item, &entry->items.array[at].name); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_dynamic_append_terminated", F_true); + + break; + } + + status = controller_entry_actions_read(global, is_entry, *range, cache, &entry->items.array[at].actions); + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) != F_interrupt) { + controller_lock_print(global.main->error.to, global.thread); + + controller_entry_print_error_cache(is_entry, global.main->error, cache->action); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_set_fine(status) == F_memory_not) { + break; + } + } + } // for + + if (is_entry && F_status_set_fine(status) == F_interrupt) { + return status; + } + + if (F_status_is_error_not(status)) { + cache->action.name_action.used = 0; + cache->action.name_item.used = 0; + + if (!(code & 0x1)) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SThe required %s item '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_main_s, global.main->error.notable); + fl_print_format("%[' was not found.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + status = F_status_set_error(F_found_not); + } + + if (F_status_is_error_not(status)) { + controller_entry_action_t *action = 0; + + f_array_length_t k = 0; + + // 0x1 = missing or not, 0x2 = one or more missing. + uint8_t missing = 0; + + for (i = 0; i < entry->items.used; ++i) { + + for (j = 0; j < entry->items.array[i].actions.used; ++j) { + + if (!controller_thread_is_enabled(is_entry, global.thread)) { + return F_status_set_error(F_interrupt); + } + + action = &entry->items.array[i].actions.array[j]; + + // only process actions that don't already have an error. + if (F_status_is_error(action->status)) continue; + + if (action->type == controller_entry_action_type_failsafe_e || action->type == controller_entry_action_type_item_e) { + missing |= 0x1; + + for (k = 0; k < entry->items.used; ++k) { + + if (fl_string_dynamic_compare(action->parameters.array[0], entry->items.array[k].name) == F_equal_to) { + if (missing & 0x1) { + missing -= 0x1; + } + + break; + } + } // for + + if (missing & 0x1) { + missing |= 0x2; + + cache->action.line_action = action->line; + cache->action.line_item = entry->items.array[i].line; + + status = controller_dynamic_append_terminated(entry->items.array[i].name, &cache->action.name_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_dynamic_append_terminated", F_true); + + break; + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SThe required %s item '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, action->parameters.array[0], global.main->error.notable); + fl_print_format("%[' does not exist.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->error, cache->action); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + action->number = 0; + action->status = controller_status_simplify_error(F_found_not); + + cache->action.name_action.used = 0; + cache->action.name_item.used = 0; + } + else { + action->number = k; + } + } + } // for + } // for + } + } + } + } + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) != F_interrupt) { + controller_entry_print_error_cache(is_entry, global.main->error, cache->action); + } + + entry->status = controller_status_simplify_error(F_status_set_fine(status)); + } + else { + entry->status = F_none; + } + + return entry->status; + } +#endif // _di_controller_entry_read_ + +#ifndef _di_controller_entry_settings_read_ + f_status_t controller_entry_settings_read(const controller_global_t global, const bool is_entry, const f_string_range_t content_range, controller_cache_t * const cache) { + + f_status_t status = F_none; + + { + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_entry, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + f_string_range_t range = content_range; + + status = fll_fss_extended_read(cache->buffer_file, state, &range, &cache->object_actions, &cache->content_actions, 0, 0, &cache->delimits, 0); + } + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fll_fss_extended_read", F_true, global.thread); + + return status; + } + + status = fl_fss_apply_delimit(cache->delimits, &cache->buffer_file); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fl_fss_apply_delimit", F_true, global.thread); + + return status; + } + + cache->delimits.used = 0; + + f_array_length_t i = 0; + f_array_length_t j = 0; + f_array_length_t line = 0; + + controller_entry_t *entry = is_entry ? &global.setting->entry : &global.setting->exit; + + for (; i < cache->object_actions.used; ++i) { + + status = f_fss_count_lines(cache->buffer_file, cache->object_actions.array[i].start, &cache->action.line_action); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_fss_count_lines", F_true, global.thread); + + break; + } + + line = ++cache->action.line_action; + cache->action.name_action.used = 0; + + status = controller_dynamic_rip_nulless_terminated(cache->buffer_file, cache->object_actions.array[i], &cache->action.name_action); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_dynamic_rip_nulless_terminated", F_true, global.thread); + + break; + } + + if (is_entry && fl_string_dynamic_compare_string(controller_control_s, cache->action.name_action, controller_control_s_length) == F_equal_to) { + if (cache->content_actions.array[i].used != 1) { + controller_entry_settings_read_print_setting_requires_exactly(global, is_entry, *cache, 1); + + continue; + } + + cache->action.generic.used = 0; + global.setting->path_control.used = 0; + + status = controller_dynamic_rip_nulless_terminated(cache->buffer_file, cache->content_actions.array[i].array[0], &global.setting->path_control); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_dynamic_rip_nulless_terminated", F_true, global.thread); + + global.setting->path_control.used = 0; + + break; + } + + if (f_path_is_relative(global.setting->path_control.string, global.setting->path_control.used) == F_true) { + + // Use the PID file path for creating a relative path to the control socket. + status = f_file_name_directory(global.setting->path_pid.string, global.setting->path_pid.used, &cache->action.generic); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_file_name_directory", F_true, global.thread); + + global.setting->path_control.used = 0; + + break; + } + + status = f_string_append(f_path_separator_s, 1, &cache->action.generic); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_append", F_true, global.thread); + + global.setting->path_control.used = 0; + + break; + } + + status = f_string_dynamic_append(global.setting->path_control, &cache->action.generic); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_append", F_true, global.thread); + + global.setting->path_control.used = 0; + + break; + } + } + else { + status = f_string_dynamic_append(global.setting->path_control, &cache->action.generic); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_append", F_true, global.thread); + + global.setting->path_control.used = 0; + + break; + } + } + + status = f_string_dynamic_terminate_after(&cache->action.generic); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, global.thread); + + global.setting->path_control.used = 0; + + break; + } + + status = fll_path_canonical(cache->action.generic.string, &global.setting->path_control); + + if (F_status_is_error(status)) { + controller_entry_print_error_file(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fll_path_canonical", F_true, cache->action.generic.string, "analyze", fll_error_file_type_path_e, global.thread); + + global.setting->path_control.used = 0; + + continue; + } + } + else if (is_entry && fl_string_dynamic_compare_string(controller_control_group_s, cache->action.name_action, controller_control_group_s_length) == F_equal_to) { + gid_t number = 0; + + status = controller_get_id_group(cache->buffer_file, cache->content_actions.array[i].array[0], cache, &number); + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_exist_not) { + controller_entry_setting_read_print_error_with_range(is_entry, global.main->error, " has an invalid group", cache->content_actions.array[i].array[0], ", because no group was found by that name", global.thread, cache); + } + else if (status == F_number_too_large) { + controller_entry_setting_read_print_error_with_range(is_entry, global.main->error, " has an invalid group", cache->content_actions.array[i].array[0], ", because the given ID is too large", global.thread, cache); + } + else if (status == F_number) { + controller_entry_setting_read_print_error_with_range(is_entry, global.main->error, " has an invalid group", cache->content_actions.array[i].array[0], ", because the given ID is not a valid supported number", global.thread, cache); + } + else { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_get_id_group", F_true, global.thread); + } + + continue; + } + + global.setting->control_group = number; + } + else if (is_entry && fl_string_dynamic_compare_string(controller_control_mode_s, cache->action.name_action, controller_control_mode_s_length) == F_equal_to) { + mode_t mode = 0; + uint8_t replace = 0; + f_file_mode_t mode_file = f_file_mode_t_initialize; + + cache->action.generic.used = 0; + + status = controller_dynamic_rip_nulless_terminated(cache->buffer_file, cache->content_actions.array[i].array[0], &cache->action.generic); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_dynamic_rip_nulless_terminated", F_true, global.thread); + + break; + } + + status = f_file_mode_from_string(cache->action.generic.string, global.main->umask, &mode_file, &replace); + + if (F_status_is_error(status)) { + controller_entry_setting_read_print_error_with_range(is_entry, global.main->error, " has an unsupported mode", cache->content_actions.array[i].array[0], ", because the format is unknown or contains invalid data", global.thread, cache); + + continue; + } + + status = f_file_mode_to_mode(mode_file, &mode); + + if (F_status_is_error(status)) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "f_file_mode_to_mode", F_true, global.thread); + + continue; + } + + global.setting->control_mode = mode; + } + else if (is_entry && fl_string_dynamic_compare_string(controller_control_user_s, cache->action.name_action, controller_control_user_s_length) == F_equal_to) { + uid_t number = 0; + + status = controller_get_id_user(cache->buffer_file, cache->content_actions.array[i].array[0], cache, &number); + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_exist_not) { + controller_entry_setting_read_print_error_with_range(is_entry, global.main->error, " has an invalid user", cache->content_actions.array[i].array[0], ", because no user was found by that name", global.thread, cache); + } + else if (status == F_number_too_large) { + controller_entry_setting_read_print_error_with_range(is_entry, global.main->error, " has an invalid user", cache->content_actions.array[i].array[0], ", because the given ID is too large", global.thread, cache); + } + else if (status == F_number) { + controller_entry_setting_read_print_error_with_range(is_entry, global.main->error, " has an invalid user", cache->content_actions.array[i].array[0], ", because the given ID is not a valid supported number", global.thread, cache); + } + else { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "controller_get_id_user", F_true, global.thread); + } + + continue; + } + + global.setting->control_user = number; + } + else if (is_entry && fl_string_dynamic_compare_string(controller_mode_s, cache->action.name_action, controller_mode_s_length) == F_equal_to) { + if (cache->content_actions.array[i].used != 1) { + controller_entry_settings_read_print_setting_requires_exactly(global, is_entry, *cache, 1); + + continue; + } + + if (fl_string_dynamic_partial_compare_string(controller_service_s, cache->buffer_file, controller_service_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + global.setting->mode = controller_setting_mode_service_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_program_s, cache->buffer_file, controller_program_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + global.setting->mode = controller_setting_mode_program_e; + } + else { + controller_entry_settings_read_print_setting_unknown_action_value(global, is_entry, *cache, i); + + continue; + } + } + else if (fl_string_dynamic_compare_string(controller_pid_s, cache->action.name_action, controller_pid_s_length) == F_equal_to) { + if (cache->content_actions.array[i].used != 1) { + controller_entry_settings_read_print_setting_requires_exactly(global, is_entry, *cache, 1); + + continue; + } + + if (fl_string_dynamic_partial_compare_string(controller_disable_s, cache->buffer_file, controller_disable_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + entry->pid = controller_entry_pid_disable_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_ready_s, cache->buffer_file, controller_ready_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + entry->pid = controller_entry_pid_ready_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_require_s, cache->buffer_file, controller_require_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + entry->pid = controller_entry_pid_require_e; + } + else { + controller_entry_settings_read_print_setting_unknown_action_value(global, is_entry, *cache, i); + + continue; + } + } + else if (fl_string_dynamic_compare_string(controller_session_s, cache->action.name_action, controller_session_s_length) == F_equal_to) { + if (cache->content_actions.array[i].used != 1) { + controller_entry_settings_read_print_setting_requires_exactly(global, is_entry, *cache, 1); + + continue; + } + + if (fl_string_dynamic_partial_compare_string(controller_new_s, cache->buffer_file, controller_new_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + entry->session = controller_entry_session_new_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_same_s, cache->buffer_file, controller_same_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + entry->session = controller_entry_session_same_e; + } + else { + controller_entry_settings_read_print_setting_unknown_action_value(global, is_entry, *cache, i); + + continue; + } + } + else if (fl_string_dynamic_compare_string(controller_show_s, cache->action.name_action, controller_show_s_length) == F_equal_to) { + if (cache->content_actions.array[i].used != 1) { + controller_entry_settings_read_print_setting_requires_exactly(global, is_entry, *cache, 1); + + continue; + } + + if (fl_string_dynamic_partial_compare_string(controller_normal_s, cache->buffer_file, controller_normal_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + entry->show = controller_entry_show_normal_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_init_s, cache->buffer_file, controller_init_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + entry->show = controller_entry_show_init_e; + } + else { + controller_entry_settings_read_print_setting_unknown_action_value(global, is_entry, *cache, i); + + continue; + } + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_entry_settings_read_print_setting_unknown_action(global, is_entry, *cache); + } + + continue; + } + } // for + + return status; + } +#endif // _di_controller_entry_settings_read_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/entry/private-entry.h b/level_3/controller/c/entry/private-entry.h new file mode 100644 index 0000000..f457ba5 --- /dev/null +++ b/level_3/controller/c/entry/private-entry.h @@ -0,0 +1,229 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_entry_h +#define _PRIVATE_entry_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Determine if the type code represents a Rule type. + * + * @param type + * The type code to compare against. + * + * @return + * TRUE if Rule type. + * FALSE otherwise. + */ +#ifndef _di_controller_entry_action_type_is_rule_ + extern f_status_t controller_entry_action_type_is_rule(uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_action_type_is_rule_ + +/** + * Get a string representing the entry action type. + * + * @param type + * The entry action type code. + * + * @return + * The string with used > 0 on success. + * The string with used == 0 if no match was found. + */ +#ifndef _di_controller_entry_action_type_name_ + extern f_string_static_t controller_entry_action_type_name(const uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_action_type_name_ + +/** + * Convert the Entry Action type to Rule Action type. + * + * @param type + * The Entry Action type. + * + * @return + * TRUE if Rule type. + * FALSE otherwise. + */ +#ifndef _di_controller_entry_action_type_to_rule_action_type_ + extern uint8_t controller_entry_action_type_to_rule_action_type(uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_action_type_to_rule_action_type_ + +/** + * Read the entry list, extracting all items and values. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param content_range + * The range in the list buffer representing the content. + * @param cache + * A structure for containing and caching relevant data. + * @param actions + * The processed actions. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: controller_entry_actions_increase_by(). + * Errors (with error bit) from: f_fss_count_lines(). + * Errors (with error bit) from: fl_fss_apply_delimit(). + * Errors (with error bit) from: f_string_dynamic_partial_append_nulless(). + * Errors (with error bit) from: fl_string_dynamic_rip_nulless(). + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * Errors (with error bit) from: f_string_dynamics_increase_by(). + * Errors (with error bit) from: fll_fss_extended_read(). + * + * @see controller_entry_actions_increase_by() + * @see f_fss_count_lines() + * @see f_string_dynamic_partial_append_nulless() + * @see f_string_dynamic_terminate_after() + * @see f_string_dynamics_increase_by() + * @see fl_fss_apply_delimit() + * @see fl_string_dynamic_rip_nulless() + * @see fll_fss_extended_read() + */ +#ifndef _di_controller_entry_actions_read_ + extern f_status_t controller_entry_actions_read(const controller_global_t global, const bool is_entry, const f_string_range_t content_range, controller_cache_t * const cache, controller_entry_actions_t *actions) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_actions_read_ + +/** + * Pre-process all items for the loaded entry. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this operate as an entry. + * If FALSE, then this operate as an exit. + * @param cache + * The main/global cache to use. + * + * @return + * F_none on success. + * F_recurse (with error bit) on a recursion error. + * F_valid_not (with error bit) on invalid entry item, entry item action, or entry item action value. + * + * Errors (with error bit) from: macro_f_array_lengths_t_increase_by(). + * Errors (with error bit) from: f_string_dynamic_append(). + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * + * This will detect and report all errors, but only the first error is returned. + * Memory related errors return immediately. + + * @see macro_f_array_lengths_t_increase_by() + * @see f_string_dynamic_append() + * @see f_string_dynamic_terminate_after() + */ +#ifndef _di_controller_entry_preprocess_ + extern f_status_t controller_entry_preprocess(const controller_global_t global, const bool is_entry, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_preprocess_ + +/** + * Process (execute) all Items for the loaded Entry or Exit. + * + * @param global + * The global data. + * @param cache + * The main/global cache to use. + * @param failsafe + * If TRUE, operate in failsafe mode (starts at designated failsafe Item). + * If FALSE, operate in normal mode (starts at "main" Item). + * @param is_entry + * If TRUE, then this operate as an entry. + * If FALSE, then this operate as an exit. + * + * @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 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: macro_f_array_lengths_t_increase_by(). + * Errors (with error bit) from: controller_perform_ready(). + * Errors (with error bit) from: controller_dynamic_append_terminated(). + * + * @see macro_f_array_lengths_t_increase_by() + * @see controller_perform_ready() + * @see controller_dynamic_append_terminated() + */ +#ifndef _di_controller_entry_process_ + extern f_status_t controller_entry_process(const controller_global_t *global, controller_cache_t * const cache, const bool failsafe, const bool is_entry) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_process_ + +/** + * Read the entry, extracting all lists. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param cache + * The cache for the specific thread. + * This should be the cache global.thread->asynchronouss.array[global.id].cache. + * + * @return + * F_none on success. + * F_file_found_not on file not found for a an exit file (is_entry is FALSE). + * + * Errors (with error bit) from: controller_entry_actions_read(). + * Errors (with error bit) from: controller_entry_items_increase_by(). + * Errors (with error bit) from: controller_file_load(). + * Errors (with error bit) from: controller_status_simplify_error(). + * Errors (with error bit) from: controller_dynamic_append_terminated(). + * Errors (with error bit) from: controller_dynamic_partial_append_terminated(). + * Errors (with error bit) from: f_fss_count_lines(). + * Errors (with error bit) from: fl_fss_apply_delimit(). + * Errors (with error bit) from: f_string_dynamic_append(). + * Errors (with error bit) from: f_string_dynamic_partial_append_nulless(). + * Errors (with error bit) from: f_string_dynamic_terminate(). + * Errors (with error bit) from: fll_fss_basic_list_read(). + * + * @see controller_entry_actions_read() + * @see controller_entry_items_increase_by() + * @see controller_file_load() + * @see controller_status_simplify_error() + * @see controller_dynamic_append_terminated() + * @see controller_dynamic_partial_append_terminated() + * @see f_fss_count_lines() + * @see fl_fss_apply_delimit() + * @see f_string_dynamic_append() + * @see f_string_dynamic_partial_append_nulless() + * @see f_string_dynamic_terminate() + * @see fll_fss_basic_list_read() + */ +#ifndef _di_controller_entry_read_ + extern f_status_t controller_entry_read(const controller_global_t global, const bool is_entry, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_read_ + +/** + * Read the entry settings, loading all settings. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param content_range + * The range in the list buffer representing the content. + * @param cache + * A structure for containing and caching relevant data. + */ +#ifndef _di_controller_entry_settings_read_ + extern f_status_t controller_entry_settings_read(const controller_global_t global, const bool is_entry, const f_string_range_t content_range, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_settings_read_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_entry_h diff --git a/level_3/controller/c/entry/private-entry_print.c b/level_3/controller/c/entry/private-entry_print.c new file mode 100644 index 0000000..86aeb15 --- /dev/null +++ b/level_3/controller/c/entry/private-entry_print.c @@ -0,0 +1,196 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../entry/private-entry_print.h" +#include "../lock/private-lock_print.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_entry_action_parameters_print_ + void controller_entry_action_parameters_print(FILE * const stream, const controller_entry_action_t action) { + + for (f_array_length_t index = 0; ;) { + + f_print_dynamic_safely(action.parameters.array[index], stream); + + ++index; + + if (index == action.parameters.used) break; + + f_print_terminated(f_string_space_s, stream); + } // for + } +#endif // _di_controller_entry_action_parameters_print_ + +#ifndef _di_controller_entry_preprocess_print_simulate_setting_value_ + void controller_entry_preprocess_print_simulate_setting_value(const controller_global_t global, const bool is_entry, const f_string_t name, const f_string_t name_sub, const f_string_static_t value, const f_string_t suffix) { + + if (global.main->error.verbosity != f_console_verbosity_debug_e && !(global.main->error.verbosity == f_console_verbosity_verbose_e && global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e)) { + return; + } + + controller_lock_print(global.main->output.to, global.thread); + + fl_print_format("%cProcessing %s item action '", global.main->output.to.stream, f_string_eol_s[0], is_entry ? controller_entry_s : controller_exit_s); + + fl_print_format("%[%S%]' setting ", global.main->output.to.stream, global.main->context.set.title, name, global.main->context.set.title); + + if (name_sub) { + fl_print_format("'%[%S%]'", global.main->output.to.stream, global.main->context.set.notable, name_sub, global.main->context.set.notable); + } + else { + fl_print_format("value", global.main->output.to.stream); + } + + fl_print_format(" to '%[%Q%]", global.main->output.to.stream, global.main->context.set.important, value, global.main->context.set.important); + + fl_print_format("'%S.%c", global.main->output.to.stream, suffix, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->output.to, global.thread); + } +#endif // _di_controller_entry_preprocess_print_simulate_setting_value_ + +#ifndef _di_controller_entry_print_error_ + void controller_entry_print_error(const bool is_entry, const fl_print_t print, const controller_cache_action_t cache, const f_status_t status, const f_string_t function, const bool fallback, controller_thread_t *thread) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + if (status == F_interrupt) return; + + // fll_error_print() automatically locks, so manually handle only the mutex locking and flushing rather than calling controller_lock_print(). + f_thread_mutex_lock(&thread->lock.print); + + fll_error_print(print, status, function, fallback); + + flockfile(print.to.stream); + + controller_entry_print_error_cache(is_entry, print, cache); + + controller_unlock_print_flush(print.to, thread); + } +#endif // _di_controller_entry_print_error_ + +#ifndef _di_controller_entry_print_error_cache_ + void controller_entry_print_error_cache(const bool is_entry, const fl_print_t output, const controller_cache_action_t cache) { + + fl_print_format("%c%[%SWhile processing ", output.to.stream, f_string_eol_s[0], output.context, output.prefix); + + if (cache.name_action.used) { + fl_print_format("action '%]", output.to.stream, output.context); + fl_print_format("%[%Q%]", output.to.stream, output.notable, cache.name_action, output.notable); + fl_print_format("%[' on line%] ", output.to.stream, output.context, output.context); + fl_print_format("%[%un%]", output.to.stream, output.notable, cache.line_action, output.notable); + fl_print_format("%[ for ", output.to.stream, output.context); + } + + if (cache.name_item.used) { + fl_print_format("%s item '%]", output.to.stream, is_entry ? controller_entry_s : controller_exit_s, output.context); + fl_print_format("%[%Q%]", output.to.stream, output.notable, cache.name_item, output.notable); + fl_print_format("%[' on line%] ", output.to.stream, output.context, output.context); + fl_print_format("%[%un%]", output.to.stream, output.notable, cache.line_item, output.notable); + fl_print_format("%[ for ", output.to.stream, output.context); + } + + if (cache.name_file.used) { + fl_print_format("%s file '%]", output.to.stream, is_entry ? controller_entry_s : controller_exit_s, output.context); + fl_print_format("%[%Q%]%['", output.to.stream, output.notable, cache.name_file, output.notable, output.context); + } + + fl_print_format(".%]%c", output.to.stream, output.context, f_string_eol_s[0]); + } +#endif // _di_controller_entry_print_error_cache_ + +#ifndef _di_controller_entry_print_error_file_ + void controller_entry_print_error_file(const bool is_entry, const fl_print_t print, const controller_cache_action_t cache, const f_status_t status, const f_string_t function, const bool fallback, const f_string_t name, const f_string_t operation, const uint8_t type, controller_thread_t *thread) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + if (status == F_interrupt) return; + + // fll_error_file_print() automatically locks, so manually handle only the mutex locking and flushing rather than calling controller_lock_print(). + f_thread_mutex_lock(&thread->lock.print); + + fll_error_file_print(print, status, function, fallback, name, operation, type); + + flockfile(print.to.stream); + + controller_entry_print_error_cache(is_entry, print, cache); + + controller_unlock_print_flush(print.to, thread); + } +#endif // _di_controller_entry_print_error_file_ + +#ifndef _di_controller_entry_setting_read_print_error_with_range_ + void controller_entry_setting_read_print_error_with_range(const bool is_entry, const fl_print_t print, const f_string_t before, const f_string_range_t range, const f_string_t after, controller_thread_t * const thread, controller_cache_t * const cache) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + + controller_lock_print(print.to, thread); + + fl_print_format("%c%[%S%s setting%S '%]", print.to.stream, f_string_eol_s[0], print.context, print.prefix, is_entry ? "Entry" : "Exit", before, print.context); + fl_print_format("%[%/Q%]", print.to.stream, print.notable, cache->buffer_file, range, print.notable); + fl_print_format("%['%S.%]%c", print.to.stream, print.context, after, print.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, print, cache->action); + + controller_unlock_print_flush(print.to, thread); + } +#endif // _di_controller_entry_setting_read_print_error_with_range_ + +#ifndef _di_controller_entry_settings_read_print_setting_requires_exactly_ + void controller_entry_settings_read_print_setting_requires_exactly(const controller_global_t global, const bool is_entry, const controller_cache_t cache, const f_number_unsigned_t total) { + + if (global.main->error.verbosity == f_console_verbosity_quiet_e) return; + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SThe %s item setting '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, cache.action.name_action, global.main->error.notable); + fl_print_format("%[' requires exactly %]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%un%]", global.main->error.to.stream, global.main->error.notable, total, global.main->error.notable); + fl_print_format("%[' %s.%]%c", global.main->error.to.stream, global.main->error.context, total > 1 ? controller_parameters_s : controller_parameter_s, global.main->error.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->error, cache.action); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } +#endif // _di_controller_entry_settings_read_print_setting_requires_exactly_ + +#ifndef _di_controller_entry_settings_read_print_setting_unknown_action_ + void controller_entry_settings_read_print_setting_unknown_action(const controller_global_t global, const bool is_entry, const controller_cache_t cache) { + + if (global.main->warning.verbosity != f_console_verbosity_debug_e) return; + + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SUnknown %s item setting '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->warning.context); + fl_print_format("%[%Q%]", global.main->warning.to.stream, global.main->warning.notable, cache.action.name_action, global.main->warning.notable); + fl_print_format("%['.%]%c", global.main->warning.to.stream, global.main->warning.context, global.main->warning.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->warning, cache.action); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } +#endif // _di_controller_entry_settings_read_print_setting_unknown_action_ + +#ifndef _di_controller_entry_settings_read_print_setting_unknown_action_value_ + void controller_entry_settings_read_print_setting_unknown_action_value(const controller_global_t global, const bool is_entry, const controller_cache_t cache, const f_array_length_t index) { + + if (global.main->warning.verbosity != f_console_verbosity_debug_e) return; + + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SThe %s item setting '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->warning.context); + fl_print_format("%[%Q%]", global.main->warning.to.stream, global.main->warning.notable, cache.action.name_action, global.main->warning.notable); + fl_print_format("%[' has an unknown value '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->warning.context); + fl_print_format("%[%Q%]", global.main->warning.to.stream, global.main->warning.notable, cache.content_actions.array[index].array[0], global.main->warning.notable); + fl_print_format("%['.%]%c", global.main->warning.to.stream, global.main->warning.context, global.main->warning.context, f_string_eol_s[0]); + + controller_entry_print_error_cache(is_entry, global.main->warning, cache.action); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } +#endif // _di_controller_entry_settings_read_print_setting_unknown_action_value_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/entry/private-entry_print.h b/level_3/controller/c/entry/private-entry_print.h new file mode 100644 index 0000000..180a9a2 --- /dev/null +++ b/level_3/controller/c/entry/private-entry_print.h @@ -0,0 +1,216 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_entry_print_h +#define _PRIVATE_entry_print_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Print all parameters for some action, separated by a space. + * + * @param stream + * The file stream to print to. + * @param action + * The entry action whose parameters will be printed. + */ +#ifndef _di_controller_entry_action_parameters_print_ + extern void controller_entry_action_parameters_print(FILE * const stream, const controller_entry_action_t action) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_action_parameters_print_ + +/** + * Print message regarding the population of a setting when in simulation or verbose mode. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this operate as an entry. + * If FALSE, then this operate as an exit. + * @param name + * The Object name of the setting being populated. + * @param name_sub + * (optional) A sub-name associated with the setting being populated. + * Set to NULL to disable. + * @param value + * The value being set. + * @param suffix + * An additional message to append at the end (before the final period). + */ +#ifndef _di_controller_entry_preprocess_print_simulate_setting_value_ + extern void controller_entry_preprocess_print_simulate_setting_value(const controller_global_t global, const bool is_entry, const f_string_t name, const f_string_t name_sub, const f_string_static_t value, const f_string_t suffix) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_preprocess_print_simulate_setting_value_ + +/** + * Print the entry related error, locking the print mutex during the print. + * + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param print + * Designates how printing is to be performed. + * @param cache + * The action cache. + * @param status + * The status code to process. + * Make sure this has F_status_set_fine() called if the status code has any error or warning bits. + * @param function + * The name of the function where the error happened. + * Set to 0 to disable. + * @param fallback + * Set to F_true to print the fallback error message for unknown errors. + * @param thread + * The thread data. + * + * @see fll_error_print() + * @see controller_entry_print_error_cache() + */ +#ifndef _di_controller_entry_print_error_ + extern void controller_entry_print_error(const bool is_entry, const fl_print_t print, const controller_cache_action_t cache, const f_status_t status, const f_string_t function, const bool fallback, controller_thread_t *thread) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_print_error_ + +/** + * Print additional error/warning information in addition to existing error that is found within the cache. + * + * This is explicitly intended to be used in addition to the error message. + * + * This neither locks the thread nor does it check to see if output is enabled or disabled. + * + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param output + * Designates how printing is to be performed. + * @param cache + * The action cache. + * + * @see controller_entry_actions_read() + * @see controller_entry_read() + */ +#ifndef _di_controller_entry_print_error_cache_ + extern void controller_entry_print_error_cache(const bool is_entry, const fl_print_t output, const controller_cache_action_t cache) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_print_error_cache_ + +/** + * Print the entry related file error, locking the print mutex during the print. + * + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param print + * Designates how printing is to be performed. + * @param cache + * The action cache. + * @param status + * The status code to process. + * Make sure this has F_status_set_fine() called if the status code has any error or warning bits. + * @param function + * The name of the function where the error happened. + * Set to 0 to disable. + * @param fallback + * Set to F_true to print the fallback error message for unknown errors. + * @param name + * The name of the file or directory. + * @param operation + * The operation that fails, such as 'create' or 'access'. + * @param type + * A valid file type code from the fll_error_file_type enum. + * @param thread + * The thread data. + * + * @see fll_error_file_print() + * @see controller_entry_print_error_cache() + */ +#ifndef _di_controller_entry_print_error_file_ + extern void controller_entry_print_error_file(const bool is_entry, const fl_print_t print, const controller_cache_action_t cache, const f_status_t status, const f_string_t function, const bool fallback, const f_string_t name, const f_string_t operation, const uint8_t type, controller_thread_t *thread) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_print_error_file_ + +/** + * Print a message about an entry setting problem, with additional messages about the value. + * + * This is intended to be explicitly called by controller_entry_settings_read(). + * This is intended only to be used for simple messages. + * + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param print + * The error or warning output structure. + * @param before + * The string to add to the message being printed (before the value). + * @param range + * The range within the cache item buffer representing the value. + * @param after + * The string to add to the message being printed (after the value). + * @param thread + * The thread data. + * @param cache + * A structure for containing and caching relevant data. + * + * @see controller_entry_settings_read() + */ +#ifndef _di_controller_entry_setting_read_print_error_with_range_ + extern void controller_entry_setting_read_print_error_with_range(const bool is_entry, const fl_print_t print, const f_string_t before, const f_string_range_t range, const f_string_t after, controller_thread_t * const thread, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_setting_read_print_error_with_range_ + +/** + * Print a message for when an entry setting action has the incorrect number of parameters. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param cache + * A structure for containing and caching relevant data. + * @param total + * The expected number of arguments. + */ +#ifndef _di_controller_entry_settings_read_print_setting_requires_exactly_ + extern void controller_entry_settings_read_print_setting_requires_exactly(const controller_global_t global, const bool is_entry, const controller_cache_t cache, const f_number_unsigned_t total) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_settings_read_print_setting_requires_exactly_ + +/** + * Print a message for when an entry setting action is unknown. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param cache + * A structure for containing and caching relevant data. + */ +#ifndef _di_controller_entry_settings_read_print_setting_unknown_action_ + extern void controller_entry_settings_read_print_setting_unknown_action(const controller_global_t global, const bool is_entry, const controller_cache_t cache) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_settings_read_print_setting_unknown_action_ + +/** + * Print a message for when an entry setting action has an unknown value. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param cache + * A structure for containing and caching relevant data. + * @param total + * The expected number of arguments. + * @param index + * The location in the content actions array representing the action value. + */ +#ifndef _di_controller_entry_settings_read_print_setting_unknown_action_value_ + extern void controller_entry_settings_read_print_setting_unknown_action_value(const controller_global_t global, const bool is_entry, const controller_cache_t cache, const f_array_length_t index) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_settings_read_print_setting_unknown_action_value_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_entry_print_h diff --git a/level_3/controller/c/lock/private-lock.c b/level_3/controller/c/lock/private-lock.c new file mode 100644 index 0000000..6717c3e --- /dev/null +++ b/level_3/controller/c/lock/private-lock.c @@ -0,0 +1,117 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../controller/private-controller.h" +#include "../lock/private-lock.h" +#include "../thread/private-thread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_lock_create_ + f_status_t controller_lock_create(controller_lock_t *lock) { + + f_status_t status = f_thread_mutex_create(0, &lock->print); + if (F_status_is_error(status)) return status; + + status = f_thread_mutex_create(0, &lock->alert); + if (F_status_is_error(status)) return status; + + status = f_thread_lock_create(0, &lock->process); + if (F_status_is_error(status)) return status; + + status = f_thread_lock_create(0, &lock->rule); + if (F_status_is_error(status)) return status; + + status = f_thread_condition_create(0, &lock->alert_condition); + if (F_status_is_error(status)) return status; + + return F_none; + } +#endif // _di_controller_lock_create_ + +#ifndef _di_controller_lock_read_ + f_status_t controller_lock_read(const bool is_normal, controller_thread_t * const thread, f_thread_lock_t *lock) { + + struct timespec time; + + f_status_t status = F_none; + + for (;;) { + + controller_time(controller_thread_lock_read_timeout_seconds_d, controller_thread_lock_read_timeout_nanoseconds_d, &time); + + status = f_thread_lock_read_timed(&time, lock); + + if (status == F_time) { + if (!controller_thread_is_enabled(is_normal, thread)) { + return F_status_set_error(F_interrupt); + } + } + else { + break; + } + } // for + + return status; + } +#endif // _di_controller_lock_read_ + +#ifndef _di_controller_lock_read_process_ + f_status_t controller_lock_read_process(controller_process_t * const process, controller_thread_t * const thread, f_thread_lock_t *lock) { + + return controller_lock_read_process_type(process->type, thread, lock); + } +#endif // _di_controller_lock_read_process_ + +#ifndef _di_controller_lock_read_process_type_ + f_status_t controller_lock_read_process_type(const uint8_t type, controller_thread_t * const thread, f_thread_lock_t *lock) { + + return controller_lock_read(type != controller_process_type_exit_e, thread, lock); + } +#endif // _di_controller_lock_read_process_type_ + +#ifndef _di_controller_lock_write_ + f_status_t controller_lock_write(const bool is_normal, controller_thread_t * const thread, f_thread_lock_t *lock) { + + struct timespec time; + + f_status_t status = F_none; + + for (;;) { + + controller_time(controller_thread_lock_write_timeout_seconds_d, controller_thread_lock_write_timeout_nanoseconds_d, &time); + + status = f_thread_lock_write_timed(&time, lock); + + if (status == F_time) { + if (!controller_thread_is_enabled(is_normal, thread)) { + return F_status_set_error(F_interrupt); + } + } + else { + break; + } + } // for + + return status; + } +#endif // _di_controller_lock_write_ + +#ifndef _di_controller_lock_write_process_ + f_status_t controller_lock_write_process(controller_process_t * const process, controller_thread_t * const thread, f_thread_lock_t *lock) { + + return controller_lock_write_process_type(process->type, thread, lock); + } +#endif // _di_controller_lock_write_process_ + +#ifndef _di_controller_lock_write_process_type_ + f_status_t controller_lock_write_process_type(const uint8_t type, controller_thread_t * const thread, f_thread_lock_t *lock) { + + return controller_lock_write(type != controller_process_type_exit_e, thread, lock); + } +#endif // _di_controller_lock_write_process_type_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/lock/private-lock.h b/level_3/controller/c/lock/private-lock.h new file mode 100644 index 0000000..5c78ddf --- /dev/null +++ b/level_3/controller/c/lock/private-lock.h @@ -0,0 +1,192 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_lock_h +#define _PRIVATE_lock_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Perform the initial, required, allocation for the lock. + * + * @param lock + * The lock to allocate. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_thread_lock_delete(). + * Errors (with error bit) from: f_thread_mutex_delete(). + * + * @see f_thread_lock_delete() + * @see f_thread_mutex_delete() + */ +#ifndef _di_controller_lock_create_ + extern f_status_t controller_lock_create(controller_lock_t *lock) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_create_ + +/** + * Wait to get a read lock. + * + * 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 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 + * The r/w lock to obtain a read lock on. + * + * @return + * F_none on success. + * F_status if main thread is disabled and write lock was never achieved. + * + * F_interrupt (with error bit set) on (exit) signal received, lock will not be set when this is returned. + * + * Status from: f_thread_lock_read_timed(). + * + * Errors (with error bit) from: f_thread_lock_read_timed(). + * + * @see f_thread_lock_read_timed() + */ +#ifndef _di_controller_lock_read_ + extern f_status_t controller_lock_read(const bool is_normal, controller_thread_t * const thread, f_thread_lock_t *lock) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_read_ + +/** + * Wait to get a read lock for some process. + * + * Given a r/w lock, periodically check to see if main thread is disabled while waiting. + * + * @param process + * The process to use when checking if thread is enabled. + * @param thread + * The thread data used to determine if the main thread is disabled or not. + * @param lock + * The r/w lock to obtain a read lock on. + * + * @return + * + * Status from: controller_lock_read(). + * + * Errors (with error bit) from: controller_lock_read(). + * + * @see controller_lock_read() + */ +#ifndef _di_controller_lock_read_process_ + extern f_status_t controller_lock_read_process(controller_process_t * const process, controller_thread_t * const thread, f_thread_lock_t *lock) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_read_process_ + +/** + * Wait to get a read lock for some process type. + * + * Given a r/w lock, periodically check to see if main thread is disabled while waiting. + * + * @param type + * The process type to use when checking if thread is enabled. + * @param thread + * The thread data used to determine if the main thread is disabled or not. + * @param lock + * The r/w lock to obtain a read lock on. + * + * @return + * + * Status from: controller_lock_read(). + * + * Errors (with error bit) from: controller_lock_read(). + * + * @see controller_lock_read() + */ +#ifndef _di_controller_lock_read_process_type_ + extern f_status_t controller_lock_read_process_type(const uint8_t type, controller_thread_t * const thread, f_thread_lock_t *lock) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_read_process_type_ + +/** + * Wait to get a write lock. + * + * 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 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 + * The r/w lock to obtain a write lock on. + * + * @return + * F_none on success. + * F_status if main thread is disabled and write lock was never achieved. + * + * F_interrupt (with error bit set) on (exit) signal received, lock will not be set when this is returned. + * + * Status from: f_thread_lock_write_timed(). + * + * Errors (with error bit) from: f_thread_lock_write_timed(). + * + * @see f_thread_lock_write_timed() + */ +#ifndef _di_controller_lock_write_ + extern f_status_t controller_lock_write(const bool is_normal, controller_thread_t * const thread, f_thread_lock_t *lock) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_write_ + +/** + * Wait to get a write lock for some process. + * + * Given a r/w lock, periodically check to see if main thread is disabled while waiting. + * + * @param process + * The process to use when checking if thread is enabled. + * @param thread + * The thread data used to determine if the main thread is disabled or not. + * @param lock + * The r/w lock to obtain a write lock on. + * + * @return + * + * Status from: controller_lock_write_process_type(). + * + * Errors (with error bit) from: controller_lock_write_process_type(). + * + * @see controller_lock_write_process_type() + */ +#ifndef _di_controller_lock_write_process_ + extern f_status_t controller_lock_write_process(controller_process_t * const process, controller_thread_t * const thread, f_thread_lock_t *lock) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_write_process_ + +/** + * Wait to get a write lock for some process type. + * + * Given a r/w lock, periodically check to see if main thread is disabled while waiting. + * + * @param type + * The process type to use when checking if thread is enabled. + * @param thread + * The thread data used to determine if the main thread is disabled or not. + * @param lock + * The r/w lock to obtain a write lock on. + * + * @return + * + * Status from: controller_lock_write(). + * + * Errors (with error bit) from: controller_lock_write(). + * + * @see controller_lock_write() + */ +#ifndef _di_controller_lock_write_process_type_ + extern f_status_t controller_lock_write_process_type(const uint8_t type, controller_thread_t * const thread, f_thread_lock_t *lock) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_write_process_type_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_lock_h diff --git a/level_3/controller/c/lock/private-lock_print.c b/level_3/controller/c/lock/private-lock_print.c new file mode 100644 index 0000000..1cb3751 --- /dev/null +++ b/level_3/controller/c/lock/private-lock_print.c @@ -0,0 +1,71 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../lock/private-lock_print.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_lock_print_error_critical_ + void controller_lock_print_error_critical(const fl_print_t print, const f_status_t status, const bool read, controller_thread_t *thread) { + + // A signal is not an error. + if (status == F_interrupt) return; + + if (print.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(print.to, thread); + + fl_print_format("%c%[%SThe pid file '%]", print.to.stream, f_string_eol_s[0], print.context, print.prefix ? print.prefix : f_string_empty_s, print.context); + fl_print_format("%['Critical failure while attempting to establish '%]", print.to.stream, print.context, print.context); + fl_print_format("%[%s lock%]", print.to.stream, print.notable, read ? "read" : "write", print.notable); + + if (status != F_failure) { + fl_print_format(" %['due to%] ", print.to.stream, print.context, print.context); + + if (status == F_parameter) { + fl_print_format("%[Invalid Parameter%]", print.to.stream, print.notable, print.notable); + } + else if (status == F_deadlock) { + fl_print_format("%[Deadlock%]", print.to.stream, print.notable, print.notable); + } + else if (status == F_resource_not) { + fl_print_format("%[Too Many Locks%]", print.to.stream, print.notable, print.notable); + } + else { + fl_print_format("%[Unknown Error%]", print.to.stream, print.notable, print.notable); + } + } + + fl_print_format("%['.%]%c", print.to.stream, print.context, print.context, f_string_eol_s[0]); + + controller_unlock_print_flush(print.to, thread); + } + } +#endif // _di_controller_lock_print_error_critical_ + +#ifndef _di_controller_lock_print_ + void controller_lock_print(const f_file_t to, controller_thread_t * const thread) { + + if (thread) { + f_thread_mutex_lock(&thread->lock.print); + } + + flockfile(to.stream); + } +#endif // _di_controller_lock_print_ + +#ifndef _di_controller_unlock_print_flush_ + void controller_unlock_print_flush(const f_file_t to, controller_thread_t * const thread) { + + fflush(to.stream); + funlockfile(to.stream); + + if (thread) { + f_thread_mutex_unlock(&thread->lock.print); + } + } +#endif // _di_controller_unlock_print_flush_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/lock/private-lock_print.h b/level_3/controller/c/lock/private-lock_print.h new file mode 100644 index 0000000..0f60359 --- /dev/null +++ b/level_3/controller/c/lock/private-lock_print.h @@ -0,0 +1,80 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_lock_print_h +#define _PRIVATE_lock_print_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Print a r/w lock related error message, locking the print mutex during the print. + * + * This will ignore F_interrupt and not print any messages, if passed. + * + * @param print + * Designates how printing is to be performed. + * @param status + * The status code to process. + * Make sure this has F_status_set_fine() called if the status code has any error or warning bits. + * @param read + * If TRUE, then this is for a read lock. + * If FALSE, then this is for a write lock. + * @param thread + * The thread data. + */ +#ifndef _di_controller_lock_print_error_critical_ + extern void controller_lock_print_error_critical(const fl_print_t print, const f_status_t status, const bool read, controller_thread_t *thread) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_print_error_critical_ + +/** + * Lock the mutex and the stream. + * + * This is implemented as a compliment to controller_unlock_print_flush() for consistency reasons. + * + * @param to + * The file stream to lock. + * @param thread + * The thread containing the print mutex to lock. + * + * @see flockfile() + * + * @see f_thread_mutex_unlock() + */ +#ifndef _di_controller_lock_print_ + extern void controller_lock_print(const f_file_t to, controller_thread_t * const thread) F_attribute_visibility_internal_d; +#endif // _di_controller_lock_print_ + +/** + * Flush the stream buffer and then unlock the mutex. + * + * This unlocks both the stream and the mutex locks. + * + * Weird behavior was observed when piping data from this program. + * The behavior appears related to how this handles locks in addition to the file streams own locking mechanisms. + * + * As a work-around, this performs a flush immediately before unlocking the print mutex. + * + * @param to + * The file stream to unlock and flush. + * @param thread + * The thread containing the print mutex to unlock. + * + * @see funlockfile() + * + * @see f_thread_mutex_unlock() + */ +#ifndef _di_controller_unlock_print_flush_ + void controller_unlock_print_flush(const f_file_t to, controller_thread_t * const thread) F_attribute_visibility_internal_d; +#endif // _di_controller_unlock_print_flush_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_lock_print_h diff --git a/level_3/controller/c/main.c b/level_3/controller/c/main.c index d82171e..7858f71 100644 --- a/level_3/controller/c/main.c +++ b/level_3/controller/c/main.c @@ -1,4 +1,4 @@ -#include "controller.h" +#include "controller/controller.h" int main(const int argc, const f_string_t *argv) { diff --git a/level_3/controller/c/process/private-process.c b/level_3/controller/c/process/private-process.c new file mode 100644 index 0000000..304f834 --- /dev/null +++ b/level_3/controller/c/process/private-process.c @@ -0,0 +1,189 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../controller/private-controller.h" +#include "../lock/private-lock.h" +#include "../lock/private-lock_print.h" +#include "../process/private-process.h" +#include "../rule/private-rule.h" +#include "../thread/private-thread.h" +#include "../thread/private-thread_process.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_process_find_ + f_status_t controller_process_find(const f_array_length_t action, const f_string_static_t alias, const controller_processs_t processs, f_array_length_t *at) { + + if (!alias.used) return F_none; + if (!processs.used) return F_false; + + for (f_array_length_t i = 0; i < processs.used; ++i) { + + if (processs.array[i] && processs.array[i]->action == action && fl_string_dynamic_compare(alias, processs.array[i]->rule.alias) == F_equal_to) { + if (at) *at = i; + + return F_true; + } + } // for + + return F_false; + } +#endif // _di_controller_process_find_ + +#ifndef _di_controller_process_prepare_ + f_status_t controller_process_prepare(const controller_global_t global, const bool is_normal, const uint8_t action, const f_string_static_t alias, f_array_length_t *id) { + + f_status_t status = F_none; + + if (controller_process_find(action, alias, global.thread->processs, id) == F_false) { + f_thread_unlock(&global.thread->lock.process); + + status = controller_lock_write(is_normal, global.thread, &global.thread->lock.process); + + if (F_status_is_error(status)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status), F_false, global.thread); + } + else { + status = controller_processs_increase(&global.thread->processs); + } + + if (F_status_is_error_not(status) && global.thread->processs.array[global.thread->processs.used]) { + + controller_process_t *process = global.thread->processs.array[global.thread->processs.used]; + + status = controller_lock_write(is_normal, global.thread, &process->lock); + + if (F_status_is_error(status)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status), F_false, global.thread); + } + else { + process->action = action; + process->rule.alias.used = 0; + + status = f_string_dynamic_append(alias, &process->rule.alias); + + if (F_status_is_error_not(status)) { + status = f_string_dynamic_terminate_after(&process->rule.alias); + + if (F_status_is_error_not(status)) { + process->id = global.thread->processs.used++; + status = F_none; + + if (id) { + *id = process->id; + } + } + } + + f_thread_unlock(&process->lock); + } + } + + f_thread_unlock(&global.thread->lock.process); + + // The read lock must be restored on return. + const f_status_t status_lock = controller_lock_read(is_normal, global.thread, &global.thread->lock.process); + + if (F_status_is_error(status_lock)) { + return F_status_set_error(F_lock); + } + } + else { + status = F_found; + } + + return status; + } +#endif // _di_controller_process_prepare_ + +#ifndef _di_controller_process_prepare_process_type_ + f_status_t controller_process_prepare_process_type(const controller_global_t global, const uint8_t type, const uint8_t action, const f_string_static_t alias, f_array_length_t *id) { + + return controller_process_prepare(global, type != controller_process_type_exit_e, action, alias, id); + } +#endif // _di_controller_process_prepare_process_type_ + +#ifndef _di_controller_process_wait_ + f_status_t controller_process_wait(const controller_global_t global, controller_process_t * const process) { + + if (!controller_thread_is_enabled_process(process, global.thread)) { + return F_status_set_error(F_interrupt); + } + + struct timespec time; + + f_status_t status = F_none; + f_status_t status_lock = F_none; + + uint8_t count = 0; + + do { + f_thread_mutex_lock(&process->wait_lock); + + if (count < controller_thread_wait_timeout_1_before_d) { + controller_time(controller_thread_wait_timeout_1_seconds_d, controller_thread_wait_timeout_1_nanoseconds_d, &time); + } + else if (count < controller_thread_wait_timeout_2_before_d) { + controller_time(controller_thread_wait_timeout_2_seconds_d, controller_thread_wait_timeout_2_nanoseconds_d, &time); + } + else if (count < controller_thread_wait_timeout_3_before_d) { + controller_time(controller_thread_wait_timeout_3_seconds_d, controller_thread_wait_timeout_3_nanoseconds_d, &time); + } + else { + controller_time(controller_thread_wait_timeout_4_seconds_d, controller_thread_wait_timeout_4_nanoseconds_d, &time); + } + + status = f_thread_condition_wait_timed(&time, &process->wait, &process->wait_lock); + + f_thread_mutex_unlock(&process->wait_lock); + + if (!controller_thread_is_enabled_process(process, global.thread)) { + return F_status_set_error(F_interrupt); + } + + if (F_status_is_error(status)) break; + + status_lock = controller_lock_read_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + break; + } + + if (!controller_rule_status_is_available(process->action, process->rule) && !(process->state == controller_process_state_active_e || process->state == controller_process_state_busy_e)) { + f_thread_unlock(&process->lock); + + return F_none; + } + + if (status != F_time) { + + // move up the wait timer after a trigger was received. + if (count < controller_thread_wait_timeout_2_before_d) { + count = 0; + } + else if (count < controller_thread_wait_timeout_3_before_d) { + count = controller_thread_wait_timeout_1_before_d; + } + else { + count = controller_thread_wait_timeout_2_before_d; + } + } + + f_thread_unlock(&process->lock); + + if (count < controller_thread_wait_timeout_3_before_d) { + ++count; + } + + } while (status == F_time && controller_thread_is_enabled_process(process, global.thread)); + + return status; + } +#endif // _di_controller_process_wait_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/process/private-process.h b/level_3/controller/c/process/private-process.h new file mode 100644 index 0000000..c818851 --- /dev/null +++ b/level_3/controller/c/process/private-process.h @@ -0,0 +1,146 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_process_h +#define _PRIVATE_process_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Prepare the process. + * + * The process is initialized with the process id, the rule alias, and the rule action type. + * These are the necessary parts for uniquely identifying the process. + * + * If a process by the given Rule alias and Rule Action already exists, then nothing is done. + * + * This requires that a global.thread->lock.process lock be set on process->lock before being called. + * + * @param global + * The global data. + * @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 action + * The Rule Action to use. + * @param alias + * The Rule alias to use. + * @param id + * (optional) The process ID when found or created. + * Set to NULL to not use. + * + * @return + * F_none on success. + * F_found on success, but nothing was done because an existing process was found. + * + * F_lock (with error bit) if failed to re-establish read lock on global.thread->lock.process while returning. + * + * Errors (with error bit) from: f_string_dynamic_append(). + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * + * Errors (with error bit) from: controller_lock_read(). + * Errors (with error bit) from: controller_lock_write(). + * + * @see f_string_dynamic_append() + * @see f_string_dynamic_terminate_after() + * @see controller_lock_read() + * @see controller_lock_write() + */ +#ifndef _di_controller_process_prepare_ + extern f_status_t controller_process_prepare(const controller_global_t global, const bool is_normal, const uint8_t action, const f_string_static_t alias, f_array_length_t *id) F_attribute_visibility_internal_d; +#endif // _di_controller_process_prepare_ + +/** + * Prepare the process for some process type. + * + * The process is initialized with the process id, the rule alias, and the rule action type. + * These are the necessary parts for uniquely identifying the process. + * + * If a process by the given Rule alias and Rule Action already exists, then nothing is done. + * + * This requires that a global.thread->lock.process lock be set on process->lock before being called. + * + * @param global + * The global data. + * @param type + * The process type to use when checking if thread is enabled. + * @param action + * The Rule Action to use. + * @param alias + * The Rule alias to use. + * @param id + * (optional) The process ID when found or created. + * Set to NULL to not use. + * + * @return + * Success from: controller_process_prepare() + * + * Errors (with error bit) from: controller_process_prepare(). + * + * @see controller_process_prepare() + */ +#ifndef _di_controller_process_prepare_process_type_ + extern f_status_t controller_process_prepare_process_type(const controller_global_t global, const uint8_t type, const uint8_t action, const f_string_static_t alias, f_array_length_t *id) F_attribute_visibility_internal_d; +#endif // _di_controller_process_prepare_process_type_ + +/** + * Find an existing process, for the given Rule Action. + * + * Do not confuse this with a process in the context of a PID. + * This is a stucture for the current processing of some rule. + * + * This does not do any locking or unlocking for the processs data, be sure to lock appropriately before and after calling this. + * + * @param action + * The Rule Action to find. + * @param alias + * The Rule alias to find. + * @param processs + * The array of processes to. + * @param at + * The location within processs the id was found. + * (optional) Set to NULL to disable. + * + * @return + * F_none if not given a valid id to search. + * F_false if there is no process found. + * F_true if there is a process found (address is stored in "at"). + */ +#ifndef _di_controller_process_find_ + f_status_t controller_process_find(const f_array_length_t action, const f_string_static_t alias, const controller_processs_t processs, f_array_length_t *at) F_attribute_visibility_internal_d; +#endif // _di_controller_process_find_ + +/*** + * Safely wait for a process, periodically checking to see if process completed or check if exiting. + * + * @param global + * The global data. + * @param process + * The process to wait on. + * + * @return + * F_none on success. + * + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * + * Success from: f_thread_condition_wait_timed(). + * + * Errors (with error bit) from: f_thread_condition_wait_timed(). + * + * @see f_thread_condition_wait_timed() + */ +#ifndef _di_controller_process_wait_ + extern f_status_t controller_process_wait(const controller_global_t global, controller_process_t * const process) F_attribute_visibility_internal_d; +#endif // _di_controller_process_wait_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_process_h diff --git a/level_3/controller/c/rule/private-rule.c b/level_3/controller/c/rule/private-rule.c new file mode 100644 index 0000000..2e695be --- /dev/null +++ b/level_3/controller/c/rule/private-rule.c @@ -0,0 +1,6207 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../controller/private-controller.h" +#include "../controller/private-controller_print.h" +#include "../lock/private-lock.h" +#include "../lock/private-lock_print.h" +#include "../process/private-process.h" +#include "../rule/private-rule.h" +#include "../rule/private-rule_print.h" +#include "../thread/private-thread.h" +#include "../thread/private-thread_process.h" +#include "../thread/private-thread_signal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_rule_action_method_name_ + f_string_static_t controller_rule_action_method_name(const uint8_t type) { + + f_string_static_t buffer = f_string_static_t_initialize; + + switch (type) { + case controller_rule_action_method_extended_e: + buffer.string = controller_rule_action_method_string_extended_s; + buffer.used = controller_rule_action_method_string_extended_s_length; + break; + + case controller_rule_action_method_extended_list_e: + buffer.string = controller_rule_action_method_string_extended_list_s; + buffer.used = controller_rule_action_method_string_extended_list_s_length; + break; + } + + buffer.size = buffer.used; + + return buffer; + } +#endif // _di_controller_rule_action_method_name_ + +#ifndef _di_controller_rule_find_ + f_status_t controller_rule_find(const f_string_static_t alias, const controller_rules_t rules, f_array_length_t *at) { + + if (!alias.used) return F_none; + if (!rules.used) return F_false; + + for (f_array_length_t i = 0; i < rules.used; ++i) { + + if (fl_string_dynamic_compare(alias, rules.array[i].alias) == F_equal_to) { + if (at) *at = i; + + return F_true; + } + } // for + + return F_false; + } +#endif // _di_controller_rule_find_ + +#ifndef _di_controller_rule_parameters_read_ + f_status_t controller_rule_parameters_read(const controller_global_t global, const f_string_static_t buffer, f_fss_object_t *object, f_fss_content_t *content, f_string_dynamics_t *parameters) { + + f_status_t status = F_none; + + parameters->used = 0; + + if (object && object->start <= object->start) { + + status = f_string_dynamics_increase(controller_common_allocation_small_d, parameters); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamics_increase", F_true); + + return status; + } + + parameters->array[parameters->used].used = 0; + + status = f_string_dynamic_partial_append_nulless(buffer, *object, ¶meters->array[0]); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true); + + return status; + } + + status = f_string_dynamic_terminate_after(¶meters->array[0]); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + + return status; + } + + ++parameters->used; + } + + if (content && content->used) { + + for (f_array_length_t i = 0; i < content->used; ++i) { + + if (content->array[i].start > content->array[i].start) continue; + + status = f_string_dynamics_increase(controller_common_allocation_small_d, parameters); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamics_increase", F_true); + + return status; + } + + parameters->array[parameters->used].used = 0; + + status = f_string_dynamic_partial_append_nulless(buffer, content->array[i], ¶meters->array[parameters->used]); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true); + + return status; + } + + status = f_string_dynamic_terminate_after(¶meters->array[parameters->used]); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + + return status; + } + + ++parameters->used; + } // for + } + + return F_none; + } +#endif // _di_controller_rule_parameters_read_ + +#ifndef _di_controller_rule_action_type_to_action_execute_type_ + uint8_t controller_rule_action_type_to_action_execute_type(const uint8_t type) { + + if (type == controller_rule_action_type_freeze_e) { + return controller_rule_action_type_execute_freeze_e; + } + + if (type == controller_rule_action_type_kill_e) { + return controller_rule_action_type_execute_kill_e; + } + + if (type == controller_rule_action_type_pause_e) { + return controller_rule_action_type_execute_pause_e; + } + + if (type == controller_rule_action_type_reload_e) { + return controller_rule_action_type_execute_reload_e; + } + + if (type == controller_rule_action_type_restart_e) { + return controller_rule_action_type_execute_restart_e; + } + + if (type == controller_rule_action_type_resume_e) { + return controller_rule_action_type_execute_resume_e; + } + + if (type == controller_rule_action_type_start_e) { + return controller_rule_action_type_execute_start_e; + } + + if (type == controller_rule_action_type_stop_e) { + return controller_rule_action_type_execute_stop_e; + } + + if (type == controller_rule_action_type_thaw_e) { + return controller_rule_action_type_execute_thaw_e; + } + + return controller_rule_action_type_execute__enum_size_e; + } +#endif // _di_controller_rule_action_type_to_action_execute_type_ + +#ifndef _di_controller_rule_action_type_name_ + f_string_static_t controller_rule_action_type_name(const uint8_t type) { + + f_string_static_t buffer = f_string_static_t_initialize; + + switch (type) { + case controller_rule_action_type_freeze_e: + buffer.string = controller_freeze_s; + buffer.used = controller_freeze_s_length; + break; + + case controller_rule_action_type_group_e: + buffer.string = controller_group_s; + buffer.used = controller_group_s_length; + break; + + case controller_rule_action_type_kill_e: + buffer.string = controller_kill_s; + buffer.used = controller_kill_s_length; + break; + + case controller_rule_action_type_pause_e: + buffer.string = controller_pause_s; + buffer.used = controller_pause_s_length; + break; + + case controller_rule_action_type_pid_file_e: + buffer.string = controller_pid_file_s; + buffer.used = controller_pid_file_s_length; + break; + + case controller_rule_action_type_rerun_e: + buffer.string = controller_rerun_s; + buffer.used = controller_rerun_s_length; + break; + + case controller_rule_action_type_reload_e: + buffer.string = controller_reload_s; + buffer.used = controller_reload_s_length; + break; + + case controller_rule_action_type_restart_e: + buffer.string = controller_restart_s; + buffer.used = controller_restart_s_length; + break; + + case controller_rule_action_type_resume_e: + buffer.string = controller_resume_s; + buffer.used = controller_resume_s_length; + break; + + case controller_rule_action_type_start_e: + buffer.string = controller_start_s; + buffer.used = controller_start_s_length; + break; + + case controller_rule_action_type_stop_e: + buffer.string = controller_stop_s; + buffer.used = controller_stop_s_length; + break; + + case controller_rule_action_type_thaw_e: + buffer.string = controller_thaw_s; + buffer.used = controller_thaw_s_length; + break; + + case controller_rule_action_type_user_e: + buffer.string = controller_user_s; + buffer.used = controller_user_s_length; + break; + + case controller_rule_action_type_with_e: + buffer.string = controller_with_s; + buffer.used = controller_with_s_length; + break; + } + + buffer.size = buffer.used; + + return buffer; + } +#endif // _di_controller_rule_action_type_name_ + +#ifndef _di_controller_rule_action_type_execute_name_ + f_string_static_t controller_rule_action_type_execute_name(const uint8_t type) { + + f_string_static_t buffer = f_string_static_t_initialize; + + switch (type) { + case controller_rule_action_type_execute_freeze_e: + buffer.string = controller_freeze_s; + buffer.used = controller_freeze_s_length; + break; + + case controller_rule_action_type_execute_kill_e: + buffer.string = controller_kill_s; + buffer.used = controller_kill_s_length; + break; + + case controller_rule_action_type_execute_pause_e: + buffer.string = controller_pause_s; + buffer.used = controller_pause_s_length; + break; + + case controller_rule_action_type_execute_reload_e: + buffer.string = controller_reload_s; + buffer.used = controller_reload_s_length; + break; + + case controller_rule_action_type_execute_restart_e: + buffer.string = controller_restart_s; + buffer.used = controller_restart_s_length; + break; + + case controller_rule_action_type_execute_resume_e: + buffer.string = controller_resume_s; + buffer.used = controller_resume_s_length; + break; + + case controller_rule_action_type_execute_start_e: + buffer.string = controller_start_s; + buffer.used = controller_start_s_length; + break; + + case controller_rule_action_type_execute_stop_e: + buffer.string = controller_stop_s; + buffer.used = controller_stop_s_length; + break; + + case controller_rule_action_type_execute_thaw_e: + buffer.string = controller_thaw_s; + buffer.used = controller_thaw_s_length; + break; + } + + buffer.size = buffer.used; + + return buffer; + } +#endif // _di_controller_rule_action_type_execute_name_ + +#ifndef _di_controller_rule_action_read_ + f_status_t controller_rule_action_read(const controller_global_t global, const bool is_normal, const uint8_t type, const uint8_t method, controller_cache_t * const cache, controller_rule_item_t *item, controller_rule_actions_t *actions, f_string_range_t *range) { + + f_status_t status = F_none; + + if (method == controller_rule_action_method_extended_list_e) { + cache->comments.used = 0; + cache->delimits.used = 0; + cache->content_action.used = 0; + cache->content_actions.used = 0; + cache->object_actions.used = 0; + + if (actions->size) { + actions->array[actions->used].parameters.used = 0; + } + + { + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_normal, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + + status = fl_fss_extended_list_content_read(cache->buffer_item, state, range, &cache->content_action, &cache->delimits, &cache->comments); + } + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_fss_extended_list_content_read", F_true); + } + else if (status == F_fss_found_content) { + status = fl_fss_apply_delimit(cache->delimits, &cache->buffer_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_fss_apply_delimit", F_true); + } + else { + + // "script" and "utility" types use the entire content and can be directly passed through. + if (item->type == controller_rule_item_type_script_e || item->type == controller_rule_item_type_utility_e) { + actions->array[actions->used].parameters.used = 0; + + status = f_string_dynamics_increase(controller_common_allocation_small_d, &actions->array[actions->used].parameters); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamics_increase", F_true); + } + else { + actions->array[actions->used].type = type; + actions->array[actions->used].line = cache->action.line_action; + actions->array[actions->used].parameters.used = 0; + actions->array[actions->used].status = F_known_not; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_action.array[0], &actions->array[actions->used].parameters.array[0]); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_append_nulless", F_true); + } + + status = f_string_dynamic_terminate_after(&actions->array[actions->used].parameters.array[0]); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + } + else { + actions->array[actions->used++].parameters.used = 1; + } + } + + return status; + } + + { + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_normal, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + + cache->delimits.used = 0; + + // the object_actions and content_actions caches are being used for the purposes of getting the parameters a given the action. + status = fll_fss_extended_read(cache->buffer_item, state, &cache->content_action.array[0], &cache->object_actions, &cache->content_actions, 0, 0, &cache->delimits, 0); + } + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fll_fss_extended_read", F_true); + } + else { + status = fl_fss_apply_delimit(cache->delimits, &cache->buffer_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_fss_apply_delimit", F_true); + } + else { + f_array_length_t i = 0; + f_array_length_t j = 0; + + for (; i < cache->object_actions.used; ++i) { + status = controller_rule_actions_increase_by(controller_common_allocation_small_d, actions); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_rule_actions_increase_by", F_true); + + break; + } + + status = f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &actions->array[actions->used].line); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_fss_count_lines", F_true); + + break; + } + + actions->array[actions->used].type = type; + actions->array[actions->used].line += ++item->line; + actions->array[actions->used].parameters.used = 0; + actions->array[actions->used].status = F_known_not; + + status = f_string_dynamics_increase(controller_common_allocation_small_d, &actions->array[actions->used].parameters); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamics_increase", F_true); + + actions->array[actions->used].status = controller_status_simplify_error(F_status_set_fine(status)); + + break; + } + + status = controller_rule_parameters_read(global, cache->buffer_item, &cache->object_actions.array[i], &cache->content_actions.array[i], &actions->array[actions->used].parameters); + + actions->array[actions->used++].status = controller_status_simplify_error(F_status_set_fine(status)); + } // for + + range->start = cache->content_action.array[0].start; + } + } + } + } + else { + status = F_data_not; + } + } + else { + cache->content_action.used = 0; + cache->delimits.used = 0; + + { + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_normal, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + + status = fl_fss_extended_content_read(cache->buffer_item, state, range, &cache->content_action, 0, &cache->delimits); + } + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fll_fss_extended_content_read", F_true); + } + else if (status == F_fss_found_content) { + status = fl_fss_apply_delimit(cache->delimits, &cache->buffer_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_fss_apply_delimit", F_true); + } + else if (type == controller_rule_action_type_pid_file_e) { + item->pid_file.used = 0; + + status = fl_string_dynamic_rip(cache->buffer_item, cache->content_action.array[0], &item->pid_file); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_string_dynamic_rip", F_true); + } + else { + status = f_string_dynamic_terminate_after(&item->pid_file); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + } + } + } + else if (type == controller_rule_action_type_rerun_e) { + uint8_t type_rerun = 0; + + if (cache->content_action.used) { + if (fl_string_dynamic_partial_compare_string(controller_freeze_s, cache->buffer_item, controller_freeze_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_freeze_e; + } + if (fl_string_dynamic_partial_compare_string(controller_kill_s, cache->buffer_item, controller_kill_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_kill_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_pause_s, cache->buffer_item, controller_pause_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_pause_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_reload_s, cache->buffer_item, controller_reload_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_reload_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_restart_s, cache->buffer_item, controller_restart_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_restart_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_resume_s, cache->buffer_item, controller_resume_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_resume_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_start_s, cache->buffer_item, controller_start_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_start_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_stop_s, cache->buffer_item, controller_stop_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_stop_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_thaw_s, cache->buffer_item, controller_thaw_s_length, cache->content_action.array[0]) == F_equal_to) { + type_rerun = controller_rule_action_type_execute_thaw_e; + } + } + + if (!type_rerun) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule item action '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_rerun_s, global.main->error.notable); + fl_print_format("%[' has '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_action.array[0], global.main->error.notable); + fl_print_format("%[' as the first value, only the following are allowed: '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[', '%]", global.main->error.to.stream, global.main->error.notable, controller_freeze_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[', '%]", global.main->error.to.stream, global.main->error.notable, controller_kill_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[', '%]", global.main->error.to.stream, global.main->error.notable, controller_pause_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[', '%]", global.main->error.to.stream, global.main->error.notable, controller_reload_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[', '%]", global.main->error.to.stream, global.main->error.notable, controller_restart_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[', '%]", global.main->error.to.stream, global.main->error.notable, controller_resume_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[', '%]", global.main->error.to.stream, global.main->error.notable, controller_start_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[', or '%]", global.main->error.to.stream, global.main->error.notable, controller_stop_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_thaw_s, global.main->error.notable, global.main->error.context); + fl_print_format("%['.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + return F_status_set_error(F_valid_not); + } + + controller_rule_rerun_item_t *rerun_item = 0; + + if (cache->content_action.used > 1) { + if (fl_string_dynamic_partial_compare_string(controller_failure_s, cache->buffer_item, controller_failure_s_length, cache->content_action.array[1]) == F_equal_to) { + rerun_item = &item->reruns[type_rerun].failure; + item->reruns[type_rerun].is |= controller_rule_rerun_is_failure_d; + } + else if (fl_string_dynamic_partial_compare_string(controller_success_s, cache->buffer_item, controller_success_s_length, cache->content_action.array[1]) == F_equal_to) { + rerun_item = &item->reruns[type_rerun].success; + item->reruns[type_rerun].is |= controller_rule_rerun_is_success_d; + } + } + else { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule item action '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_rerun_s, global.main->error.notable); + fl_print_format("%[' has '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_action.array[1], global.main->error.notable); + fl_print_format("%[' as the second value, only the following are allowed: '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]%[' or '%]", global.main->error.to.stream, global.main->error.notable, controller_stop_s, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_thaw_s, global.main->error.notable, global.main->error.context); + fl_print_format("%['.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + return F_status_set_error(F_valid_not); + } + + for (f_array_length_t i = 2; i < cache->content_action.used; ++i) { + + if (fl_string_dynamic_partial_compare_string(controller_delay_s, cache->buffer_item, controller_delay_s_length, cache->content_action.array[i]) == F_equal_to) { + status = controller_rule_action_read_rerun_number(global, controller_delay_s, cache, &i, &rerun_item->delay); + } + else if (fl_string_dynamic_partial_compare_string(controller_max_s, cache->buffer_item, controller_max_s_length, cache->content_action.array[i]) == F_equal_to) { + status = controller_rule_action_read_rerun_number(global, controller_max_s, cache, &i, &rerun_item->max); + } + else if (fl_string_dynamic_partial_compare_string(controller_reset_s, cache->buffer_item, controller_reset_s_length, cache->content_action.array[i]) == F_equal_to) { + item->reruns[type_rerun].is |= rerun_item == &item->reruns[type_rerun].failure ? controller_rule_rerun_is_failure_reset_d : controller_rule_rerun_is_success_reset_d; + } + else { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule item action '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_rerun_s, global.main->error.notable); + fl_print_format("%[' has an unknown value '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_action.array[i], global.main->error.notable); + fl_print_format("%['.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + return F_status_set_error(F_valid_not); + } + } // for + } + else if (type == controller_rule_action_type_with_e) { + + for (f_array_length_t i = 0; i < cache->content_action.used; ++i) { + + if (fl_string_dynamic_partial_compare_string(controller_full_path_s, cache->buffer_item, controller_full_path_s_length, cache->content_action.array[i]) == F_equal_to) { + item->with |= controller_with_full_path_d; + } + else if (fl_string_dynamic_partial_compare_string(controller_session_new_s, cache->buffer_item, controller_session_new_s_length, cache->content_action.array[i]) == F_equal_to) { + item->with |= controller_with_session_new_d; + + // "session_new" and "session_same" are mutually exclusive. + if (item->with & controller_with_session_same_d) { + item->with -= controller_with_session_same_d; + } + } + else if (fl_string_dynamic_partial_compare_string(controller_session_same_s, cache->buffer_item, controller_session_same_s_length, cache->content_action.array[i]) == F_equal_to) { + item->with |= controller_with_session_same_d; + + // "session_new" and "session_same" are mutually exclusive. + if (item->with & controller_with_session_new_d) { + item->with -= controller_with_session_new_d; + } + } + else { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SUnknown value '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_action.array[i], global.main->error.notable); + fl_print_format("%[' for rule item action '%]%[%s%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, global.main->error.notable, controller_with_s, global.main->error.notable); + fl_print_format("%['.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + status = F_status_set_error(F_valid_not); + break; + } + } // for + } + else if (item->type == controller_rule_item_type_script_e || item->type == controller_rule_item_type_utility_e) { + status = f_string_dynamics_increase(controller_common_allocation_small_d, &actions->array[actions->used].parameters); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamics_increase", F_true); + } + else { + + // "script" types use the entire content as a single string piped to the script, so merge all arguments together. + actions->array[actions->used].type = type; + actions->array[actions->used].line = cache->action.line_action; + actions->array[actions->used].parameters.used = 0; + actions->array[actions->used].status = F_known_not; + + for (f_array_length_t i = 0; i < cache->content_action.used; ++i) { + + status = f_string_dynamic_partial_mash_nulless(f_string_space_s, F_string_space_s_length, cache->buffer_item, cache->content_action.array[i], &actions->array[actions->used].parameters.array[0]); + if (F_status_is_error(status)) break; + } // for + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_partial_mash_nulless", F_true); + } + else { + status = f_string_dynamic_terminate_after(&actions->array[actions->used].parameters.array[0]); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + } + else { + actions->array[actions->used++].parameters.used = 1; + } + } + } + } + else { + status = f_fss_count_lines(cache->buffer_item, range->start, &actions->array[actions->used].line); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_fss_count_lines", F_true); + } + else { + actions->array[actions->used].type = type; + actions->array[actions->used].line += ++item->line; + actions->array[actions->used].parameters.used = 0; + actions->array[actions->used].status = F_known_not; + + status = controller_rule_parameters_read(global, cache->buffer_item, 0, &cache->content_action, &actions->array[actions->used].parameters); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_rule_parameters_read", F_true); + + actions->array[actions->used++].status = controller_status_simplify_error(F_status_set_fine(status)); + } + else { + actions->array[actions->used++].status = status; + } + } + } + } + else { + status = F_data_not; + } + } + + if (F_status_is_error_not(status) && status == F_data_not) { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SAction is empty, nothing to do.%]%c", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, global.main->warning.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->warning, cache->action, F_true); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + } + + return status; + } +#endif // _di_controller_rule_action_read_ + +#ifndef _di_controller_rule_action_read_rerun_number_ + f_status_t controller_rule_action_read_rerun_number(const controller_global_t global, const f_string_t name, controller_cache_t * const cache, f_array_length_t * const index, f_number_unsigned_t * const number) { + + f_status_t status = F_none; + f_number_unsigned_t parsed = 0; + + if (*index + 1 == cache->content_action.used) { + status = F_status_set_error(F_valid_not); + } + else { + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, cache->content_action.array[++(*index)], &parsed); + + if (F_status_set_fine(status) == F_number_positive) { + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, controller_range_after_number_sign(cache->buffer_item, cache->content_action.array[*index]), &parsed); + } + + if (status == F_data_not) { + status = F_status_set_error(F_valid_not); + } + } + + if (F_status_is_error(status)) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + status = F_status_set_fine(status); + + if (status != F_valid_not && status != F_number && status != F_number_decimal && status != F_number_overflow && status != F_number_underflow && status != F_number_negative) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamics_increase", F_true); + } + else { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule item action '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_rerun_s, global.main->error.notable); + fl_print_format("%[' requires a positive whole number or 0 for the '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%S%]", global.main->error.to.stream, global.main->error.notable, name, global.main->error.notable); + fl_print_format("%[' value", global.main->error.to.stream, global.main->error.context, global.main->error.context); + + if (*index + 1 == cache->content_action.used) { + fl_print_format(", but none were given.%]%c", global.main->error.to.stream, global.main->error.context, f_string_eol_s[0]); + } + else { + fl_print_format(", but '%]%[%/Q%]", global.main->error.to.stream, global.main->error.context, global.main->error.notable, cache->buffer_item, cache->content_action.array[*index], global.main->error.notable); + + if (status == F_number || status == F_number_decimal) { + fl_print_format("%[' was given.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + } + else if (status == F_number_overflow) { + fl_print_format("%[' is too large.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + } + else { + fl_print_format("%[' is negative.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + } + } + + controller_rule_print_error_cache(global.main->error, cache->action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + } + + return status; + } + + *number = parsed; + + return F_none; + } +#endif // _di_controller_rule_action_read_rerun_number_ + +#ifndef _di_controller_rule_copy_ + f_status_t controller_rule_copy(const controller_rule_t source, controller_rule_t *destination) { + + f_status_t status = F_none; + + // Delete the third party structures. + macro_f_control_group_t_delete_simple(destination->cgroup) + f_capability_delete(&destination->capability); + + for (f_array_length_t i = 0; i < controller_rule_action_type__enum_size_e; ++i) { + destination->status[i] = source.status[i]; + } // for + + destination->timeout_kill = source.timeout_kill; + destination->timeout_start = source.timeout_start; + destination->timeout_stop = source.timeout_stop; + + destination->has = source.has; + destination->nice = source.nice; + destination->user = source.user; + destination->group = source.group; + + destination->timestamp.seconds = source.timestamp.seconds; + destination->timestamp.nanoseconds = source.timestamp.nanoseconds; + + destination->alias.used = 0; + destination->name.used = 0; + destination->path.used = 0; + destination->script.used = 0; + + destination->define.used = 0; + destination->parameter.used = 0; + destination->environment.used = 0; + + destination->affinity.used = 0; + destination->groups.used = 0; + destination->limits.used = 0; + destination->scheduler.policy = source.scheduler.policy; + destination->scheduler.priority = source.scheduler.priority; + + for (f_array_length_t i = 0; i < destination->ons.size; ++i) { + + destination->ons.array[i].action = 0; + destination->ons.array[i].need.used = 0; + destination->ons.array[i].want.used = 0; + destination->ons.array[i].wish.used = 0; + } // for + + destination->ons.used = 0; + destination->items.used = 0; + + if (source.alias.used) { + status = f_string_dynamic_append(source.alias, &destination->alias); + if (F_status_is_error(status)) return status; + } + + if (source.name.used) { + status = f_string_dynamic_append(source.name, &destination->name); + if (F_status_is_error(status)) return status; + } + + if (source.path.used) { + status = f_string_dynamic_append(source.path, &destination->path); + if (F_status_is_error(status)) return status; + } + + if (source.script.used) { + status = f_string_dynamic_append(source.script, &destination->script); + if (F_status_is_error(status)) return status; + } + + if (source.define.used) { + status = f_string_maps_append(source.define, &destination->define); + if (F_status_is_error(status)) return status; + } + + if (source.parameter.used) { + status = f_string_maps_append(source.parameter, &destination->parameter); + if (F_status_is_error(status)) return status; + } + + if (source.environment.used) { + status = f_string_dynamics_append(source.environment, &destination->environment); + if (F_status_is_error(status)) return status; + } + + if (source.ons.used) { + if (destination->ons.used < source.ons.used) { + status = controller_rule_ons_resize(source.ons.used, &destination->ons); + if (F_status_is_error(status)) return status; + } + + for (f_array_length_t i = 0; i < source.ons.used; ++i) { + + destination->ons.array[i].action = source.ons.array[i].action; + + if (source.ons.array[i].need.used) { + destination->ons.array[i].need.used = 0; + + status = f_string_dynamics_append(source.ons.array[i].need, &destination->ons.array[i].need); + if (F_status_is_error(status)) return status; + } + + if (source.ons.array[i].want.used) { + destination->ons.array[i].want.used = 0; + + status = f_string_dynamics_append(source.ons.array[i].want, &destination->ons.array[i].want); + if (F_status_is_error(status)) return status; + } + + if (source.ons.array[i].wish.used) { + destination->ons.array[i].wish.used = 0; + + status = f_string_dynamics_append(source.ons.array[i].wish, &destination->ons.array[i].wish); + if (F_status_is_error(status)) return status; + } + } // for + + destination->ons.used = source.ons.used; + } + + if (source.affinity.used) { + status = f_type_int32s_append(source.affinity, &destination->affinity); + if (F_status_is_error(status)) return status; + } + + if (source.capability) { + status = f_capability_copy(source.capability, &destination->capability); + if (F_status_is_error(status)) return status; + } + + status = f_control_group_copy(source.cgroup, &destination->cgroup); + if (F_status_is_error(status)) return status; + + if (source.groups.used) { + status = f_type_int32s_append(source.groups, &destination->groups); + if (F_status_is_error(status)) return status; + } + + if (source.limits.used) { + status = f_limit_sets_copy(source.limits, &destination->limits); + if (F_status_is_error(status)) return status; + } + + if (source.items.used) { + controller_rule_item_t *item_source = 0; + controller_rule_item_t *item_destination = 0; + + controller_rule_action_t *action_source = 0; + controller_rule_action_t *action_destination = 0; + + if (source.items.used > destination->items.size) { + status = controller_rule_items_increase_by(source.items.used - destination->items.size, &destination->items); + if (F_status_is_error(status)) return status; + } + + f_array_length_t i = 0; + f_array_length_t j = 0; + + for (; i < source.items.used; ++i) { + + item_source = &source.items.array[i]; + item_destination = &destination->items.array[i]; + + if (item_source->actions.used > item_destination->actions.size) { + status = controller_rule_actions_increase_by(item_source->actions.used - item_destination->actions.size, &item_destination->actions); + if (F_status_is_error(status)) return status; + } + + item_destination->type = item_source->type; + item_destination->with = item_source->with; + item_destination->line = item_source->line; + item_destination->pid_file.used = 0; + + status = f_string_dynamic_append(item_source->pid_file, &item_destination->pid_file); + if (F_status_is_error(status)) return status; + + status = f_string_dynamic_terminate_after(&item_destination->pid_file); + if (F_status_is_error(status)) return status; + + for (j = 0; j < controller_rule_action_type_execute__enum_size_e; ++j) { + item_destination->reruns[j].is = item_source->reruns[j].is; + item_destination->reruns[j].failure.count = item_source->reruns[j].failure.count; + item_destination->reruns[j].failure.delay = item_source->reruns[j].failure.delay; + item_destination->reruns[j].failure.max = item_source->reruns[j].failure.max; + item_destination->reruns[j].success.count = item_source->reruns[j].success.count; + item_destination->reruns[j].success.delay = item_source->reruns[j].success.delay; + item_destination->reruns[j].success.max = item_source->reruns[j].success.max; + } // for + + for (j = 0; j < item_source->actions.used; ++j) { + + action_source = &item_source->actions.array[j]; + action_destination = &item_destination->actions.array[j]; + + action_destination->type = action_source->type; + action_destination->line = action_source->line; + action_destination->status = action_source->status; + + action_destination->parameters.used = 0; + + status = f_string_dynamics_append(action_source->parameters, &action_destination->parameters); + if (F_status_is_error(status)) return status; + } // for + + item_destination->actions.used = item_source->actions.used; + } // for + + destination->items.used = source.items.used; + } + + return status; + } +#endif // _di_controller_rule_copy_ + +#ifndef _di_controller_rule_execute_ + f_status_t controller_rule_execute(const controller_global_t global, const uint8_t action, const uint8_t options, controller_process_t * const process) { + + f_status_t status = F_none; + f_status_t success = F_false; + + f_array_length_t i = 0; + f_array_length_t j = 0; + f_array_length_t k = 0; + + // Child processes should receive all signals and handle the signals as they see fit. + f_signal_how_t signals = f_signal_how_t_initialize; + f_signal_set_empty(&signals.block); + f_signal_set_fill(&signals.block_not); + + f_string_maps_t environment = f_string_maps_t_initialize; + + const f_string_dynamics_t arguments_none = f_string_dynamics_t_initialize; + + controller_execute_set_t execute_set = macro_controller_execute_set_t_initialize(0, 0, process->rule.has & controller_rule_has_environment_d ? &environment : 0, &signals, 0, fl_execute_as_t_initialize); + + if (process->rule.affinity.used) { + execute_set.as.affinity = &process->rule.affinity; + } + + if (process->rule.capability) { + execute_set.as.capability = process->rule.capability; + } + + if (process->rule.has & controller_rule_has_cgroup_d) { + execute_set.as.control_group = &process->rule.cgroup; + + // Make sure all required cgroup directories exist. + if (controller_rule_status_is_available(action, process->rule)) { + status = fll_control_group_prepare(process->rule.cgroup); + + if (F_status_is_error(status)) { + controller_print_error_file(global.thread, global.main->error, F_status_set_fine(status), "fll_control_group_prepare", F_true, process->rule.cgroup.path.string, "prepare control groups for", fll_error_file_type_directory_e); + + return status; + } + } + } + + if (process->rule.has & controller_rule_has_group_d) { + execute_set.as.id_group = &process->rule.group; + + if (process->rule.groups.used) { + execute_set.as.id_groups = &process->rule.groups; + } + } + + if (process->rule.limits.used) { + execute_set.as.limits = &process->rule.limits; + } + + if (process->rule.has & controller_rule_has_scheduler_d) { + execute_set.as.scheduler = &process->rule.scheduler; + } + + if (process->rule.has & controller_rule_has_nice_d) { + execute_set.as.nice = &process->rule.nice; + } + + if (process->rule.has & controller_rule_has_user_d) { + execute_set.as.id_user = &process->rule.user; + } + + status = fl_environment_load_names(process->rule.environment, &environment); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_environment_load_names", F_true); + + return status; + } + + for (i = 0; i < process->rule.items.used && controller_thread_is_enabled_process(process, global.thread); ++i) { + + if (process->rule.items.array[i].type == controller_rule_item_type_setting_e) continue; + + for (j = 0; j < process->rule.items.array[i].actions.used; ++j) { + + if (!controller_thread_is_enabled_process(process, global.thread)) { + status = F_status_set_error(F_interrupt); + + break; + } + + if (process->rule.items.array[i].actions.array[j].type != action) continue; + + execute_set.parameter.data = 0; + execute_set.parameter.option = FL_execute_parameter_option_threadsafe_d | FL_execute_parameter_option_return_d; + + if (process->rule.items.array[i].with & controller_with_full_path_d) { + execute_set.parameter.option |= FL_execute_parameter_option_path_d; + } + + if (process->rule.items.array[i].with & controller_with_session_new_d) { + execute_set.parameter.option |= FL_execute_parameter_option_session_d; + } + + if (process->rule.items.array[i].type == controller_rule_item_type_command_e) { + for (;;) { + + status = controller_rule_execute_foreground(process->rule.items.array[i].type, 0, process->rule.items.array[i].actions.array[j].parameters, options, &execute_set, process); + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock) break; + if (F_status_is_error(status) && F_status_set_fine(status) != F_failure) break; + + if (controller_rule_execute_rerun(controller_rule_action_type_to_action_execute_type(action), process, &process->rule.items.array[i]) > 0) { + continue; + } + + break; + } // for + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock) break; + + if (F_status_is_error(status)) { + process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); + + if (!(options & controller_process_option_simulate_d)) break; + + success = F_status_set_error(F_failure); + } + else if (success == F_false || success == F_ignore) { + success = F_true; + } + } + else if (process->rule.items.array[i].type == controller_rule_item_type_script_e) { + execute_set.parameter.data = &process->rule.items.array[i].actions.array[j].parameters.array[0]; + + for (;;) { + + status = controller_rule_execute_foreground(process->rule.items.array[i].type, process->rule.script.used ? process->rule.script.string : controller_default_program_script_s, arguments_none, options, &execute_set, process); + + if (status == F_child || F_status_set_fine(status) == F_lock) break; + if (F_status_is_error(status) && F_status_set_fine(status) != F_failure) break; + + if (controller_rule_execute_rerun(controller_rule_action_type_to_action_execute_type(action), process, &process->rule.items.array[i]) > 0) { + continue; + } + + break; + } // for + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock) break; + + if (F_status_is_error(status)) { + process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); + + if (!(options & controller_process_option_simulate_d)) break; + + success = F_status_set_error(F_failure); + } + else if (success == F_false || success == F_ignore) { + success = F_true; + } + } + else if (process->rule.items.array[i].type == controller_rule_item_type_service_e) { + if (process->rule.items.array[i].pid_file.used) { + for (;;) { + + status = controller_rule_execute_pid_with(process->rule.items.array[i].pid_file, process->rule.items.array[i].type, 0, process->rule.items.array[i].actions.array[j].parameters, options, process->rule.items.array[i].with, &execute_set, process); + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock) break; + if (F_status_is_error(status) && F_status_set_fine(status) != F_failure) break; + + if (controller_rule_execute_rerun(controller_rule_action_type_to_action_execute_type(action), process, &process->rule.items.array[i]) > 0) { + continue; + } + + break; + } // for + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock) break; + + if (F_status_is_error(status)) { + process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); + + if (!(options & controller_process_option_simulate_d)) break; + + success = F_status_set_error(F_failure); + } + else if (success == F_false || success == F_ignore) { + success = F_true; + } + } + else { + success = F_status_set_error(F_failure); + + // @todo make this more specific. + controller_rule_action_print_error_missing_pid(global.main->error, process->rule.alias.string); + } + } + else if (process->rule.items.array[i].type == controller_rule_item_type_utility_e) { + if (process->rule.items.array[i].pid_file.used) { + execute_set.parameter.data = &process->rule.items.array[i].actions.array[j].parameters.array[0]; + + for (;;) { + + status = controller_rule_execute_pid_with(process->rule.items.array[i].pid_file, process->rule.items.array[i].type, process->rule.script.used ? process->rule.script.string : controller_default_program_script_s, arguments_none, options, process->rule.items.array[i].with, &execute_set, process); + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock) break; + if (F_status_is_error(status) && F_status_set_fine(status) != F_failure) break; + + if (controller_rule_execute_rerun(controller_rule_action_type_to_action_execute_type(action), process, &process->rule.items.array[i]) > 0) { + continue; + } + + break; + } // for + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock) break; + + if (F_status_is_error(status)) { + process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); + + if (!(options & controller_process_option_simulate_d)) break; + + success = F_status_set_error(F_failure); + } + else if (success == F_false || success == F_ignore) { + success = F_true; + } + } + else { + success = F_status_set_error(F_failure); + + // @todo make this more specific. + controller_rule_action_print_error_missing_pid(global.main->error, process->rule.alias.string); + } + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SAction type is unknown, ignoring.%]%c", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, global.main->warning.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->warning, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + + if (success == F_false) { + success = F_ignore; + } + + continue; + } + } // for + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_is_error(status) && !(options & controller_process_option_simulate_d)) { + break; + } + } // for + + macro_f_string_maps_t_delete_simple(environment); + + // Lock failed, attempt to re-establish lock before returning. + if (F_status_set_fine(status) == F_lock) { + status = controller_lock_read(process, global.thread, &process->lock); + + if (F_status_is_error(status)) { + return F_status_set_error(F_lock); + } + + success = F_false; + } + + if (!controller_thread_is_enabled_process(process, global.thread)) { + return F_status_set_error(F_interrupt); + } + + if (status == F_child || F_status_is_error(status)) { + return status; + } + + if (success == F_false || success == F_failure) { + return F_status_set_error(F_failure); + } + + if (success == F_ignore) { + return F_ignore; + } + + return F_none; + } +#endif // _di_controller_rule_execute_ + +#ifndef _di_controller_rule_execute_foreground_ + f_status_t controller_rule_execute_foreground(const uint8_t type, const f_string_t program, const f_string_statics_t arguments, const uint8_t options, controller_execute_set_t * const execute_set, controller_process_t * const process) { + + f_status_t status = F_none; + f_status_t status_lock = F_none; + + f_string_dynamics_t arguments_none = f_string_dynamics_t_initialize; + + controller_main_t * const main = (controller_main_t *) process->main_data; + controller_thread_t * const thread = (controller_thread_t *) process->main_thread; + + f_execute_result_t result = f_execute_result_t_initialize; + + status = controller_pids_increase(&process->childs); + + if (F_status_is_error(status)) { + controller_print_error(thread, main->error, F_status_set_fine(status), "controller_pids_increase", F_true); + + return status; + } + + pid_t *child = 0; + f_string_dynamic_t *child_pid_file = 0; + + { + f_array_length_t i = 0; + + while (i < process->childs.used && process->childs.array[i]) { + ++i; + } // while + + child = &process->childs.array[i]; + + if (i == process->childs.used) { + ++process->childs.used; + } + } + + if (options & controller_process_option_simulate_d) { + if (main->output.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->output.to, thread); + + fl_print_format("%cSimulating execution of '%[", main->output.to.stream, f_string_eol_s[0], main->context.set.title); + + if (program) { + f_print_safely_terminated(program, main->output.to.stream); + } + else { + f_print_dynamic_safely(arguments.array[0], main->output.to.stream); + } + + fl_print_format("%]' with the arguments: '%[", main->output.to.stream, main->context.set.title, main->context.set.important); + + for (f_array_length_t i = program ? 0 : 1; i < arguments.used; ++i) { + + if (program && i || !program && i > 1) { + f_print_terminated(f_string_space_s, main->output.to.stream); + } + + f_print_dynamic_safely(arguments.array[i], main->output.to.stream); + } // for + + fl_print_format("%]' from '", main->output.to.stream, main->context.set.important); + fl_print_format("%[%Q%]'.%c", main->output.to.stream, main->context.set.notable, process->rule.name, main->context.set.notable, f_string_eol_s[0]); + + controller_unlock_print_flush(main->output.to, thread); + } + + // Sleep for less than a second to better show simulation of synchronous vs asynchronous. + { + const struct timespec delay = controller_time_milliseconds(controller_thread_simulation_timeout_d); + + if (controller_time_sleep_nanoseconds(main, (controller_setting_t *) process->main_setting, delay) == -1) { + status = F_status_set_error(F_interrupt); + } + } + + if (F_status_set_fine(status) != F_interrupt) { + const f_string_static_t simulated_program = macro_f_string_static_t_initialize(f_string_empty_s, 0); + const f_string_statics_t simulated_arguments = f_string_statics_t_initialize; + fl_execute_parameter_t simulated_parameter = macro_fl_execute_parameter_t_initialize(execute_set->parameter.option, execute_set->parameter.wait, process->rule.has & controller_rule_has_environment_d ? execute_set->parameter.environment : 0, execute_set->parameter.signals, &simulated_program); + + status = fll_execute_program(controller_default_program_script_s, simulated_arguments, &simulated_parameter, &execute_set->as, (void *) &result); + } + } + else { + status = fll_execute_program(program, arguments, &execute_set->parameter, &execute_set->as, (void *) &result); + } + + if (status == F_parent) { + const pid_t id_child = result.pid; + result.status = 0; + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_write_process(process, thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(main->error, F_status_set_fine(status_lock), F_false, thread); + + if (F_status_set_fine(status_lock) != F_interrupt) { + status = controller_lock_read_process(process, thread, &process->lock); + + if (status == F_none) { + return status_lock; + } + } + + return F_status_set_error(F_lock); + } + + // Assign the child process id to allow for the cancel process to send appropriate termination signals to the child process. + *child = id_child; + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_read_process(process, thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(main->error, F_status_set_fine(status_lock), F_true, thread); + } + + if (F_status_set_fine(status_lock) != F_interrupt) { + + // Have the parent wait for the child process to finish. + waitpid(id_child, &result.status, 0); + } + + if (F_status_set_fine(status_lock) == F_interrupt || !controller_thread_is_enabled_process(process, thread)) { + if (status_lock == F_none) { + return F_status_set_error(F_interrupt); + } + + return F_status_set_error(F_lock); + } + + if (status_lock == F_none) { + f_thread_unlock(&process->lock); + } + + status_lock = controller_lock_write_process(process, thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(main->error, F_status_set_fine(status_lock), F_false, thread); + + if (F_status_set_fine(status_lock) != F_interrupt) { + status = controller_lock_read_process(process, thread, &process->lock); + + if (status == F_none) { + return status_lock; + } + } + + return F_status_set_error(F_lock); + } + + process->result = result.status; + + // Remove the pid now that waidpid() has returned. + *child = 0; + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_read_process(process, thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(main->error, F_status_set_fine(status_lock), F_true, thread); + + return F_status_set_error(F_lock); + } + + if (WIFEXITED(result.status) ? WEXITSTATUS(result.status) : 0) { + status = F_status_set_error(F_failure); + } + else { + status = F_none; + } + } + else { + main->child = result.status; + + if (!controller_thread_is_enabled_process(process, thread)) { + return F_status_set_error(F_interrupt); + } + } + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_child || status == F_capability || status == F_group || status == F_nice || status == F_user) { + status = F_child; + } + else { + status = F_status_set_error(status); + } + } + + if (status == F_child || F_status_set_fine(status) == F_interrupt) { + return status; + } + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if ((WIFEXITED(process->result) && WEXITSTATUS(process->result)) || status == F_control_group || status == F_failure || status == F_limit || status == F_processor || status == F_schedule) { + controller_rule_item_print_error_execute(type == controller_rule_item_type_script_e, program ? program : arguments.used ? arguments.array[0].string : f_string_empty_s, status, process); + } + else { + controller_print_error(thread, main->error, F_status_set_fine(status), "fll_execute_program", F_true); + } + + status = F_status_set_error(status); + } + + return status; + } +#endif // _di_controller_rule_execute_foreground_ + +#ifndef _di_controller_rule_execute_pid_with_ + f_status_t controller_rule_execute_pid_with(const f_string_dynamic_t pid_file, const uint8_t type, const f_string_t program, const f_string_statics_t arguments, const uint8_t options, const uint8_t with, controller_execute_set_t * const execute_set, controller_process_t * const process) { + + f_status_t status = F_none; + f_status_t status_lock = F_none; + + controller_main_t * const main = (controller_main_t *) process->main_data; + controller_thread_t * const thread = (controller_thread_t *) process->main_thread; + + f_execute_result_t result = f_execute_result_t_initialize; + + status = controller_pids_increase(&process->childs); + + if (F_status_is_error(status)) { + controller_print_error(thread, main->error, F_status_set_fine(status), "controller_pids_increase", F_true); + + return status; + } + + status = f_string_dynamics_increase(controller_common_allocation_small_d, &process->path_pids); + + if (F_status_is_error(status)) { + controller_print_error(thread, main->error, F_status_set_fine(status), "f_string_dynamics_increase", F_true); + + return status; + } + + pid_t *child = 0; + f_string_dynamic_t *child_pid_file = 0; + + { + f_array_length_t i = 0; + + while (i < process->childs.used && process->childs.array[i]) { + ++i; + } // while + + child = &process->childs.array[i]; + + if (i == process->childs.used) { + ++process->childs.used; + } + + for (i = 0; i < process->path_pids.used && process->path_pids.array[i].used; ++i) { + // Do nothing. + } // for + + child_pid_file = &process->path_pids.array[i]; + + if (i == process->path_pids.used) { + ++process->path_pids.used; + } + } + + status = f_file_exists(pid_file.string); + + if (F_status_is_error(status)) { + controller_print_error_file(thread, main->error, F_status_set_fine(status), "f_file_exists", F_true, pid_file.string, "find", fll_error_file_type_file_e); + + return status; + } + + if (status == F_true) { + controller_print_error_file(thread, main->error, F_file_found, "f_file_exists", F_true, pid_file.string, "find", fll_error_file_type_file_e); + + return F_status_set_error(F_file_found); + } + + status = controller_dynamic_append_terminated(pid_file, child_pid_file); + + if (F_status_is_error(status)) { + controller_print_error(thread, main->error, F_status_set_fine(status), "controller_dynamic_append_terminated", F_true); + + return status; + } + + if (options & controller_process_option_simulate_d) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, thread); + + fl_print_format("%cSimulating execution of '%[", main->error.to.stream, f_string_eol_s[0], main->context.set.title); + + if (program) { + f_print_safely_terminated(program, main->error.to.stream); + } + else { + f_print_dynamic_safely(arguments.array[0], main->error.to.stream); + } + + fl_print_format("%]' with the arguments: '%[", main->error.to.stream, main->context.set.title, main->context.set.important); + + for (f_array_length_t i = program ? 0 : 1; i < arguments.used; ++i) { + + if (program && i || !program && i > 1) { + f_print_terminated(f_string_space_s, main->error.to.stream); + } + + f_print_dynamic_safely(arguments.array[i], main->error.to.stream); + } // for + + fl_print_format("%]' from '", main->error.to.stream, main->context.set.important); + fl_print_format("%[%Q%]'.%c", main->error.to.stream, main->context.set.notable, process->rule.name, main->context.set.notable, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, thread); + } + + // Sleep for less than a second to better show simulation of synchronous vs asynchronous. + { + const struct timespec delay = controller_time_milliseconds(controller_thread_simulation_timeout_d); + + if (controller_time_sleep_nanoseconds(main, (controller_setting_t *) process->main_setting, delay) == -1) { + status = F_status_set_error(F_interrupt); + } + } + + if (F_status_set_fine(status) != F_interrupt) { + const f_string_static_t simulated_program = macro_f_string_static_t_initialize(f_string_empty_s, 0); + const f_string_statics_t simulated_arguments = f_string_statics_t_initialize; + fl_execute_parameter_t simulated_parameter = macro_fl_execute_parameter_t_initialize(execute_set->parameter.option, execute_set->parameter.wait, process->rule.has & controller_rule_has_environment_d ? execute_set->parameter.environment : 0, execute_set->parameter.signals, &simulated_program); + + status = fll_execute_program(controller_default_program_script_s, simulated_arguments, &simulated_parameter, &execute_set->as, (void *) &result); + } + } + else { + status = fll_execute_program(program, arguments, &execute_set->parameter, &execute_set->as, (void *) &result); + } + + if (status == F_parent) { + const pid_t id_child = result.pid; + result.status = 0; + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_write_process(process, thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(main->error, F_status_set_fine(status_lock), F_false, thread); + + if (F_status_set_fine(status_lock) != F_interrupt) { + status = controller_lock_read_process(process, thread, &process->lock); + + if (status == F_none) { + return status_lock; + } + } + + return F_status_set_error(F_lock); + } + + // Assign the child process id to allow for the cancel process to send appropriate termination signals to the child process. + *child = id_child; + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_read_process(process, thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(main->error, F_status_set_fine(status_lock), F_true, thread); + } + + if (F_status_set_fine(status_lock) != F_interrupt) { + + // The child process should perform the change into background, therefore it is safe to wait for the child to exit (another process is spawned). + waitpid(id_child, &result.status, 0); + } + + if (!controller_thread_is_enabled_process(process, thread)) { + if (status_lock == F_none) { + return F_status_set_error(F_interrupt); + } + + return F_status_set_error(F_lock); + } + + if (status_lock == F_none) { + f_thread_unlock(&process->lock); + } + + status_lock = controller_lock_write_process(process, thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(main->error, F_status_set_fine(status_lock), F_false, thread); + + if (F_status_set_fine(status_lock) != F_interrupt) { + status = controller_lock_read_process(process, thread, &process->lock); + + if (status == F_none) { + return status_lock; + } + } + + return F_status_set_error(F_lock); + } + + process->result = result.status; + + // Remove the pid now that waidpid() has returned. + *child = 0; + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_read_process(process, thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(main->error, F_status_set_fine(status_lock), F_true, thread); + + return F_status_set_error(F_lock); + } + + if (WIFEXITED(result.status) ? WEXITSTATUS(result.status) : 0) { + status = F_status_set_error(F_failure); + } + else { + status = F_none; + } + } + else { + main->child = result.status; + + if (!controller_thread_is_enabled_process(process, thread)) { + return F_status_set_error(F_interrupt); + } + } + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_child || status == F_capability || status == F_group || status == F_nice || status == F_user) { + status = F_child; + } + else { + status = F_status_set_error(status); + } + } + + if (status == F_child || F_status_set_fine(status) == F_interrupt) { + return status; + } + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if ((WIFEXITED(process->result) && WEXITSTATUS(process->result)) || status == F_control_group || status == F_failure || status == F_limit || status == F_processor || status == F_schedule) { + controller_rule_item_print_error_execute(type == controller_rule_item_type_utility_e, program ? program : arguments.used ? arguments.array[0].string : f_string_empty_s, status, process); + } + else { + controller_print_error(thread, main->error, F_status_set_fine(status), "fll_execute_program", F_true); + } + + return F_status_set_error(status); + } + + return status; + } +#endif // _di_controller_rule_execute_pid_with_ + +#ifndef _di_controller_rule_execute_rerun_ + int8_t controller_rule_execute_rerun(const uint8_t action, controller_process_t * const process, controller_rule_item_t * const item) { + + const int result = WIFEXITED(process->result) ? WEXITSTATUS(process->result) : 0; + + if (item->reruns[action].is & (result ? controller_rule_rerun_is_failure_d : controller_rule_rerun_is_success_d)) { + controller_main_t * const main = (controller_main_t *) process->main_data; + controller_thread_t * const thread = (controller_thread_t *) process->main_thread; + controller_rule_rerun_item_t *rerun_item = result ? &item->reruns[action].failure : &item->reruns[action].success; + + if (!controller_thread_is_enabled_process(process, thread)) return -2; + + if (!rerun_item->max || rerun_item->count < rerun_item->max) { + if (main->error.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(main->output.to, thread); + + fl_print_format("%cRe-running '", main->output.to.stream, f_string_eol_s[0]); + fl_print_format("%[%q%]", main->output.to.stream, main->context.set.title, process->rule.alias, main->context.set.title); + f_print_terminated("' '", main->output.to.stream); + fl_print_format("%[%q%]", main->output.to.stream, main->context.set.notable, controller_rule_action_type_execute_name(action), main->context.set.notable); + f_print_terminated("' with a ", main->output.to.stream); + fl_print_format("%[%s%]", main->output.to.stream, main->context.set.notable, controller_delay_s, main->context.set.notable); + f_print_terminated(" of ", main->output.to.stream); + fl_print_format("%[%ul%] MegaTime", main->output.to.stream, main->context.set.notable, rerun_item->delay, main->context.set.notable); + + if (rerun_item->max) { + f_print_terminated(" for ", main->output.to.stream); + fl_print_format("%[%ul%]", main->output.to.stream, main->context.set.notable, rerun_item->count, main->context.set.notable); + f_print_terminated(" of ", main->output.to.stream); + fl_print_format("%[%s%] ", main->output.to.stream, main->context.set.notable, controller_max_s, main->context.set.notable); + fl_print_format("%[%ul%]", main->output.to.stream, main->context.set.notable, rerun_item->max, main->context.set.notable); + fl_print_format(".%c", main->output.to.stream, f_string_eol_s[0]); + } + else { + fl_print_format(" with no %[%s%].%c", main->output.to.stream, main->context.set.notable, controller_max_s, main->context.set.notable, f_string_eol_s[0]); + } + + controller_unlock_print_flush(main->output.to, thread); + } + + if (rerun_item->delay) { + const struct timespec delay = controller_time_milliseconds(rerun_item->delay); + + if (controller_time_sleep_nanoseconds(main, (controller_setting_t *) process->main_setting, delay) == -1) { + return -1; + } + + if (!controller_thread_is_enabled_process(process, thread)) return -2; + } + + if (item->reruns[action].is & (result ? controller_rule_rerun_is_failure_reset_d : controller_rule_rerun_is_success_reset_d)) { + if (result) { + item->reruns[action].success.count = 0; + } + else { + item->reruns[action].failure.count = 0; + } + } + + if (rerun_item->max) { + ++rerun_item->count; + } + + return F_true; + } + } + + return F_false; + } +#endif // _di_controller_rule_execute_rerun_ + +#ifndef _di_controller_rule_id_construct_ + f_status_t controller_rule_id_construct(const controller_global_t global, const f_string_static_t source, const f_string_range_t directory, const f_string_range_t basename, f_string_dynamic_t * const alias) { + + f_status_t status = F_none; + + alias->used = 0; + + status = f_string_dynamic_partial_append_nulless(source, directory, alias); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true); + + return status; + } + + status = f_string_append(f_path_separator_s, F_path_separator_s_length, alias); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_append", F_true); + + return status; + } + + status = f_string_dynamic_partial_append_nulless(source, basename, alias); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true); + + return status; + } + + status = f_string_dynamic_terminate_after(alias); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + } + + return status; + } +#endif // _di_controller_rule_id_construct_ + +#ifndef _di_controller_rule_status_is_available_ + f_status_t controller_rule_status_is_available(const uint8_t action, const controller_rule_t rule) { + + return F_status_is_error_not(rule.status[0]) && rule.status[action] == F_known_not; + } +#endif // _di_controller_rule_status_is_available_ + +#ifndef _di_controller_rule_status_is_error_ + f_status_t controller_rule_status_is_error(const uint8_t action, const controller_rule_t rule) { + + return F_status_is_error(rule.status[0]) || F_status_is_error(rule.status[action]); + } +#endif // _di_controller_rule_status_is_error_ + +#ifndef _di_controller_rule_item_read_ + f_status_t controller_rule_item_read(const controller_global_t global, const bool is_normal, controller_cache_t * const cache, controller_rule_item_t * const item) { + + f_status_t status = F_none; + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_normal, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + f_string_range_t range = macro_f_string_range_t_initialize(cache->buffer_item.used); + f_array_length_t last = 0; + + uint8_t type = 0; + uint8_t method = 0; + bool multiple = F_false; + + item->actions.used = 0; + + for (; range.start < cache->buffer_item.used && range.start <= range.stop; last = range.start, cache->delimits.used = 0, cache->comments.used = 0) { + + status = fl_fss_extended_list_object_read(cache->buffer_item, state, &range, &cache->range_action, &cache->delimits); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_fss_extended_list_object_read", F_true); + + break; + } + + if (status == F_fss_found_object) { + multiple = F_true; + } + else { + range.start = last; + multiple = F_false; + cache->delimits.used = 0; + + // The current line is not an Extended List object, so the next possibility is a Basic List (and Extended List, both use the same Object structure). + status = fl_fss_extended_object_read(cache->buffer_item, state, &range, &cache->range_action, 0, &cache->delimits); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_fss_extended_object_read", F_true); + + break; + } + + // Nothing of importance here, so continue onto the next line. + // @todo present an error if this line is anything but whitespace. + if (status != F_fss_found_object) continue; + } + + status = fl_fss_apply_delimit(cache->delimits, &cache->buffer_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_fss_apply_delimit", F_true); + + break; + } + + status = f_fss_count_lines(cache->buffer_item, cache->range_action.start, &cache->action.line_action); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_fss_count_lines", F_true); + + break; + } + + cache->action.line_action += ++item->line; + cache->action.name_action.used = 0; + + status = controller_dynamic_rip_nulless_terminated(cache->buffer_item, cache->range_action, &cache->action.name_action); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_dynamic_rip_nulless_terminated", F_true); + + break; + } + + if (fl_string_dynamic_compare_string(controller_freeze_s, cache->action.name_action, controller_freeze_s_length) == F_equal_to) { + type = controller_rule_action_type_freeze_e; + } + else if (fl_string_dynamic_compare_string(controller_group_s, cache->action.name_action, controller_group_s_length) == F_equal_to) { + type = controller_rule_action_type_group_e; + } + else if (fl_string_dynamic_compare_string(controller_kill_s, cache->action.name_action, controller_kill_s_length) == F_equal_to) { + type = controller_rule_action_type_kill_e; + } + else if (fl_string_dynamic_compare_string(controller_pause_s, cache->action.name_action, controller_pause_s_length) == F_equal_to) { + type = controller_rule_action_type_pause_e; + } + else if (fl_string_dynamic_compare_string(controller_pid_file_s, cache->action.name_action, controller_pid_file_s_length) == F_equal_to) { + type = controller_rule_action_type_pid_file_e; + } + else if (fl_string_dynamic_compare_string(controller_reload_s, cache->action.name_action, controller_reload_s_length) == F_equal_to) { + type = controller_rule_action_type_reload_e; + } + else if (fl_string_dynamic_compare_string(controller_rerun_s, cache->action.name_action, controller_rerun_s_length) == F_equal_to) { + type = controller_rule_action_type_rerun_e; + } + else if (fl_string_dynamic_compare_string(controller_restart_s, cache->action.name_action, controller_restart_s_length) == F_equal_to) { + type = controller_rule_action_type_restart_e; + } + else if (fl_string_dynamic_compare_string(controller_resume_s, cache->action.name_action, controller_resume_s_length) == F_equal_to) { + type = controller_rule_action_type_resume_e; + } + else if (fl_string_dynamic_compare_string(controller_start_s, cache->action.name_action, controller_start_s_length) == F_equal_to) { + type = controller_rule_action_type_start_e; + } + else if (fl_string_dynamic_compare_string(controller_stop_s, cache->action.name_action, controller_stop_s_length) == F_equal_to) { + type = controller_rule_action_type_stop_e; + } + else if (fl_string_dynamic_compare_string(controller_thaw_s, cache->action.name_action, controller_thaw_s_length) == F_equal_to) { + type = controller_rule_action_type_thaw_e; + } + else if (fl_string_dynamic_compare_string(controller_user_s, cache->action.name_action, controller_user_s_length) == F_equal_to) { + type = controller_rule_action_type_user_e; + } + else if (fl_string_dynamic_compare_string(controller_with_s, cache->action.name_action, controller_with_s_length) == F_equal_to) { + type = controller_rule_action_type_with_e; + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SUnknown rule item action '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, global.main->warning.context); + fl_print_format("%[%Q%]", global.main->warning.to.stream, global.main->warning.notable, cache->action.name_action, global.main->warning.notable); + fl_print_format("%['.%]%c", global.main->warning.to.stream, global.main->warning.context, global.main->warning.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->warning, cache->action, F_true); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + + continue; + } + + if (multiple) { + if (type == controller_rule_action_type_group_e || type == controller_rule_action_type_pid_file_e || type == controller_rule_action_type_user_e) { + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SFSS Extended List is not allowed for the rule item action '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, cache->action.name_action, global.main->error.notable); + fl_print_format("%['.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + status = F_status_set_error(F_supported_not); + break; + } + + method = controller_rule_action_method_extended_list_e; + } + else { + method = controller_rule_action_method_extended_e; + } + + status = controller_rule_actions_increase_by(controller_common_allocation_small_d, &item->actions); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_rule_actions_increase_by", F_true); + + break; + } + + status = controller_rule_action_read(global, is_normal, type, method, cache, item, &item->actions, &range); + if (F_status_is_error(status)) break; + } // for + + return status; + } +#endif // _di_controller_rule_item_read_ + +#ifndef _di_controller_rule_item_type_name_ + f_string_static_t controller_rule_item_type_name(const uint8_t type) { + + f_string_static_t buffer = f_string_static_t_initialize; + + switch (type) { + case controller_rule_item_type_command_e: + buffer.string = controller_command_s; + buffer.used = controller_command_s_length; + break; + + case controller_rule_item_type_script_e: + buffer.string = controller_script_s; + buffer.used = controller_script_s_length; + break; + + case controller_rule_item_type_service_e: + buffer.string = controller_service_s; + buffer.used = controller_service_s_length; + break; + + case controller_rule_item_type_setting_e: + buffer.string = controller_setting_s; + buffer.used = controller_setting_s_length; + break; + + case controller_rule_item_type_utility_e: + buffer.string = controller_utility_s; + buffer.used = controller_utility_s_length; + break; + } + + buffer.size = buffer.used; + + return buffer; + } +#endif // _di_controller_rule_item_type_name_ + +#ifndef _di_controller_rule_items_increase_by_ + f_status_t controller_rule_items_increase_by(const f_array_length_t amount, controller_rule_items_t * const items) { + + if (items->used + amount > items->size) { + if (items->used + amount > F_array_length_t_size_d) { + return F_status_set_error(F_array_too_large); + } + + const f_status_t status = f_memory_resize(items->size, items->used + amount, sizeof(controller_rule_item_t), (void **) & items->array); + + if (F_status_is_error_not(status)) { + items->size = items->used + amount; + } + + return status; + } + + return F_data_not; + } +#endif // _di_controller_rule_items_increase_by_ + +#ifndef _di_controller_rule_setting_limit_type_name_ + f_string_static_t controller_rule_setting_limit_type_name(const uint8_t type) { + + f_string_static_t buffer = f_string_static_t_initialize; + + switch (type) { + case controller_resource_limit_type_as_e: + buffer.string = controller_as_s; + buffer.used = controller_as_s_length; + break; + + case controller_resource_limit_type_core_e: + buffer.string = controller_core_s; + buffer.used = controller_core_s_length; + break; + + case controller_resource_limit_type_cpu_e: + buffer.string = controller_cpu_s; + buffer.used = controller_cpu_s_length; + break; + + case controller_resource_limit_type_data_e: + buffer.string = controller_data_s; + buffer.used = controller_data_s_length; + break; + + case controller_resource_limit_type_fsize_e: + buffer.string = controller_fsize_s; + buffer.used = controller_fsize_s_length; + break; + + case controller_resource_limit_type_locks_e: + buffer.string = controller_locks_s; + buffer.used = controller_locks_s_length; + break; + + case controller_resource_limit_type_memlock_e: + buffer.string = controller_memlock_s; + buffer.used = controller_memlock_s_length; + break; + + case controller_resource_limit_type_msgqueue_e: + buffer.string = controller_msgqueue_s; + buffer.used = controller_msgqueue_s_length; + break; + + case controller_resource_limit_type_nice_e: + buffer.string = controller_nice_s; + buffer.used = controller_nice_s_length; + break; + + case controller_resource_limit_type_nofile_e: + buffer.string = controller_nofile_s; + buffer.used = controller_nofile_s_length; + break; + + case controller_resource_limit_type_nproc_e: + buffer.string = controller_nproc_s; + buffer.used = controller_nproc_s_length; + break; + + case controller_resource_limit_type_rss_e: + buffer.string = controller_rss_s; + buffer.used = controller_rss_s_length; + break; + + case controller_resource_limit_type_rtprio_e: + buffer.string = controller_rtprio_s; + buffer.used = controller_rtprio_s_length; + break; + + case controller_resource_limit_type_rttime_e: + buffer.string = controller_rttime_s; + buffer.used = controller_rttime_s_length; + break; + + case controller_resource_limit_type_sigpending_e: + buffer.string = controller_sigpending_s; + buffer.used = controller_sigpending_s_length; + break; + + case controller_resource_limit_type_stack_e: + buffer.string = controller_stack_s; + buffer.used = controller_stack_s_length; + break; + } + + buffer.size = buffer.used; + + return buffer; + } +#endif // _di_controller_rule_setting_limit_type_name_ + +#ifndef _di_controller_rule_process_ + f_status_t controller_rule_process(const controller_global_t global, controller_process_t * const process) { + + switch (process->action) { + case controller_rule_action_type_freeze_e: + case controller_rule_action_type_kill_e: + case controller_rule_action_type_pause_e: + case controller_rule_action_type_reload_e: + case controller_rule_action_type_restart_e: + case controller_rule_action_type_resume_e: + case controller_rule_action_type_start_e: + case controller_rule_action_type_stop_e: + case controller_rule_action_type_thaw_e: + break; + + default: + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SUnsupported action type '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%q%]", global.main->error.to.stream, global.main->error.notable, controller_rule_action_type_name(process->action), global.main->error.notable); + fl_print_format("%[' while attempting to execute rule.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + return F_status_set_error(F_parameter); + } + + f_status_t status = F_none; + f_status_t status_lock = F_none; + + process->cache.action.name_action.used = 0; + process->cache.action.name_item.used = 0; + process->cache.action.name_file.used = 0; + + status = f_string_append(controller_rules_s, controller_rules_s_length, &process->cache.action.name_file); + + if (F_status_is_error_not(status)) { + status = f_string_append(f_path_separator_s, F_path_separator_s_length, &process->cache.action.name_file); + } + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, process->cache.action, F_status_set_fine(status), "f_string_append", F_true, F_true); + + return status; + } + + status = f_string_dynamic_append(process->rule.alias, &process->cache.action.name_file); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, process->cache.action, F_status_set_fine(status), "f_string_dynamic_append", F_true, F_true); + + return status; + } + + status = f_string_append(F_path_extension_separator_s, F_path_extension_separator_s_length, &process->cache.action.name_file); + + if (F_status_is_error_not(status)) { + status = f_string_append(controller_rule_s, controller_rule_s_length, &process->cache.action.name_file); + } + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, process->cache.action, F_status_set_fine(status), "f_string_append", F_true, F_true); + + return status; + } + + status = f_string_dynamic_terminate_after(&process->cache.action.name_file); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, process->cache.action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, F_true); + + return status; + } + + if ((process->options & controller_process_option_simulate_d) && (process->options & controller_process_option_validate_d)) { + controller_rule_validate(global, process->rule, process->action, process->options, &process->cache); + } + + f_array_length_t i = 0; + + { + f_array_length_t j = 0; + f_array_length_t k = 0; + f_array_length_t id_rule = 0; + f_array_length_t id_dependency = 0; + + bool found = F_false; + + controller_process_t *dependency = 0; + + uint8_t options_process = 0; + + const f_string_t strings[3] = { + "needed", + "wanted", + "wished for", + }; + + f_string_dynamics_t empty = f_string_dynamics_t_initialize; + f_string_dynamics_t *dynamics[3] = { &empty, &empty, &empty }; + + if (process->action) { + + for (i = 0; i < process->rule.ons.used; ++i) { + + if (process->rule.ons.array[i].action == process->action) { + dynamics[0] = &process->rule.ons.array[i].need; + dynamics[1] = &process->rule.ons.array[i].want; + dynamics[2] = &process->rule.ons.array[i].wish; + + break; + } + } // for + } + + // i==0 is need, i==1 is want, i==2 is wish. + // loop through all dependencies: wait for depedency, execute dependency, fail due to missing required dependency, or skip unrequired missing dependencies. + for (i = 0; i < 3 && controller_thread_is_enabled_process(process, global.thread); ++i) { + + for (j = 0; j < dynamics[i]->used && controller_thread_is_enabled_process(process, global.thread); ++j) { + + id_dependency = 0; + dependency = 0; + found = F_false; + + status_lock = controller_lock_read_process(process, global.thread, &global.thread->lock.process); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + } + else { + status = controller_process_prepare_process_type(global, process->type, process->action, dynamics[i]->array[j], &id_dependency); + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_lock) { + if (!controller_thread_is_enabled_process_type(process->type, global.thread)) { + return F_status_set_error(F_interrupt); + } + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_item_print_error_rule_not_loaded(global.main->error, dynamics[i]->array[j].string); + controller_rule_print_error_cache(global.main->error, process->cache.action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + return status; + } + + status = F_true; + } + + if (status == F_true) { + found = F_true; + + dependency = global.thread->processs.array[id_dependency]; + + status_lock = controller_lock_read_process(process, global.thread, &dependency->active); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + status = F_false; + dependency = 0; + + f_thread_unlock(&global.thread->lock.process); + } + else { + f_thread_unlock(&global.thread->lock.process); + + status_lock = controller_lock_read_process(process, global.thread, &global.thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + status = F_false; + } + else { + status = controller_rule_find(dynamics[i]->array[j], global.setting->rules, &id_rule); + + f_thread_unlock(&global.thread->lock.rule); + } + } + } + else { + f_thread_unlock(&global.thread->lock.process); + } + + if (status != F_true) { + found = F_false; + id_rule = 0; + + if (i == 0) { + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_item_print_error_need_want_wish(global.main->error, strings[i], dynamics[i]->array[j].string, "was not found"); + controller_rule_print_error_cache(global.main->error, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + + status = F_status_set_error(F_found_not); + + if (!(process->options & controller_process_option_simulate_d)) { + if (dependency) { + f_thread_unlock(&dependency->active); + } + + break; + } + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + controller_rule_item_print_error_need_want_wish(global.main->warning, strings[i], dynamics[i]->array[j].string, "was not found"); + + controller_rule_print_error_cache(global.main->warning, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + } + } + else if (found) { + status_lock = controller_lock_read_process(process, global.thread, &global.thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + found = F_false; + status = status_lock; + } + } + + if (found) { + + // the dependency may have write locks, which needs to be avoided, so copy the alias from the rule. + char alias_other_buffer[global.setting->rules.array[id_rule].alias.used + 1]; + + memcpy(alias_other_buffer, global.setting->rules.array[id_rule].alias.string, global.setting->rules.array[id_rule].alias.used); + alias_other_buffer[global.setting->rules.array[id_rule].alias.used] = 0; + + const f_string_static_t alias_other = macro_f_string_static_t_initialize(alias_other_buffer, global.setting->rules.array[id_rule].alias.used); + + f_thread_unlock(&global.thread->lock.rule); + + status_lock = controller_lock_read_process(process, global.thread, &dependency->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + status = status_lock; + } + else if (dependency->state == controller_process_state_active_e || dependency->state == controller_process_state_busy_e) { + f_thread_unlock(&dependency->lock); + + status = controller_process_wait(global, dependency); + + if (F_status_is_error(status) && !(process->options & controller_process_option_simulate_d)) break; + + status = dependency->rule.status[process->action]; + } + else { + status_lock = controller_lock_read_process(process, global.thread, &global.thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + f_thread_unlock(&dependency->lock); + + status = status_lock; + } + else if (controller_rule_status_is_available(process->action, global.setting->rules.array[id_rule])) { + f_thread_unlock(&global.thread->lock.rule); + f_thread_unlock(&dependency->lock); + + options_process = 0; + + if (global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e) { + options_process |= controller_process_option_simulate_d; + } + + if (process->options & controller_process_option_validate_d) { + options_process |= controller_process_option_validate_d; + } + + // Synchronously execute dependency. + status = controller_rule_process_begin(global, 0, alias_other, process->action, options_process, process->type, process->stack, dependency->cache); + + if (status == F_child || F_status_set_fine(status) == F_interrupt) { + f_thread_unlock(&dependency->active); + + break; + } + + if (F_status_is_error(status)) { + if (i == 0 || i == 1 || F_status_set_fine(status) == F_memory_not) { + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_item_print_error_need_want_wish(global.main->error, strings[i], alias_other_buffer, "failed during execution"); + controller_rule_print_error_cache(global.main->error, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + + if (!(dependency->options & controller_process_option_simulate_d) || F_status_set_fine(status) == F_memory_not) { + f_thread_unlock(&dependency->active); + + break; + } + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + controller_rule_item_print_error_need_want_wish(global.main->warning, strings[i], alias_other_buffer, "failed during execution"); + + controller_rule_print_error_cache(global.main->warning, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + } + } + } + else { + status = global.setting->rules.array[id_rule].status[process->action]; + + f_thread_unlock(&global.thread->lock.rule); + f_thread_unlock(&dependency->lock); + } + } + + if (!controller_thread_is_enabled_process(process, global.thread)) { + f_thread_unlock(&dependency->active); + + break; + } + + if (F_status_is_error_not(status_lock)) { + status_lock = controller_lock_read_process(process, global.thread, &global.thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + } + } + + if (F_status_is_error(status_lock)) { + if (F_status_is_error(status_lock)) { + controller_rule_item_print_error_need_want_wish(global.main->error, strings[i], alias_other_buffer, "due to lock failure"); + } + + status = status_lock; + } + else if (controller_rule_status_is_error(process->action, global.setting->rules.array[id_rule])) { + f_thread_unlock(&global.thread->lock.rule); + + if (i == 0 || i == 1) { + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_item_print_error_need_want_wish(global.main->error, strings[i], alias_other_buffer, "is in a failed state"); + + controller_rule_print_error_cache(global.main->error, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + + status = F_status_set_error(F_found_not); + + if (!(dependency->options & controller_process_option_simulate_d)) { + f_thread_unlock(&dependency->active); + + break; + } + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + controller_rule_item_print_error_need_want_wish(global.main->warning, strings[i], alias_other_buffer, "is in a failed state"); + + controller_rule_print_error_cache(global.main->warning, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + } + } + else { + f_thread_unlock(&global.thread->lock.rule); + } + } + + if (dependency) { + f_thread_unlock(&dependency->active); + } + } // for + + if (status == F_child || F_status_set_fine(status) == F_interrupt) break; + + if (F_status_is_error(status) && !(process->options & controller_process_option_simulate_d)) break; + } // for + } + + if (status == F_child || F_status_set_fine(status) == F_interrupt) { + return status; + } + + if (!controller_thread_is_enabled_process(process, global.thread)) { + return F_status_set_error(F_interrupt); + } + + if ((process->options & controller_process_option_wait_d) && F_status_is_error_not(status) && (process->options & controller_process_option_validate_d)) { + status_lock = controller_rule_wait_all_process_type(global, process->type, F_false, process); + + if (F_status_set_fine(status_lock) == F_interrupt) { + return status_lock; + } + } + + if (!(process->options & controller_process_option_validate_d) && F_status_is_error_not(status)) { + + // find at least one of the requested action when the rule is required. + if (process->options & controller_process_option_require_d) { + bool missing = F_true; + + f_array_length_t j = 0; + + for (i = 0; i < process->rule.items.used; ++i) { + + for (j = 0; j < process->rule.items.array[i].actions.used; ++j) { + + if (process->rule.items.array[i].actions.array[j].type == process->action) { + missing = F_false; + break; + } + } // for + } // for + + if (missing) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + if (process->rule.items.used) { + fl_print_format("%c%[%SThe rule '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, process->rule.name, global.main->error.notable); + fl_print_format("%[' has no '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%q%]", global.main->error.to.stream, global.main->error.notable, controller_rule_action_type_name(process->action), global.main->error.notable); + fl_print_format("%[' action to execute.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + } + else { + fl_print_format("%c%[%SThe rule '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, process->rule.name, global.main->error.notable); + fl_print_format("%[ has no known '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s %s%]", global.main->error.to.stream, global.main->error.notable, controller_rule_s, controller_type_s, global.main->error.notable); + fl_print_format("%[' (such as '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_command_s, global.main->error.notable); + fl_print_format("%[', '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_service_s, global.main->error.notable); + fl_print_format("%[', '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_script_s, global.main->error.notable); + fl_print_format("%[', or '%]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%s%]", global.main->error.to.stream, global.main->error.notable, controller_utility_s, global.main->error.notable); + fl_print_format("%[') to execute.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + } + + controller_rule_print_error_cache(global.main->error, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + status = F_status_set_error(F_parameter); + } + } + + if (F_status_is_error_not(status)) { + status = controller_rule_execute(global, process->action, process->options, process); + + if (status == F_child || F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock) { + return status; + } + + if (F_status_is_error(status)) { + controller_rule_item_print_error(global.thread, global.main->error, process->cache.action, F_true, F_status_set_fine(status)); + } + } + } + + f_array_length_t id_rule = 0; + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_write_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + if (F_status_set_fine(status) != F_interrupt) { + status = controller_lock_read_process(process, global.thread, &process->lock); + + if (F_status_is_error_not(status)) { + return status_lock; + } + } + + return F_status_set_error(F_lock); + } + + if (F_status_is_error(status)) { + process->rule.status[process->action] = controller_status_simplify_error(F_status_set_fine(status)); + } + else { + process->rule.status[process->action] = status; + } + + status_lock = controller_lock_write_process(process, global.thread, &global.thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + f_thread_unlock(&process->lock); + + status = controller_lock_read_process(process, global.thread, &process->lock); + + if (F_status_is_error_not(status)) { + return status_lock; + } + + return F_status_set_error(F_lock); + } + + // Update the global rule status, which is stored separately from the rule status for this process. + if (controller_rule_find(process->rule.alias, global.setting->rules, &id_rule) == F_true) { + controller_rule_t *rule = &global.setting->rules.array[id_rule]; + + rule->status[process->action] = process->rule.status[process->action]; + + f_array_length_t j = 0; + + controller_rule_item_t *rule_item = 0; + controller_rule_action_t *rule_action = 0; + + // @todo implement a "version" counter and detect if the rule version is different before attempting update. + // Copy all rule item action statuses from the rule process to the rule. + for (i = 0; i < rule->items.used; ++i) { + + rule_item = &rule->items.array[i]; + + for (j = 0; j < rule_item->actions.used; ++j) { + rule_item->actions.array[j].status = process->rule.items.array[i].actions.array[j].status; + } // for + } // for + } + + f_thread_unlock(&global.thread->lock.rule); + f_thread_unlock(&process->lock); + + status_lock = controller_lock_read_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + return F_status_set_error(F_lock); + } + + return process->rule.status[process->action]; + } +#endif // _di_controller_rule_process_ + +#ifndef _di_controller_rule_process_begin_ + f_status_t controller_rule_process_begin(const controller_global_t global, const uint8_t options_force, const f_string_static_t alias_rule, const uint8_t action, const uint8_t options, const uint8_t type, const f_array_lengths_t stack, const controller_cache_t cache) { + + if (!controller_thread_is_enabled_process_type(type, global.thread)) { + return F_status_set_error(F_interrupt); + } + + f_status_t status = F_none; + f_status_t status_lock = F_none; + + controller_process_t *process = 0; + + status = controller_lock_read_process_type(type, global.thread, &global.thread->lock.process); + + if (F_status_is_error(status)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status), F_true, global.thread); + + return status; + } + + { + f_array_length_t at = 0; + + status = controller_process_prepare(global, type != controller_process_type_exit_e, action, alias_rule, &at); + + if (F_status_is_error(status)) { + f_thread_unlock(&global.thread->lock.process); + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_item_print_error_rule_not_loaded(global.main->error, alias_rule.string); + controller_rule_print_error_cache(global.main->error, cache.action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + return status; + } + + process = global.thread->processs.array[at]; + + status = controller_lock_read_process_type(type, global.thread, &process->active); + + if (F_status_is_error(status)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status), F_true, global.thread); + controller_rule_item_print_error(global.thread, global.main->error, cache.action, F_false, F_status_set_fine(status)); + + f_thread_unlock(&global.thread->lock.process); + + return status; + } + + status_lock = controller_lock_write_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + f_thread_unlock(&process->active); + f_thread_unlock(&global.thread->lock.process); + + return status_lock; + } + + // once a write lock on the process is achieved, it is safe to unlock the global process read lock. + f_thread_unlock(&global.thread->lock.process); + + // if the process is already running, then there is nothing to do. + if (process->state == controller_process_state_active_e || process->state == controller_process_state_busy_e) { + f_thread_unlock(&process->lock); + f_thread_unlock(&process->active); + + return F_busy; + } + + // the thread is done, so close the thread. + if (process->state == controller_process_state_done_e) { + controller_thread_join(&process->id_thread); + + f_thread_mutex_lock(&process->wait_lock); + f_thread_condition_signal_all(&process->wait); + f_thread_mutex_unlock(&process->wait_lock); + } + + process->id = at; + } + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_write_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + f_thread_unlock(&process->active); + + return status_lock; + } + + process->state = controller_process_state_active_e; + process->action = action; + process->options = options; + process->type = type; + + macro_f_time_spec_t_clear(process->cache.timestamp) + macro_f_string_range_t_clear(process->cache.range_action) + + process->cache.ats.used = 0; + process->cache.stack.used = 0; + process->cache.comments.used = 0; + process->cache.delimits.used = 0; + process->cache.content_action.used = 0; + process->cache.content_actions.used = 0; + process->cache.content_items.used = 0; + process->cache.object_actions.used = 0; + process->cache.object_items.used = 0; + process->cache.buffer_file.used = 0; + process->cache.buffer_item.used = 0; + process->cache.buffer_path.used = 0; + process->cache.action.line_action = cache.action.line_action; + process->cache.action.line_item = cache.action.line_item; + process->cache.action.name_action.used = 0; + process->cache.action.name_file.used = 0; + process->cache.action.name_item.used = 0; + process->cache.action.generic.used = 0; + + process->stack.used = 0; + + process->main_data = (void *) global.main; + process->main_setting = (void *) global.setting; + process->main_thread = (void *) global.thread; + + if (F_status_is_error_not(status) && stack.used) { + if (process->stack.size < stack.used) { + status = f_type_array_lengths_resize(stack.used, &process->stack); + } + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_type_array_lengths_resize", F_true); + } + else { + for (f_array_length_t i = 0; i < stack.used; ++i) { + process->stack.array[i] = stack.array[i]; + } // for + + process->stack.used = stack.used; + } + } + + if (F_status_is_error_not(status)) { + status = f_string_dynamic_append(cache.action.name_action, &process->cache.action.name_action); + + if (F_status_is_error_not(status)) { + status = f_string_dynamic_append(cache.action.name_file, &process->cache.action.name_file); + } + + if (F_status_is_error_not(status)) { + status = f_string_dynamic_append(cache.action.name_item, &process->cache.action.name_item); + } + else { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_append", F_true); + } + } + + f_thread_unlock(&process->lock); + + if (F_status_is_error_not(status)) { + if (process->action && (options_force & controller_process_option_asynchronous_d)) { + if (process->type == controller_process_type_exit_e) { + 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_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_thread_create", F_true); + } + } + else { + status = controller_rule_process_do(options_force, process); + + if (status == F_child || F_status_set_fine(status) == F_interrupt) { + f_thread_unlock(&process->active); + + return status; + } + } + } + + if (!action || F_status_is_error(status) && (process->state == controller_process_state_active_e || process->state == controller_process_state_busy_e)) { + { + status_lock = controller_lock_write_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + f_thread_unlock(&process->active); + + return status_lock; + } + } + + if (!action || (options_force & controller_process_option_asynchronous_d)) { + process->state = controller_process_state_done_e; + } + else { + process->state = controller_process_state_idle_e; + } + + f_thread_mutex_lock(&process->wait_lock); + f_thread_condition_signal_all(&process->wait); + f_thread_mutex_unlock(&process->wait_lock); + + f_thread_unlock(&process->lock); + } + + f_thread_unlock(&process->active); + + if (F_status_is_error(status)) { + return status; + } + + return F_none; + } +#endif // _di_controller_rule_process_begin_ + +#ifndef _di_controller_rule_process_do_ + f_status_t controller_rule_process_do(const uint8_t options_force, controller_process_t * const process) { + + f_status_t status_lock = F_none; + + controller_global_t global = macro_controller_global_t_initialize((controller_main_t *) process->main_data, (controller_setting_t *) process->main_setting, (controller_thread_t *) process->main_thread); + + // The process and active locks shall be held for the duration of this processing (aside from switching between read to/from write). + if (options_force & controller_process_option_asynchronous_d) { + status_lock = controller_lock_read_process(process, global.thread, &process->active); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + return status_lock; + } + } + + status_lock = controller_lock_read_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status_lock; + } + + f_status_t status = F_none; + + f_array_length_t id_rule = 0; + + const f_array_length_t used_original_stack = process->stack.used; + + status_lock = controller_lock_read_process(process, global.thread, &global.thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + f_thread_unlock(&process->lock); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status_lock; + } + + if (controller_rule_find(process->rule.alias, global.setting->rules, &id_rule) == F_true) { + f_thread_unlock(&process->lock); + + status_lock = controller_lock_write_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + f_thread_unlock(&global.thread->lock.rule); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status_lock; + } + + controller_rule_delete_simple(&process->rule); + + status = controller_rule_copy(global.setting->rules.array[id_rule], &process->rule); + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_read_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + f_thread_unlock(&global.thread->lock.rule); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status_lock; + } + + f_thread_unlock(&global.thread->lock.rule); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_rule_copy", F_true); + } + else if (!process->action) { + + // This is a "consider" Action, so do not actually execute the rule. + f_thread_unlock(&process->lock); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return F_process_not; + } + else { + for (f_array_length_t i = 0; i < process->stack.used && controller_thread_is_enabled_process(process, global.thread); ++i) { + + if (process->stack.array[i] == id_rule) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SThe rule '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, process->rule.alias, global.main->error.notable); + fl_print_format("%[' is already on the execution dependency stack, this recursion is prohibited.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, process->cache.action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + // Never continue on circular recursion errors even in simulate mode. + status = F_status_set_error(F_recurse); + + break; + } + } // for + + if (!controller_thread_is_enabled_process(process, global.thread)) { + f_thread_unlock(&process->lock); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return F_status_set_error(F_interrupt); + } + + if (F_status_is_error_not(status)) { + status = f_type_array_lengths_increase(controller_common_allocation_small_d, &process->stack); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_type_array_lengths_increase", F_true); + } + else { + f_thread_unlock(&process->lock); + + status_lock = controller_lock_write_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status_lock; + } + + process->stack.array[process->stack.used++] = id_rule; + + f_thread_unlock(&process->lock); + + status_lock = controller_lock_read_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status_lock; + } + } + } + } + + if (F_status_is_error_not(status)) { + status = controller_rule_process(global, process); + } + } + else { + f_thread_unlock(&global.thread->lock.rule); + + status = F_status_set_error(F_found_not); + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_item_print_error_rule_not_loaded(global.main->error, process->rule.alias.string); + controller_rule_print_error_cache(global.main->error, process->cache.action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + } + + if (status == F_child) { + f_thread_unlock(&process->lock); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status; + } + + status_lock = controller_lock_write_process(process, global.thread, &global.thread->lock.rule); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + if (F_status_set_fine(status) != F_lock) { + f_thread_unlock(&process->lock); + } + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status_lock; + } + + if (F_status_set_fine(status) == F_lock) { + if (controller_rule_find(process->rule.alias, global.setting->rules, &id_rule) == F_true) { + global.setting->rules.array[id_rule].status[process->action] = status; + } + } + + f_thread_unlock(&global.thread->lock.rule); + + if (F_status_set_fine(status) != F_lock) { + f_thread_unlock(&process->lock); + } + + if (F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_lock && !controller_thread_is_enabled_process(process, global.thread)) { + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return F_status_set_error(F_interrupt); + } + + status_lock = controller_lock_write_process(process, global.thread, &process->lock); + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + return status_lock; + } + + if (options_force & controller_process_option_asynchronous_d) { + process->state = controller_process_state_done_e; + } + else { + process->state = controller_process_state_idle_e; + } + + process->stack.used = used_original_stack; + + // inform all things waiting that the process has finished running. + f_thread_mutex_lock(&process->wait_lock); + f_thread_condition_signal_all(&process->wait); + f_thread_mutex_unlock(&process->wait_lock); + + f_thread_unlock(&process->lock); + + if (options_force & controller_process_option_asynchronous_d) { + f_thread_unlock(&process->active); + } + + if (controller_thread_is_enabled_process(process, global.thread)) { + return status; + } + + return F_status_set_error(F_interrupt); + } +#endif // _di_controller_rule_process_do_ + +#ifndef _di_controller_rule_read_ + f_status_t controller_rule_read(const controller_global_t global, const bool is_normal, const f_string_static_t alias, controller_cache_t * const cache, controller_entry_t * const entry, controller_rule_t * const rule) { + + f_status_t status = F_none; + + bool for_item = F_true; + + for (f_array_length_t i = 0; i < controller_rule_action_type__enum_size_e; ++i) { + rule->status[i] = F_known_not; + } // for + + rule->timeout_kill = entry->timeout_kill ? entry->timeout_kill : 0; + rule->timeout_start = entry->timeout_start ? entry->timeout_start : 0; + rule->timeout_stop = entry->timeout_stop ? entry->timeout_stop : 0; + + rule->has = 0; + rule->group = 0; + rule->user = 0; + rule->nice = 0; + + macro_f_time_spec_t_clear(rule->timestamp); + + rule->alias.used = 0; + rule->name.used = 0; + rule->path.used = 0; + rule->script.used = 0; + + rule->define.used = 0; + rule->parameter.used = 0; + rule->environment.used = 0; + + rule->affinity.used = 0; + + if (rule->capability) { + f_capability_delete(&rule->capability); + rule->capability = 0; + } + + for (f_array_length_t i = 0; i < rule->cgroup.groups.size; ++i) { + rule->cgroup.groups.array[i].used = 0; + } // for + + rule->cgroup.as_new = F_false; + rule->cgroup.path.used = 0; + rule->cgroup.groups.used = 0; + + macro_f_control_group_t_clear(rule->cgroup); + + rule->groups.used = 0; + rule->limits.used = 0; + + rule->scheduler.policy = 0; + rule->scheduler.priority = 0; + + for (f_array_length_t i = 0; i < rule->ons.size; ++i) { + rule->ons.array[i].need.used = 0; + rule->ons.array[i].want.used = 0; + rule->ons.array[i].wish.used = 0; + } // for + + rule->ons.used = 0; + rule->items.used = 0; + + cache->action.line_item = 0; + cache->action.line_action = 0; + + cache->range_action.start = 1; + cache->range_action.stop = 0; + + cache->comments.used = 0; + cache->delimits.used = 0; + + cache->buffer_file.used = 0; + cache->buffer_item.used = 0; + cache->buffer_path.used = 0; + + for (f_array_length_t i = 0; i < cache->content_items.used; ++i) { + cache->content_items.array[i].used = 0; + } // for + + cache->content_items.used = 0; + cache->object_items.used = 0; + + cache->action.name_action.used = 0; + cache->action.name_file.used = 0; + cache->action.name_item.used = 0; + + status = f_string_dynamic_append_nulless(alias, &rule->alias); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_append_nulless", F_true); + } + else { + status = f_string_dynamic_terminate_after(&rule->alias); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + } + else { + status = controller_file_load(global, F_true, controller_rules_s, rule->alias, controller_rule_s, controller_rules_s_length, controller_rule_s_length, cache); + } + } + + if (F_status_is_error_not(status)) { + rule->timestamp = cache->timestamp; + + if (cache->buffer_file.used) { + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_normal, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + f_string_range_t range = macro_f_string_range_t_initialize(cache->buffer_file.used); + + status = fll_fss_basic_list_read(cache->buffer_file, state, &range, &cache->object_items, &cache->content_items, &cache->delimits, 0, &cache->comments); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fll_fss_basic_list_read", F_true); + } + else { + status = fl_fss_apply_delimit(cache->delimits, &cache->buffer_file); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "fl_fss_apply_delimit", F_true); + } + } + } + } + + if (F_status_is_error_not(status) && cache->object_items.used) { + status = controller_rule_items_increase_by(cache->object_items.used, &rule->items); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_rule_items_increase_by", F_true); + } + else { + f_array_length_t i = 0; + f_array_length_t j = 0; + + for (; i < cache->object_items.used; ++i) { + + cache->action.line_item = 0; + cache->action.line_action = 0; + + cache->range_action.start = 1; + cache->range_action.stop = 0; + + cache->comments.used = 0; + cache->delimits.used = 0; + + cache->content_action.used = 0; + + for (j = 0; j < cache->content_actions.used; ++j) { + cache->content_actions.array[j].used = 0; + } // for + + cache->content_actions.used = 0; + cache->object_actions.used = 0; + + cache->buffer_item.used = 0; + cache->buffer_path.used = 0; + + cache->action.name_action.used = 0; + cache->action.name_item.used = 0; + + for_item = F_true; + + status = f_fss_count_lines(cache->buffer_file, cache->object_items.array[i].start, &cache->action.line_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_fss_count_lines", F_true); + + break; + } + + rule->items.array[rule->items.used].line = ++cache->action.line_item; + + status = controller_dynamic_rip_nulless_terminated(cache->buffer_file, cache->object_items.array[i], &cache->action.name_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_dynamic_rip_nulless_terminated", F_true); + + break; + } + + if (fl_string_dynamic_compare_string(controller_setting_s, cache->action.name_item, controller_setting_s_length) == F_equal_to) { + rule->items.array[rule->items.used].type = 0; + } + else if (fl_string_dynamic_compare_string(controller_command_s, cache->action.name_item, controller_command_s_length) == F_equal_to) { + rule->items.array[rule->items.used].type = controller_rule_item_type_command_e; + } + else if (fl_string_dynamic_compare_string(controller_script_s, cache->action.name_item, controller_script_s_length) == F_equal_to) { + rule->items.array[rule->items.used].type = controller_rule_item_type_script_e; + } + else if (fl_string_dynamic_compare_string(controller_service_s, cache->action.name_item, controller_service_s_length) == F_equal_to) { + rule->items.array[rule->items.used].type = controller_rule_item_type_service_e; + } + else if (fl_string_dynamic_compare_string(controller_utility_s, cache->action.name_item, controller_utility_s_length) == F_equal_to) { + rule->items.array[rule->items.used].type = controller_rule_item_type_utility_e; + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SUnknown rule item '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, global.main->warning.context); + fl_print_format("%[%Q%]", global.main->warning.to.stream, global.main->warning.notable, cache->action.name_item, global.main->warning.notable); + fl_print_format("%['.%]%c", global.main->warning.to.stream, global.main->warning.context, global.main->warning.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->warning, cache->action, F_true); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + + continue; + } + + status = f_string_dynamic_partial_append(cache->buffer_file, cache->content_items.array[i].array[0], &cache->buffer_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_partial_append", F_true); + + break; + } + + status = f_string_dynamic_terminate_after(&cache->buffer_item); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true); + + break; + } + + if (rule->items.array[rule->items.used].type) { + + // Initialize the item with settings. + rule->items.array[rule->items.used].with = 0; + + if (entry->session == controller_entry_session_new_e) { + rule->items.array[rule->items.used].with |= controller_with_session_new_d; + } + else { + rule->items.array[rule->items.used].with |= controller_with_session_same_d; + } + + status = controller_rule_item_read(global, is_normal, cache, &rule->items.array[rule->items.used]); + if (F_status_is_error(status)) break; + + ++rule->items.used; + } + else { + for_item = F_false; + + status = controller_rule_setting_read(global, is_normal, *global.setting, cache, rule); + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_memory_not) { + break; + } + } + } + } // for + } + } + + if (F_status_is_error(status)) { + controller_rule_item_print_error(global.thread, global.main->error, cache->action, for_item, F_status_set_fine(status)); + + rule->status[0] = controller_status_simplify_error(F_status_set_fine(status)); + + return rule->status[0]; + } + + return F_none; + } +#endif // _di_controller_rule_read_ + +#ifndef _di_controller_rule_setting_read_ + f_status_t controller_rule_setting_read(const controller_global_t global, const bool is_normal, const controller_setting_t setting, controller_cache_t * const cache, controller_rule_t * const rule) { + + f_status_t status = F_none; + f_status_t status_return = F_none; + + f_string_range_t range = macro_f_string_range_t_initialize(cache->buffer_item.used); + f_string_range_t range2 = f_string_range_t_initialize; + + { + controller_state_interrupt_t custom = macro_controller_state_interrupt_t_initialize(is_normal, global.thread); + f_state_t state = macro_f_state_t_initialize(controller_common_allocation_large_d, controller_common_allocation_small_d, 0, &controller_thread_signal_state_fss, 0, (void *) &custom, 0); + + status = fll_fss_extended_read(cache->buffer_item, state, &range, &cache->object_actions, &cache->content_actions, 0, 0, &cache->delimits, 0); + } + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "fll_fss_extended_read", F_true, F_false); + + return status; + } + + f_array_length_t path_original_length = 0; + f_string_dynamic_t *setting_value = 0; + f_string_dynamics_t *setting_values = 0; + f_string_maps_t *setting_maps = 0; + + f_array_length_t i = 0; + f_array_length_t j = 0; + uint8_t type = 0; + uint8_t action = 0; + bool empty_disallow = F_true; + + // Save the current name item and line number to restore on return. + const f_array_length_t line_item = cache->action.line_item; + const f_array_length_t length_name_item = cache->action.name_item.used; + + char name_item[length_name_item]; + name_item[length_name_item] = 0; + + memcpy(name_item, cache->action.name_item.string, length_name_item); + + for (; i < cache->content_actions.used; ++i, type = 0) { + + // The name_action is used to store all setting values for a single setting. + cache->action.name_action.used = 0; + + // The name_item is used to store the setting name. + cache->action.name_item.used = 0; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->object_actions.array[i], &cache->action.name_item); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, F_false); + } + else { + status = f_string_dynamic_terminate_after(&cache->action.name_item); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, F_false); + } + } + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + empty_disallow = F_true; + + if (fl_string_dynamic_compare_string(controller_affinity_s, cache->action.name_item, controller_affinity_s_length) == F_equal_to) { + type = controller_rule_setting_type_affinity_e; + } + else if (fl_string_dynamic_compare_string(controller_capability_s, cache->action.name_item, controller_capability_s_length) == F_equal_to) { + type = controller_rule_setting_type_capability_e; + } + else if (fl_string_dynamic_compare_string(controller_cgroup_s, cache->action.name_item, controller_cgroup_s_length) == F_equal_to) { + type = controller_rule_setting_type_cgroup_e; + } + else if (fl_string_dynamic_compare_string(controller_define_s, cache->action.name_item, controller_define_s_length) == F_equal_to) { + type = controller_rule_setting_type_define_e; + } + else if (fl_string_dynamic_compare_string(controller_environment_s, cache->action.name_item, controller_environment_s_length) == F_equal_to) { + type = controller_rule_setting_type_environment_e; + empty_disallow = F_false; + } + else if (fl_string_dynamic_compare_string(controller_group_s, cache->action.name_item, controller_group_s_length) == F_equal_to) { + type = controller_rule_setting_type_group_e; + } + else if (fl_string_dynamic_compare_string(controller_limit_s, cache->action.name_item, controller_limit_s_length) == F_equal_to) { + type = controller_rule_setting_type_limit_e; + } + else if (fl_string_dynamic_compare_string(controller_name_s, cache->action.name_item, controller_name_s_length) == F_equal_to) { + type = controller_rule_setting_type_name_e; + } + else if (fl_string_dynamic_compare_string(controller_nice_s, cache->action.name_item, controller_nice_s_length) == F_equal_to) { + type = controller_rule_setting_type_nice_e; + } + else if (fl_string_dynamic_compare_string(controller_on_s, cache->action.name_item, controller_on_s_length) == F_equal_to) { + type = controller_rule_setting_type_on_e; + } + else if (fl_string_dynamic_compare_string(controller_parameter_s, cache->action.name_item, controller_parameter_s_length) == F_equal_to) { + type = controller_rule_setting_type_parameter_e; + } + else if (fl_string_dynamic_compare_string(controller_path_s, cache->action.name_item, controller_path_s_length) == F_equal_to) { + type = controller_rule_setting_type_path_e; + } + else if (fl_string_dynamic_compare_string(controller_scheduler_s, cache->action.name_item, controller_scheduler_s_length) == F_equal_to) { + type = controller_rule_setting_type_scheduler_e; + } + else if (fl_string_dynamic_compare_string(controller_script_s, cache->action.name_item, controller_script_s_length) == F_equal_to) { + type = controller_rule_setting_type_script_e; + } + else if (fl_string_dynamic_compare_string(controller_timeout_s, cache->action.name_item, controller_timeout_s_length) == F_equal_to) { + type = controller_rule_setting_type_timeout_e; + } + else if (fl_string_dynamic_compare_string(controller_user_s, cache->action.name_item, controller_user_s_length) == F_equal_to) { + type = controller_rule_setting_type_user_e; + } + else { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SUnknown rule setting '%]", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, global.main->warning.context); + fl_print_format("%[%Q%]", global.main->warning.to.stream, global.main->warning.notable, cache->action.name_item, global.main->warning.notable); + fl_print_format("%['.%]%c", global.main->warning.to.stream, global.main->warning.context, global.main->warning.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->warning, cache->action, F_false); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + + continue; + } + + if (cache->content_actions.array[i].used) { + range2.start = cache->content_actions.array[i].array[0].start; + range2.stop = cache->content_actions.array[i].array[cache->content_actions.array[i].used - 1].stop; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, range2, &cache->action.name_action); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, F_false); + } + else { + status = f_string_dynamic_terminate_after(&cache->action.name_action); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, F_false); + } + } + + if (F_status_is_error(status)) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + continue; + } + } + else { + if (empty_disallow) { + if (global.main->warning.verbosity == f_console_verbosity_debug_e) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->warning.to, global.thread); + + fl_print_format("%c%[%SEmpty rule setting.%]%c", global.main->warning.to.stream, f_string_eol_s[0], global.main->warning.context, global.main->warning.prefix, global.main->warning.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->warning, cache->action, F_false); + + controller_unlock_print_flush(global.main->warning.to, global.thread); + } + + continue; + } + } + + if (type == controller_rule_setting_type_affinity_e) { + // @todo use sched_getaffinity() to get the available cpus and do not add an invalid cpu to the affinity array. + + if (!cache->content_actions.array[i].used) { + controller_rule_setting_read_print_error(global.main->error, "requires one or more Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + rule->affinity.used = 0; + + f_number_signed_t number = 0; + + for (j = 0; j < cache->content_actions.array[i].used; ++j, number = 0) { + + // @todo this needs to be in a function such as f_int32s_increase(). + if (rule->affinity.used + 1 > rule->affinity.size) { + f_array_length_t size = rule->affinity.used + controller_common_allocation_small_d; + + if (size > F_array_length_t_size_d) { + if (rule->affinity.used + 1 > F_array_length_t_size_d) { + status = F_status_set_error(F_array_too_large); + } + else { + size = F_array_length_t_size_d; + } + } + + if (F_status_is_error_not(status)) { + macro_f_int32s_t_resize(status, rule->affinity, size); + } + } + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "macro_f_int32s_t_resize", F_true, F_false); + break; + } + + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, cache->content_actions.array[i].array[j], &number); + + if (F_status_set_fine(status) == F_number_positive) { + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, controller_range_after_number_sign(cache->buffer_item, cache->content_actions.array[i].array[j]), &number); + + // Restore error on parameter problem. + if (F_status_set_fine(status) == F_parameter) { + status = F_status_set_error(F_number_positive); + } + } + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_data_not || status == F_number || status == F_number_overflow || status == F_number_underflow || status == F_number_negative || status == F_number_decimal) { + if (status == F_number_underflow) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an unsupported number", cache->content_actions.array[i].array[j], ", the number is too small for this system", i, line_item, global.thread, cache); + } + else if (status == F_number_overflow || status == F_number_positive) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an unsupported number", cache->content_actions.array[i].array[j], ", the number is too large for this system", i, line_item, global.thread, cache); + } + else { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an invalid number", cache->content_actions.array[i].array[j], ", only whole numbers are allowed for an affinity value", i, line_item, global.thread, cache); + } + + status = F_status_set_error(F_valid_not); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + } + else { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "fl_conversion_string_to_number_signed", F_true, F_false); + + status = F_status_set_error(status); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + } + + continue; + } + + rule->affinity.array[rule->affinity.used++] = number; + } // for + + controller_rule_setting_read_print_values(global, controller_affinity_s, i, cache); + + continue; + } + + if (type == controller_rule_setting_type_define_e || type == controller_rule_setting_type_parameter_e) { + if (cache->content_actions.array[i].used != 2) { + controller_rule_setting_read_print_error(global.main->error, "requires exactly two Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + if (type == controller_rule_setting_type_define_e) { + setting_maps = &rule->define; + } + else if (type == controller_rule_setting_type_parameter_e) { + setting_maps = &rule->parameter; + } + + status = f_string_maps_increase(controller_common_allocation_small_d, setting_maps); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_maps_increase", F_true, F_false); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + setting_maps->array[setting_maps->used].name.used = 0; + setting_maps->array[setting_maps->used].value.used = 0; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_actions.array[i].array[0], &setting_maps->array[setting_maps->used].name); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, F_false); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_actions.array[i].array[1], &setting_maps->array[setting_maps->used].value); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, F_false); + } + else { + status = f_string_dynamic_terminate_after(&setting_maps->array[setting_maps->used].value); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, F_false); + } + } + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + controller_rule_setting_read_print_value(global, type == controller_rule_setting_type_define_e ? controller_define_s : controller_parameter_s, 0, setting_maps->array[setting_maps->used].name, 0); + + ++setting_maps->used; + + continue; + } + + if (type == controller_rule_setting_type_cgroup_e) { + if (cache->content_actions.array[i].used < 2 || rule->has & controller_rule_has_cgroup_d) { + controller_rule_setting_read_print_error(global.main->error, "requires two or more Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + if (fl_string_dynamic_partial_compare_string(controller_existing_s, cache->buffer_item, controller_existing_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + rule->cgroup.as_new = F_false; + } + else if (fl_string_dynamic_partial_compare_string(controller_new_s, cache->buffer_item, controller_new_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + rule->cgroup.as_new = F_true; + } + else { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an unknown option", cache->content_actions.array[i].array[0], "", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + rule->cgroup.path.used = 0; + + status = f_string_dynamic_append(global.setting->path_cgroup, &rule->cgroup.path); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_append", F_true, F_false); + } + else { + rule->cgroup.groups.used = 0; + + for (j = 1; j < cache->content_actions.array[i].used; ++j) { + + status = f_string_dynamics_increase(controller_common_allocation_small_d, &rule->cgroup.groups); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamics_increase", F_true, F_false); + break; + } + + rule->cgroup.groups.array[rule->cgroup.groups.used].used = 0; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_actions.array[i].array[j], &rule->cgroup.groups.array[rule->cgroup.groups.used]); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, F_false); + break; + } + + ++rule->cgroup.groups.used; + } // for + } + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + rule->cgroup.path.used = 0; + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + rule->has |= controller_rule_has_cgroup_d; + + controller_rule_setting_read_print_values(global, controller_cgroup_s, i, cache); + + continue; + } + + if (type == controller_rule_setting_type_limit_e) { + if (cache->content_actions.array[i].used != 3) { + controller_rule_setting_read_print_error(global.main->error, "requires three Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + if (fl_string_dynamic_partial_compare_string(controller_as_s, cache->buffer_item, controller_as_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_as_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_core_s, cache->buffer_item, controller_core_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_core_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_cpu_s, cache->buffer_item, controller_cpu_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_cpu_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_data_s, cache->buffer_item, controller_data_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_data_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_fsize_s, cache->buffer_item, controller_fsize_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_fsize_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_locks_s, cache->buffer_item, controller_locks_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_locks_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_memlock_s, cache->buffer_item, controller_memlock_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_memlock_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_msgqueue_s, cache->buffer_item, controller_msgqueue_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_msgqueue_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_nice_s, cache->buffer_item, controller_nice_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_nice_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_nofile_s, cache->buffer_item, controller_nofile_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_nofile_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_nproc_s, cache->buffer_item, controller_nproc_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_nproc_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_rss_s, cache->buffer_item, controller_rss_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_rss_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_rtprio_s, cache->buffer_item, controller_rtprio_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_rtprio_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_rttime_s, cache->buffer_item, controller_rttime_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_rttime_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_sigpending_s, cache->buffer_item, controller_sigpending_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_sigpending_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_stack_s, cache->buffer_item, controller_stack_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + type = controller_resource_limit_type_stack_e; + } + else { + if (global.main->error.verbosity == f_console_verbosity_debug_e) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SUnknown resource limit type '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, cache->action.name_action, global.main->error.notable); + fl_print_format("%['.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_true); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + for (j = 0; j < rule->limits.used; ++j) { + + if (type == rule->limits.array[j].type) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + + // get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SThe resource limit type is already specified%]%c", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + status = F_status_set_error(F_valid_not); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + } + } // for + + if (F_status_is_error(status)) continue; + + macro_f_limit_sets_t_increase(status, controller_common_allocation_small_d, rule->limits); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_limit_sets_increase", F_true, F_false); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + f_number_signed_t number = 0; + + for (j = 1; j < 3; ++j, number = 0) { + + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, cache->content_actions.array[i].array[j], &number); + + if (F_status_set_fine(status) == F_number_positive) { + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, controller_range_after_number_sign(cache->buffer_item, cache->content_actions.array[i].array[j]), &number); + + // Restore error on parameter problem. + if (F_status_set_fine(status) == F_parameter) { + status = F_status_set_error(F_number_positive); + } + } + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_data_not || status == F_number || status == F_number_overflow || status == F_number_underflow || status == F_number_negative || status == F_number_positive || status == F_number_decimal) { + if (status == F_number_underflow) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an unsupported number", cache->content_actions.array[i].array[j], ", the number is too small for this system", i, line_item, global.thread, cache); + } + else if (status == F_number_overflow) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an unsupported number", cache->content_actions.array[i].array[j], ", the number is too large for this system", i, line_item, global.thread, cache); + } + else { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an unsupported number", cache->content_actions.array[i].array[j], ", only whole numbers are allowed for a resource limit value", i, line_item, global.thread, cache); + } + + status = F_status_set_error(F_valid_not); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + } + else { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "fl_conversion_string_to_number_signed", F_true, F_false); + + status = F_status_set_error(status); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + } + + break; + } + + if (j == 1) { + rule->limits.array[rule->limits.used].value.rlim_cur = number; + } + else { + rule->limits.array[rule->limits.used].value.rlim_max = number; + } + } // for + + if (F_status_is_error(status)) continue; + + rule->limits.array[rule->limits.used++].type = type; + + controller_rule_setting_read_print_values(global, controller_limit_s, i, cache); + + continue; + } + + if (type == controller_rule_setting_type_name_e || type == controller_rule_setting_type_path_e || type == controller_rule_setting_type_script_e) { + + if (type == controller_rule_setting_type_name_e) { + setting_value = &rule->name; + } + else if (type == controller_rule_setting_type_path_e) { + setting_value = &rule->path; + } + else if (type == controller_rule_setting_type_script_e) { + setting_value = &rule->script; + } + + if (setting_value->used || cache->content_actions.array[i].used != 1) { + controller_rule_setting_read_print_error(global.main->error, "requires exactly one Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + if (type == controller_rule_setting_type_name_e || type == controller_rule_setting_type_script_e) { + status = controller_dynamic_rip_nulless_terminated(cache->buffer_item, cache->content_actions.array[i].array[0], setting_value); + + if (F_status_is_error(status)) { + setting_value->used = 0; + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + status = controller_validate_has_graph(*setting_value); + + if (status == F_false || F_status_set_fine(status) == F_complete_not_utf) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + + // get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + } + + if (status == F_false) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule setting has an invalid name '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, *setting_value, global.main->error.notable); + fl_print_format("%[', there must be at least 1 graph character.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + } + else { + + // this function should only return F_complete_not_utf on error. + controller_rule_print_error(global.thread, global.main->error, cache->action, F_complete_not_utf, "controller_validate_has_graph", F_true, F_false); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + } + + setting_value->used = 0; + + continue; + } + + controller_rule_setting_read_print_value(global, type == controller_rule_setting_type_name_e ? controller_name_s : controller_script_s, 0, *setting_value, 0); + } + else if (type == controller_rule_setting_type_path_e) { + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_actions.array[i].array[0], setting_value); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, F_false); + } + else { + status = f_string_dynamic_terminate_after(setting_value); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, F_false); + } + } + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + setting_value->used = 0; + + // get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + controller_rule_setting_read_print_value(global, controller_path_s, 0, *setting_value, 0); + } + + continue; + } + + if (type == controller_rule_setting_type_scheduler_e) { + + if (cache->content_actions.array[i].used < 1 || cache->content_actions.array[i].used > 2 || rule->has & controller_rule_has_scheduler_d) { + controller_rule_setting_read_print_error(global.main->error, "requires either one or two Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + if (fl_string_dynamic_partial_compare_string(controller_batch_s, cache->buffer_item, controller_batch_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + rule->scheduler.policy = SCHED_BATCH; + rule->scheduler.priority = 0; + } + else if (fl_string_dynamic_partial_compare_string(controller_deadline_s, cache->buffer_item, controller_deadline_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + rule->scheduler.policy = SCHED_DEADLINE; + rule->scheduler.priority = 49; + } + else if (fl_string_dynamic_partial_compare_string(controller_fifo_s, cache->buffer_item, controller_fifo_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + rule->scheduler.policy = SCHED_FIFO; + rule->scheduler.priority = 49; + } + else if (fl_string_dynamic_partial_compare_string(controller_idle_s, cache->buffer_item, controller_idle_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + rule->scheduler.policy = SCHED_IDLE; + rule->scheduler.priority = 0; + } + else if (fl_string_dynamic_partial_compare_string(controller_other_s, cache->buffer_item, controller_other_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + rule->scheduler.policy = SCHED_OTHER; + rule->scheduler.priority = 0; + } + else if (fl_string_dynamic_partial_compare_string(controller_round_robin_s, cache->buffer_item, controller_round_robin_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + rule->scheduler.policy = SCHED_RR; + rule->scheduler.priority = 49; + } + else { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an unknown scheduler", cache->content_actions.array[i].array[0], "", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + if (cache->content_actions.array[i].used > 1) { + const bool zero_only = rule->scheduler.policy == SCHED_BATCH || rule->scheduler.policy == SCHED_IDLE || rule->scheduler.policy == SCHED_OTHER; + + f_number_signed_t number = 0; + + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, cache->content_actions.array[i].array[1], &number); + + if (F_status_set_fine(status) == F_number_positive) { + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, controller_range_after_number_sign(cache->buffer_item, cache->content_actions.array[i].array[1]), &number); + + // Restore error on parameter problem. + if (F_status_set_fine(status) == F_parameter) { + status = F_status_set_error(F_number_positive); + } + } + + if (F_status_is_error(status) || (zero_only && number) || (!zero_only && (number < 1 || number > 99))) { + status = F_status_set_fine(status); + + if ((zero_only && number) || (!zero_only && (number < 1 || number > 99)) || status == F_data_not || status == F_number || status == F_number_overflow || status == F_number_negative || status == F_number_positive) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + + // get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule setting has an invalid number '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_actions.array[i].array[1], global.main->error.notable); + + if (zero_only) { + fl_print_format("%[', only%] ", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[0%]%[ is", global.main->error.to.stream, global.main->error.notable, global.main->error.notable, global.main->error.context); + } + else { + fl_print_format("%[', only the whole numbers inclusively between%] ", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[1%] %[and%] ", global.main->error.to.stream, global.main->error.notable, global.main->error.notable, global.main->error.context, global.main->error.context); + fl_print_format("%[99%] %[are", global.main->error.to.stream, global.main->error.notable, global.main->error.notable, global.main->error.context); + } + + fl_print_format(" allowed for the designated scheduler.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + } + else { + controller_rule_print_error(global.thread, global.main->error, cache->action, status, "fl_conversion_string_to_number_signed", F_true, F_false); + status = F_status_set_error(status); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + } + + continue; + } + + rule->scheduler.priority = number; + } + + rule->has |= controller_rule_has_scheduler_d; + + controller_rule_setting_read_print_values(global, controller_scheduler_s, i, cache); + + continue; + } + + if (type == controller_rule_setting_type_timeout_e) { + if (cache->content_actions.array[i].used != 2) { + controller_rule_setting_read_print_error(global.main->error, "requires exactly two Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + uint8_t timeout_code = 0; + + if (fl_string_dynamic_partial_compare_string(controller_kill_s, cache->buffer_item, controller_kill_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + timeout_code = controller_rule_timeout_code_kill_d; + } + else if (fl_string_dynamic_partial_compare_string(controller_start_s, cache->buffer_item, controller_start_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + timeout_code = controller_rule_timeout_code_start_d; + } + else if (fl_string_dynamic_partial_compare_string(controller_stop_s, cache->buffer_item, controller_stop_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + timeout_code = controller_rule_timeout_code_stop_d; + } + else { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule setting's first value has '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_actions.array[i].array[0], global.main->error.notable); + fl_print_format("%[' but only supports %s, %s, and %s.%]%c", global.main->error.to.stream, global.main->error.context, controller_kill_s, controller_start_s, controller_stop_s, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + f_number_unsigned_t number = 0; + + status = fl_conversion_string_to_number_unsigned(cache->buffer_item.string, cache->content_actions.array[i].array[1], &number); + + if (F_status_set_fine(status) == F_number_positive) { + status = fl_conversion_string_to_number_unsigned(cache->buffer_item.string, controller_range_after_number_sign(cache->buffer_item, cache->content_actions.array[i].array[1]), &number); + + // Restore error on parameter problem. + if (F_status_set_fine(status) == F_parameter) { + status = F_status_set_error(F_number_positive); + } + } + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_number_overflow) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an unsupported number", cache->content_actions.array[i].array[1], ", the number is too large for this system", i, line_item, global.thread, cache); + } + else if (status == F_data_not || status == F_number || status == F_number_underflow || status == F_number_negative || status == F_number_positive || status == F_number_decimal) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an invalid number", cache->content_actions.array[i].array[1], ", only positive whole numbers are allowed", i, line_item, global.thread, cache); + } + else { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_print_error(global.thread, global.main->error, cache->action, status, "fl_conversion_string_to_number_signed", F_true, F_false); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + } + else { + if (timeout_code == controller_rule_timeout_code_kill_d) { + rule->timeout_kill = number; + } + else if (timeout_code == controller_rule_timeout_code_start_d) { + rule->timeout_start = number; + } + else { + rule->timeout_stop = number; + } + + if (global.main->error.verbosity == f_console_verbosity_debug_e || (global.main->error.verbosity == f_console_verbosity_verbose_e && global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e)) { + f_string_t name_sub = controller_stop_s; + + if (timeout_code == controller_rule_timeout_code_kill_d) { + name_sub = controller_kill_s; + } + else if (timeout_code == controller_rule_timeout_code_start_d) { + name_sub = controller_start_s; + } + + cache->action.generic.used = 0; + + status = controller_dynamic_rip_nulless_terminated(cache->buffer_item, cache->content_actions.array[i].array[1], &cache->action.generic); + + if (F_status_is_error(status)) { + controller_print_error(global.thread, global.main->error, F_status_set_fine(status), "controller_dynamic_rip_nulless_terminated", F_true); + + break; + } + + controller_rule_setting_read_print_value(global, controller_timeout_s, name_sub, cache->action.generic, 0); + } + } + + continue; + } + + if (type == controller_rule_setting_type_capability_e || type == controller_rule_setting_type_nice_e || type == controller_rule_setting_type_user_e) { + if (cache->content_actions.array[i].used != 1 || type == controller_rule_setting_type_capability_e && rule->capability || type == controller_rule_setting_type_group_e && (rule->has & controller_rule_has_group_d) || type == controller_rule_setting_type_nice_e && (rule->has & controller_rule_has_nice_d) || type == controller_rule_setting_type_user_e && (rule->has & controller_rule_has_user_d)) { + controller_rule_setting_read_print_error(global.main->error, "requires exactly one Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + if (type == controller_rule_setting_type_capability_e) { + cache->action.generic.used = 0; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_actions.array[i].array[0], &cache->action.generic); + + if (F_status_is_error(status)) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, F_false); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + } + + status = f_string_dynamic_terminate_after(&cache->action.generic); + + if (F_status_is_error(status)) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, F_false); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + } + + status = f_capability_from_text(cache->action.generic.string, &rule->capability); + + if (F_status_is_error(status) && F_status_set_fine(status) != F_supported_not) { + if (F_status_set_fine(status) == F_memory_not) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_capability_from_text", F_true, F_false); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + + status_return = status; + break; + } + + controller_rule_setting_read_print_error(global.main->error, "failed to process the capabilities", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + controller_rule_setting_read_print_value(global, controller_capability_s, 0, cache->action.generic, 0); + } + else if (type == controller_rule_setting_type_nice_e) { + f_number_signed_t number = 0; + + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, cache->content_actions.array[i].array[0], &number); + + if (F_status_set_fine(status) == F_number_positive) { + status = fl_conversion_string_to_number_signed(cache->buffer_item.string, controller_range_after_number_sign(cache->buffer_item, cache->content_actions.array[i].array[0]), &number); + + // Restore error on parameter problem. + if (F_status_set_fine(status) == F_parameter) { + status = F_status_set_error(F_number_positive); + } + } + + if (F_status_is_error(status) || number < -20 || number > 19) { + status = F_status_set_fine(status); + + if (number < -20 || number > 19 || status == F_data_not || status == F_number || status == F_number_overflow || status == F_number_underflow || status == F_number_decimal) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule setting has an invalid number '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_actions.array[i].array[0], global.main->error.notable); + fl_print_format("%[', only the whole numbers inclusively between%] ", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[-20%]", global.main->error.to.stream, global.main->error.notable, global.main->error.notable); + fl_print_format(" %[and%] ", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[19%]", global.main->error.to.stream, global.main->error.notable, global.main->error.notable); + fl_print_format(" %[are allowed.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + } + else { + controller_rule_print_error(global.thread, global.main->error, cache->action, status, "fl_conversion_string_to_number_signed", F_true, F_false); + status = F_status_set_error(status); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + } + } + else { + rule->nice = number; + rule->has |= controller_rule_has_nice_d; + + if (global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e || global.main->error.verbosity == f_console_verbosity_verbose_e) { + cache->action.generic.used = 0; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_actions.array[i].array[0], &cache->action.generic); + + if (F_status_is_error_not(status)) { + status = f_string_dynamic_terminate_after(&cache->action.generic); + } + + if (F_status_is_error(status)) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), cache->action.generic.used ? "f_string_dynamic_partial_append_nulless" : "f_string_dynamic_terminate_after", F_true, F_false); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + } + + if (F_status_is_error_not(status)) { + controller_rule_setting_read_print_value(global, controller_nice_s, 0, cache->action.generic, 0); + } + } + } + } + else if (type == controller_rule_setting_type_user_e) { + uid_t number = 0; + + status = controller_get_id_user(cache->buffer_item, cache->content_actions.array[i].array[0], cache, &number); + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_exist_not) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an invalid user", cache->content_actions.array[i].array[0], ", because no user was found by that name", i, line_item, global.thread, cache); + } + else if (status == F_number_too_large) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an invalid user", cache->content_actions.array[i].array[0], ", because the given ID is too large", i, line_item, global.thread, cache); + } + else if (status == F_number) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an invalid user", cache->content_actions.array[i].array[0], ", because the given ID is not a valid supported number", i, line_item, global.thread, cache); + } + else { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_print_error(global.thread, global.main->error, cache->action, status, "controller_get_id_user", F_true, F_false); + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(status); + } + } + else { + rule->user = number; + rule->has |= controller_rule_has_user_d; + + if (global.main->error.verbosity == f_console_verbosity_debug_e || (global.main->error.verbosity == f_console_verbosity_verbose_e && global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e)) { + cache->action.generic.used = 0; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_actions.array[i].array[0], &cache->action.generic); + + if (F_status_is_error_not(status)) { + status = f_string_dynamic_terminate_after(&cache->action.generic); + } + + controller_rule_setting_read_print_value(global, controller_user_s, 0, cache->action.generic, 0); + } + } + } + + continue; + } + + if (type == controller_rule_setting_type_group_e) { + if (!cache->content_actions.array[i].used) { + controller_rule_setting_read_print_error(global.main->error, "requires one or more Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + gid_t number = 0; + + rule->groups.used = 0; + + for (j = 0; j < cache->content_actions.array[i].used; ++j) { + + macro_f_int32s_t_increase_by(status, rule->groups, controller_common_allocation_small_d) + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "macro_f_array_lengths_t_increase_by", F_true, F_false); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + status = controller_get_id_group(cache->buffer_item, cache->content_actions.array[i].array[j], cache, &number); + + if (F_status_is_error(status)) { + status = F_status_set_fine(status); + + if (status == F_exist_not) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an invalid group", cache->content_actions.array[i].array[j], ", because no group was found by that name", i, line_item, global.thread, cache); + } + else if (status == F_number_too_large) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an invalid group", cache->content_actions.array[i].array[j], ", because the given ID is too large", i, line_item, global.thread, cache); + } + else if (status == F_number) { + controller_rule_setting_read_print_error_with_range(global.main->error, " has an invalid group", cache->content_actions.array[i].array[j], ", because the given ID is not a valid supported number", i, line_item, global.thread, cache); + } + else { + + // get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_print_error(global.thread, global.main->error, cache->action, status, "f_account_id_group_by_name", F_true, F_false); + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(status); + } + } + else { + if (rule->has & controller_rule_has_group_d) { + rule->groups.array[rule->groups.used++] = number; + } + else { + rule->group = number; + rule->has |= controller_rule_has_group_d; + } + } + } // for + + controller_rule_setting_read_print_values(global, controller_group_s, i, cache); + + continue; + } + + if (type == controller_rule_setting_type_environment_e) { + setting_values = &rule->environment; + + for (j = 0; j < cache->content_actions.array[i].used; ++j) { + + status = f_string_dynamics_increase(controller_common_allocation_small_d, setting_values); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamics_increase", F_true, F_false); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + setting_values->array[setting_values->used].used = 0; + + status = f_string_dynamic_partial_append_nulless(cache->buffer_item, cache->content_actions.array[i].array[j], &setting_values->array[setting_values->used]); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_partial_append_nulless", F_true, F_false); + } + else { + status = f_string_dynamic_terminate_after(&setting_values->array[setting_values->used]); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamic_terminate_after", F_true, F_false); + } + } + + if (F_status_is_error(status)) { + setting_values->array[setting_values->used].used = 0; + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + status = controller_validate_environment_name(setting_values->array[setting_values->used]); + + if (status == F_false || F_status_set_fine(status) == F_complete_not_utf) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + if (status == F_false) { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule setting has an invalid environment variable name '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, setting_values->array[setting_values->used], global.main->error.notable); + fl_print_format("%['.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + } + else { + + // This function should only return F_complete_not_utf on error. + controller_rule_print_error(global.thread, global.main->error, cache->action, F_complete_not_utf, "controller_validate_environment_name", F_true, F_false); + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + } + + setting_values->array[setting_values->used].used = 0; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + ++setting_values->used; + } // for + + rule->has |= controller_rule_has_environment_d; + + if (cache->content_actions.array[i].used) { + controller_rule_setting_read_print_values(global, controller_environment_s, i, cache); + } + else { + if (global.main->error.verbosity == f_console_verbosity_debug_e || (global.main->error.verbosity == f_console_verbosity_verbose_e && global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e)) { + controller_lock_print(global.main->output.to, global.thread); + + fl_print_format("%cProcessing rule item action '%[%s%]' setting value to an empty set.%c", global.main->output.to.stream, f_string_eol_s[0], global.main->context.set.title, controller_environment_s, global.main->context.set.title, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->output.to, global.thread); + } + } + + continue; + } + + // The "on" Rule Setting. + if (cache->content_actions.array[i].used != 4) { + controller_rule_setting_read_print_error(global.main->error, "requires exactly four Content", i, line_item, global.thread, cache); + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + if (fl_string_dynamic_partial_compare_string(controller_freeze_s, cache->buffer_item, controller_freeze_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_freeze_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_kill_s, cache->buffer_item, controller_kill_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_kill_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_pause_s, cache->buffer_item, controller_pause_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_pause_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_reload_s, cache->buffer_item, controller_reload_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_reload_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_restart_s, cache->buffer_item, controller_restart_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_restart_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_resume_s, cache->buffer_item, controller_resume_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_resume_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_start_s, cache->buffer_item, controller_start_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_start_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_stop_s, cache->buffer_item, controller_stop_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_stop_e; + } + else if (fl_string_dynamic_partial_compare_string(controller_thaw_s, cache->buffer_item, controller_thaw_s_length, cache->content_actions.array[i].array[0]) == F_equal_to) { + action = controller_rule_action_type_thaw_e; + } + else { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + f_thread_mutex_lock(&global.thread->lock.print); + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule setting's second value has '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_actions.array[i].array[1], global.main->error.notable); + fl_print_format("%[' but only supports %s, %s, %s, %s, %s", global.main->error.to.stream, global.main->error.context, controller_freeze_s, controller_kill_s, controller_pause_s, controller_reload_s, controller_restart_s); + fl_print_format("%s, %s, %s, and %s.%]%c", global.main->error.to.stream, controller_resume_s, controller_start_s, controller_stop_s, controller_thaw_s, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + for (j = 0; j < rule->ons.used; ++j) { + if (rule->ons.array[j].action == action) break; + } // for + + if (j == rule->ons.used) { + status = controller_rule_ons_increase(&rule->ons); + } + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "controller_rule_ons_increase", F_true, F_false); + } + else { + if (fl_string_dynamic_partial_compare_string(controller_need_s, cache->buffer_item, controller_need_s_length, cache->content_actions.array[i].array[1]) == F_equal_to) { + setting_values = &rule->ons.array[j].need; + } + else if (fl_string_dynamic_partial_compare_string(controller_want_s, cache->buffer_item, controller_want_s_length, cache->content_actions.array[i].array[1]) == F_equal_to) { + setting_values = &rule->ons.array[j].want; + } + else if (fl_string_dynamic_partial_compare_string(controller_wish_s, cache->buffer_item, controller_wish_s_length, cache->content_actions.array[i].array[1]) == F_equal_to) { + setting_values = &rule->ons.array[j].wish; + } + else { + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SRule setting's second value has '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_actions.array[i].array[1], global.main->error.notable); + fl_print_format("%[' but only supports %s, %s, and %s.%]%c", global.main->error.to.stream, global.main->error.context, controller_need_s, controller_want_s, controller_wish_s, global.main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(global.main->error, cache->action, F_false); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + if (F_status_is_error_not(status_return)) { + status_return = F_status_set_error(F_valid_not); + } + + continue; + } + + status = f_string_dynamics_increase_by(controller_common_allocation_small_d, setting_values); + + if (F_status_is_error(status)) { + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_string_dynamics_increase_by", F_true, F_false); + } + } + + if (F_status_is_error(status)) { + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + status = controller_rule_id_construct(global, cache->buffer_item, cache->content_actions.array[i].array[2], cache->content_actions.array[i].array[3], &setting_values->array[setting_values->used]); + + if (F_status_is_error(status)) { + setting_values->array[setting_values->used].used = 0; + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[i].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_rule_item_print_error(global.thread, global.main->error, cache->action, F_false, F_status_set_fine(status)); + + continue; + } + + cache->buffer_path.used = 0; + + status = f_file_name_base(setting_values->array[setting_values->used].string, setting_values->array[setting_values->used + 1].used, &cache->buffer_path); + + if (F_status_is_error(status)) { + setting_values->array[setting_values->used].used = 0; + + controller_rule_print_error(global.thread, global.main->error, cache->action, F_status_set_fine(status), "f_file_name_base", F_true, F_false); + + if (F_status_set_fine(status) == F_memory_not) { + status_return = status; + break; + } + + if (F_status_is_error_not(status_return)) { + status_return = status; + } + + continue; + } + + if (fl_string_dynamic_partial_compare_string(cache->buffer_item.string, cache->buffer_path, cache->buffer_item.used, cache->content_actions.array[i].array[1]) == F_equal_to_not) { + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%c%[%SThe rule item action third parameter '%]", global.main->error.to.stream, f_string_eol_s[0], global.main->error.context, global.main->error.prefix, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_item, cache->content_actions.array[i].array[2], global.main->error.notable); + fl_print_format("%[' must be a base path name, such as %un '.%]", global.main->error.to.stream, global.main->error.context, cache->buffer_path.used, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, cache->buffer_path, global.main->error.notable); + fl_print_format("%['.%]%c", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } + + setting_values->array[setting_values->used].used = 0; + + continue; + } + + rule->ons.array[j].action = action; + + ++setting_values->used; + + if (j == rule->ons.used) { + ++rule->ons.used; + } + + if (global.main->error.verbosity == f_console_verbosity_debug_e || (global.main->error.verbosity == f_console_verbosity_verbose_e && global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e)) { + controller_lock_print(global.main->output.to, global.thread); + + fl_print_format("%cProcessing rule item action '%[%S%]', adding ", global.main->output.to.stream, f_string_eol_s[0], global.main->context.set.title, controller_on_s, global.main->context.set.title); + fl_print_format("'%[%/Q%]' of ", global.main->output.to.stream, global.main->context.set.notable, cache->buffer_item, cache->content_actions.array[i].array[1], global.main->context.set.notable); + fl_print_format("'%[%/Q%]/", global.main->output.to.stream, global.main->context.set.important, cache->buffer_item, cache->content_actions.array[i].array[2], global.main->context.set.important); + fl_print_format("%[%/Q%]'.%c", global.main->output.to.stream, global.main->context.set.important, cache->buffer_item, cache->content_actions.array[i].array[3], global.main->context.set.important, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->output.to, global.thread); + } + } // for + + // Restore the current name item and line number, which there should already be enough allocated space for. + memcpy(cache->action.name_item.string, name_item, length_name_item); + + cache->action.name_item.string[length_name_item] = 0; + cache->action.name_item.used = length_name_item; + + cache->action.line_item = line_item; + + return status_return; + } +#endif // _di_controller_rule_setting_read_ + +#ifndef _di_controller_rule_validate_ + void controller_rule_validate(const controller_global_t global, const controller_rule_t rule, const uint8_t action, const uint8_t options, controller_cache_t * const cache) { + + const controller_main_t *main = global.main; + + switch (action) { + case controller_rule_action_type_freeze_e: + case controller_rule_action_type_kill_e: + case controller_rule_action_type_pause_e: + case controller_rule_action_type_reload_e: + case controller_rule_action_type_restart_e: + case controller_rule_action_type_resume_e: + case controller_rule_action_type_start_e: + case controller_rule_action_type_stop_e: + case controller_rule_action_type_thaw_e: + break; + + default: + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, global.thread); + + fl_print_format("%c%[%SUnsupported action type '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[%q%]", main->error.to.stream, main->error.notable, controller_rule_action_type_name(action), main->error.notable); + fl_print_format("%[' while attempting to validate rule execution.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(main->error, cache->action, F_true); + + controller_unlock_print_flush(main->error.to, global.thread); + } + + return; + } + + f_array_length_t i = 0; + f_array_length_t j = 0; + + // find at least one of the requested action. + { + bool missing = F_true; + + for (; i < rule.items.used; ++i) { + + for (j = 0; j < rule.items.array[i].actions.used; ++j) { + + if (!action || rule.items.array[i].actions.array[j].type == action) { + missing = F_false; + break; + } + } // for + } // for + + if (missing) { + controller_lock_print(main->output.to, global.thread); + + if (rule.items.used) { + fl_print_format("%cRule '", main->output.to.stream, f_string_eol_s[0]); + fl_print_format("%[%Q%]' has no '", main->output.to.stream, main->context.set.title, rule.name, main->context.set.title); + fl_print_format("%[%q%]' action to execute and would '", main->output.to.stream, main->context.set.title, controller_rule_action_type_name(action), main->context.set.title); + fl_print_format("%[%s%]' because it is '", main->output.to.stream, main->context.set.important, options & controller_process_option_require_d ? controller_fail_s : controller_succeed_s, main->context.set.important); + fl_print_format("%[%s%]'.%c", main->output.to.stream, main->context.set.important, options & controller_process_option_require_d ? controller_required_s : controller_optional_s, main->context.set.important, f_string_eol_s[0]); + } + else { + fl_print_format("%cRule '", main->output.to.stream, f_string_eol_s[0]); + fl_print_format("%[%Q%]' has no known '", main->output.to.stream, main->context.set.title, rule.name, main->context.set.title); + fl_print_format("%[%s %s%]' (such as ", main->output.to.stream, main->context.set.title, controller_rule_s, controller_type_s, main->context.set.title); + fl_print_format("'%[%s%]', ", main->output.to.stream, main->context.set.title, controller_command_s, main->context.set.title); + fl_print_format("'%[%s%]', ", main->output.to.stream, main->context.set.title, controller_service_s, main->context.set.title); + fl_print_format("'%[%s%]', or ", main->output.to.stream, main->context.set.title, controller_script_s, main->context.set.title); + fl_print_format("'%[%s%]'", main->output.to.stream, main->context.set.title, controller_utility_s, main->context.set.title); + fl_print_format(") and would '%[%s%]' because it is '", main->output.to.stream, main->context.set.important, options & controller_process_option_require_d ? controller_fail_s : controller_succeed_s, main->context.set.important); + fl_print_format("%[%s%]'.%c", main->output.to.stream, main->context.set.important, options & controller_process_option_require_d ? controller_required_s : controller_optional_s, main->context.set.important, f_string_eol_s[0]); + } + + controller_unlock_print_flush(main->output.to, global.thread); + } + } + + controller_lock_print(main->output.to, global.thread); + + fl_print_format("%cRule %[%Q%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.title, rule.alias, main->context.set.title, f_string_eol_s[0]); + + // name. + fl_print_format(" %[%s%] %Q%c", main->output.to.stream, main->context.set.important, controller_name_s, main->context.set.important, rule.name, f_string_eol_s[0]); + + // capability. + fl_print_format(" %[%s%] ", main->output.to.stream, main->context.set.important, controller_capability_s, main->context.set.important); + + if (f_capability_supported()) { + if (rule.capability) { + cache->action.generic.used = 0; + + if (F_status_is_error_not(f_capability_to_text(rule.capability, &cache->action.generic))) { + f_print_dynamic_safely(cache->action.generic, main->output.to.stream); + } + } + + f_print_terminated(f_string_eol_s, main->output.to.stream); + } + else { + fl_print_format("%[(unsupported)%]%c", main->output.to.stream, main->context.set.warning, main->context.set.warning, f_string_eol_s[0]); + } + + // control group. + fl_print_format(" %[%s%]", main->output.to.stream, main->context.set.important, controller_cgroup_s, main->context.set.important); + + if (rule.has & controller_rule_has_cgroup_d) { + fl_print_format(" %s", main->output.to.stream, rule.cgroup.as_new ? controller_new_s : controller_existing_s); + + for (i = 0; i < rule.cgroup.groups.used; ++i) { + + if (rule.cgroup.groups.array[i].used) { + fl_print_format(" %Q", main->output.to.stream, rule.cgroup.groups.array[i]); + } + } // for + } + + f_print_terminated(f_string_eol_s, main->output.to.stream); + + // how. + fl_print_format(" %[%s%] %s%c", main->output.to.stream, main->context.set.important, controller_how_s, main->context.set.important, options & controller_process_option_asynchronous_d ? controller_asynchronous_s : controller_synchronous_s, f_string_eol_s[0]); + + // nice. + fl_print_format(" %[%s%]", main->output.to.stream, main->context.set.important, controller_nice_s, main->context.set.important); + + if (rule.has & controller_rule_has_nice_d) { + fl_print_format(" %i", main->output.to.stream, rule.nice); + } + + f_print_terminated(f_string_eol_s, main->output.to.stream); + + // scheduler. + fl_print_format(" %[%s%]", main->output.to.stream, main->context.set.important, controller_scheduler_s, main->context.set.important); + + if (rule.has & controller_rule_has_scheduler_d) { + f_string_t policy = ""; + + if (rule.scheduler.policy == SCHED_BATCH) { + policy = controller_batch_s; + } + else if (rule.scheduler.policy == SCHED_DEADLINE) { + policy = controller_deadline_s; + } + else if (rule.scheduler.policy == SCHED_FIFO) { + policy = controller_fifo_s; + } + else if (rule.scheduler.policy == SCHED_IDLE) { + policy = controller_idle_s; + } + else if (rule.scheduler.policy == SCHED_OTHER) { + policy = controller_other_s; + } + else if (rule.scheduler.policy == SCHED_RR) { + policy = controller_round_robin_s; + } + + fl_print_format(" %s %i", main->output.to.stream, policy, rule.scheduler.priority); + } + + f_print_terminated(f_string_eol_s, main->output.to.stream); + + // script. + fl_print_format(" %[%s%] %Q%c", main->output.to.stream, main->context.set.important, controller_script_s, main->context.set.important, rule.script, f_string_eol_s[0]); + + // user. + fl_print_format(" %[%s%]", main->output.to.stream, main->context.set.important, controller_user_s, main->context.set.important); + + if (rule.has & controller_rule_has_user_d) { + fl_print_format(" %i", main->output.to.stream, rule.user); + } + + f_print_terminated(f_string_eol_s, main->output.to.stream); + + // wait. + fl_print_format(" %[%s%] %s%c", main->output.to.stream, main->context.set.important, controller_wait_s, main->context.set.important, options & controller_process_option_wait_d ? controller_yes_s : controller_no_s, f_string_eol_s[0]); + + // affinity. + fl_print_format(" %[%s%] {%c", main->output.to.stream, main->context.set.important, controller_affinity_s, main->context.set.important, f_string_eol_s[0]); + + for (i = 0; i < rule.affinity.used; ++i) { + fl_print_format(" %i%c", main->output.to.stream, rule.affinity.array[i], f_string_eol_s[0]); + } // for + + // define. + fl_print_format(" }%c %[%s%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.important, controller_define_s, main->context.set.important, f_string_eol_s[0]); + + for (i = 0; i < rule.define.used; ++i) { + + if (rule.define.array[i].name.used && rule.define.array[i].value.used) { + fl_print_format(" %Q %[=%] %Q%c", main->output.to.stream, rule.define.array[i].name, main->context.set.important, main->context.set.important, rule.define.array[i].value, f_string_eol_s[0]); + } + } // for + + // environment. + fl_print_format(" }%c %[%s%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.important, controller_environment_s, main->context.set.important, f_string_eol_s[0]); + + for (i = 0; i < rule.environment.used; ++i) { + + if (rule.environment.array[i].used) { + fl_print_format(" %Q%c", main->output.to.stream, rule.environment.array[i], f_string_eol_s[0]); + } + } // for + + fl_print_format(" }%c %[%s%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.important, controller_parameter_s, main->context.set.important, f_string_eol_s[0]); + + // parameter. + for (i = 0; i < rule.parameter.used; ++i) { + + if (rule.parameter.array[i].name.used && rule.parameter.array[i].value.used) { + fl_print_format(" %Q %[=%] %Q%c", main->output.to.stream, rule.parameter.array[i].name, main->context.set.important, main->context.set.important, rule.parameter.array[i].value, f_string_eol_s[0]); + } + } // for + + // group. + fl_print_format(" }%c %[%s%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.important, controller_group_s, main->context.set.important, f_string_eol_s[0]); + + if (rule.has & controller_rule_has_group_d) { + fl_print_format(" %i%c", main->output.to.stream, rule.group, f_string_eol_s[0]); + + for (i = 0; i < rule.groups.used; ++i) { + fl_print_format(" %i%c", main->output.to.stream, rule.groups.array[i], f_string_eol_s[0]); + } // for + } + + // limit. + fl_print_format(" }%c %[%s%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.important, controller_limit_s, main->context.set.important, f_string_eol_s[0]); + + for (i = 0; i < rule.limits.used; ++i) { + fl_print_format(" %Q %[=%] %un %un%c", main->output.to.stream, controller_rule_setting_limit_type_name(rule.limits.array[i].type), main->context.set.important, main->context.set.important, rule.limits.array[i].value.rlim_cur, rule.limits.array[i].value.rlim_max, f_string_eol_s[0]); + } // for + + // on. + fl_print_format(" }%c %[%s%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.important, controller_on_s, main->context.set.important, f_string_eol_s[0]); + + for (i = 0; i < rule.ons.used; ++i) { + + fl_print_format(" %[%s%] {%c", main->output.to.stream, main->context.set.important, controller_action_s, main->context.set.important, f_string_eol_s[0]); + + { + f_string_t action = ""; + + if (rule.ons.array[i].action == controller_rule_action_type_freeze_e) { + action = controller_freeze_s; + } + else if (rule.ons.array[i].action == controller_rule_action_type_kill_e) { + action = controller_kill_s; + } + else if (rule.ons.array[i].action == controller_rule_action_type_pause_e) { + action = controller_pause_s; + } + else if (rule.ons.array[i].action == controller_rule_action_type_reload_e) { + action = controller_reload_s; + } + else if (rule.ons.array[i].action == controller_rule_action_type_restart_e) { + action = controller_restart_s; + } + else if (rule.ons.array[i].action == controller_rule_action_type_resume_e) { + action = controller_resume_s; + } + else if (rule.ons.array[i].action == controller_rule_action_type_start_e) { + action = controller_start_s; + } + else if (rule.ons.array[i].action == controller_rule_action_type_stop_e) { + action = controller_stop_s; + } + else if (rule.ons.array[i].action == controller_rule_action_type_thaw_e) { + action = controller_thaw_s; + } + + fl_print_format(" %[%s%] %s%c", main->output.to.stream, main->context.set.important, controller_type_s, main->context.set.important, action, f_string_eol_s[0]); + } + + fl_print_format(" %[%s%] {%c", main->output.to.stream, main->context.set.important, controller_need_s, main->context.set.important, f_string_eol_s[0]); + + for (j = 0; j < rule.ons.array[i].need.used; ++j) { + + if (rule.ons.array[i].need.array[j].used) { + fl_print_format(" %Q%c", main->output.to.stream, rule.ons.array[i].need.array[j], f_string_eol_s[0]); + } + } // for + + fl_print_format(" }%c %[%s%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.important, controller_want_s, main->context.set.important, f_string_eol_s[0]); + + for (j = 0; j < rule.ons.array[i].want.used; ++j) { + + if (rule.ons.array[i].want.array[j].used) { + fl_print_format(" %Q%c", main->output.to.stream, rule.ons.array[i].want.array[j], f_string_eol_s[0]); + } + } // for + + fl_print_format(" }%c %[%s%] {%c", main->output.to.stream, f_string_eol_s[0], main->context.set.important, controller_wish_s, main->context.set.important, f_string_eol_s[0]); + + for (j = 0; j < rule.ons.array[i].wish.used; ++j) { + + if (rule.ons.array[i].wish.array[j].used) { + fl_print_format(" %Q%c", main->output.to.stream, rule.ons.array[i].wish.array[j], f_string_eol_s[0]); + } + } // for + + fl_print_format(" }%c }%c", main->output.to.stream, f_string_eol_s[0], f_string_eol_s[0]); + } // for + + fl_print_format(" }%c", main->output.to.stream, f_string_eol_s[0]); + + // items. + if (rule.items.used) { + controller_rule_action_t *action = 0; + controller_rule_item_t *item = 0; + controller_rule_rerun_item_t *rerun_item = 0; + f_string_dynamic_t *parameter = 0; + + f_array_length_t j = 0; + f_array_length_t k = 0; + + for (i = 0; i < rule.items.used; ++i) { + + item = &rule.items.array[i]; + + fl_print_format(" %[%s%] {%c", main->output.to.stream, main->context.set.important, controller_item_s, main->context.set.important, f_string_eol_s[0]); + + // type. + fl_print_format(" %[%s%] %Q%c", main->output.to.stream, main->context.set.important, controller_type_s, main->context.set.important, controller_rule_item_type_name(item->type), f_string_eol_s[0]); + + // pid_file. + fl_print_format(" %[%s%]", main->output.to.stream, main->context.set.important, controller_pid_file_s, main->context.set.important); + if (item->pid_file.used) { + fl_print_format(" %Q", main->output.to.stream, item->pid_file); + } + f_print_terminated(f_string_eol_s, main->output.to.stream); + + // with. + fl_print_format(" %[%s%]", main->output.to.stream, main->context.set.important, controller_with_s, main->context.set.important); + if (item->with & controller_with_full_path_d) { + fl_print_format(" %s", main->output.to.stream, controller_full_path_s); + } + if (item->with & controller_with_session_new_d) { + fl_print_format(" %s", main->output.to.stream, controller_session_new_s); + } + if (item->with & controller_with_session_same_d) { + fl_print_format(" %s", main->output.to.stream, controller_session_same_s); + } + f_print_terminated(f_string_eol_s, main->output.to.stream); + + // actions. + for (j = 0; j < item->actions.used; ++j) { + + action = &item->actions.array[j]; + + fl_print_format(" %[%s%] {%c", main->output.to.stream, main->context.set.important, controller_action_s, main->context.set.important, f_string_eol_s[0]); + fl_print_format(" %[%s%] %q%c", main->output.to.stream, main->context.set.important, controller_type_s, main->context.set.important, controller_rule_action_type_name(action->type), f_string_eol_s[0]); + + if (item->type == controller_rule_item_type_script_e || item->type == controller_rule_item_type_utility_e) { + fl_print_format(" %[%s%] {%c", main->output.to.stream, main->context.set.important, controller_parameter_s, main->context.set.important, f_string_eol_s[0]); + + parameter = &action->parameters.array[0]; + + if (parameter->used) { + f_print_terminated(" ", main->output.to.stream); + + for (k = 0; k < parameter->used; ++k) { + + if (parameter->string[k] == f_fss_eol_s[0]) { + if (k + 1 < parameter->used) { + f_print_terminated(f_string_eol_s, main->output.to.stream); + f_print_terminated(" ", main->output.to.stream); + } + } + else { + // @fixme change this to handle UTF-8 characters (cannot use f_print_character_safely() as it is not UTF-8 safe as-is). + f_print_character_safely(parameter->string[k], main->output.to.stream); + } + } // for + + f_print_terminated(f_string_eol_s, main->output.to.stream); + } + + fl_print_format(" }%c", main->output.to.stream, f_string_eol_s[0]); + } + else { + for (k = 0; k < action->parameters.used; ++k) { + fl_print_format(" %[%s%] %Q%c", main->output.to.stream, main->context.set.important, controller_parameter_s, main->context.set.important, action->parameters.array[k], f_string_eol_s[0]); + } // for + } + + fl_print_format(" }%c", main->output.to.stream, f_string_eol_s[0]); + } // for + + // rerun. + fl_print_format(" %[%s%] {%c", main->output.to.stream, main->context.set.important, controller_rerun_s, main->context.set.important, f_string_eol_s[0]); + for (j = 0; j < controller_rule_action_type_execute__enum_size_e; ++j) { + + for (k = 0; k < 2; ++k) { + if (!k && (item->reruns[j].is & controller_rule_rerun_is_failure_d)) { + rerun_item = &item->reruns[j].failure; + } + else if (k && (item->reruns[j].is & controller_rule_rerun_is_success_d)) { + rerun_item = &item->reruns[j].success; + } + else { + rerun_item = 0; + continue; + } + + fl_print_format(" %[", main->output.to.stream, main->context.set.important); + switch (j) { + case controller_rule_action_type_execute_freeze_e: + f_print_terminated(controller_freeze_s, main->output.to.stream); + break; + + case controller_rule_action_type_execute_kill_e: + f_print_terminated(controller_kill_s, main->output.to.stream); + break; + + case controller_rule_action_type_execute_pause_e: + f_print_terminated(controller_pause_s, main->output.to.stream); + break; + + case controller_rule_action_type_execute_reload_e: + f_print_terminated(controller_reload_s, main->output.to.stream); + break; + + case controller_rule_action_type_execute_restart_e: + f_print_terminated(controller_restart_s, main->output.to.stream); + break; + + case controller_rule_action_type_execute_resume_e: + f_print_terminated(controller_resume_s, main->output.to.stream); + break; + + case controller_rule_action_type_execute_start_e: + f_print_terminated(controller_start_s, main->output.to.stream); + break; + + case controller_rule_action_type_execute_stop_e: + f_print_terminated(controller_stop_s, main->output.to.stream); + break; + + case controller_rule_action_type_execute_thaw_e: + f_print_terminated(controller_thaw_s, main->output.to.stream); + break; + + default: + break; + } + + fl_print_format("%] %s", main->output.to.stream, main->context.set.important, k ? controller_success_s : controller_failure_s); + fl_print_format(" %s %ul %s %ul", main->output.to.stream, controller_delay_s, rerun_item->delay, controller_max_s, rerun_item->max); + + if (!k && (item->reruns[j].is & controller_rule_rerun_is_failure_reset_d) || k && (item->reruns[j].is & controller_rule_rerun_is_success_reset_d)) { + fl_print_format(" %s", main->output.to.stream, controller_reset_s); + } + + f_print_terminated(f_string_eol_s, main->output.to.stream); + } // for + } // for + fl_print_format(" }%c", main->output.to.stream, f_string_eol_s[0]); + + fl_print_format(" }%c", main->output.to.stream, f_string_eol_s[0]); + } // for + } + + fl_print_format("}%c", main->output.to.stream, f_string_eol_s[0]); + + controller_unlock_print_flush(main->output.to, global.thread); + } +#endif // _di_controller_rule_validate_ + +#ifndef _di_controller_rule_wait_all_ + f_status_t controller_rule_wait_all(const controller_global_t global, const bool is_normal, const bool required, controller_process_t * const caller) { + + f_status_t status_lock = F_none; + + if (caller) { + status_lock = controller_lock_read_process(caller, global.thread, &global.thread->lock.process); + } + else { + status_lock = controller_lock_read(is_normal, global.thread, &global.thread->lock.process); + } + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + return status_lock; + } + + if (!global.thread->processs.used) { + f_thread_unlock(&global.thread->lock.process); + + return F_data_not; + } + + f_status_t status = F_none; + + bool required_not_run = F_false; + bool skip = F_false; + + f_array_length_t i = 0; + f_array_length_t j = 0; + + // build a list of what to wait for so that anything new after this point will not be waited for. + const f_array_length_t process_total = global.thread->processs.used; + controller_process_t *process_list[process_total]; + + for (; i < process_total; ++i) { + process_list[i] = global.thread->processs.array[i]; + } // for + + f_thread_unlock(&global.thread->lock.process); + + for (i = 0; i < process_total; ++i) { + + if (caller) { + if (!controller_thread_is_enabled_process(caller, global.thread)) break; + } + else { + if (!controller_thread_is_enabled(is_normal, global.thread)) break; + } + + // re-establish global process read lock to wait for or protect from the cleanup thread while checking the read process. + if (caller) { + status_lock = controller_lock_read_process(caller, global.thread, &global.thread->lock.process); + } + else { + status_lock = controller_lock_read(is_normal, global.thread, &global.thread->lock.process); + } + + if (F_status_is_error(status_lock)) break; + + if (!process_list[i]) { + f_thread_unlock(&global.thread->lock.process); + + continue; + } + + if (caller) { + status_lock = controller_lock_read_process(caller, global.thread, &process_list[i]->active); + } + else { + status_lock = controller_lock_read(is_normal, global.thread, &process_list[i]->active); + } + + if (F_status_is_error(status_lock)) { + f_thread_unlock(&global.thread->lock.process); + + break; + } + + // once the active lock is obtained, then the main process read lock can be safely released. + f_thread_unlock(&global.thread->lock.process); + + if (caller) { + if (caller) { + status_lock = controller_lock_read_process(caller, global.thread, &global.thread->lock.rule); + } + else { + status_lock = controller_lock_read(is_normal, global.thread, &global.thread->lock.rule); + } + + if (F_status_is_error(status_lock)) { + f_thread_unlock(&process_list[i]->active); + + break; + } + + if (fl_string_dynamic_compare(caller->rule.alias, process_list[i]->rule.alias) == F_equal_to) { + f_thread_unlock(&global.thread->lock.rule); + f_thread_unlock(&process_list[i]->active); + + continue; + } + + skip = F_false; + + for (j = 0; j < caller->stack.used; ++j) { + + if (caller) { + if (!controller_thread_is_enabled_process(caller, global.thread)) break; + } + else { + if (!controller_thread_is_enabled(is_normal, global.thread)) break; + } + + if (global.thread->processs.array[caller->stack.array[j]] && fl_string_dynamic_compare(process_list[i]->rule.alias, global.thread->processs.array[caller->stack.array[j]]->rule.alias) == F_equal_to) { + skip = F_true; + } + + if (skip) break; + } // for + + f_thread_unlock(&global.thread->lock.rule); + + if (skip) { + f_thread_unlock(&process_list[i]->active); + + continue; + } + } + + if (caller) { + status_lock = controller_lock_read_process(caller, global.thread, &process_list[i]->lock); + } + else { + status_lock = controller_lock_read(is_normal, global.thread, &process_list[i]->lock); + } + + if (F_status_is_error(status_lock)) { + f_thread_unlock(&process_list[i]->active); + + break; + } + + if (required) { + if (!(process_list[i]->options & controller_process_option_require_d)) { + f_thread_unlock(&process_list[i]->lock); + f_thread_unlock(&process_list[i]->active); + + continue; + } + } + + if (!process_list[i]->state || process_list[i]->state == controller_process_state_idle_e || process_list[i]->state == controller_process_state_done_e) { + + if (process_list[i]->state == controller_process_state_done_e) { + f_thread_unlock(&process_list[i]->lock); + + if (caller) { + status_lock = controller_lock_write_process(process_list[i], global.thread, &process_list[i]->lock); + } + else { + status_lock = controller_lock_write(is_normal, global.thread, &process_list[i]->lock); + } + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_false, global.thread); + + f_thread_unlock(&process_list[i]->active); + + return status_lock; + } + + if (process_list[i]->state == controller_process_state_done_e) { + f_thread_unlock(&process_list[i]->active); + + if (f_thread_lock_write_try(&process_list[i]->active) == F_none) { + + controller_thread_join(&process_list[i]->id_thread); + + process_list[i]->state = controller_process_state_idle_e; + + f_thread_unlock(&process_list[i]->active); + + f_thread_mutex_lock(&process_list[i]->wait_lock); + f_thread_condition_signal_all(&process_list[i]->wait); + f_thread_mutex_unlock(&process_list[i]->wait_lock); + } + + if (caller) { + status_lock = controller_lock_read_process(caller, global.thread, &process_list[i]->active); + } + else { + status_lock = controller_lock_read(is_normal, global.thread, &process_list[i]->active); + } + + if (F_status_is_error(status_lock)) { + f_thread_unlock(&process_list[i]->lock); + + break; + } + } + + f_thread_unlock(&process_list[i]->lock); + + if (caller) { + status_lock = controller_lock_read_process(caller, global.thread, &process_list[i]->lock); + } + else { + status_lock = controller_lock_read(is_normal, global.thread, &process_list[i]->lock); + } + + if (F_status_is_error(status_lock)) break; + } + + if (process_list[i]->options & controller_process_option_require_d) { + if (controller_rule_status_is_error(process_list[i]->action, process_list[i]->rule)) { + status = F_status_set_error(F_require); + + f_thread_unlock(&process_list[i]->lock); + f_thread_unlock(&process_list[i]->active); + + break; + } + else if (controller_rule_status_is_available(process_list[i]->action, process_list[i]->rule)) { + required_not_run = F_true; + } + } + + f_thread_unlock(&process_list[i]->lock); + f_thread_unlock(&process_list[i]->active); + + if (F_status_set_fine(status) == F_require) break; + + continue; + } + + if (!controller_rule_status_is_error(process_list[i]->action, process_list[i]->rule) && (process_list[i]->state == controller_process_state_active_e || process_list[i]->state == controller_process_state_busy_e)) { + f_thread_unlock(&process_list[i]->lock); + + status = controller_process_wait(global, process_list[i]); + + if (F_status_set_fine(status) == F_interrupt) { + f_thread_unlock(&process_list[i]->active); + + break; + } + + if (caller) { + status_lock = controller_lock_read_process(caller, global.thread, &process_list[i]->lock); + } + else { + status_lock = controller_lock_read(is_normal, global.thread, &process_list[i]->lock); + } + + if (F_status_is_error(status_lock)) { + f_thread_unlock(&process_list[i]->active); + + break; + } + + if ((process_list[i]->options & controller_process_option_require_d)) { + f_thread_unlock(&process_list[i]->lock); + + if (controller_rule_status_is_error(process_list[i]->action, process_list[i]->rule)) { + status = F_status_set_error(F_require); + + f_thread_unlock(&process_list[i]->active); + break; + } + } + else { + f_thread_unlock(&process_list[i]->lock); + } + } + else { + f_thread_unlock(&process_list[i]->lock); + } + + f_thread_unlock(&process_list[i]->active); + + if (F_status_set_fine(status) == F_interrupt || F_status_set_fine(status) == F_require) break; + } // for + + if (F_status_is_error(status_lock)) { + controller_lock_print_error_critical(global.main->error, F_status_set_fine(status_lock), F_true, global.thread); + + return status_lock; + } + + if (caller) { + if (!controller_thread_is_enabled_process(caller, global.thread)) { + return F_status_set_error(F_interrupt); + } + } + else { + if (!controller_thread_is_enabled(is_normal, global.thread)) { + return F_status_set_error(F_interrupt); + } + } + + if (F_status_set_fine(status) == F_require) { + return status; + } + + if (required_not_run) { + return F_require; + } + + return F_none; + } +#endif // _di_controller_rule_wait_all_ + +#ifndef _di_controller_rule_wait_all_process_type_ + f_status_t controller_rule_wait_all_process_type(const controller_global_t global, const uint8_t type, const bool required, controller_process_t * const caller) { + + return controller_rule_wait_all(global, type != controller_process_type_exit_e, required, caller); + } +#endif // _di_controller_rule_wait_all_process_type_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/rule/private-rule.h b/level_3/controller/c/rule/private-rule.h new file mode 100644 index 0000000..a5f19a9 --- /dev/null +++ b/level_3/controller/c/rule/private-rule.h @@ -0,0 +1,794 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_rule_h +#define _PRIVATE_rule_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get a string representing the rule action method. + * + * @param type + * The rule action type code. + * + * @return + * The string with used > 0 on success. + * The string with used == 0 if no match was found. + */ +#ifndef _di_controller_rule_action_method_name_ + extern f_string_static_t controller_rule_action_method_name(const uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_action_method_name_ + +/** + * Find the location of the Rule by the Rule alias. + * + * @param alias + * The Rule alias to find. + * @param rules + * The rules to search through. + * @param at + * The index the rule was found at. + * (optional) Set to NULL to disable. + * + * @return + * F_none on success, but the id.used is 0. + * F_true on success and rule was found, index is updated. + * F_false on success and rule was not found. + */ +#ifndef _di_controller_rule_find_ + extern f_status_t controller_rule_find(const f_string_static_t alias, const controller_rules_t rules, f_array_length_t *at) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_find_ + +/** + * Read the parameters for some rule action. + * + * The object and content ranges are merged together (in that order) as the action parameters. + * + * @param global + * The global data. + * @param buffer + * The buffer containing the content. + * @param object + * (optional) The range representing where the object is found within the buffer. + * Set pointer address to 0 to disable. + * @param content + * (optional) The ranges representing where the content is found within the buffer. + * Set pointer address to 0 to disable. + * @param parameters + * The processed parameters. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_fss_count_lines(). + * Errors (with error bit) from: f_string_dynamic_partial_append_nulless(). + * Errors (with error bit) from: f_string_dynamics_increase(). + * + * @see f_fss_count_lines() + * @see f_string_dynamic_partial_append_nulless() + * @see f_string_dynamics_increase() + */ +#ifndef _di_controller_rule_parameters_read_ + extern f_status_t controller_rule_parameters_read(const controller_global_t global, const f_string_static_t buffer, f_fss_object_t *object, f_fss_content_t *content, f_string_dynamics_t *parameters) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_parameters_read_ + +/** + * Convert the action type to an action execute type. + * + * @param type + * The action type to convert from. + * + * @return + * The converted action type, converted into an action execute type. + * + * The code controller_rule_action_type_execute__enum_size_e is returned for unknown types. + * + */ +#ifndef _di_controller_rule_action_type_to_action_execute_type_ + extern uint8_t controller_rule_action_type_to_action_execute_type(const uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_action_type_to_action_execute_type_ + +/** + * Get a string representing the rule action type. + * + * @param type + * The rule action type code. + * + * @return + * The string with used > 0 on success. + * The string with used == 0 if no match was found. + */ +#ifndef _di_controller_rule_action_type_name_ + extern f_string_static_t controller_rule_action_type_name(const uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_action_type_name_ + +/** + * Get a string representing the rule action execute type. + * + * @param type + * The rule action type execute code. + * + * @return + * The string with used > 0 on success. + * The string with used == 0 if no match was found. + */ +#ifndef _di_controller_rule_action_type_execute_name_ + extern f_string_static_t controller_rule_action_type_execute_name(const uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_action_type_execute_name_ + +/** + * Read the content within the buffer, processing the action (or a set of within a list) for the given item. + * + * This will automatically increase the size of the actions array as needed. + * + * @param global + * The global data. + * @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 type + * The action type for this action or set of actions. + * @param method + * The action method for this action or set of actions. + * @param cache + * A structure for containing and caching relevant data. + * @param item + * The processed item. + * @param actions + * The processed actions. + * @param range + * The current positions within the buffer being operated on. + * This is expected to be set to a position immediately after a valid object read. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: controller_rule_actions_increase_by(). + * Errors (with error bit) from: controller_rule_parameters_read(). + * Errors (with error bit) from: f_fss_count_lines(). + * + * @see controller_rule_actions_increase_by() + * @see controller_rule_parameters_read() + * @see f_fss_count_lines() + */ +#ifndef _di_controller_rule_action_read_ + extern f_status_t controller_rule_action_read(const controller_global_t global, const bool is_normal, const uint8_t type, const uint8_t method, controller_cache_t * const cache, controller_rule_item_t *item, controller_rule_actions_t *actions, f_string_range_t *range) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_action_read_ + +/** + * Copy a rule, allocating new space as necessary. + * + * This does not do any locking or unlocking for the rule data, be sure to lock appropriately before and after calling this. + * + * @param source + * The source rule to copy from. + * @param destination + * The destination rule to copy to. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_capability_copy(). + * Errors (with error bit) from: f_control_group_copy(). + * Errors (with error bit) from: f_limit_sets_copy(). + * Errors (with error bit) from: f_string_dynamic_append(). + * Errors (with error bit) from: f_string_dynamics_append(). + * Errors (with error bit) from: f_string_maps_append(). + * Errors (with error bit) from: f_type_int32s_append(). + * + * @see f_capability_copy() + * @see f_control_group_copy() + * @see f_limit_sets_append() + * @see f_string_dynamic_append() + * @see f_string_dynamics_append() + * @see f_string_maps_append() + * @see f_type_int32s_append() + */ +#ifndef _di_controller_rule_copy_ + extern f_status_t controller_rule_copy(const controller_rule_t source, controller_rule_t *destination) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_copy_ + +/** + * Perform an execution of the given rule. + * + * This requires that a read lock be set on process->lock before being called. + * + * @param global + * The global data. + * @param action + * The action to perform based on the action type codes. + * + * Only subset of the action type codes are supported: + * - controller_rule_action_type_kill_e + * - controller_rule_action_type_pause_e + * - controller_rule_action_type_reload_e + * - controller_rule_action_type_restart_e + * - controller_rule_action_type_resume_e + * - controller_rule_action_type_start_e + * - controller_rule_action_type_stop_e + * @param options + * Process options to consider when executing. + * If bit controller_process_option_simulate_d, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). + * @param process + * The process data for processing this rule. + * + * @return + * F_none on success. + * F_child on child process exiting. + * F_ignore if the rule is unknown and nothing can be done. + * + * F_failure (with error bit) if failed to execute. + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * F_lock (with error bit) if failed to re-establish read lock on process->lock while returning. + * + * On success and the rule is run synchronously, then the individual status for the rule is set to F_complete. + * On success and the rule is run asynchronously, then the individual status for the rule is set to F_busy. + * On failure, the individual status for the rule is set to an appropriate error status. + */ +#ifndef _di_controller_rule_execute_ + extern f_status_t controller_rule_execute(const controller_global_t global, const uint8_t action, const uint8_t options, controller_process_t * const process) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_execute_ + +/** + * Perform an execution of the given rule in the foreground. + * + * This requires that a read lock be set on process->lock before being called. + * + * @param type + * The item type code. + * @param program + * The program to use (such as "bash"). + * @param arguments + * The arguments to pass to the program. + * @param options + * Process options to consider when executing. + * If bit controller_process_option_simulate_d, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). + * @param execute_set + * The execute parameter and as settings. + * @param process + * The process data for processing this rule. + * + * @return + * F_none on success. + * F_child on child process exiting. + * + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * F_lock (with error bit) if failed to re-establish read lock on process->lock while returning. + * + * Errors (with error bit) from: fll_execute_program(). + * + * @see fll_execute_program() + */ +#ifndef _di_controller_rule_execute_foreground_ + extern f_status_t controller_rule_execute_foreground(const uint8_t type, const f_string_t program, const f_string_statics_t arguments, const uint8_t options, controller_execute_set_t * const execute_set, controller_process_t * const process) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_execute_foreground_ + +/** + * Perform an execution of the given rule in the foreground or background and creating a PID file. + * + * This requires that a read lock be set on process->lock before being called. + * + * When this is synchronous, this will wait for the PID file to be generated before continuing. + * When this is asynchronous, this will continue on adding the rule id and action to the asynchronous list. + * + * @param pid_file + * The path to the PID file. + * @param type + * The item type code. + * @param program + * The program to use (such as "bash"). + * @param arguments + * The arguments to pass to the program. + * @param options + * Process options to consider when executing. + * If bit controller_process_option_simulate_d, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). + * @param with + * The "with" option flags. + * @param execute_set + * The execute parameter and as settings. + * @param process + * The process data for processing this rule. + * + * @return + * F_none on success. + * F_child on child process exiting. + * + * F_file_found (with error bit) if the PID file already exists. + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * F_lock (with error bit) if failed to re-establish read lock on process->lock while returning. + * + * Errors (with error bit) from: fll_execute_program(). + * + * @see fll_execute_program() + */ +#ifndef _di_controller_rule_execute_pid_with_ + extern f_status_t controller_rule_execute_pid_with(const f_string_dynamic_t pid_file, const uint8_t type, const f_string_t program, const f_string_statics_t arguments, const uint8_t options, const uint8_t with, controller_execute_set_t * const execute_set, controller_process_t * const process) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_execute_pid_with_ + +/** + * Determine whether or not an execute rule should be re-run, applying a delay as requested. + * + * @param action + * The action type. + * @param process + * The process data for processing this rule. + * @param item + * The rule item being executed. + * + * @return + * A positive number to designate re-run. + * 0 to designate do not re-run. + * -1 to designate an error from nanosleep(), with errno set to values like: + * - EFAULT: Designates that there was a problem copying information from user space. + * - EINTR: Consider this having returned F_interrupt. + * - EINVAL: Consider this having returned F_status_set_error(F_parameter); + * -2 to designate exit due to signal/disabled thread. + */ +#ifndef _di_controller_rule_execute_rerun_ + extern int8_t controller_rule_execute_rerun(const uint8_t action, controller_process_t * const process, controller_rule_item_t * const item) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_execute_rerun_ + +/** + * Construct an id from two distinct strings found within a single given source. + * + * @param global + * The global data. + * @param source + * The source string that both the directory and basename are copied from. + * @param directory + * A range within the source representing the directory part of a rule id. + * @param basename + * A range within the source representing the basename part of a rule id. + * @param alias + * The constructed alias. + * + * @return + * F_none on success. + * + * Errors (with error bit) from: f_string_dynamic_partial_append_nulless(). + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * + * @see f_string_append() + * @see f_string_dynamic_partial_append_nulless() + * @see f_string_dynamic_terminate_after() + */ +#ifndef _di_controller_rule_id_construct_ + extern f_status_t controller_rule_id_construct(const controller_global_t global, const f_string_static_t source, const f_string_range_t directory, const f_string_range_t basename, f_string_dynamic_t * const alias) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_id_construct_ + +/** + * Check to see if the given Rule has status F_known_not for the given Rule Action. + * + * The global Rule status is checked for error and any errors on the global Rule status will result in F_false. + * + * @param action + * The Rule Action type. + * @param rule + * The Rule. + * + * @return + * F_true on available (status is F_known_not). + * F_false on unavailable. + */ +#ifndef _di_controller_rule_status_is_available_ + extern f_status_t controller_rule_status_is_available(const uint8_t action, const controller_rule_t rule) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_status_is_available_ + +/** + * Check to see if the given Rule has status is designated as an error for the given Rule Action. + * + * The global Rule status is checked for error and any errors on the global Rule status will result in F_true. + * + * @param action + * The Rule Action type. + * @param rule + * The Rule. + * + * @return + * F_true if status represents an error. + * F_false if status does not represent an error. + */ +#ifndef _di_controller_rule_status_is_error_ + extern f_status_t controller_rule_status_is_error(const uint8_t action, const controller_rule_t rule) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_status_is_error_ + +/** + * Read the content within the buffer, extracting all valid items after determining their type for some rule file. + * + * This will perform additional FSS read functions as appropriate. + * + * @param global + * The global data. + * @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 cache + * A structure for containing and caching relevant data. + * @param item + * The processed item. + * + * @return + * F_none on success. + * F_valid_not (with error bit) on invalid data. + * + * Errors (with error bit) from: f_fss_count_lines(). + * Errors (with error bit) from: f_string_dynamic_partial_append_nulless(). + * Errors (with error bit) from: f_string_dynamic_terminate_after(). + * + * @see controller_rule_action_read() + * @see f_fss_count_lines() + * @see f_string_dynamic_partial_append_nulless() + * @see f_string_dynamic_terminate_after() + */ +#ifndef _di_controller_rule_item_read_ + extern f_status_t controller_rule_item_read(const controller_global_t global, const bool is_normal, controller_cache_t * const cache, controller_rule_item_t * const item) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_item_read_ + +/** + * Get a string representing the rule item type. + * + * @param type + * The rule item type code. + * + * @return + * The string with used > 0 on success. + * The string with used == 0 if no match was found. + */ +#ifndef _di_controller_rule_item_type_name_ + extern f_string_static_t controller_rule_item_type_name(const uint8_t type) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_item_type_name_ + +/** + * Increase the size of the rule items array by the specified amount, but only if necessary. + * + * This only increases size if the current used plus amount is greater than the currently allocated size. + * + * @param amount + * A positive number representing how much to increase the size by. + * @param items + * The items to resize. + * + * @return + * F_none on success. + * F_array_too_large (with error bit) if the resulting new size is bigger than the max array length. + * + * Errors (with error bit) from: f_memory_resize(). + * + * @see f_memory_resize() + */ +#ifndef _di_controller_rule_items_increase_by_ + extern f_status_t controller_rule_items_increase_by(const f_array_length_t amount, controller_rule_items_t * const items) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_items_increase_by_ + +/** + * Get a string representing the rule setting limit type. + * + * @param type + * The rule setting limit type code. + * + * @return + * The string with used > 0 on success. + * The string with used == 0 if no match was found. + */ +#ifndef _di_controller_rule_setting_limit_type_name_ + extern f_string_static_t controller_rule_setting_limit_type_name(const uint8_t type) F_attribute_visibility_internal_d; +#endif // di_controller_rule_setting_limit_type_name_ + +/** + * Process and execute the given rule. + * + * Any dependent rules are processed and executed as per "need", "want", and "wish" rule settings. + * All dependent rules must be already loaded, this function will not load any rules. + * + * This requires that a read lock be set on process->lock before being called. + * + * This function is recursively called for each "need", "want", and "wish", and has a max recursion length of the max size of the f_array_lengths_t array. + * + * The rule status will be updated by this function. + * + * @param global + * The global data. + * @param process + * The process data for processing this rule. + * + * @return + * F_none on success. + * F_child on child process exiting. + * F_failure on execution failure. + * + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * F_lock (with error bit) if failed to re-establish read lock on process->lock while returning. + * + * Errors (with error bit) from: controller_lock_read(). + * Errors (with error bit) from: controller_lock_write(). + */ +#ifndef _di_controller_rule_process_ + extern f_status_t controller_rule_process(const controller_global_t global, controller_process_t * const process) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_process_ + +/** + * Synchronously or asynchronously begin processing some rule. + * + * @param global + * The global data. + * @param options_force + * Force the given process options, only supporting a subset of process options. + * + * If controller_process_option_asynchronous_d, then asynchronously execute. + * If not controller_process_option_asynchronous_d, then synchronously execute. + * @param alias_rule + * The alias of the rule, such as "boot/init". + * @param action + * The action to perform based on the action type codes. + * @param options + * The process options to pass to the process. + * @param type + * The process type, such as controller_process_type_entry_e. + * @param stack + * A stack representing the processes already running in this rule process dependency tree. + * This is used to prevent circular dependencies. + * @param cache + * A structure for containing and caching relevant data. + * + * @return + * F_none on success. + * F_busy on success and the process was found to already be running (nothing to do). + * + * F_found_not (with error bit) if unable to for a process for the given rule id. + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * F_recurse (with error bit) on recursion error (the process is already on the process stack). + * + * Status from: controller_rule_process(). + * + * Errors (with error bit) from: controller_rule_process(). + * Errors (with error bit) from: f_string_dynamic_append(). + * Errors (with error bit) from: f_thread_create(). + * + * @see controller_rule_process() + * @see f_string_dynamic_append() + * @see f_thread_create() + */ +#ifndef _di_controller_rule_process_begin_ + extern f_status_t controller_rule_process_begin(const controller_global_t global, const uint8_t options_force, const f_string_static_t alias_rule, const uint8_t action, const uint8_t options, const uint8_t type, const f_array_lengths_t stack, const controller_cache_t cache) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_process_begin_ + +/** + * Helper for calling controller_rule_process(). + * + * This does all the preparation work that needs to be synchronously performed within the same thread. + * This will copy the rule by the alias to the process structure. + * + * @param options_force + * Force the given process options, only supporting a subset of process options. + * + * If controller_process_option_asynchronous_d, then asynchronously execute. + * If not controller_process_option_asynchronous_d, then synchronously execute. + * @param process + * The process data. + * + * @return + * F_none on success. + * F_found on the process was found to already be running (nothing to do). + * F_process_not if the process was not executed because it is a "consider" Action. + * + * F_found_not (with error bit) if unable to for a process for the given rule id. + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * + * Status from: controller_rule_process(). + * + * Errors (with error bit) from: controller_rule_copy(). + * Errors (with error bit) from: controller_rule_process(). + * + * @see controller_rule_copy() + * @see controller_rule_process() + * @see controller_rule_process_begin() + */ +#ifndef _di_controller_rule_process_do_ + extern f_status_t controller_rule_process_do(const uint8_t options_force, controller_process_t * const process) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_process_do_ + +/** + * Read the rule file, extracting all valid items. + * + * @param global + * The global data. + * @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 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. + * "/etc/controller/rules/example/my.rule" would have a rule id of "example/my". + * @param cache + * A structure for containing and caching relevant data. + * @param entry + * The entry containing the rule being read. + * @param rule + * The processed rule. + * The rule status will be updated by this function. + * + * @return + * F_none on success. + * + * Simplified status (with error bit) from controller_status_simplify_error() on failure. + * + * @see controller_rule_items_increase_by(). + * @see controller_rule_item_read(). + * @see f_fss_count_lines(). + * @see fl_fss_apply_delimit(). + * @see f_string_dynamic_partial_append(). + * @see f_string_dynamic_partial_append_nulless(). + * @see f_string_dynamic_terminate_after(). + * @see fll_fss_basic_list_read(). + */ +#ifndef _di_controller_rule_read_ + extern f_status_t controller_rule_read(const controller_global_t global, const bool is_normal, const f_string_static_t alias, controller_cache_t * const cache, controller_entry_t * const entry, controller_rule_t * const rule) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_read_ + +/** + * Process a number from a rule file, incrementing index as necessary. + * + * This prints error messages as necessary. + * + * This is intended to be called by controller_rule_action_read(). + * + * @param global + * The global data. + * @param name + * The name representing the value whose number is being processed. + * @param cache + * A structure for containing and caching relevant data. + * @param index + * The position within the content action array for some rule to process. + * @param number + * The processed number will be saved here. + * + * @return + * F_none on success. + * + * F_valid_not (with error bit) on failure due to invalid value. + * + * Errors (with error bit) from: fl_conversion_string_to_number_signed(). + * + * @see controller_rule_action_read() + * @see fl_conversion_string_to_number_signed() + */ +#ifndef _di_controller_rule_action_read_rerun_number_ + extern f_status_t controller_rule_action_read_rerun_number(const controller_global_t global, const f_string_t name, controller_cache_t * const cache, f_array_length_t * const index, f_number_unsigned_t * const number) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_action_read_rerun_number_ + +/** + * Read the content within the buffer, extracting all valid settings. + * + * This will perform additional FSS read functions as appropriate. + * + * Errors from this are not considered fatal, but the first error code encountered is returned. + * Memory failure errors are always immediately returned. + * + * @param global + * The global data. + * @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 setting + * The controller settings data. + * @param cache + * A structure for containing and caching relevant data. + * @param rule + * The processed rule. + * + * @return + * F_none on success. + * + * F_valid_not (with error bit) on success but there were one or more invalid settings encountered. + * + * Errors (with error bit) from: f_string_dynamic_partial_append_nulless(). + * Errors (with error bit) from: fl_string_dynamic_rip_nulless(). + * Errors (with error bit) from: f_string_dynamics_increase(). + * Errors (with error bit) from: f_string_maps_increase(). + * Errors (with error bit) from: fll_fss_extended_read(). + * Errors (with error bit) from: fll_path_canonical(). + * + * @see f_string_dynamic_partial_append_nulless() + * @see f_string_dynamics_increase() + * @see f_string_maps_increase() + * @see fl_string_dynamic_rip_nulless() + * @see fll_fss_extended_read() + * @see fll_path_canonical() + */ +#ifndef _di_controller_rule_setting_read_ + extern f_status_t controller_rule_setting_read(const controller_global_t global, const bool is_normal, const controller_setting_t setting, controller_cache_t * const cache, controller_rule_t * const rule) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_setting_read_ + +/** + * Perform a simulated execution of the given rule. + * + * This simply prints information about the rule. + * + * This automatically sets the rule's status to F_complete. + * + * @param global + * The global data. + * @param rule + * The rule to process. + * @param action + * The action to perform based on the action type codes. + * @param options + * A number using bits to represent specific boolean options. + * If no bits set, then operate normally in a synchronous manner. + * If bit controller_process_option_simulate_d, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). + * If bit controller_process_option_asynchronous_d, then run asynchronously. + * @param cache + * A structure for containing and caching relevant data. + */ +#ifndef _di_controller_rule_validate_ + extern void controller_rule_validate(const controller_global_t global, const controller_rule_t rule, const uint8_t action, const uint8_t options, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_validate_ + +/** + * Wait until all currently running Rule processes are complete. + * + * @param global + * The global data. + * @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. + * This is ignored when caller is not NULL. + * @param required + * If TRUE, then only process required rules and if a required rule has failed, return. + * If FALSE, process all waits, returning normally (required rules still result in failure). + * @param caller + * The process representing the caller so that the process never waits on itself. + * (optional) set to 0 when calling from a thread that is not running/executing any process. + * Failure to set this to the process on a thread running/executing a process will likely result in a deadlock. + * + * @return + * F_none on success. + * F_data_not on success and nothing to do. + * F_require on success, but a required rule has not been run yet. + * + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + * F_require (with error bit set) if a required process is in failed status when required is TRUE. + */ +#ifndef _di_controller_rule_wait_all_ + extern f_status_t controller_rule_wait_all(const controller_global_t global, const bool is_normal, const bool required, controller_process_t * const caller) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_wait_all_ + +/** + * Wait until all currently running Rule processes are complete for some process type. + * + * @param global + * The global data. + * @param type + * The process type to use when checking if thread is enabled. + * @param required + * If TRUE, then only process required rules and if a required rule has failed, return. + * If FALSE, process all waits, returning normally. + * @param caller + * The process representing the caller so that the process never waits on itself. + * (optional) set to 0 when calling from a thread that is not running/executing any process. + * Failure to set this to the process on a thread running/executing a process will likely result in a deadlock. + * + * @return + * Success from controller_rule_wait_all(). + * + * Errors (with error bit) from: controller_rule_wait_all(). + * + * @see controller_rule_wait_all() + */ +#ifndef _di_controller_rule_wait_all_process_type_ + extern f_status_t controller_rule_wait_all_process_type(const controller_global_t global, const uint8_t type, const bool required, controller_process_t * const caller) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_wait_all_process_type_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_rule_h diff --git a/level_3/controller/c/rule/private-rule_print.c b/level_3/controller/c/rule/private-rule_print.c new file mode 100644 index 0000000..f12909c --- /dev/null +++ b/level_3/controller/c/rule/private-rule_print.c @@ -0,0 +1,363 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../rule/private-rule_print.h" +#include "../lock/private-lock_print.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_rule_print_error_ + void controller_rule_print_error(controller_thread_t * const thread, const fl_print_t print, const controller_cache_action_t cache, const f_status_t status, const f_string_t function, const bool fallback, const bool item) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + if (status == F_interrupt) return; + + // fll_error_print() automatically locks, so manually handle only the mutex locking and flushing rather than calling controller_lock_print(). + f_thread_mutex_lock(&thread->lock.print); + + fll_error_print(print, status, function, fallback); + + flockfile(print.to.stream); + + controller_rule_print_error_cache(print, cache, item); + + controller_unlock_print_flush(print.to, thread); + } +#endif // _di_controller_rule_print_error_ + +#ifndef _di_controller_rule_print_error_cache_ + void controller_rule_print_error_cache(const fl_print_t print, const controller_cache_action_t cache, const bool item) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + + fl_print_format("%c%[%SWhile processing ", print.to.stream, f_string_eol_s[0], print.context, print.prefix); + + if (cache.name_action.used) { + fl_print_format("%s '%]", print.to.stream, item ? controller_action_s : controller_value_s, print.context); + fl_print_format("%[%Q%]", print.to.stream, print.notable, cache.name_action, print.notable); + fl_print_format("%[' on line%] ", print.to.stream, print.context, print.context); + fl_print_format("%[%un%]", print.to.stream, print.notable, cache.line_action, print.notable); + fl_print_format("%[ for ", print.to.stream, print.context); + } + + if (cache.name_item.used) { + fl_print_format("rule %s '%]", print.to.stream, item ? controller_item_s : controller_setting_s, print.context); + fl_print_format("%[%Q%]", print.to.stream, print.notable, cache.name_item, print.notable); + fl_print_format("%[' on line%] ", print.to.stream, print.context, print.context); + fl_print_format("%[%un%]", print.to.stream, print.notable, cache.line_item, print.notable); + fl_print_format("%[ for ", print.to.stream, print.context); + } + + if (cache.name_file.used) { + fl_print_format("rule file '%]%[%Q%]%['", print.to.stream, print.context, print.notable, cache.name_file, print.notable, print.context); + } + + fl_print_format(".%]%c", print.to.stream, print.context, f_string_eol_s[0]); + } +#endif // _di_controller_rule_print_error_cache_ + +#ifndef _di_controller_rule_item_print_error_ + void controller_rule_item_print_error(controller_thread_t * const thread, const fl_print_t print, const controller_cache_action_t cache, const bool item, const f_status_t status) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + if (status == F_interrupt) return; + + // fll_error_print() automatically locks, so manually handle only the mutex locking and flushing rather than calling controller_lock_print(). + f_thread_mutex_lock(&thread->lock.print); + + controller_rule_print_error_cache(print, cache, item); + + flockfile(print.to.stream); + + controller_unlock_print_flush(print.to, thread); + } +#endif // _di_controller_rule_item_print_error_ + +#ifndef _di_controller_rule_item_print_error_execute_ + void controller_rule_item_print_error_execute(const bool script_is, const f_string_t name, const f_status_t status, controller_process_t * const process) { + + if (((controller_main_t *) process->main_data)->error.verbosity != f_console_verbosity_quiet_e) { + fl_print_t * const print = &((controller_main_t *) process->main_data)->error; + + controller_lock_print(print->to, (controller_thread_t *) process->main_thread); + + fl_print_format("%c%[%SThe %s '%]", print->to.stream, f_string_eol_s[0], print->context, print->prefix, script_is ? controller_script_s : controller_program_s, print->context); + fl_print_format("%[%S%]", print->to.stream, print->notable, name, print->notable); + + if (status == F_control_group || status == F_limit || status == F_processor || status == F_schedule) { + fl_print_format("%[' failed due to a failure to setup the '%]%[", print->to.stream, print->context, print->context, print->notable); + + if (status == F_control_group) { + f_print_terminated(controller_cgroup_s, print->to.stream); + } + else if (status == F_limit) { + f_print_terminated(controller_limit_s, print->to.stream); + } + else if (status == F_processor) { + f_print_terminated(controller_processor_s, print->to.stream); + } + else if (status == F_schedule) { + f_print_terminated(controller_scheduler_s, print->to.stream); + } + + fl_print_format("%]%['.%]%c", print->to.stream, print->notable, print->context, print->context, f_string_eol_s[0]); + } + else if (WIFEXITED(process->result) ? WEXITSTATUS(process->result) : 0) { + const uint8_t code = WIFEXITED(process->result) ? WEXITSTATUS(process->result) : 0; + + if (code == F_execute_access) { + fl_print_format("%[' access denied.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_bad) { + fl_print_format("%[' cannot execute, unsupported format.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_buffer) { + fl_print_format("%[' invalid memory access in arguments buffer.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_busy) { + fl_print_format("%[' required resources are unavailable, too busy.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_capability) { + fl_print_format("%[' failed to setup capabilities.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_control_group) { + fl_print_format("%[' failed to setup control group.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_child) { + fl_print_format("%[' failed to setup child process.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_directory_not) { + fl_print_format("%[' invalid path, part of the path is not a valid directory.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_failure) { + fl_print_format("%[' failed during execution.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_file_found_not) { + fl_print_format("%[' could not be executed, unable to find file.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_file_type_directory) { + fl_print_format("%[' ELF interpreter is a directory.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_fork_not) { + fl_print_format("%[' fork failure.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_format_not) { + fl_print_format("%[' could not be executed because the program has an invalid ELF header.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_group) { + fl_print_format("%[' failed to setup group.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_input_output) { + fl_print_format("%[' I/O failure.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_limit) { + fl_print_format("%[' failed to setup resource limits.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_loop) { + fl_print_format("%[' max recursion reached.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_memory_not) { + fl_print_format("%[' out of memory.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_name_not) { + fl_print_format("%[' file name or path is too long.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_nice) { + fl_print_format("%[' failed to setup niceness.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_parameter) { + fl_print_format("%[' invalid parameter.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_pipe) { + fl_print_format("%[' pipe failed.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_processor) { + fl_print_format("%[' failed to setup processor affinity.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_prohibited) { + fl_print_format("%[' access prohibited.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_resource_not) { + fl_print_format("%[' resource limit reached.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_schedule) { + fl_print_format("%[' failed to setup scheduler.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_terminal) { + fl_print_format("%[' failed while processing the terminal.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_terminal_known_not) { + fl_print_format("%[' cannot process terminal, unknown terminal control command.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_terminal_not) { + fl_print_format("%[' cannot process terminal, not a known terminal.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_terminal_prohibited) { + fl_print_format("%[' insufficient permissions to process the terminal.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_terminal_valid_not) { + fl_print_format("%[' invalid parameter while processing the terminal.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_too_large) { + fl_print_format("%[' too many arguments or arguments are too large.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_user) { + fl_print_format("%[' failed to setup user.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else if (code == F_execute_valid_not) { + fl_print_format("%[' unknown ELF interpreter format.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + else { + fl_print_format("%[' failed with the execute error code %]", print->to.stream, print->context, print->context); + fl_print_format("%[%i%]", print->to.stream, print->notable, code, print->notable); + fl_print_format("%[.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + } + else { + fl_print_format("%[' failed.%]%c", print->to.stream, print->context, print->context, f_string_eol_s[0]); + } + + controller_unlock_print_flush(print->to, (controller_thread_t *) process->main_thread); + } + } +#endif // _di_controller_rule_item_print_error_execute_ + +#ifndef _di_controller_rule_action_print_error_missing_pid_ + void controller_rule_action_print_error_missing_pid(const fl_print_t print, const f_string_t alias) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + + fl_print_format("%c%[%SThe rule '%]", print.to.stream, f_string_eol_s[0], print.context, print.prefix, print.context); + fl_print_format("%[%S%]", print.to.stream, print.notable, alias, print.notable); + fl_print_format("%[' is not designating a pid file.%]%c", print.to.stream, print.context, print.context, f_string_eol_s[0]); + } +#endif // _di_controller_rule_action_print_error_missing_pid_ + +#ifndef _di_controller_rule_item_print_error_need_want_wish_ + void controller_rule_item_print_error_need_want_wish(const fl_print_t print, const f_string_t need_want_wish, const f_string_t value, const f_string_t why) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + + fl_print_format("%c%[%SThe %s rule '%]", print.to.stream, f_string_eol_s[0], print.context, print.prefix, need_want_wish, print.context); + fl_print_format("%[%S%]", print.to.stream, print.notable, value, print.notable); + fl_print_format("%[' %S.%]%c", print.to.stream, print.context, why, print.context, f_string_eol_s[0]); + } +#endif // _di_controller_rule_item_print_error_need_want_wish_ + +#ifndef _di_controller_rule_item_print_error_rule_not_loaded_ + void controller_rule_item_print_error_rule_not_loaded(const fl_print_t print, const f_string_t alias) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + + fl_print_format("%c%[%SThe rule '%]", print.to.stream, f_string_eol_s[0], print.context, print.prefix, print.context); + fl_print_format("%[%S%]", print.to.stream, print.notable, alias, print.notable); + fl_print_format("%[' is no longer loaded.%]%c", print.to.stream, print.context, print.context, f_string_eol_s[0]); + } +#endif // _di_controller_rule_item_print_error_rule_not_loaded_ + +#ifndef _di_controller_rule_setting_read_print_error_ + void controller_rule_setting_read_print_error(const fl_print_t print, const f_string_t message, const f_array_length_t index, const f_array_length_t line_item, controller_thread_t * const thread, controller_cache_t * const cache) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[index].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(print.to, thread); + + fl_print_format("%c%[%SRule setting %S.%]%c", print.to.stream, f_string_eol_s[0], print.context, print.prefix, message, print.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(print, cache->action, F_false); + + controller_unlock_print_flush(print.to, thread); + } +#endif // _di_controller_rule_setting_read_print_error_ + +#ifndef _di_controller_rule_setting_read_print_error_with_range_ + void controller_rule_setting_read_print_error_with_range(const fl_print_t print, const f_string_t before, const f_string_range_t range, const f_string_t after, const f_array_length_t index, const f_array_length_t line_item, controller_thread_t * const thread, controller_cache_t * const cache) { + + if (print.verbosity == f_console_verbosity_quiet_e) return; + + // Get the current line number within the settings item. + cache->action.line_item = line_item; + f_fss_count_lines(cache->buffer_item, cache->object_actions.array[index].start, &cache->action.line_item); + + cache->action.line_action = ++cache->action.line_item; + + controller_lock_print(print.to, thread); + + fl_print_format("%c%[%SRule setting%S '%]", print.to.stream, f_string_eol_s[0], print.context, print.prefix, before, print.context); + fl_print_format("%[%/Q%]", print.to.stream, print.notable, cache->buffer_item, range, print.notable); + fl_print_format("%['%S.%]%c", print.to.stream, print.context, after, print.context, f_string_eol_s[0]); + + controller_rule_print_error_cache(print, cache->action, F_false); + + controller_unlock_print_flush(print.to, thread); + } +#endif // _di_controller_rule_setting_read_print_error_with_range_ + +#ifndef _di_controller_rule_setting_read_print_value_ + void controller_rule_setting_read_print_value(const controller_global_t global, const f_string_t name, const f_string_t name_sub, const f_string_static_t value, const f_string_t suffix) { + + if (global.main->error.verbosity != f_console_verbosity_debug_e && !(global.main->error.verbosity == f_console_verbosity_verbose_e && global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e)) { + return; + } + + controller_lock_print(global.main->output.to, global.thread); + + fl_print_format("%cProcessing rule item action '%[%S%]' setting ", global.main->output.to.stream, f_string_eol_s[0], global.main->context.set.title, name, global.main->context.set.title); + + if (name_sub) { + fl_print_format("'%[%S%]'", global.main->output.to.stream, global.main->context.set.notable, name_sub, global.main->context.set.notable); + } + else { + f_print_terminated("value", global.main->output.to.stream); + } + + fl_print_format(" to '%[%Q%]'", global.main->output.to.stream, global.main->context.set.important, value, global.main->context.set.important); + fl_print_format("%S.%c", global.main->output.to.stream, suffix, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->output.to, global.thread); + } +#endif // _di_controller_rule_setting_read_print_value_ + +#ifndef _di_controller_rule_setting_read_print_values_ + void controller_rule_setting_read_print_values(const controller_global_t global, const f_string_t name, const f_array_length_t index, controller_cache_t * const cache) { + + if (global.main->error.verbosity != f_console_verbosity_debug_e && !(global.main->error.verbosity == f_console_verbosity_verbose_e && global.main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e)) { + return; + } + + controller_lock_print(global.main->output.to, global.thread); + + fl_print_format("%cProcessing rule item action '%[%S%]' setting value to", global.main->output.to.stream, f_string_eol_s[0], global.main->context.set.title, name, global.main->context.set.title); + + for (f_array_length_t j = 0; j < cache->content_actions.array[index].used; ++j) { + + fl_print_format(" '%[%/Q%]'", global.main->output.to.stream, global.main->context.set.important, cache->buffer_item, cache->content_actions.array[index].array[j], global.main->context.set.important); + + if (j + 2 == cache->content_actions.array[index].used) { + if (cache->content_actions.array[index].used > 2) { + f_print_terminated(",", global.main->output.to.stream); + } + + f_print_terminated(" and", global.main->output.to.stream); + } + else if (j + 1 < cache->content_actions.array[index].used) { + f_print_terminated(",", global.main->output.to.stream); + } + } // for + + fl_print_format(".%c", global.main->output.to.stream, f_string_eol_s[0]); + + controller_unlock_print_flush(global.main->output.to, global.thread); + } +#endif // _di_controller_rule_setting_read_print_value_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/rule/private-rule_print.h b/level_3/controller/c/rule/private-rule_print.h new file mode 100644 index 0000000..144642a --- /dev/null +++ b/level_3/controller/c/rule/private-rule_print.h @@ -0,0 +1,248 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_rule_print_h +#define _PRIVATE_rule_print_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Print generic error/warning information. + * + * This is essentially a wrapper to fll_error_print() that includes locking. + * + * @param thread + * The thread data. + * @param print + * Designates how printing is to be performed. + * @param cache + * The action cache. + * @param status + * The status code to process. + * Make sure this has F_status_set_fine() called if the status code has any error or warning bits. + * @param function + * The name of the function where the error happened. + * Set to 0 to disable. + * @param fallback + * Set to F_true to print the fallback error message for unknown errors. + * @param item + * If TRUE, then this error is associated with an item. + * If FALSE, then this error is associated with a rule setting. + * + * @see fll_error_print() + * @see controller_rule_print_error_cache() + */ +#ifndef _di_controller_rule_print_error_ + extern void controller_rule_print_error(controller_thread_t * const thread, const fl_print_t print, const controller_cache_action_t cache, const f_status_t status, const f_string_t function, const bool fallback, const bool item) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_print_error_ + +/** + * Print additional error/warning information in addition to existing error. + * + * This is explicitly intended to be used in addition to the error message. + * + * This neither locks the thread nor does it check to see if output is enabled or disabled. + * + * @param print + * The error or warning output structure. + * @param cache + * A structure for containing and caching relevant data. + * @param item + * If TRUE, then this error is associated with an item. + * If FALSE, then this error is associated with a rule setting. + * + * @see controller_rule_action_read() + * @see controller_rule_item_read() + * @see controller_rule_items_read() + * @see controller_rule_read() + * @see controller_rule_setting_read() + */ +#ifndef _di_controller_rule_print_error_cache_ + extern void controller_rule_print_error_cache(const fl_print_t print, const controller_cache_action_t cache, const bool item) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_print_error_cache_ + +/** + * Print additional error/warning information in addition to existing error. + * + * This is explicitly intended to be used in addition to the error message. + * + * @param thread + * The thread data. + * @param print + * The error or warning print structure. + * @param cache + * A structure for containing and caching relevant data. + * @param item + * If TRUE, then this error is associated with an item. + * If FALSE, then this error is associated with a rule setting. + * @param status + * The status code representing the failure (without the error bit set). + * + * @see controller_rule_print_error_cache() + */ +#ifndef _di_controller_rule_item_print_error_ + extern void controller_rule_item_print_error(controller_thread_t * const thread, const fl_print_t print, const controller_cache_action_t cache, const bool item, const f_status_t status) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_item_print_error_ + +/** + * Print an error or warning message related to the failed execution of some program or script. + * + * @param script_is + * If TRUE, then this represents a script. + * If FALSE, then this represents a program. + * @param name + * The name of the program or script. + * @param code + * The code returned by the executed program or script. + * @param status + * The status code representing the failure (without the error bit set). + * @param process + * The process to use. + */ +#ifndef _di_controller_rule_item_print_error_execute_ + extern void controller_rule_item_print_error_execute(const bool script_is, const f_string_t name, const f_status_t status, controller_process_t * const process) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_item_print_error_execute_ + +/** + * Print an error or warning message about some rule not having the pid file information. + * + * @param print + * The error or warning output structure. + * @param alias + * The rule alias of the rule that is missing the pid file designation. + */ +#ifndef _di_controller_rule_action_print_error_missing_pid_ + extern void controller_rule_action_print_error_missing_pid(const fl_print_t print, const f_string_t alias) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_action_print_error_missing_pid_ + +/** + * Print an error or warning message related to need/want/wish settings of some rule. + * + * @param print + * The error or warning output structure. + * @param need_want_wish + * The appropriate string, such as "needs", "wants", or "wishes for" to output when describing this error/warning. + * This string is expected to already be "safe" (no control characters, etc..). + * @param value + * The value that is the error or warning. + * @param why + * A short explanation on why this is an error or warning. + */ +#ifndef _di_controller_rule_item_print_error_need_want_wish_ + extern void controller_rule_item_print_error_need_want_wish(const fl_print_t print, const f_string_t need_want_wish, const f_string_t value, const f_string_t why) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_item_print_error_need_want_wish_ + +/** + * Print an error or warning message about some rule not being loaded. + * + * @param print + * The error or warning output structure. + * @param alias + * The rule alias of the rule that is not loaded. + */ +#ifndef _di_controller_rule_item_print_error_rule_not_loaded_ + extern void controller_rule_item_print_error_rule_not_loaded(const fl_print_t print, const f_string_t alias) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_item_print_error_rule_not_loaded_ + +/** + * Print a message about a rule setting problem. + * + * This is intended to be explicitly called by controller_rule_setting_read(). + * This is intended only to be used for simple messages. + * + * @param print + * The error or warning output structure. + * @param message + * The string to append to the message being printed. + * @param index + * The position in the object actions cache representing the object. + * @param line_item + * The current line number. + * @param thread + * The thread data. + * @param cache + * A structure for containing and caching relevant data. + * + * @see controller_rule_setting_read() + */ +#ifndef _di_controller_rule_setting_read_print_error_ + extern void controller_rule_setting_read_print_error(const fl_print_t print, const f_string_t message, const f_array_length_t index, const f_array_length_t line_item, controller_thread_t * const thread, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_setting_read_print_error_ + +/** + * Print a message about a rule setting problem, with additional messages about the value. + * + * This is intended to be explicitly called by controller_rule_setting_read(). + * This is intended only to be used for simple messages. + * + * @param print + * The error or warning output structure. + * @param before + * The string to add to the message being printed (before the value). + * @param range + * The range within the cache item buffer representing the value. + * @param after + * The string to add to the message being printed (after the value). + * @param index + * The position in the object actions cache representing the object. + * @param line_item + * The current line number. + * @param thread + * The thread data. + * @param cache + * A structure for containing and caching relevant data. + * + * @see controller_rule_setting_read() + */ +#ifndef _di_controller_rule_setting_read_print_error_with_range_ + extern void controller_rule_setting_read_print_error_with_range(const fl_print_t print, const f_string_t before, const f_string_range_t range, const f_string_t after, const f_array_length_t index, const f_array_length_t line_item, controller_thread_t * const thread, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_setting_read_print_error_with_range_ + +/** + * Print message regarding the population of a setting when in simulation or verbose mode. + * + * @param global + * The global data. + * @param name + * The Object name of the setting being populated. + * @param name_sub + * (optional) A sub-name associated with the setting being populated. + * Set to NULL to disable. + * @param value + * The value being set. + * @param suffix + * An additional message to append at the end (before the final period). + */ +#ifndef _di_controller_rule_setting_read_print_value_ + extern void controller_rule_setting_read_print_value(const controller_global_t global, const f_string_t name, const f_string_t name_sub, const f_string_static_t value, const f_string_t suffix) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_setting_read_print_value_ + +/** + * Print message regarding the population of a setting when in simulation or verbose mode. + * + * This handles the case where there are multiple values stored in the buffer_item at a given content_actions position. + * + * @param global + * The global data. + * @param name + * The Object name of the setting being populated. + * @param index + * Position within the content_actions range cache array. + * @param cache + * A structure for containing and caching relevant data. + */ +#ifndef _di_controller_rule_setting_read_print_values_ + extern void controller_rule_setting_read_print_values(const controller_global_t global, const f_string_t name, const f_array_length_t index, controller_cache_t * const cache) F_attribute_visibility_internal_d; +#endif // _di_controller_rule_setting_read_print_values_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_rule_print_h diff --git a/level_3/controller/c/task/private-task.c b/level_3/controller/c/task/private-task.c new file mode 100644 index 0000000..68f0575 --- /dev/null +++ b/level_3/controller/c/task/private-task.c @@ -0,0 +1,11 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../task/private-task.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/task/private-task.h b/level_3/controller/c/task/private-task.h new file mode 100644 index 0000000..16c620f --- /dev/null +++ b/level_3/controller/c/task/private-task.h @@ -0,0 +1,19 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_task_h +#define _PRIVATE_task_h + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_task_h diff --git a/level_3/controller/c/task/private-task_print.c b/level_3/controller/c/task/private-task_print.c new file mode 100644 index 0000000..b17f5e4 --- /dev/null +++ b/level_3/controller/c/task/private-task_print.c @@ -0,0 +1,12 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../task/private-task.h" +#include "../task/private-task_print.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/task/private-task_print.h b/level_3/controller/c/task/private-task_print.h new file mode 100644 index 0000000..9fdea4a --- /dev/null +++ b/level_3/controller/c/task/private-task_print.h @@ -0,0 +1,19 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_task_print_h +#define _PRIVATE_task_print_h + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_task_print_h diff --git a/level_3/controller/c/thread/private-thread.c b/level_3/controller/c/thread/private-thread.c new file mode 100644 index 0000000..62b8fee --- /dev/null +++ b/level_3/controller/c/thread/private-thread.c @@ -0,0 +1,358 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../controller/private-controller.h" +#include "../controller/private-controller_print.h" +#include "../lock/private-lock.h" +#include "../lock/private-lock_print.h" +#include "../rule/private-rule.h" +#include "../thread/private-thread.h" +#include "../thread/private-thread_control.h" +#include "../thread/private-thread_entry.h" +#include "../thread/private-thread_process.h" +#include "../thread/private-thread_rule.h" +#include "../thread/private-thread_signal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_thread_cleanup_ + void * controller_thread_cleanup(void * const arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + const controller_global_t *global = (controller_global_t *) arguments; + + if (global->thread->enabled != controller_thread_enabled_e) return 0; + + const struct timespec delay = controller_time_seconds(global->main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e ? controller_thread_cleanup_interval_short_d : controller_thread_cleanup_interval_long_d); + + f_status_t status = F_none; + + while (global->thread->enabled == controller_thread_enabled_e) { + + nanosleep(&delay, 0); + + if (global->thread->enabled != controller_thread_enabled_e) break; + + if (f_thread_lock_write_try(&global->thread->lock.process) == F_none) { + controller_process_t *process = 0; + + f_array_length_t i = 0; + + for (; i < global->thread->processs.size && global->thread->enabled == controller_thread_enabled_e; ++i) { + + if (!global->thread->processs.array[i]) continue; + + process = global->thread->processs.array[i]; + + // If "active" has a read lock, then do not attempt to clean it. + if (f_thread_lock_write_try(&process->active) != F_none) { + continue; + } + + // If "lock" has a read or write lock, then do not attempt to clean it. + if (f_thread_lock_write_try(&process->lock) != F_none) { + f_thread_unlock(&process->active); + + continue; + } + + // If process is active or busy, then do not attempt to clean it. + if (process->state == controller_process_state_active_e || process->state == controller_process_state_busy_e) { + f_thread_unlock(&process->active); + f_thread_unlock(&process->lock); + + continue; + } + + // If process has a PID file, then it is running in the background, only cleanup if the PID file no longer exists. + if (process->path_pids.used) { + f_array_length_t j = 0; + + for (; j < process->path_pids.used; ++j) { + + if (process->path_pids.array[j].used && f_file_exists(process->path_pids.array[j].string) == F_true) { + break; + } + } // for + + if (j < process->path_pids.used) { + f_thread_unlock(&process->active); + f_thread_unlock(&process->lock); + + continue; + } + } + + f_thread_unlock(&process->lock); + + // Close any still open thread. + if (process->id_thread) { + status = f_thread_join(process->id_thread, 0); + + if (F_status_is_error_not(status) || F_status_set_fine(status) == F_found_not) { + status = f_thread_lock_write(&process->lock); + + if (F_status_is_error(status)) { + controller_lock_print_error_critical(global->main->error, F_status_set_fine(status), F_false, global->thread); + + f_thread_unlock(&process->active); + continue; + } + + process->state = controller_process_state_idle_e; + process->id_thread = 0; + + f_thread_mutex_lock(&process->wait_lock); + f_thread_condition_signal_all(&process->wait); + f_thread_mutex_unlock(&process->wait_lock); + + f_thread_unlock(&process->lock); + } + else { + f_thread_unlock(&process->active); + + continue; + } + } + + // Deallocate dynamic portions of the structure that are only ever needed while the process is running. + controller_cache_delete_simple(&process->cache); + f_type_array_lengths_resize(0, &process->stack); + + // Shrink the childs array. + if (process->childs.used) { + for (; process->childs.used; --process->childs.used) { + if (process->childs.array[process->childs.used]) break; + } // for + + if (process->childs.used < process->childs.size) { + controller_pids_resize(process->childs.used, &process->childs); + } + } + + // Deallocate the PID files. + if (process->path_pids.used) { + process->path_pids.used = 0; + f_string_dynamics_resize(0, &process->path_pids); + } + + // Deallocate any rules in the space that is declared to be unused. + if (i >= global->thread->processs.used) { + controller_rule_delete_simple(&process->rule); + } + + f_thread_unlock(&process->active); + } // for + + f_thread_unlock(&global->thread->lock.process); + } + } // while + + return 0; + } +#endif // _di_controller_thread_cleanup_ + +#ifndef _di_controller_thread_is_enabled_ + f_status_t controller_thread_is_enabled(const bool is_normal, controller_thread_t * const thread) { + + if (is_normal) { + return thread->enabled == controller_thread_enabled_e; + } + + return thread->enabled; + } +#endif // _di_controller_thread_is_enabled_ + +#ifndef _di_controller_thread_is_enabled_process_ + f_status_t controller_thread_is_enabled_process(controller_process_t * const process, controller_thread_t * const thread) { + + return controller_thread_is_enabled_process_type(process->type, thread); + } +#endif // _di_controller_thread_is_enabled_process_ + +#ifndef _di_controller_thread_is_enabled_process_type_ + f_status_t controller_thread_is_enabled_process_type(const uint8_t type, controller_thread_t * const thread) { + + return controller_thread_is_enabled(type != controller_process_type_exit_e, thread); + } +#endif // _di_controller_thread_is_enabled_process_type_ + +#ifndef _di_controller_thread_main_ + f_status_t controller_thread_main(controller_main_t * const main, controller_setting_t * const setting) { + + f_status_t status = F_none; + + controller_thread_t thread = controller_thread_t_initialize; + controller_global_t global = macro_controller_global_t_initialize(main, setting, &thread); + + // The global locks must be initialized, but only once, so initialize immediately upon allocation. + status = controller_lock_create(&thread.lock); + + if (F_status_is_error(status)) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + fll_error_print(main->error, status, "controller_lock_create", F_true); + } + } + else { + status = controller_processs_increase(&thread.processs); + + if (F_status_is_error(status)) { + controller_print_error(&thread, main->error, F_status_set_fine(status), "controller_processs_increase", F_true); + } + } + + if (F_status_is_error_not(status)) { + status = f_thread_create(0, &thread.id_signal, &controller_thread_signal_normal, (void *) &global); + } + + if (F_status_is_error(status)) { + thread.id_signal = 0; + + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error(&thread, main->error, F_status_set_fine(status), "f_thread_create", F_true); + } + } + else { + if (main->parameters[controller_parameter_daemon_e].result == f_console_result_found_e) { + setting->ready = controller_setting_ready_done_e; + + if (f_file_exists(setting->path_pid.string) == F_true) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, &thread); + + fl_print_format("%c%[%SThe pid file '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[%S%]", main->error.to.stream, main->error.notable, setting->path_pid.string, main->error.notable); + fl_print_format("%[' must not already exist.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, &thread); + } + + setting->ready = controller_setting_ready_abort_e; + status = F_status_set_error(F_available_not); + } + } + else if (global.setting->name_entry.used) { + const controller_main_entry_t entry = macro_controller_main_entry_t_initialize(&global, global.setting); + + status = f_thread_create(0, &thread.id_entry, &controller_thread_entry, (void *) &entry); + + if (F_status_is_error(status)) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error(&thread, main->error, F_status_set_fine(status), "f_thread_create", F_true); + } + } + else { + controller_thread_join(&thread.id_entry); + + status = thread.status; + thread.id_entry = 0; + } + } + } + + // Only make the rule and control threads available once any/all pre-processing and are completed. + if (F_status_is_error_not(status) && status != F_failure && status != F_child && thread.enabled == controller_thread_enabled_e) { + if (main->parameters[controller_parameter_validate_e].result == f_console_result_none_e) { + + // Wait for the entry thread to complete before starting the rule thread. + controller_thread_join(&thread.id_rule); + + if (thread.enabled && setting->mode == controller_setting_mode_service_e) { + status = f_thread_create(0, &thread.id_rule, &controller_thread_rule, (void *) &global); + + if (F_status_is_error(status)) { + thread.id_rule = 0; + } + else { + status = f_thread_create(0, &thread.id_cleanup, &controller_thread_cleanup, (void *) &global); + } + + if (F_status_is_error(status)) { + thread.id_cleanup = 0; + + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error(&thread, main->error, F_status_set_fine(status), "f_thread_create", F_true); + } + } + } + } + } + + if (status == F_child) { + controller_thread_delete_simple(&thread); + + return F_child; + } + + if (F_status_is_error_not(status) && status != F_failure && main->parameters[controller_parameter_validate_e].result == f_console_result_none_e && controller_thread_is_enabled(F_true, &thread)) { + + if (setting->mode == controller_setting_mode_service_e) { + controller_thread_join(&thread.id_signal); + } + else if (setting->mode == controller_setting_mode_program_e) { + status = controller_rule_wait_all(global, F_true, F_false, 0); + } + } + + controller_thread_process_cancel(global, F_true, controller_thread_cancel_call_e, 0); + + controller_thread_process_exit(&global); + + if (thread.id_listen) { + f_thread_cancel(thread.id_listen); + } + + if (thread.id_signal) f_thread_join(thread.id_signal, 0); + if (thread.id_cleanup) f_thread_join(thread.id_cleanup, 0); + if (thread.id_control) f_thread_join(thread.id_control, 0); + if (thread.id_listen) f_thread_join(thread.id_listen, 0); + if (thread.id_entry) f_thread_join(thread.id_entry, 0); + if (thread.id_rule) f_thread_join(thread.id_rule, 0); + + thread.id_cleanup = 0; + thread.id_control = 0; + thread.id_listen = 0; + thread.id_entry = 0; + thread.id_rule = 0; + thread.id_signal = 0; + + controller_thread_delete_simple(&thread); + + if (F_status_is_error(status)) { + return F_status_set_error(F_failure); + } + + if (F_status_set_fine(status) == F_interrupt) { + controller_print_signal_received(main, thread.signal); + + if (main->output.verbosity != f_console_verbosity_quiet_e) { + fll_print_terminated(f_string_eol_s, main->output.to.stream); + } + + return F_status_set_error(F_interrupt); + } + + return F_none; + } +#endif // _di_controller_thread_main_ + +#ifndef _di_controller_thread_join_ + f_status_t controller_thread_join(f_thread_id_t * const id) { + + if (!id || !*id) return F_data_not; + + const f_status_t status = f_thread_join(*id, 0); + + if (F_status_is_error_not(status) || F_status_set_fine(status) == F_found_not) { + *id = 0; + } + + return status; + } +#endif // _di_controller_thread_join_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/thread/private-thread.h b/level_3/controller/c/thread/private-thread.h new file mode 100644 index 0000000..cc63292 --- /dev/null +++ b/level_3/controller/c/thread/private-thread.h @@ -0,0 +1,126 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_thread_h +#define _PRIVATE_thread_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Thread for periodically cleanup data when not busy. + * + * @param arguments + * The thread arguments. + * Must be of type controller_global_t. + * + * @return + * 0, always. + */ +#ifndef _di_controller_thread_cleanup_ + extern void * controller_thread_cleanup(void * const arguments) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_cleanup_ + +/** + * 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 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. + * + * @return + * TRUE when enabled. + * FALSE when disabled. + */ +#ifndef _di_controller_thread_is_enabled_ + extern f_status_t controller_thread_is_enabled(const bool is_normal, controller_thread_t * const thread) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_is_enabled_ + +/** + * Check to see if thread is enabled for the normal operations like entry and control or for exit operations for some process. + * + * @param process + * The process to use when checking if thread is enabled. + * @param thread + * The thread data. + * + * @return + * + * Success from controller_thread_is_enabled_process_type(). + * + * @see controller_thread_is_enabled_process_type() + */ +#ifndef _di_controller_thread_is_enabled_process_ + extern f_status_t controller_thread_is_enabled_process(controller_process_t * const process, controller_thread_t * const thread) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_is_enabled_process_ + +/** + * Check to see if thread is enabled for the normal operations like entry and control or for exit operations for some process type. + * + * @param type + * The process type to use when checking if thread is enabled. + * @param thread + * The thread data. + * + * @return + * + * Success from controller_thread_is_enabled(). + * + * @see controller_thread_is_enabled() + */ +#ifndef _di_controller_thread_is_enabled_process_type_ + extern f_status_t controller_thread_is_enabled_process_type(const uint8_t type, controller_thread_t * const thread) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_is_enabled_process_type_ + +/** + * Start all threads, wait on threads, and handle requests. + * + * @param main + * The main program data. + * @param setting + * The controller settings data. + * + * @return + * F_none on success. + * F_child on child process exiting. + * + * F_failure (with error bit) on any failure. + * F_interrupt (with error bit) on receiving a process signal, such as an interrupt signal. + */ +#ifndef _di_controller_thread_main_ + extern f_status_t controller_thread_main(controller_main_t * const main, controller_setting_t * const setting) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_main_ + +/*** + * Join a thread, assigning id to NULL on success. + * + * If the ID is not found, then it is also set to NULL. + * + * @param id + * The thread ID. + * + * @return + * F_none on success. + * + * Success from: f_thread_join(). + * + * Errors (with error bit) from: f_thread_join(). + * + * @see f_thread_join() + */ +#ifndef _di_controller_thread_join_ + extern f_status_t controller_thread_join(f_thread_id_t * const id) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_join_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_thread_h diff --git a/level_3/controller/c/thread/private-thread_control.c b/level_3/controller/c/thread/private-thread_control.c new file mode 100644 index 0000000..952c762 --- /dev/null +++ b/level_3/controller/c/thread/private-thread_control.c @@ -0,0 +1,92 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../control/private-control.h" +#include "../controller/private-controller_print.h" +#include "private-thread.h" +#include "private-thread_control.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_thread_control_ + void * controller_thread_control(void * const arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + const controller_global_t *global = (controller_global_t *) arguments; + + if (global->thread->enabled != controller_thread_enabled_e) return 0; + + f_status_t status = F_none; + controller_control_t control = macro_controller_control_t_initialize(&global->setting->control_socket, 0); + + do { + + // Shrink any overly large buffers. + if (control.cache_1.size > controller_control_default_socket_cache_d) { + status = f_string_dynamic_resize(controller_control_default_socket_cache_d, &control.cache_1); + } + + if (F_status_is_error_not(status) && control.cache_2.size > controller_control_default_socket_buffer_d) { + status = f_string_dynamic_resize(controller_control_default_socket_buffer_d, &control.cache_2); + } + + if (F_status_is_error_not(status) && control.cache_3.size > controller_control_default_socket_buffer_d) { + status = f_string_dynamic_resize(controller_control_default_socket_buffer_d, &control.cache_3); + } + + if (F_status_is_error_not(status) && control.input.size > controller_control_default_socket_buffer_d) { + status = f_string_dynamic_resize(controller_control_default_socket_buffer_d, &control.input); + } + + if (F_status_is_error_not(status) && control.output.size > controller_control_default_socket_buffer_d) { + status = f_string_dynamic_resize(controller_control_default_socket_buffer_d, &control.output); + } + + if (F_status_is_error(status)) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_string_dynamic_resize", F_true); + } + + status = controller_control_accept(global, &control); + + if (F_status_is_error(status)) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "controller_control_accept", F_true); + } + + } while (F_status_is_fine(status) && status != F_child && global->thread->enabled == controller_thread_enabled_e); + + controller_control_delete_simple(&control); + + return 0; + } +#endif // _di_controller_thread_control_ + +#ifndef _di_controller_thread_control_listen_ + void * controller_thread_control_listen(void * const arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_ASYNCHRONOUS, 0); + + const controller_global_t *global = (controller_global_t *) arguments; + + if (global->thread->enabled != controller_thread_enabled_e) return 0; + + if (global->setting->interruptible) { + f_signal_mask(SIG_UNBLOCK, &global->main->signal.set, 0); + } + + f_socket_t * const server = &global->setting->control_socket; + + const f_status_t status = f_socket_listen(server, controller_control_default_socket_backlog_d); + + if (F_status_is_error(status)) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_socket_listen", F_true); + } + + return 0; + } +#endif // _di_controller_thread_control_listen_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/thread/private-thread_control.h b/level_3/controller/c/thread/private-thread_control.h new file mode 100644 index 0000000..b8f49d2 --- /dev/null +++ b/level_3/controller/c/thread/private-thread_control.h @@ -0,0 +1,50 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_thread_control_h +#define _PRIVATE_thread_control_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Thread for handling control requests and responses. + * + * @param arguments + * The thread arguments. + * Must be of type controller_global_t. + * + * @return + * 0, always. + */ +#ifndef _di_controller_thread_control_ + extern void * controller_thread_control(void * const arguments) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_control_ + +/** + * Thread for handling the control listener. + * + * This runs on a separate thread entirely to be interuptable and closable distinctly from the main control thread. + * This is simple and has nothing that needs to be cleaned up and so immediately exits on cancel. + * + * @param arguments + * The thread arguments. + * Must be of type controller_global_t. + * + * @return + * 0, always. + */ +#ifndef _di_controller_thread_control_listen_ + extern void * controller_thread_control_listen(void * const arguments) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_control_listen_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_thread_control_h diff --git a/level_3/controller/c/thread/private-thread_entry.c b/level_3/controller/c/thread/private-thread_entry.c new file mode 100644 index 0000000..09fc765 --- /dev/null +++ b/level_3/controller/c/thread/private-thread_entry.c @@ -0,0 +1,259 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../entry/private-entry.h" +#include "../lock/private-lock_print.h" +#include "../thread/private-thread.h" +#include "private-thread_entry.h" +#include "private-thread_signal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_thread_entry_ + void * controller_thread_entry(void * const arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + controller_main_entry_t *entry = (controller_main_entry_t *) arguments; + + if (!controller_thread_is_enabled(F_true, entry->global->thread)) return 0; + + controller_main_t *main = entry->global->main; + controller_cache_t *cache = &entry->global->thread->cache; + f_status_t *status = &entry->global->thread->status; + + *status = controller_entry_read(*entry->global, F_true, cache); + + if (F_status_set_fine(*status) == F_interrupt) { + entry->setting->ready = controller_setting_ready_abort_e; + } + else if (F_status_is_error(*status)) { + entry->setting->ready = controller_setting_ready_fail_e; + } + else if (*status != F_child) { + *status = controller_entry_preprocess(*entry->global, F_true, cache); + } + + if (F_status_is_error_not(*status) && *status != F_child) { + if (main->parameters[controller_parameter_validate_e].result == f_console_result_none_e || main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e) { + + if (entry->setting->entry.pid == controller_entry_pid_require_e && f_file_exists(entry->setting->path_pid.string) == F_true) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, entry->global->thread); + + fl_print_format("%c%[%SThe pid file '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[%Q%]", main->error.to.stream, main->error.notable, entry->setting->path_pid, main->error.notable); + fl_print_format("%[' must not already exist.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, entry->global->thread); + } + + entry->setting->ready = controller_setting_ready_fail_e; + *status = F_status_set_error(F_available_not); + } + else { + *status = controller_entry_process(entry->global, cache, F_false, F_true); + + if (F_status_is_error(*status)) { + entry->setting->ready = controller_setting_ready_fail_e; + + if ((F_status_set_fine(*status) == F_execute || F_status_set_fine(*status) == F_require) && entry->global->setting->failsafe_enabled) { + const uint8_t original_enabled = entry->global->thread->enabled; + + // Restore operating mode so that the failsafe can execute. + *status = f_thread_mutex_lock(&entry->global->thread->lock.alert); + + if (F_status_is_error_not(*status)) { + entry->global->thread->enabled = controller_thread_enabled_e; + + f_thread_mutex_unlock(&entry->global->thread->lock.alert); + } + + // Restart the signal thread to allow for signals while operating the failsafe Items. + if (!entry->global->thread->id_signal) { + f_thread_create(0, &entry->global->thread->id_signal, &controller_thread_signal_normal, (void *) entry->global); + } + + const f_status_t status_failsafe = controller_entry_process(entry->global, cache, F_true, F_true); + + if (F_status_is_error(status_failsafe)) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, entry->global->thread); + + fl_print_format("%c%[%SFailed while processing requested failsafe item '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[%Q%]", main->error.to.stream, main->error.notable, entry->global->setting->entry.items.array[entry->global->setting->failsafe_enabled].name, main->error.notable); + fl_print_format("%['.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, entry->global->thread); + } + + *status = F_status_set_error(F_failure); + } + else { + + // Restore operating mode to value prior to failsafe mode. + *status = f_thread_mutex_lock(&entry->global->thread->lock.alert); + + if (F_status_is_error_not(*status)) { + entry->global->thread->enabled = original_enabled; + + f_thread_mutex_unlock(&entry->global->thread->lock.alert); + } + + *status = F_failure; + } + } + } + else if (F_status_set_fine(*status) == F_interrupt) { + entry->setting->ready = controller_setting_ready_abort_e; + } + else if (*status != F_child) { + entry->setting->ready = controller_setting_ready_done_e; + } + } + } + } + + if (*status == F_child) { + + // A forked child process should deallocate memory on exit. + // It seems that this function doesn't return to the calling thread for a forked child process, even with the "return 0;" below. + // Deallocate as much as possible. + controller_thread_delete_simple(entry->global->thread); + controller_setting_delete_simple(entry->global->setting); + controller_main_delete(entry->global->main); + + // According to the manpages, pthread_exit() calls exit(0), which is not good because a non-zero exit code may be returned. + if (main->child) exit(main->child); + + return 0; + } + + f_thread_condition_signal_all(&entry->global->thread->lock.alert_condition); + + return 0; + } +#endif // _di_controller_thread_entry_ + +#ifndef _di_controller_thread_exit_ + void * controller_thread_exit(void * const arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + controller_main_entry_t *entry = (controller_main_entry_t *) arguments; + + controller_main_t *main = entry->global->main; + controller_cache_t *cache = &entry->global->thread->cache; + f_status_t *status = &entry->global->thread->status; + + *status = controller_entry_read(*entry->global, F_false, cache); + + if (F_status_set_fine(*status) == F_interrupt) { + entry->setting->ready = controller_setting_ready_abort_e; + } + else if (F_status_is_error(*status)) { + entry->setting->ready = controller_setting_ready_fail_e; + } + else if (*status == F_file_found_not) { + entry->setting->ready = controller_setting_ready_done_e; + } + else if (*status != F_child) { + *status = controller_entry_preprocess(*entry->global, F_false, cache); + } + + if (F_status_is_error_not(*status) && *status != F_child && *status != F_file_found_not) { + if (main->parameters[controller_parameter_validate_e].result == f_console_result_none_e || main->parameters[controller_parameter_simulate_e].result == f_console_result_found_e) { + + *status = controller_entry_process(entry->global, cache, F_false, F_false); + + if (F_status_is_error(*status)) { + entry->setting->ready = controller_setting_ready_fail_e; + + if ((F_status_set_fine(*status) == F_execute || F_status_set_fine(*status) == F_require) && entry->global->setting->failsafe_enabled) { + + const uint8_t original_enabled = entry->global->thread->enabled; + + // Restore operating mode so that the failsafe can execute. + if (F_status_set_fine(*status) == F_execute) { + *status = f_thread_mutex_lock(&entry->global->thread->lock.alert); + + if (F_status_is_error_not(*status)) { + entry->global->thread->enabled = controller_thread_enabled_exit_e; + + f_thread_mutex_unlock(&entry->global->thread->lock.alert); + } + + // Restart the signal thread to allow for signals while operating the failsafe Items. + if (!entry->global->thread->id_signal) { + f_thread_create(0, &entry->global->thread->id_signal, &controller_thread_signal_other, (void *) entry->global); + } + } + + const f_status_t status_failsafe = controller_entry_process(entry->global, cache, F_true, F_false); + + if (F_status_is_error(status_failsafe)) { + if (main->error.verbosity != f_console_verbosity_quiet_e) { + controller_lock_print(main->error.to, entry->global->thread); + + fl_print_format("%c%[%SFailed while processing requested failsafe item '%]", main->error.to.stream, f_string_eol_s[0], main->error.context, main->error.prefix ? main->error.prefix : f_string_empty_s, main->error.context); + fl_print_format("%[%Q%]", main->error.to.stream, main->error.notable, entry->global->setting->entry.items.array[entry->global->setting->failsafe_enabled].name, main->error.notable); + fl_print_format("%['.%]%c", main->error.to.stream, main->error.context, main->error.context, f_string_eol_s[0]); + + controller_unlock_print_flush(main->error.to, entry->global->thread); + } + + *status = F_status_set_error(F_failure); + } + else { + + // Restore operating mode to value prior to failsafe mode. + *status = f_thread_mutex_lock(&entry->global->thread->lock.alert); + + if (F_status_is_error_not(*status)) { + entry->global->thread->enabled = original_enabled; + + f_thread_mutex_unlock(&entry->global->thread->lock.alert); + } + + *status = F_failure; + } + } + } + else if (F_status_set_fine(*status) == F_interrupt) { + entry->setting->ready = controller_setting_ready_abort_e; + } + else if (*status != F_child) { + entry->setting->ready = controller_setting_ready_done_e; + } + } + } + + if (*status == F_child) { + + // A forked child process should deallocate memory on exit. + // It seems that this function doesn't return to the calling thread for a forked child process, even with the "return 0;" below. + // Deallocate as much as possible. + + controller_thread_delete_simple(entry->global->thread); + controller_setting_delete_simple(entry->global->setting); + controller_main_delete(entry->global->main); + + return 0; + } + + if (F_status_is_error_not(f_thread_mutex_lock(&entry->global->thread->lock.alert))) { + entry->global->thread->enabled = controller_thread_enabled_not_e; + + f_thread_mutex_unlock(&entry->global->thread->lock.alert); + } + + f_thread_condition_signal_all(&entry->global->thread->lock.alert_condition); + + return 0; + } +#endif // _di_controller_thread_exit_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/thread/private-thread_entry.h b/level_3/controller/c/thread/private-thread_entry.h new file mode 100644 index 0000000..553096e --- /dev/null +++ b/level_3/controller/c/thread/private-thread_entry.h @@ -0,0 +1,56 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_thread_entry_h +#define _PRIVATE_thread_entry_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Thread for handling entry processing. + * + * This acts as the main rule thread during entry processing. + * This runs all synchronous rules or spawns asynchronous rules. + * + * @param arguments + * The thread arguments. + * Must be of type controller_main_entry_t. + * + * @return + * 0, always. + */ +#ifndef _di_controller_thread_entry_ + extern void * controller_thread_entry(void * const arguments) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_entry_ + +/** + * Thread for handling exit file processing. + * + * This acts as the main rule thread during exit processing. + * This runs all synchronous rules or spawns asynchronous rules. + * + * Do not confuse this with exiting a thread, this is the what process the exit files (similar to that of an entry file). + * Exit files process the "stop" action, whereas the Entry files process the "start" Action + * + * @param arguments + * The thread arguments. + * Must be of type controller_main_entry_t. + * + * @return + * 0, always. + */ +#ifndef _di_controller_thread_exit_ + extern void * controller_thread_exit(void * const arguments) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_exit_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_thread_entry_h diff --git a/level_3/controller/c/thread/private-thread_process.c b/level_3/controller/c/thread/private-thread_process.c new file mode 100644 index 0000000..4bbe3d4 --- /dev/null +++ b/level_3/controller/c/thread/private-thread_process.c @@ -0,0 +1,372 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../controller/private-controller.h" +#include "../controller/private-controller_print.h" +#include "../rule/private-rule.h" +#include "private-thread.h" +#include "private-thread_entry.h" +#include "private-thread_process.h" +#include "private-thread_signal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_thread_process_ + void controller_thread_process(const bool is_normal, controller_process_t * const process) { + + { + controller_thread_t *thread = (controller_thread_t *) process->main_thread; + + if (!controller_thread_is_enabled(is_normal, thread)) return; + } + + const f_status_t status = controller_rule_process_do(controller_process_option_asynchronous_d, process); + + if (status == F_child) { + + // A forked child process should deallocate memory on exit. + // It seems that this function doesn't return to the calling thread for a forked child process, even with the "return 0;" below. + // Deallocate as much as possible. + controller_main_t *main = (controller_main_t *) process->main_data; + controller_setting_t *setting = (controller_setting_t *) process->main_setting; + controller_thread_t *thread = (controller_thread_t *) process->main_thread; + + controller_thread_delete_simple(thread); + controller_setting_delete_simple(setting); + controller_main_delete(main); + + // According to the manpages, pthread_exit() calls exit(0), which is not good because a non-zero exit code may be returned. + if (main->child) exit(main->child); + } + } +#endif // _di_controller_thread_process_ + +#ifndef _di_controller_thread_process_cancel_ + void controller_thread_process_cancel(const controller_global_t global, const bool is_normal, const uint8_t by, controller_process_t * const caller) { + + // Only cancel when enabled. + if (!controller_thread_is_enabled(is_normal, global.thread)) { + return; + } + + // Use the alert lock to toggle enabled (being used as if it were a write like and signal lock). + f_status_t status = f_thread_mutex_lock(&global.thread->lock.alert); + + if (F_status_is_error(status)) { + global.thread->enabled = controller_thread_enabled_not_e; + } + else { + if (by == controller_thread_cancel_execute_e) { + global.thread->enabled = controller_thread_enabled_execute_e; + } + else if (by == controller_thread_cancel_exit_e) { + global.thread->enabled = controller_thread_enabled_not_e; + } + else if (by == controller_thread_cancel_exit_execute_e) { + global.thread->enabled = controller_thread_enabled_exit_execute_e; + } + else { + global.thread->enabled = controller_thread_enabled_exit_e; + } + + f_thread_mutex_unlock(&global.thread->lock.alert); + } + + f_array_length_t spent = 0; + + struct timespec time; + + controller_process_t *process = 0; + + f_array_length_t i = 0; + f_array_length_t j = 0; + pid_t pid = 0; + + if (global.thread->id_cleanup) { + f_thread_cancel(global.thread->id_cleanup); + f_thread_join(global.thread->id_cleanup, 0); + + global.thread->id_cleanup = 0; + } + + // The sigtimedwait() function that is run inside of signal must be interrupted via the f_thread_cancel(). + if (by != controller_thread_cancel_signal_e && global.thread->id_signal) { + f_thread_cancel(global.thread->id_signal); + f_thread_join(global.thread->id_signal, 0); + + global.thread->id_signal = 0; + } + + for (; i < global.thread->processs.used; ++i) { + + if (!global.thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; + + process = global.thread->processs.array[i]; + + // Do not cancel exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) { + continue; + } + + for (j = 0; j < process->childs.used; ++j) { + + if (process->childs.array[j] > 0) { + f_signal_send(global.thread->signal ? global.thread->signal : F_signal_termination, process->childs.array[j]); + } + } // for + + for (j = 0; j < process->path_pids.used; ++j) { + + if (process->path_pids.array[j].used && f_file_exists(process->path_pids.array[j].string) == F_true) { + status = controller_file_pid_read(process->path_pids.array[j], &pid); + + if (pid) { + f_signal_send(global.thread->signal ? global.thread->signal : F_signal_termination, pid); + } + } + } // for + } // for + + for (i = 0; i < global.thread->processs.size && spent < controller_thread_exit_process_cancel_total_d; ++i) { + + if (!global.thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; + + process = global.thread->processs.array[i]; + + // Do not cancel exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) continue; + + do { + if (!process->id_thread) break; + + f_thread_signal(process->id_thread, global.thread->signal ? global.thread->signal : F_signal_termination); + + controller_time(0, controller_thread_exit_process_cancel_wait_d, &time); + + status = f_thread_join_timed(process->id_thread, time, 0); + + if (status == F_none) { + for (j = 0; j < process->childs.size; ++j) { + process->childs.array[j] = 0; + } // for + + process->childs.used = 0; + process->id_thread = 0; + } + + ++spent; + + } while (status == F_time && spent < controller_thread_exit_process_cancel_total_d); + + if (process->path_pids.used) { + for (j = 0; j < process->path_pids.used; ++j) { + + for (; spent < controller_thread_exit_process_cancel_total_d; ++spent) { + + if (process->path_pids.array[j].used && f_file_exists(process->path_pids.array[j].string) == F_true) { + status = controller_file_pid_read(process->path_pids.array[j], &pid); + + if (pid) { + + // A hackish way to determine if the pid exists while waiting. + if (getpgid(pid) >= 0) { + time.tv_sec = 0; + time.tv_nsec = controller_thread_exit_process_cancel_wait_d; + + nanosleep(&time, 0); + + continue; + } + else { + f_file_remove(process->path_pids.array[j].string); + process->path_pids.array[j].used = 0; + } + } + } + + break; + } // for + } // for + } + } // for + + for (i = 0; i < global.thread->processs.size; ++i) { + + if (!global.thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; + + process = global.thread->processs.array[i]; + + // Do not kill exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) continue; + + if (process->id_thread) { + if (process->childs.used) { + for (j = 0; j < process->childs.used; ++j) { + + if (process->childs.array[j] > 0) { + f_signal_send(F_signal_kill, process->childs.array[j]); + + time.tv_sec = 0; + time.tv_nsec = controller_thread_exit_process_cancel_wait_d; + + process->childs.array[j] = 0; + } + } // for + + nanosleep(&time, 0); + } + + f_thread_join(process->id_thread, 0); + + process->id_thread = 0; + } + + for (j = 0; j < process->childs.size; ++j) { + process->childs.array[j] = 0; + } // for + + process->childs.used = 0; + + for (j = 0; j < process->path_pids.used; ++j) { + + if (f_file_exists(process->path_pids.array[j].string) == F_true) { + status = controller_file_pid_read(process->path_pids.array[j], &pid); + + if (pid) { + f_signal_send(F_signal_kill, pid); + } + + f_file_remove(process->path_pids.array[j].string); + process->path_pids.array[j].used = 0; + } + } // for + + process->path_pids.used = 0; + } // for + } +#endif // _di_controller_thread_process_cancel_ + +#ifndef _di_controller_thread_process_exit_ + void controller_thread_process_exit(controller_global_t * const global) { + + if (global->thread->enabled != controller_thread_enabled_exit_e) { + return; + } + + if (global->setting->ready == controller_setting_ready_done_e) { + + // The exit processing runs using the entry thread. + if (global->thread->id_entry) { + f_thread_cancel(global->thread->id_entry); + f_thread_join(global->thread->id_entry, 0); + + global->thread->id_entry = 0; + } + + // Restart the signal thread to allow for signals while operating the Exit. + if (!global->thread->id_signal) { + f_thread_create(0, &global->thread->id_signal, &controller_thread_signal_other, (void *) global); + } + + const controller_main_entry_t entry = macro_controller_main_entry_t_initialize(global, global->setting); + + f_status_t status = f_thread_create(0, &global->thread->id_entry, &controller_thread_exit, (void *) &entry); + + if (F_status_is_error(status)) { + if (global->main->error.verbosity != f_console_verbosity_quiet_e) { + controller_print_error(global->thread, global->main->error, F_status_set_fine(status), "f_thread_create", F_true); + } + + if (F_status_is_error_not(f_thread_mutex_lock(&global->thread->lock.alert))) { + global->thread->enabled = controller_thread_enabled_not_e; + + f_thread_mutex_unlock(&global->thread->lock.alert); + } + else { + global->thread->enabled = controller_thread_enabled_not_e; + } + } + else { + struct timespec time; + + do { + status = f_thread_mutex_lock(&global->thread->lock.alert); + + if (F_status_is_error(status)) { + global->thread->enabled = controller_thread_enabled_not_e; + + break; + } + + controller_time(controller_thread_exit_ready_timeout_seconds_d, controller_thread_exit_ready_timeout_nanoseconds_d, &time); + + status = f_thread_condition_wait_timed(&time, &global->thread->lock.alert_condition, &global->thread->lock.alert); + + f_thread_mutex_unlock(&global->thread->lock.alert); + + } while (F_status_is_error_not(status) && global->thread->enabled == controller_thread_enabled_exit_e); + + if (F_status_is_error(status)) { + if (F_status_is_error_not(f_thread_mutex_lock(&global->thread->lock.alert))) { + global->thread->enabled = controller_thread_enabled_not_e; + + f_thread_mutex_unlock(&global->thread->lock.alert); + } + else { + global->thread->enabled = controller_thread_enabled_not_e; + } + } + } + + // The sigtimedwait() function that is run inside of signal must be interrupted via the f_thread_cancel(). + if (global->thread->id_signal) { + f_thread_cancel(global->thread->id_signal); + f_thread_join(global->thread->id_signal, 0); + + global->thread->id_signal = 0; + } + + controller_thread_process_cancel(*global, F_false, controller_thread_cancel_exit_e, 0); + } + else { + if (F_status_is_error_not(f_thread_mutex_lock(&global->thread->lock.alert))) { + global->thread->enabled = controller_thread_enabled_not_e; + + f_thread_mutex_unlock(&global->thread->lock.alert); + } + else { + global->thread->enabled = controller_thread_enabled_not_e; + } + } + } +#endif // _di_controller_thread_process_exit_ + +#ifndef _di_controller_thread_process_normal_ + void * controller_thread_process_normal(void * const 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_normal_ + +#ifndef _di_controller_thread_process_other_ + void * controller_thread_process_other(void * const 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_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/thread/private-thread_process.h b/level_3/controller/c/thread/private-thread_process.h new file mode 100644 index 0000000..2e9cbf5 --- /dev/null +++ b/level_3/controller/c/thread/private-thread_process.h @@ -0,0 +1,98 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_thread_process_h +#define _PRIVATE_thread_process_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * 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 * const process) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_process_ + +/** + * Cancel all process threads. + * + * @param global + * The global thread data. + * @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_e, then this was called from within the signal handling thread, so do not cancel the signal thread. + * If controller_thread_cancel_call_e, then this was not called from within the signal handling thread, so cancel the signal thread. + * If controller_thread_cancel_execute_e, then this was called from within the Entry/Exit for executing a process, so cancel the signal thread but not the Entry thread. + * @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_ + extern void controller_thread_process_cancel(const controller_global_t global, const bool is_normal, const uint8_t by, controller_process_t * const caller) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_process_cancel_ + +/** + * Process the Exit file, if applicable. + * + * @param global + * The global thread data. + */ +#ifndef _di_controller_thread_process_exit_ + extern void controller_thread_process_exit(controller_global_t * const global) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_process_exit_ + +/** + * Asynchronously execute a Rule process during normal 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_normal_ + extern void * controller_thread_process_normal(void * const arguments) F_attribute_visibility_internal_d; +#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 * const arguments) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_process_other_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_thread_process_h diff --git a/level_3/controller/c/thread/private-thread_rule.c b/level_3/controller/c/thread/private-thread_rule.c new file mode 100644 index 0000000..b2cef6d --- /dev/null +++ b/level_3/controller/c/thread/private-thread_rule.c @@ -0,0 +1,25 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "private-thread.h" +#include "private-thread_rule.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_thread_rule_ + void * controller_thread_rule(void * const arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + const controller_global_t *global = (controller_global_t *) arguments; + + if (!controller_thread_is_enabled(F_true, global->thread)) return 0; + + return 0; + } +#endif // _di_controller_thread_rule_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/thread/private-thread_rule.h b/level_3/controller/c/thread/private-thread_rule.h new file mode 100644 index 0000000..61cf96d --- /dev/null +++ b/level_3/controller/c/thread/private-thread_rule.h @@ -0,0 +1,38 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_thread_rule_h +#define _PRIVATE_thread_rule_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Thread for handling rule processing. + * + * This acts as the main rule thread after entry processing. + * This runs all synchronous rules or spawns asynchronous rules. + * + * @todo the control thread should send commands to this thread, somehow. + * + * @param arguments + * The thread arguments. + * Must be of type controller_global_t. + * + * @return + * 0, always. + */ +#ifndef _di_controller_thread_rule_ + extern void * controller_thread_rule(void * const arguments) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_rule_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_thread_rule_h diff --git a/level_3/controller/c/thread/private-thread_signal.c b/level_3/controller/c/thread/private-thread_signal.c new file mode 100644 index 0000000..1ea524e --- /dev/null +++ b/level_3/controller/c/thread/private-thread_signal.c @@ -0,0 +1,125 @@ +#include "../controller/controller.h" +#include "../common/private-common.h" +#include "../controller/private-controller.h" +#include "private-thread.h" +#include "private-thread_entry.h" +#include "private-thread_process.h" +#include "private-thread_signal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _di_controller_thread_signal_ + void controller_thread_signal(controller_global_t * const global, const bool is_normal) { + + if (!controller_thread_is_enabled(is_normal, global->thread)) return; + + siginfo_t information; + struct timespec time; + int error = 0; + + while (controller_thread_is_enabled(is_normal, global->thread)) { + + controller_time(controller_thread_exit_ready_timeout_seconds_d, controller_thread_exit_ready_timeout_nanoseconds_d, &time); + + error = sigtimedwait(&global->main->signal.set, &information, &time); + + if (error == -1) { + if (errno == EAGAIN) continue; + } + + if (global->setting->interruptible) { + if (information.si_signo == F_signal_interrupt || information.si_signo == F_signal_abort || information.si_signo == F_signal_quit || information.si_signo == F_signal_termination) { + global->thread->signal = information.si_signo; + + controller_thread_process_cancel(*global, is_normal, controller_thread_cancel_signal_e, 0); + + break; + } + } + } // while + } +#endif // _di_controller_thread_signal_ + +#ifndef _di_controller_thread_signal_state_fss_ + f_status_t controller_thread_signal_state_fss(void * const state, void * const internal) { + + if (!state) { + return F_interrupt_not; + } + + f_state_t *state_ptr = (f_state_t *) state; + + if (!state_ptr->custom) { + return F_interrupt_not; + } + + controller_state_interrupt_t *custom = (controller_state_interrupt_t *) state_ptr->custom; + controller_thread_t *thread = custom->thread; + + if (!controller_thread_is_enabled(custom->is_normal, thread)) { + return F_status_set_error(F_interrupt); + } + + if (thread->signal == F_signal_interrupt || thread->signal == F_signal_abort || thread->signal == F_signal_quit || thread->signal == F_signal_termination) { + return F_status_set_error(F_interrupt); + } + + return F_interrupt_not; + } +#endif // _di_controller_thread_signal_state_fss_ + +#ifndef _di_controller_thread_signal_state_iki_ + f_status_t controller_thread_signal_state_iki(void * const state, void * const internal) { + + if (!state) { + return F_interrupt_not; + } + + f_state_t *state_ptr = (f_state_t *) state; + + if (!state_ptr->custom) { + return F_interrupt_not; + } + + controller_state_interrupt_t *custom = (controller_state_interrupt_t *) state_ptr->custom; + controller_thread_t *thread = custom->thread; + + if (!controller_thread_is_enabled(custom->is_normal, thread)) { + return F_status_set_error(F_interrupt); + } + + if (thread->signal == F_signal_interrupt || thread->signal == F_signal_abort || thread->signal == F_signal_quit || thread->signal == F_signal_termination) { + return F_status_set_error(F_interrupt); + } + + return F_interrupt_not; + } +#endif // _di_controller_thread_signal_state_iki_ + +#ifndef _di_controller_thread_signal_normal_ + void * controller_thread_signal_normal(void * const arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + controller_thread_signal((controller_global_t *) arguments, F_true); + + return 0; + } +#endif // _di_controller_thread_signal_normal_ + +#ifndef _di_controller_thread_signal_other_ + void * controller_thread_signal_other(void * const arguments) { + + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + + controller_thread_signal((controller_global_t *) arguments, F_false); + + return 0; + } +#endif // _di_controller_thread_signal_other_ + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/level_3/controller/c/thread/private-thread_signal.h b/level_3/controller/c/thread/private-thread_signal.h new file mode 100644 index 0000000..e0fefd6 --- /dev/null +++ b/level_3/controller/c/thread/private-thread_signal.h @@ -0,0 +1,102 @@ +/** + * FLL - Level 3 + * + * Project: Controller + * API Version: 0.5 + * Licenses: lgpl-2.1-or-later + */ +#ifndef _PRIVATE_thread_signal_h +#define _PRIVATE_thread_signal_h + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Thread for handling signals/interrupts. + * + * @param global + * The global data. + * @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. + */ +#ifndef _di_controller_thread_signal_ + extern void controller_thread_signal(controller_global_t * const global, const bool is_normal) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_signal_ + +/** + * Callback passed to FSS functions for checking for interrupts. + * + * @param state + * The f_state_t data. + * @param internal + * Not used. + * + * @return + * F_interrupt_not if not interrupted. + * + * F_interrupt (with error bit) if interrupted. + */ +#ifndef _di_controller_thread_signal_state_fss_ + extern f_status_t controller_thread_signal_state_fss(void * const state, void * const internal) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_signal_state_fss_ + +/** + * Callback passed to IKI functions for checking for interrupts. + * + * @param state + * The f_state_t data. + * @param internal + * Not used. + * + * @return + * F_interrupt_not if not interrupted. + * + * F_interrupt (with error bit) if interrupted. + */ +#ifndef _di_controller_thread_signal_state_iki_ + extern f_status_t controller_thread_signal_state_iki(void * const state, void * const internal) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_signal_state_iki_ + +/** + * 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_global_t. + * + * @return + * 0, always. + * + * @see controller_thread_signal() + */ +#ifndef _di_controller_thread_signal_normal_ + extern void * controller_thread_signal_normal(void * const arguments) F_attribute_visibility_internal_d; +#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_global_t. + * + * @return + * 0, always. + * + * @see controller_thread_signal() + */ +#ifndef _di_controller_thread_signal_other_ + extern void * controller_thread_signal_other(void * const arguments) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_signal_other_ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // _PRIVATE_thread_signal_h diff --git a/level_3/controller/data/build/settings b/level_3/controller/data/build/settings index 84190ab..72c01b0 100644 --- a/level_3/controller/data/build/settings +++ b/level_3/controller/data/build/settings @@ -30,11 +30,18 @@ build_libraries-level -lfll_2 -lfll_1 -lfll_0 build_libraries-monolithic -lfll build_libraries_shared build_libraries_static -build_sources_library controller.c private-common.c private-process.c -build_sources_library common/private-cache.c common/private-control.c common/private-entry.c common/private-lock.c common/private-process.c common/private-rule.c common/private-setting.c common/private-task.c common/private-thread.c -build_sources_library private-control.c private-controller.c private-entry.c private-rule.c private-task.c -build_sources_library private-control_print.c private-controller_print.c private-entry_print.c private-lock.c private-lock_print.c private-rule_print.c private-task_print.c -build_sources_library private-thread.c private-thread_control.c private-thread_entry.c private-thread_process.c private-thread_rule.c private-thread_signal.c +build_sources_library common/private-common.c common/private-cache.c common/private-control.c common/private-entry.c common/private-lock.c common/private-process.c common/private-rule.c common/private-setting.c common/private-task.c common/private-thread.c +build_sources_library control/private-control.c control/private-control_print.c +build_sources_library controller/controller.c controller/private-controller.c controller/private-controller_print.c +build_sources_library entry/private-entry.c entry/private-entry_print.c +build_sources_library rule/private-rule.c rule/private-rule_print.c +build_sources_library process/private-process.c +build_sources_library task/private-task.c task/private-task_print.c +build_sources_library lock/private-lock.c lock/private-lock_print.c +build_sources_library thread/private-thread.c thread/private-thread_control.c thread/private-thread_entry.c thread/private-thread_process.c thread/private-thread_rule.c thread/private-thread_signal.c +build_sources_library +build_sources_library +build_sources_library build_sources_library_shared build_sources_library_static build_sources_program main.c diff --git a/level_3/controller/documents/packet.txt b/level_3/controller/documents/packet.txt new file mode 100644 index 0000000..bf4fa8e --- /dev/null +++ b/level_3/controller/documents/packet.txt @@ -0,0 +1,4 @@ +# fss-0002 + +Packet Documentation: + Todo write this. diff --git a/level_3/controller/specifications/packet.txt b/level_3/controller/specifications/packet.txt new file mode 100644 index 0000000..b3501ff --- /dev/null +++ b/level_3/controller/specifications/packet.txt @@ -0,0 +1,4 @@ +# fss-0002 + +Entry Specification: + todo write this.