7
\$\begingroup\$

Below follows wrappers around read() and write() that retry when interrupted by a signal (the case where the return value is -1 and errno has the value EINTR), two more wrappers around write() and read() that call write() and read() in a loop respectively until size bytes have been written/read, and a function to copy file permissions from one file to another.

Code:

io.h:

#ifndef IO_H
#define IO_H 1

#include <sys/types.h>

/** 
 * These functions return -1 on failure, or a non-negative integer otherwise. */

/** 
 * Version of read() that retries when interrupted by EINTR (possibly by a 
 * SIGWINCH). */
[[nodiscard, gnu::nonnull]] ssize_t io_read_eintr(int fd, void *buf, size_t size);

/**
 * Version of write() that retries when interrupted by EINTR (possibly 
 * by a SIGWINCH). */
[[nodiscard, gnu::nonnull]] ssize_t io_write_eintr(int fd, 
                                                   const void *buf, 
                                                   size_t size);

/**
 * Version of read() that retries when interrupted by EINTR (possibly by a 
 * SIGWINCH). */
[[nodiscard, gnu::nonnull]] ssize_t io_read_all(int fd, void *buf, size_t size);

/** 
 * A wrapper around io_write_eintr() that's called in a loop until size bytes 
 * have been written. */ 
[[nodiscard, gnu::nonnull]] ssize_t io_write_all(int fd, 
                                              const void *buf, 
                                              size_t size);

/** 
 * Copies src_fd's mode bits to dest_fd. */
[[nodiscard]] ssize_t io_copy_file_perms(int src_fd, int dest_fd);

#endif /* IO_H */

io.c:

#undef POSIX_C_SOURCE
#undef _XOPEN_SOURCE

#define _POSIX_C_SOURCE 200819L
#define _XOPEN_SOURCE   700

#include "io.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

ssize_t io_read_eintr(int fd, void *buf, size_t size)
{
    ssize_t ret = 0;

    do {
        errno = 0;
        ret = read(fd, buf, size);
    } while (ret == -1 && errno == EINTR);

    return ret;
}

ssize_t io_write_eintr(int fd, const void *buf, size_t size)
{
    ssize_t ret = 0;

    do {
        errno = 0;
        ret = write(fd, buf, size);
    } while (ret == -1 && errno == EINTR);

    return ret;
}

ssize_t io_read_all(int fd, void *buf, size_t size)
{
    size_t rcount = 0;
    size_t bytes_left = size;
    ssize_t ret;

    for (ret = 0; rcount < size && ret != -1; rcount += (size_t) ret) {
        ret = io_read_eintr(fd, (char *) buf + rcount, bytes_left);
        bytes_left -= (size_t) ret;
    }

    return ret == -1 ? -1 : rcount;
}

ssize_t io_write_all(int fd, const void *buf, size_t size)
{
    size_t wcount = 0;
    size_t bytes_left = size;
    ssize_t ret;

    for (ret = 0; wcount < size && ret != -1; wcount += (size_t) ret) {
        ret = io_write_eintr(fd, (char *) buf + wcount, bytes_left);
        bytes_left -= (size_t) ret;
    }

    return ret == -1 ? -1 : wcount;
}

ssize_t io_copy_file_perms(int src_fd, int dest_fd)
{
    struct stat st;

    return fstat(src_fd, &st) != -1 && fchmod(dest_fd, st.st_mode) != -1 ? 0 : -1;
}

Review request:

Any bugs, missing error checking, et cetera.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ Why does io_copy_file_perms() return a ssize_t when the return is really only doing a bool test? \$\endgroup\$ Commented Jun 19 at 21:25
  • \$\begingroup\$ @chux-ReinstateMonica It returned bool previously. Then I changed it to ssize_t for consistency with other functions in this file. \$\endgroup\$
    – Harith
    Commented Jun 19 at 22:01

2 Answers 2

6
\$\begingroup\$
  • If the requested size exceeds the source file capacity, io_read_all will stuck in the infinite loop. Perhaps it shall break if ret == 0 (typically an end-of-file condition).

  • You may want to retry reads and writes on EAGAIN too. It is a transient error. ENOBUFS may also need to be addressed.

  • There is no need to set errno = 0 before calling read and write.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ vnp, "There is no need to set errno = 0 before calling read and write." deserves explanation. I suppose it is unlikely that code will have errno = EINTR and then call io_read_eintr(fd, buf, (size_t)-1). What down-side do you see with errno = 0 inside io_xxxx_eintr()? \$\endgroup\$ Commented Jun 19 at 21:18
  • \$\begingroup\$ @vnp Is it possible to stuck in the theoretical situation where I just continue infinitely looping on EINTR? \$\endgroup\$
    – Harith
    Commented Jun 20 at 1:38
  • 2
    \$\begingroup\$ @chux and Harith: If read() or write() returns -1, then it is specified that it sets errno. If it returns a non-negative number, then this code doesn't read errno. And we provide the same interface to our callers (in particular, it's unspecified whether errno is modified in the success case). \$\endgroup\$ Commented Jun 20 at 9:08
  • 2
    \$\begingroup\$ @TobySpeight, aside, *nix does not specify which is greater SIZE_MAX or SSIZE_MAX. IIRC, they are not specified as the same bit-width. \$\endgroup\$ Commented Jun 20 at 15:48
  • 1
    \$\begingroup\$ @Harith In *nix land, such functions return SSIZE_MAX. IMO, introducing yet another type SSIZE_MAX was not the best solution. \$\endgroup\$ Commented Jun 24 at 8:50
6
\$\begingroup\$

Sign-ness change

A picky compiler may complain about return ret == -1 ? -1 : rcount; with its implied type change from size_t to ssize_t as the sign-ness changes.
Use return ret == -1 ? -1 : (ssize_t) rcount; to quell the implied type sign-ness change warnings and make it explicit.

Minor: convoluted early exit of loop

io_read_all() and io_write_all() both have for loops that quit early when ret == -1. Yet that loop stoppage involves an involved logic path.

Simplified alternate that has 2 return points.
As with such style issues, code to your group's coding guidelines.

ssize_t io_read_all_alt(int fd, void *buf, size_t size) {
  size_t rcount = 0;

  while (rcount < size) {
    ssize_t ret = io_read_eintr(fd, (char *) buf + rcount, size - rcount);
    if (ret == -1) {
      return -1;
    }
    rcount += (size_t) ret
  }

  return (ssize_t) rcount;
}
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.