2
votes

From man -e 2 wait:

The call wait(&status) is equivalent to: waitpid(-1, &status, 0);

errno values:

EINTR WNOHANG was not set and an unblocked signal or a SIGCHLD was caught; see signal(7).

So I from my understanding of above, if we are blocked in 'wait' and receiving a signal (SIGCHLD), the call should return with -1 and errno set to EINTR. Though running the following snippet will prove that wait is actually restarted (linux 4.15.0-43, glibc 2.23):

#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/wait.h>

static void sigchld_handler(int signo)
{
    const char *const msg = "\nSIGCHLD";

    (void)signo;
    write(STDERR_FILENO, msg, strlen(msg));
}

int main(void)
{
    pid_t wait_ret = 0, pid = 0;
    int status = 0, ret = -1;

    sigaction(SIGCHLD, &(struct sigaction){
       .sa_handler = sigchld_handler,
    }, NULL);

    if ((pid = fork()) < 0)
    {
       perror("\nfork: ");
       goto Exit;
    }

    if (!pid)
    {
       sleep(3);
       return 0;
    }

    if ((wait_ret = wait(&status)) < 0)
    {
       perror("\nwait: ");
       goto Exit;
    }

    fprintf(stderr, "\nwait done, pid %d", wait_ret);
    ret = 0;

Exit:
    fprintf(stderr, "\n");
    return ret;
}

SA_RESTART is not set - so why does 'wait' restart.? Actual output:

SIGCHLD
wait done, pid 15242

Expected by me output:

SIGCHLD
wait: Interrupted system call

Note kill -CHLD <waiting process> manually from shell will give the expected result.

Also note that running the code on FreeBSD 11.2 will give the expected result as well - wait is interrupted with error on child exit.

1
I think that EINTR is triggered only when SIGCHLD is caught and waitpid would not otherwise return (i.e. some child other than the one waited for exited). - Erki Aring
you mean that errno == EINTR applies only to waitpid - then the question where is it stated in specs. pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html says that EINTR does apply to both. - Volodymyr Boiko
wait() is probably just a wrapper for waitpid(). - Erki Aring
The call wait(&status) is equivalent to: waitpid(-1, &status, 0); as qouted in question. - Volodymyr Boiko
Yes, and waitpid(-1, &status, 0) also never returns EINTR. Also note, that system call is not restarted, it just returns. If you try to wait() for the same child in signal handler you see, it is already handled. - Erki Aring

1 Answers

4
votes

There is a subtlety of interpretation here. You have to start with:

on success, [wait] returns the process ID of the terminated child; on error, -1 is returned.

(Linux wait(2) manual page)

The docs about errno values need to be interpreted in that light: in the event that wait() returns -1, thus indicating an error, and errno is set to EINTR, the interpretation is that a SIGCHLD or unblocked (other) signal interrupted the wait. But that does not imply that receipt of a SIGCHLD must cause wait() to fail. In particular, wait() will complete normally instead of raising an error when the SIGCHLD arises from termination of one of the process's children.

Before being too critical of the docs here, do consider that

  • a SIGCHLD may be delivered for reasons other than that a child has terminated
  • the error descriptions apply to all the functions in that group, and some of them can wait for specific children only. These could be interrupted by a SIGCHLD from some child that they are not waiting for.

The wording of the docs also seems to suggest that a SIGCHLD might cause one or more of these functions to fail with EINTR even if that signal is blocked. I would find such behavior surprising, but remember where I started: that that result is among the possible interpretations of an EINTR error does not necessarily imply that it can actually happen.