1

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);
}
lseiller
  • 25
  • 4
  • *Which* process do you attempt to `pkill` (and what is the exit status of `pkill`)? – Oka Mar 15 '23 at 15:06
  • A child process, for example I run cat in a fork, then I send a SIGINT to this process. The exit status of `pkill` is 0. – lseiller Mar 15 '23 at 15:19

2 Answers2

1

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

Oka
  • 23,367
  • 6
  • 42
  • 53
0

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);
}
boreddad420
  • 187
  • 1
  • 7
  • Thanks for your answer, my sighandler function is only used for the parent process. In the child process I set the sa_handler to SIG_DFL. I think my question was not clear enough. But I just find out the cause of the problem. In fact I have to use the flag SA_RESTART in the parent to control the following behavior: Most system calls get interrupted when a signal occurs, and sets errno to EINTR. Most system calls gets automatically restarted when a signal occurs (so they don't fail and set errno to EINTR). waitpid() was interrupted and was returning -1. – lseiller Mar 15 '23 at 15:56