m0hithreddy
m0hithreddy

Reputation: 1829

POSIX partial write() and Signal Interrupts

From the man page of write()

Note that a successful write() may transfer fewer than count bytes. Such partial writes can occur for various reasons; for example, because there was insufficient space on the disk device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or similar was interrupted by a signal handler after it had transferred some, but before it had transferred all of the requested bytes. In the event of a partial write, the caller can make another write() call to transfer the remaining bytes. The subsequent call will either transfer further bytes or may result in an error (e.g., if the disk is now full).

I have the following questions

1) In the case of write() being interrupted by signal handler after a partial transfer, will write() set the errno to EINTR ?

2) If errno is not set, is there a way to identify such an event with out extra piece of code (Like installing signal disposition and setting a flag value to true) ?

Note : The further write() calls are successful in transferring the remaining bytes after the event of signal interrupt.

Upvotes: 1

Views: 1076

Answers (2)

R.. GitHub STOP HELPING ICE
R.. GitHub STOP HELPING ICE

Reputation: 215221

To answer your individual numbered questions:

  1. errno is only meaningful after one of the standard functions returns a value indicating an error - for write, -1 - and before any other standard function or application code that might clobber it is called. So no, if write returns a short write, errno will not be set to anything meaningful. If it's equal to EINTR, it just happens to be; this is not something meaningful you can interpret.

  2. The way you identify such an event is by the return value being strictly less than the nbytes argument. This doesn't actually tell you the cause of the short write, so it could be something else like running out of space. If you need to know, you need to arrange for the signal handler to inform you. But in almost all cases you don't actually need to know.

Regarding the note, if write is returning the full nbytes after a signal arriving, the signal handler was non-interrupting. This is the default on Linux with any modern libc (glibc, musl, anything but libc5 basically), and it's almost always the right thing. If you actually want interrupting signals you have to install the signal handler with sigaction and the SA_RESTART flag clear. (And conversely if you're installing signal handlers you want to have the normal, reasonable, non-interrupting behavior, for portability you should use sigaction and set the SA_RESTART flag rather than using the legacy function signal).

Upvotes: 3

Let's try it and see:

#define _GNU_SOURCE

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

static void handle_sigalrm(int sig) {
}

int main(void) {
    struct sigaction act;
    memset(&act, 0, sizeof act);
    act.sa_handler = handle_sigalrm;
    sigaction(SIGALRM, &act, NULL);
    int fds[2];
    pipe(fds);
    int bufsize = fcntl(fds[1], F_GETPIPE_SZ) + 10;
    char *buf = calloc(bufsize, 1);
    ssize_t written;
    printf("will attempt to write %d bytes and EINTR is %d\n", bufsize, EINTR);
    alarm(1);
    errno = 0;
    written = write(fds[1], buf, bufsize);
    printf("write returned %td and errno is %d\n", written, errno);
    return 0;
}

That program makes a pipe that nothing will ever read from, does a write to it that's bigger than the kernel's buffer, and arranges for a signal handler to run while the write is blocking. On my system, it prints this:

will attempt to write 65546 bytes and EINTR is 4
write returned 65536 and errno is 0

Thus, the answer to "In the case of write() being interrupted by signal handler after a partial transfer, will write() set the errno to EINTR?" is "no, it won't".

Upvotes: 2

Related Questions