Skip to content

Commit

Permalink
Add: fork observing mechanism
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Kraemii committed Aug 19, 2022
1 parent add44e5 commit 0b93e26
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 35 deletions.
216 changes: 182 additions & 34 deletions src/processes.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,76 +46,224 @@
*/
#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
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;
}
18 changes: 17 additions & 1 deletion src/processes.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,25 @@

#include <sys/types.h> /* 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

0 comments on commit 0b93e26

Please sign in to comment.