Reimplementing system() locally to prevent issues with blocking SIGCHLD on other threads

Viewed 30

I have had an issue with the system() call, since it was being used in a multithreaded context. My issue was that I would call system() to run a shell script in one thread, and simultaneously in a different thread I would also run a similar script using system(). The first system() call blocks SIGCHLD, and then the other script then inherits the blocked SIGCHLD signal in the child process (not 100% certain on this, but I know that SIGCHLD is somehow being blocked iff system() is run simultaneously). This causes some issues for me, since the shell script uses the wait builtin, which in my case relies on a signal being received from the child of that process.

My solution to this was to make a copy of system() locally, which does not block SIGCHLD. This works well, but I am not sure if not blocking SIGCHLD has any other adverse side effects. I have seen that SIGIGN and SIGQUIT are also ignored in the parent process for the standard system() function.

During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored, in the process that calls system() ...

My main process currently does not touch the SIGCHLD, SIGINT or SIGQUIT signals, so they are set to the default handlers. Also, the process is not used interactively, so a signal will not be propagated from the child process from a terminal.

Are there any other important reasons to block/ignore these signals?

I have included some abstracted code below to help contextualize.

main.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/wait.h>

static int
system_local (char *filename, int nargs, ...)
{
    int pid;
    int status = -1;

    pid = fork ();
    if (pid < 0)    /* Error */
    {
        return -1;
    }
    else if (pid == 0)  /* Child: execute program */
    {
        va_list va_args;
        char **args = malloc ((nargs + 2) * sizeof (char *));
        int i;

        if (!args)
        {
            exit (-1);
        }

        args[0] = filename;
        va_start (va_args, nargs);
        /* Arguments are only used starting from args[1] */
        for (i = 1; i < nargs + 1; i++)
        {
            args[i] = va_arg (va_args, char *);
        }
        /* Must be NULL terminated */
        args[i] = NULL;

        /* Run the program */
        status = execvp (filename, (char *const *) args);

        /* Not reached */
        free (args);
        va_end (va_args);
        exit (status);
    }
    /* Otherwise this is the parent, so collect the exit status */
    else if (waitpid (pid, &status, 0) != -1 && WIFEXITED (status))
    {
        return WEXITSTATUS (status);
    }

    return -1;
}

void *thread_func(void *vargp)
{
    int ret;
    sleep(1);
    ret = system_local ("script.sh", 1, "child_thread");
    if (ret == 0)
    {
        printf ("Successful child thread!");
    }
    return NULL;
}
   
int main()
{
    int ret;
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, thread_func, NULL);
    ret = system_local ("script.sh", 1, "main_thread");
    if (ret == 0)
    {
        printf ("Successful main thread!");
    }
    pthread_join(thread_id, NULL);
    exit(0);
}

script.sh:

#!/bin/sh
echo "Starting script on $1"

# Using sleep to emulate running another script
sleep 100 &
PID=$!

wait $PID

echo "Finishing script on $1"
0 Answers
Related