Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: fork observing mechanism #1165

Merged
merged 1 commit into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 178 additions & 34 deletions src/processes.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,76 +46,220 @@
*/
#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 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;
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
procs_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;
procs_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
procs_init (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 ();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove this? This code was introduced some time ago to avoid deadlock. See PR #491

pid = fork ();
gvm_log_unlock ();
int pos = -1;
if (initialized)
{
if (max_procs_reached ())
return PROCSFULL;

if (pid == 0)
while (!procs[++pos].terminated)
;
}
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;
}
16 changes: 15 additions & 1 deletion src/processes.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,23 @@

#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
procs_init (int max);

void
procs_terminate_childs (void);

int
terminate_process (pid_t pid);

pid_t
create_process (process_func_t, void *);
int terminate_process (pid_t);

#endif