From 0b93e2628efeac4f27752a0cf5e73ad207ceeeb4 Mon Sep 17 00:00:00 2001 From: Christop Kraemer Date: Fri, 19 Aug 2022 12:53:00 +0200 Subject: [PATCH] Add: fork observing mechanism This PR extends the process module and allows to manage forked childs as well sets a limit to the number of forks possible. It also cleans up child processes. --- src/processes.c | 216 ++++++++++++++++++++++++++++++++++++++++-------- src/processes.h | 18 +++- 2 files changed, 199 insertions(+), 35 deletions(-) diff --git a/src/processes.c b/src/processes.c index 3bc07d7545..ead8731393 100644 --- a/src/processes.c +++ b/src/processes.c @@ -46,39 +46,167 @@ */ #define G_LOG_DOMAIN "sd main" +typedef struct proc_entry +{ + pid_t pid; + int terminated; +} proc_entry; + +static proc_entry *procs = NULL; +static int initialized = 0; +static int last; +static int max_procs; +static int num_procs; + +/** + * @brief Function for handling SIGCHLD to clear procs + * + */ +static void +clear_child () +{ + if (!initialized) + return; + + pid_t pid; + // In case we receive multiple SIGCHLD at once + while ((pid = waitpid (-1, NULL, WNOHANG)) > 0) + { + g_message ("Ending child with pid %d", pid); + for (int i = 0; i < max_procs; i++) + { + if (procs[i].pid != pid) + continue; + procs[i].terminated = 1; + last = i; + num_procs--; + break; + } + } +} + /** - * @brief Send SIGTERM to the pid process. Try to wait the - * the process. In case of the process has still not change - the state, it sends SIGKILL to the process and must be waited - later to avoid leaving a zombie process + * @brief checks for empty space in process list + * + * @return int 1 if full, 0 when empty + */ +static int +max_procs_reached () +{ + return num_procs == max_procs; +} - @param[in] pid Process id to terminate. +/** + * @brief Cleans the process list and frees memory. This will not terminate + * child processes. Is primarily used after fork. + * + */ +static void +clean_procs () +{ + initialized = 0; + g_free (procs); + procs = NULL; +} - @return 0 on success, -1 if the process was waited but not changed - the state +/** + * @brief Terminates a given process. If termination does not work, the process + * will get killed. In case init_procs was called, only direct child processes + * can be terminated + * + * @param pid id of the child process + * @return int 0 on success, NOCHILD if child does not exist, NOINIT if not + * initialized */ int terminate_process (pid_t pid) { - int ret; - - if (pid == 0) - return 0; + if (initialized) + { + for (int i = 0; i < max_procs; i++) + { + if (procs[i].pid == pid) + { + kill (pid, SIGTERM); + usleep (10000); + if (!procs[i].terminated) + kill (pid, SIGKILL); + return 0; + } + } + return NOCHILD; + } + else + { + kill (pid, SIGTERM); + usleep (10000); + if (waitpid (pid, NULL, WNOHANG)) + kill (pid, SIGKILL); + return 0; + } +} - ret = kill (pid, SIGTERM); +/** + * @brief This function terminates all processes spawned with create_process. + * Calls terminate_child for each process active. In case init_procs was not + * called this function does nothing. + * + */ +void +terminate_childs () +{ + if (!initialized) + return; - if (ret == 0) + for (int i = 0; i < max_procs; i++) { - usleep (1000); + if (!procs[i].terminated) + terminate_process (procs[i].pid); + } +} - if (waitpid (pid, NULL, WNOHANG) >= 0) - { - kill (pid, SIGKILL); - return -1; - } +/** + * @brief Handler for a termination signal. This will terminate all childs and + * calls SIGTERM for itself afterwards. + * + */ +static void +terminate () +{ + if (!initialized) + return; + + int tries = 5; + terminate_childs (); + + while (num_procs && tries--) + { + sleep (1); } - return 0; + clean_procs (); + openvas_signal (SIGTERM, SIG_DFL); + raise (SIGTERM); +} + +/** + * @brief Init procs, must be called once per process + * + * @param max + */ +void +init_procs (int max) +{ + procs = g_malloc (max * sizeof (proc_entry)); + for (int i = 0; i < max; i++) + { + procs[i].terminated = 1; + } + max_procs = max; + num_procs = 0; + openvas_signal (SIGCHLD, clear_child); + openvas_signal (SIGTERM, terminate); + initialized = 1; } static void @@ -86,36 +214,56 @@ init_child_signal_handlers (void) { /* SIGHUP is only for reloading main scanner process. */ openvas_signal (SIGHUP, SIG_IGN); - openvas_signal (SIGTERM, make_em_die); - openvas_signal (SIGINT, make_em_die); - openvas_signal (SIGQUIT, make_em_die); openvas_signal (SIGSEGV, sighand_segv); openvas_signal (SIGPIPE, SIG_IGN); } /** - * @brief Create a new process (fork). + * @brief Calls a function with a new process + * + * @param func Function to call + * @param args arguments + * @return pid of spawned process on success or one of the following errors: + * FORKFAILED, NOINIT, PROCSFULL */ pid_t -create_process (process_func_t function, void *argument) +create_process (process_func_t func, void *args) { - int pid; - - gvm_log_lock (); - pid = fork (); - gvm_log_unlock (); + int pos = 0; + if (initialized) + { + if (max_procs_reached ()) + return PROCSFULL; - if (pid == 0) + while (!procs[last].terminated) + last = (last + 1) % max_procs; + // as last can change when a child terminates + pos = last; + } + pid_t pid = fork (); + if (!pid) { - mqtt_reset (); + initialized = 0; + usleep (1000); init_child_signal_handlers (); + clean_procs (); + mqtt_reset (); init_sentry (); srand48 (getpid () + getppid () + (long) time (NULL)); - (*function) (argument); + (*func) (args); gvm_close_sentry (); _exit (0); } + if (pid < 0) - g_warning ("Error : could not fork ! Error : %s", strerror (errno)); + { + return FORKFAILED; + } + if (initialized) + { + procs[pos].pid = pid; + procs[pos].terminated = 0; + num_procs++; + } return pid; } diff --git a/src/processes.h b/src/processes.h index 174cf304d3..ade86f516c 100644 --- a/src/processes.h +++ b/src/processes.h @@ -28,9 +28,25 @@ #include /* for pid_t */ +#define FORKFAILED -1 +#define NOINIT -2 +#define PROCSFULL -3 +#define NOCHILD -4 + typedef void (*process_func_t) (void *); + +void +init_procs (int max); + +void +terminate_childs (void); + +int +terminate_process (pid_t pid); + pid_t create_process (process_func_t, void *); -int terminate_process (pid_t); + +int terminate_child (pid_t); #endif