Handling Incomplete write() Calls Handling Incomplete write() Calls unix unix

Handling Incomplete write() Calls


Write will return a negative number if nothing is written under two circumstances:

  • A temporary error (e.g. EINTR, EAGAIN, and EWOULDBLOCK); the first of these can happen with any write, the second two (broadly) only on non-blocking I/O.

  • A permanent error.

Normally you would want to retry the first, so the routine is to repeat the write if EINTR, EAGAIN or EWOULDBLOCK is returned (though I've seen argument against the latter).

For example:

ssize_twrite_with_retry (int fd, const void* buf, size_t size){    ssize_t ret;    do    {         ret = write(fd, buf, size);    } while ((ret<0) && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));    return ret;}

Also note (from the man page) that write can return a number of bytes written less than you requested in the case of non-blocking I/O, or blocking I/O (as the linux man-page makes clear).

OS-X man-page extract:

When using non-blocking I/O on objects, such as sockets, that are subject to flow control, write() and writev() may write fewer bytes than requested; the return value must be noted, and the remainder of the operation should be retried when possible.

Linux man-page extract (my emphasis):

The number of bytes written may be less than count if, for example, there is insufficient space on the underlying physical medium, or the RLIMIT_FSIZE resource limit is encountered (see setrlimit(2)), or the call was interrupted by a signal handler after having written less than count bytes.

You would normally be handling those with select(), but to handle that case manually:

ssize_twrite_with_retry (int fd, const void* buf, size_t size){    ssize_t ret;    while (size > 0) {        do        {             ret = write(fd, buf, size);        } while ((ret < 0) && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));        if (ret < 0)            return ret;        size -= ret;        buf += ret;    }    return 0;}


The (ret < 0) condition ignores short writes and only reports formal errors. This is arguably sloppy, but fixing it requires extra code — it is plausible to think that it would mean you should use a function wrapped around the write system call.

The (ret != size) condition is much more sensitive as a test, but the follow-up actions are complex enough that again, it needs to be a function wrapped around write().

In a generic wrapper function, you need to distinguish:

  • ret > 0 && ret < size: in this case, you should loop and try to write the residue of the buffer. You could simply return the number of bytes actually written (cumulatively).

  • ret == 0: you wrote zero bytes, but there wasn't an error. This could happen if you have the file descriptor open with O_NONBLOCK. You should probably have a strategy for dealing with this — but it is going to be context dependent. Is a sleep appropriate? Or do you just retry — and risk hammering the system? Do you just try a few times? You get 0 bytes written if you request 0 bytes to be written, but that would be covered under ret == size. Otherwise, it is not clear that ret == 0 is possible. For a generic wrapper, if this occurs, maybe the best approach is to return the 0.

  • ret < 0: the chances are high that any retry will fail. Return the error condition. However, it is worth retrying if you get one of a selected few errors: EINTR if the write() is interrupted by a signal; EAGAIN for a write on a non-blocking file descriptor that would block — sometimes known as EWOULDBLOCK; maybe a few others but none of the normal errors for write() are plausible candidates.

Most serious Unix (or Linux) system programming books discuss this. For example:


The answers posted are very helpful, in particular FUZxxl's comment pointed out an important fact:

write() will never succeed without writing data.

For my own case, I'm running out of disk space, and I really wanted write() to give me ENOSPC. The solution, then, is to retry incomplete write() calls. The first call will return successfully. The second call will be unable to write any data. Since it can't write anything, it then returns ENOSPC. We won't loop endlessly trying to write data to a full disk. And we'll be able to handle other reasons that write() might reasonably fail to write out all of its data.