Add f_signal_send().
Cleanup some of the f_signal code.
Add "return" mode option to fl_execute.
This adds more thread related changes, much if this I am semi-experimenting.
I will likely do a post review and try to clean it up and remove anything unnecessary.
One thing in particular that I am trying is saving the child process PID for a foreground process.
This can then be manually sent a termination signal on exit.
The program should exit in certain validation modes.
Cleanup f_environment logic in one function.
const f_string_length_t size = strnlen(result, f_environment_max_length);
- if (!size) {
- value->used = 0;
- }
- else {
+ if (size) {
if (value->used + size > f_environment_max_length) {
return F_status_set_error(F_string_too_large);
}
memcpy(value->string + value->used, result, value->used + size);
value->used = size;
}
+ else {
+ value->used = 0;
+ }
return F_none;
}
}
#endif // _di_f_signal_read_
+#ifndef _di_f_signal_send_
+ f_status_t f_signal_send(const int signal, const pid_t process_id) {
+
+ if (kill(process_id, signal) < 0) {
+ if (errno == EINVAL) return F_status_set_error(F_parameter);
+ if (errno == EPERM) return F_status_set_error(F_prohibited);
+ if (errno == ESRCH) return F_status_set_error(F_found_not);
+
+ return F_status_set_error(F_failure);
+ }
+
+ return F_none;
+ }
+#endif // _di_f_signal_send_
+
#ifndef _di_f_signal_set_add_
f_status_t f_signal_set_add(const int signal, sigset_t *set) {
#ifndef _di_level_0_parameter_checking_
#endif // _di_level_0_parameter_checking_
if (sigaddset(set, signal) < 0) {
- if (errno == EINVAL) {
- return F_status_set_error(F_parameter);
- }
+ if (errno == EINVAL) return F_status_set_error(F_parameter);
return F_status_set_error(F_failure);
}
#endif // _di_level_0_parameter_checking_
if (sigdelset(set, signal) < 0) {
- if (errno == EINVAL) {
- return F_status_set_error(F_parameter);
- }
+ if (errno == EINVAL) return F_status_set_error(F_parameter);
return F_status_set_error(F_failure);
}
#endif // _di_level_0_parameter_checking_
if (sigemptyset(set) < 0) {
- if (errno == EINVAL) {
- return F_status_set_error(F_parameter);
- }
+ if (errno == EINVAL) return F_status_set_error(F_parameter);
return F_status_set_error(F_failure);
}
* F_none on success but no signal found.
* F_data_not on success, but no descriptor was provided to read.
* F_signal on success and signal found.
+ *
* F_block (with error bit) if file descriptor is set to non-block and the read would result in a blocking operation.
* F_buffer (with error bit) if the buffer is invalid.
* F_descriptor (with error bit) if the signal descriptor is invalid.
#endif // _di_f_signal_read_
/**
+ * Send a signal to a process.
+ *
+ * @param signal
+ * The signal to send
+ * @param process_id
+ * The process id (PID) that will receive the signal.
+ * This may also be a process group id.
+ *
+ * @return
+ * F_none on success but no signal found.
+ *
+ * F_parameter (with error bit) if a parameter is invalid.
+ * F_prohibited (with error bit) if not allowed to send signals to the given process.
+ * F_found_not (with error bit) if the given process was not found.
+ * F_failure (with error bit) for any other error.
+ *
+ * @see kill()
+ */
+#ifndef _di_f_signal_send_
+ extern f_status_t f_signal_send(const int signal, const pid_t process_id);
+#endif // _di_f_signal_send_
+
+/**
* Add a signal to the given set of signals.
*
* @param signal
*
* @return
* F_none on success but no signal found.
+ *
* F_parameter (with error bit) if a parameter is invalid.
* F_failure (with error bit) for any other error.
*
*
* @return
* F_none on success but no signal found.
+ *
* F_parameter (with error bit) if a parameter is invalid.
* F_failure (with error bit) for any other error.
*
*
* @return
* F_none on success but no signal found.
+ *
* F_parameter (with error bit) if a parameter is invalid.
* F_failure (with error bit) for any other error.
*
*
* @return
* F_none on success but no signal found.
+ *
* F_parameter (with error bit) if a parameter is invalid.
* F_failure (with error bit) for any other error.
*
*
* @return
* F_none on success but no signal found.
+ *
* F_parameter (with error bit) if a parameter is invalid.
* F_failure (with error bit) for any other error.
*
*
* @return
* F_none on success but no signal found.
+ *
* F_found_not (with error bit) if the given PID was found.
* F_parameter (with error bit) if a parameter is invalid.
* F_resource_not (with error bit) if the max signals is reached.
* @return
* F_true if signal is found.
* F_false if signal is not found.
+ *
* F_parameter (with error bit) if a parameter is invalid.
* F_failure (with error bit) for any other error.
*
* fl_execute_parameter_option_exit: used to desginate to exit after calling child otherwise child process will return.
* fl_execute_parameter_option_path: used to designate that this is a path to a program (such as '/bin/bash').
* fl_execute_parameter_option_threadsafe: used to designate that threadsafe functions are to be used (such as: f_thread_signal_mask instead of f_signal_mask).
+ * fl_execute_parameter_option_return: used to designate that the parent process will immediately return instead of waiting for the child process to complete.
*
* If thread support is disabled in the library, then fl_execute_parameter_option_threadsafe will fallback to non-threadsafe.
*
#define fl_execute_parameter_option_exit 0x1
#define fl_execute_parameter_option_path 0x2
#define fl_execute_parameter_option_threadsafe 0x4
+ #define fl_execute_parameter_option_return 0x8
typedef struct {
uint8_t option;
*
* @return
* F_none on success.
+ *
* F_failure (with error bit) on execution failure.
*
* @see execv()
* A pointer to a string to pipe as standard input to the child process.
* The parent will block until the standard input is fully read or the child process exits.
* @param as
- * (optional) This and most of its fields are optional and are disabled when set to 0.
+ * (optional) This and most of its fields are optional and are disabled when set to NULL.
* @param result
- * The code returned after finishing execution of program.
+ * (optional) The code returned after finishing execution of program.
+ * When fl_execute_parameter_option_return is passed via parameter.option, then this instead stores the child process id (PID).
+ * Set to NULL to not use.
*
* @return
* F_none on success.
- * F_child on success but this is the child thread.
+ * F_child on success and this is the child thread.
+ * F_parent on success and this is the parent thread (only happens when fl_execute_parameter_option_return is passed).
+ *
* F_capability (with error bit) on failure to set capabilities in the child (only the child process returns this).
* F_control_group (with error bit) on failure to set control group in the child (only the parent process returns this).
* F_child (with error bit) on any failure without an explicit failure code (like F_group) before calling execute but this is the child thread.
}
}
+ if (parameter && parameter->option & fl_execute_parameter_option_return) {
+
+ if (result != 0) {
+ *result = id_process;
+ }
+
+ return F_parent;
+ }
+
// have the parent wait for the child process to finish.
waitpid(id_process, result, WUNTRACED | WCONTINUED);
}
}
+ if (parameter && parameter->option & fl_execute_parameter_option_return) {
+
+ if (result != 0) {
+ *result = id_process;
+ }
+
+ return F_parent;
+ }
+
// have the parent wait for the child process to finish.
waitpid(id_process, result, WUNTRACED | WCONTINUED);
*
* @return
* F_none on success.
+ *
* F_capability (with error bit) on failure to set capabilities.
* F_group (with error bit) on failure to set GID.
* F_nice (with error bit) on failure to set process niceness.
*
* @return
* F_none on success.
+ *
* F_control_group (with error bit) on failure to set control group.
* F_limit (with error bit) on failure to set a resource limit.
* F_processor (with error bit) on failure to set processor (cpu) affinity.
* A pointer to a string to pipe as standard input to the child process.
* The parent will block until the standard input is fully read or the child process exits.
* @param as
- * (optional) This and most of its fields are optional and are disabled when set to 0.
+ * (optional) This and most of its fields are optional and are disabled when set to NULL.
* @param result
- * The code returned after finishing execution of program.
+ * (optional) The code returned after finishing execution of program.
+ * When fl_execute_parameter_option_return is passed via parameter.option, then this instead stores the child process id (PID).
+ * Set to NULL to not use.
*
* @return
* F_none on success.
- * F_child on success but this is the child thread.
+ * F_child on success and this is the child thread.
+ * F_parent on success and this is the parent thread (only happens when fl_execute_parameter_option_return is passed).
+ *
* F_capability (with error bit) on failure to set capabilities in the child (only the child process returns this).
* F_control_group (with error bit) on failure to set control group in the child (only the parent process returns this).
* F_child (with error bit) on any failure without an explicit failure code (like F_group) before calling execute but this is the child thread.
* A pointer to a string to pipe as standard input to the child process.
* The parent will block until the standard input is fully read or the child process exits.
* @param as
- * (optional) This and most of its fields are optional and are disabled when set to 0.
+ * (optional) This and most of its fields are optional and are disabled when set to NULL.
* @param result
- * The code returned after finishing execution of program.
+ * (optional) The code returned after finishing execution of program.
+ * When fl_execute_parameter_option_return is passed via parameter.option, then this instead stores the child process id (PID).
+ * Set to NULL to not use.
*
* @return
* F_none on success.
- * F_child on success but this is the child thread.
+ * F_child on success and this is the child thread.
+ * F_parent on success and this is the parent thread (only happens when fl_execute_parameter_option_return is passed).
+ *
* F_capability (with error bit) on failure to set capabilities in the child (only the child process returns this).
* F_control_group (with error bit) on failure to set control group in the child (only the parent process returns this).
* F_child (with error bit) on any failure without an explicit failure code (like F_group) before calling execute but this is the child thread.
f_status_t status;
f_thread_mutex_t lock;
+ f_thread_mutex_t wait;
f_number_unsigned_t timeout_kill;
f_number_unsigned_t timeout_start;
{ \
F_known_not, \
f_thread_mutex_t_initialize, \
+ f_thread_mutex_t_initialize, \
0, \
0, \
0, \
#define controller_macro_rule_t_delete_simple(rule) \
f_macro_thread_mutex_t_delete_simple(rule.lock) \
+ f_macro_thread_mutex_t_delete_simple(rule.wait) \
f_macro_string_dynamic_t_delete_simple(rule.id) \
f_macro_string_dynamic_t_delete_simple(rule.name) \
f_macro_string_dynamic_t_delete_simple(rule.path) \
uint8_t state;
uint8_t action;
uint8_t options;
+ pid_t child;
void *thread;
f_array_lengths_t stack;
controller_cache_action_t cache;
} controller_asynchronous_t;
- #define controller_asynchronous_t_initialize { f_thread_id_t_initialize, 0, 0, 0, 0, 0, f_array_lengths_t_initialize, controller_cache_action_t_initialize }
+ #define controller_asynchronous_t_initialize { f_thread_id_t_initialize, 0, 0, 0, 0, 0, 0, f_array_lengths_t_initialize, controller_cache_action_t_initialize }
#define controller_macro_asynchronous_t_clear(asynchronous) \
f_macro_thread_id_t_clear(asynchronous.id) \
asynchronous.state = 0; \
asynchronous.action = 0; \
asynchronous.options = 0; \
+ asynchronous.child = 0; \
asynchronous.thread = 0; \
f_macro_array_lengths_t_clear(asynchronous.stack) \
controller_macro_cache_action_t_clear(asynchronous.cache)
#ifndef _di_controller_asynchronouss_t_
typedef struct {
+ bool enabled;
+
controller_asynchronous_t *array;
f_array_length_t size;
#define controller_asynchronouss_t_initialize \
{ \
+ F_true, \
0, \
0, \
0, \
controller_entry_actions_t *actions = 0;
const bool simulate = data->parameters[controller_parameter_test].result == f_console_result_found;
- const bool validate = data->parameters[controller_parameter_validate].result == f_console_result_found;
cache->ats.used = 0;
cache->stack.used = 0;
action = &item->actions.array[j];
execute_set.parameter.data = 0;
- execute_set.parameter.option = fl_execute_parameter_option_threadsafe;
+ execute_set.parameter.option = fl_execute_parameter_option_threadsafe & fl_execute_parameter_option_return;
if (item->type == controller_rule_item_type_command) {
status = fll_execute_program(program, arguments, &execute_set->parameter, &execute_set->as, &result);
}
+ if (status == F_parent) {
+ controller_asynchronous_t *asynchronous = (controller_asynchronous_t *) thread->setting->rules.array[index].asynchronous;
+
+ // assign the child process id to the asynchronous thread to allow for the cancel process to send appropriate termination signals to the child process.
+ asynchronous->child = result;
+
+ // have the parent wait for the child process to finish. (@todo see comments above about forking into the background, this code block will need to change.)
+ waitpid(asynchronous->child, &result, WUNTRACED | WCONTINUED);
+
+ // remove the pid now that waidpid() has returned.
+ asynchronous->child = 0;
+
+ // this must explicitly check for 0 (as opposed to checking (!result)).
+ if (!WIFEXITED(result)) {
+ status = F_status_set_error(F_failure);
+ }
+ }
+
if (F_status_is_error(status)) {
status = F_status_set_fine(status);
status = fll_execute_program(program, arguments, &execute_set->parameter, &execute_set->as, &result);
}
+ if (status == F_parent) {
+ controller_asynchronous_t *asynchronous = (controller_asynchronous_t *) thread->setting->rules.array[index].asynchronous;
+
+ // assign the child process id to the asynchronous thread to allow for the cancel process to send appropriate termination signals to the child process.
+ asynchronous->child = result;
+
+ // have the parent wait for the child process to finish. (@todo see comments above about forking into the background, this code block will need to change.)
+ waitpid(asynchronous->child, &result, WUNTRACED | WCONTINUED);
+
+ // remove the pid now that waidpid() has returned.
+ asynchronous->child = 0;
+
+ // this must explicitly check for 0 (as opposed to checking (!result)).
+ if (!WIFEXITED(result)) {
+ status = F_status_set_error(F_failure);
+ }
+ }
+
if (F_status_is_error(status)) {
status = F_status_set_fine(status);
f_thread_mutex_lock(&thread->mutex->asynchronous);
+ if (!thread->asynchronouss.enabled) {
+ f_thread_mutex_unlock(&thread->mutex->asynchronous);
+
+ return F_signal;
+ }
+
f_status_t status = controller_asynchronouss_increase(&thread->asynchronouss);
if (F_status_is_error(status)) {
#ifndef _di_controller_rule_wait_all_
void controller_rule_wait_all(controller_thread_t *thread) {
- f_array_length_t i = 0;
-
- for (; i < thread->asynchronouss.used; ++i) {
+ for (f_array_length_t i = 0; i < thread->asynchronouss.used; ++i) {
if (!thread->asynchronouss.array[i].state) continue;
- if (thread->asynchronouss.array[i].state != controller_asynchronous_state_joined) {
- f_thread_join(thread->asynchronouss.array[i].id, 0);
- }
-
- f_thread_mutex_lock(&thread->mutex->asynchronous);
-
- if (thread->asynchronouss.array[i].state) {
- thread->asynchronouss.array[i].state = 0;
-
- controller_macro_cache_action_t_clear(thread->asynchronouss.array[i].cache);
- }
-
- if (i == thread->asynchronouss.used - 1) {
- thread->asynchronouss.used = 0;
- }
-
- f_thread_mutex_unlock(&thread->mutex->asynchronous);
+ controller_rule_wait_for(i, thread);
} // for
}
#endif // _di_controller_rule_wait_all_
return;
}
- controller_asynchronous_t *asynchronous = (controller_asynchronous_t *) rule->asynchronous;
+ if (f_thread_mutex_lock_try(&rule->wait) == F_none) {
+ controller_asynchronous_t *asynchronous = (controller_asynchronous_t *) rule->asynchronous;
- if (asynchronous->state != controller_asynchronous_state_joined) {
- f_thread_join(asynchronous->id, 0);
- }
+ if (asynchronous->state == controller_asynchronous_state_done) {
+ f_thread_join(asynchronous->id, 0);
+ }
- f_thread_mutex_lock(&thread->mutex->asynchronous);
+ if (thread->asynchronouss.enabled) {
+ f_thread_mutex_lock(&thread->mutex->asynchronous);
+
+ if (asynchronous->state) {
+ if (asynchronous->state == controller_asynchronous_state_done) {
+ asynchronous->state = controller_asynchronous_state_joined;
+ }
+
+ controller_macro_cache_action_t_clear(asynchronous->cache);
+ }
- if (asynchronous->state) {
- asynchronous->state = 0;
+ f_thread_mutex_unlock(&thread->mutex->asynchronous);
+ }
- controller_macro_cache_action_t_clear(asynchronous->cache);
+ f_thread_mutex_unlock(&rule->wait);
}
+ else {
- f_thread_mutex_unlock(&thread->mutex->asynchronous);
+ // a wait lock is already in place, which will also be responsible for thread joining.
+ // this can therefore immediately unlock and return.
+ f_thread_mutex_lock(&rule->wait);
+ f_thread_mutex_unlock(&rule->wait);
+ }
}
#endif // _di_controller_rule_wait_for_
{
controller_thread_t *thread_main = (controller_thread_t *) asynchronous->thread;
+ if (!thread_main->asynchronouss.enabled) {
+ return 0;
+ }
+
f_thread_mutex_lock(&thread_main->setting->rules.array[asynchronous->index].lock);
thread.cache_main = thread_main->cache_main;
controller_rule_process(asynchronous->index, asynchronous->action, asynchronous->options, &thread);
+ asynchronous->state = controller_asynchronous_state_done;
+
f_thread_mutex_unlock(&thread.setting->rules.array[asynchronous->index].lock);
return 0;
#ifndef _di_controller_thread_asynchronous_cancel_
void controller_thread_asynchronous_cancel(controller_thread_t *thread) {
+ thread->asynchronouss.enabled = F_false;
+
f_thread_mutex_lock(&thread->mutex->asynchronous);
- for (f_array_length_t i = 0; i < thread->asynchronouss.used; ++i) {
+ f_array_length_t i = 0;
+
+ for (; i < thread->asynchronouss.used; ++i) {
if (!thread->asynchronouss.array[i].state) continue;
- f_thread_cancel(thread->asynchronouss.array[i].id);
+ if (thread->asynchronouss.array[i].child > 0) {
+ f_signal_send(F_signal_termination, thread->asynchronouss.array[i].child);
+ }
+
+ // @todo perhaps a timed join here where if it takes to long, try sending a kill signal to the child process.
f_thread_join(thread->asynchronouss.array[i].id, 0);
thread->asynchronouss.array[i].state = 0;
thread->asynchronouss.used = 0;
+ for (i = 0; i < thread->setting->rules.used; ++i) {
+ f_thread_mutex_unlock(&thread->setting->rules.array[i].lock);
+ f_thread_mutex_unlock(&thread->setting->rules.array[i].wait);
+ } // for
+
+ f_thread_mutex_unlock(&thread->mutex->print);
+ f_thread_mutex_unlock(&thread->mutex->cache);
+ f_thread_mutex_unlock(&thread->mutex->rule);
f_thread_mutex_unlock(&thread->mutex->asynchronous);
}
#endif // _di_controller_thread_asynchronous_cancel_
void * controller_thread_cache(void *arguments) {
controller_thread_t *thread = (controller_thread_t *) arguments;
+ controller_rule_t *rule = 0;
+
f_array_length_t i = 0;
for (;;) {
-
- // @todo depend on a posix mutex condition that will designate when to sleep and perform actions.
- // when the condition is on/off, then the program will either sleep until condition is toggled or sleep a given interval.
- // when sleeping a given interval, then after each interval expiration, perform process cleanups until there are no asynchronous processes to cleanup.
- // once all asynchronous processes are cleaned up, toggle the condition again and wait indefinitely.
sleep(controller_thread_cache_cleanup_interval_long);
if (f_thread_mutex_lock_try(&thread->mutex->cache) == F_none) {
if (f_thread_mutex_lock_try(&thread->mutex->asynchronous) == F_none) {
- for (i = 0; i < thread->asynchronouss.size; ++i) {
+ if (thread->asynchronouss.used) {
+ for (i = 0; i < thread->asynchronouss.used; ++i) {
- if (thread->asynchronouss.array[i].state == controller_asynchronous_state_done) {
- f_thread_join(thread->asynchronouss.array[i].id, 0);
- thread->asynchronouss.array[i].state = controller_asynchronous_state_joined;
- }
+ if (thread->asynchronouss.array[i].state == controller_asynchronous_state_done) {
+ f_thread_join(thread->asynchronouss.array[i].id, 0);
+ thread->asynchronouss.array[i].state = controller_asynchronous_state_joined;
+ }
- if (thread->asynchronouss.array[i].state == controller_asynchronous_state_joined) {
- controller_macro_cache_action_t_clear(thread->asynchronouss.array[i].cache);
- thread->asynchronouss.array[i].state = 0;
- }
- } // for
+ if (thread->asynchronouss.array[i].state == controller_asynchronous_state_joined) {
+ controller_macro_asynchronous_t_delete_simple(thread->asynchronouss.array[i]);
- for (i = thread->asynchronouss.size; i; --i) {
- if (thread->asynchronouss.array[i - 1].state) break;
+ thread->asynchronouss.array[i].state = 0;
+ }
+
+ if (thread->asynchronouss.array[i].state) break;
+ } // for
- controller_macro_asynchronous_t_delete_simple(thread->asynchronouss.array[i])
- } // for
+ for (i = thread->asynchronouss.used - 1; thread->asynchronouss.used; --i, --thread->asynchronouss.used) {
- thread->asynchronouss.used = i;
+ if (thread->asynchronouss.array[i].state == controller_asynchronous_state_joined) {
+ controller_macro_asynchronous_t_delete_simple(thread->asynchronouss.array[i]);
+
+ thread->asynchronouss.array[i].state = 0;
+ }
+
+ if (thread->asynchronouss.array[i].state) break;
+ } // for
+ }
if (thread->asynchronouss.used < thread->asynchronouss.size) {
controller_asynchronouss_resize(thread->asynchronouss.used, &thread->asynchronouss);
if (f_file_exists(thread->setting->path_pid.string) == F_true) {
if (thread->data->error.verbosity != f_console_verbosity_quiet) {
- f_thread_mutex_lock(&thread->mutex->print);
+ if (thread->asynchronouss.enabled) f_thread_mutex_lock(&thread->mutex->print);
fprintf(thread->data->error.to.stream, "%c", f_string_eol_s[0]);
fprintf(thread->data->error.to.stream, "%s%sThe pid file '", thread->data->error.context.before->string, thread->data->error.prefix ? thread->data->error.prefix : f_string_empty_s);
// only make the rule and control threads available once any/all pre-processing and is completed.
if (F_status_is_error_not(status) && status != F_signal && status != F_child) {
- controller_rule_wait_all(thread);
+ if (thread->data->parameters[controller_parameter_validate].result == f_console_result_none) {
+ controller_rule_wait_all(thread);
- status = f_thread_create(0, &thread_rule, &controller_thread_rule, (void *) thread);
+ status = f_thread_create(0, &thread_rule, &controller_thread_rule, (void *) thread);
- if (F_status_is_error_not(status)) {
- status = f_thread_create(0, &thread_control, &controller_thread_control, (void *) thread);
- }
+ if (F_status_is_error_not(status)) {
+ status = f_thread_create(0, &thread_control, &controller_thread_control, (void *) thread);
+ }
- if (F_status_is_error(status)) {
- if (thread->data->error.verbosity != f_console_verbosity_quiet) {
- controller_error_print_locked(thread->data->error, F_status_set_fine(status), "f_thread_create", F_true, thread);
+ if (F_status_is_error(status)) {
+ if (thread->data->error.verbosity != f_console_verbosity_quiet) {
+ controller_error_print_locked(thread->data->error, F_status_set_fine(status), "f_thread_create", F_true, thread);
+ }
}
}
}
- if (F_status_is_error_not(status) && status != F_signal && status != F_child) {
+ if (status == F_child) {
+ return F_child;
+ }
+
+ if (F_status_is_error_not(status) && status != F_signal && (thread->data->parameters[controller_parameter_validate].result == f_console_result_none || thread->data->parameters[controller_parameter_test].result == f_console_result_found)) {
// wait until signal thread exits, which happens on any termination signal.
f_thread_join(thread_signal, 0);