waitpid() fail sometime when SIGINT is sent by a ctrl+c

276 views Asked by At

waitpid() returns -1 from time to time when the process in question has received a SIGINT via a ^C. But I can't reproduce this problem if I send a SIGINT signal via pkill for example.

The value of errno is set to EINTR.

I have a parent process that creates sub processes to execute commands received from the STDIN with execve().

sigaction handler for SIGINT and SIGQUIT is set to SIG_DFL for the child only. sigaction handler for SIGINT is set to sighandler() (see below) and SIGQUIT is set to SIG_IGN for the parent only.

void    sighandler(int signum)
{
    (void) signum;
    g_signal = true;
    rl_done = 1;
}

After launching all the forks from the parent. I make a call to waitpid() on all the pid that have been created, and with wstatus I can get back the output value of the commands, and also allows me to know if the process has been terminated by a signal.

void    pid_lst_wait(t_pid_lst **pid_lst, int *wstatus)
{
    t_pid_lst   *ptr_pid_lst;

    *wstatus = 0;
    if (*pid_lst == NULL)
        return ;
    ptr_pid_lst = *pid_lst;
    while (ptr_pid_lst)
    {
        if (waitpid(ptr_pid_lst->pid, wstatus, 0) == -1)
            ft_dprintf(STDERR_FILENO, "waitpid: Failed to wait %d\n", ptr_pid_lst->pid);
        ptr_pid_lst = ptr_pid_lst->next;
    }
    pid_lst_clear(*pid_lst);
    *pid_lst = NULL;
}

Since waitpid fail I can't set a return status to indicate a SIGINT was sent.

EDIT: As nos say here I need to use SA_RESTART to avoid this behavior.

typedef struct sigaction    t_sigaction;

uint8_t signal_init(void)
{
    t_sigaction sa;

    sa = (t_sigaction){0};
    sigemptyset(sa.sa_mask);
    sa.sa_handler = SIG_IGN;
    if (sigaction(SIGQUIT, &sa, NULL) == -1)
        return (SSYSCALL_ERROR);
    sa.sa_handler = sighandler;
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGINT, &sa, NULL) == -1)
        return (SSYSCALL_ERROR);
    return (SSUCCESS);
}
2

There are 2 answers

0
Oka On BEST ANSWER

In your common shell (e.g., Bash), ^C sends SIGINT to the entire foreground process group: parents and children.

waitpid(2) returning -1, and setting errno to EINTR, is the expected default behaviour when the function is interrupted by the delivery of an unblocked signal in the calling process (i.e., in the parent).

You can specify the SA_RESTART flag in the .sa_flags member of the struct sigaction structure provided to sigaction(2) to have waitpid automatically restart after the delivery of the signal is handled (see also: signal(7)).

Generally speaking, the delivery of a signal to a child process (say via pkill) will not cause waitpid in the parent process to return -1 (there are OS-specific behaviours, like the handling of SIGCHILD on Linux).

1
boreddad420 On

when you execute a command with any of the exec system calls, the process created by the command takes over the process where the exec system call was called. So, any variables or code that you are trying to use or access after the exec call will not work. for example:

int main(int argc, char* argv[])
{
    int pid = fork();
    if (pid == 0)
    {
          execvp(argv[0],argv);
          printf("done\n");
    } else 
    {
         return 0;
    }
}

the printf statement at the end will not be called, because the process created by the exec call completely takes over the child process.

So, the signal handlers that you create in your child process, once you make the exec system call, are lost, and the process created by the exec call may have its own signal handlers that it creates.

Without seeing how/where the processes and signal handlers are created, I don't think I can provide much more help, but if you want to terminate the child when the parent receives SIGINT, you will have to set up a signal handler to handle SIGINT in the main process, and when SIGINT is received, you can then kill the child process within that signal handler. for example:

int child_pid;

void handler(int num)
{
    kill(child_pid, SIGKILL);
}