csignalswaitpidexecvesigaction

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


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);
}

Solution

  • 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).