From 98f2c8aea52a49fad3fd6df67b1eb32c1499176c Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sun, 25 Apr 2010 19:27:45 +0200 Subject: [PATCH] Introduce the new nonblock API. This patch is an attempt to create a better API for reads from non-blocking file descriptors. It adds readv_nonblock() and implements read_nonblock() as a simple wrapper for readv_nonblock(). Both new functions check the given file descriptor for readability and read as much as possible until an error occurs or the buffer is full. Two additional parameters are introduced: An fd_set and a result pointer for the number of bytes that have been read successfully. The optional fd_set pointer is used to have the fixup code for unreliable returns of FD_ISSET() at one place only. Having an extra parameter for storing the number of bytes read allows to treat EOF as an error condition but EAGAIN as a normal condition. This will simplify callers (dccp_recv and upd_recv) a bit. --- error.h | 1 + fd.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- fd.h | 3 ++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/error.h b/error.h index 3ad88cd4..e30e8a28 100644 --- a/error.h +++ b/error.h @@ -374,6 +374,7 @@ extern const char **para_errlist[]; #define FD_ERRORS \ PARA_ERROR(FGETS, "fgets error"), \ + PARA_ERROR(EOF, "end of file"), \ #define WRITE_ERRORS \ diff --git a/fd.c b/fd.c index 46be2289..d56a3110 100644 --- a/fd.c +++ b/fd.c @@ -11,12 +11,12 @@ #include #include #include -#include #include #include "para.h" #include "error.h" #include "string.h" +#include "fd.h" /** * Write a buffer to a file descriptor, re-write on short writes. @@ -82,6 +82,101 @@ int write_nonblock(int fd, const char *buf, size_t len, return written; } +/** + * Read from a non-blocking file descriptor into multiple buffers. + * + * \param fd The file descriptor to read from. + * \param iov Scatter/gather array used in readv(). + * \param iovcnt Number of elements in \a iov. + * \param rfds An optional fd set pointer. + * \param num_bytes Result pointer. Contains the number of bytes read from \a fd. + * + * If \a rfds is not \p NULL and the (non-blocking) file descriptor \a fd is + * not set in \a rfds, this function returns early without doing anything. + * Otherwise The function tries to read up to \a sz bytes from \a fd. As for + * write_nonblock(), EAGAIN is not considered an error condition. However, EOF + * is. + * + * \return Zero or a negative error code. If the underlying call to readv(2) + * returned zero (indicating an end of file condition) or failed for some + * reason other than \p EAGAIN, a negative return value is returned. + * + * In any case, \a num_bytes contains the number of bytes that have been + * successfully read from \a fd (zero if the first readv() call failed with + * EAGAIN). Note that even if the function returns negative, some data might + * have been read before the error occured. In this case \a num_bytes is + * positive. + * + * \sa \ref write_nonblock(), read(2), readv(2). + */ +int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds, + size_t *num_bytes) +{ + int ret, i, j; + + *num_bytes = 0; + /* + * Avoid a shortcoming of select(): Reads from a non-blocking fd might + * return EAGAIN even if FD_ISSET() returns true. However, FD_ISSET() + * returning false definitely means that no data can currently be read. + * This is the common case, so it is worth to avoid the overhead of the + * read() system call in this case. + */ + if (rfds && !FD_ISSET(fd, rfds)) + return 0; + + for (i = 0, j = 0; i < iovcnt;) { + + /* fix up the first iov */ + assert(j < iov[i].iov_len); + iov[i].iov_base += j; + iov[i].iov_len -= j; + ret = readv(fd, iov + i, iovcnt - i); + iov[i].iov_base -= j; + iov[i].iov_len += j; + + if (ret == 0) + return -E_EOF; + if (ret < 0) { + if (errno == EAGAIN) + return 0; + return -ERRNO_TO_PARA_ERROR(errno); + } + *num_bytes += ret; + while (ret > 0) { + if (ret < iov[i].iov_len - j) { + j += ret; + break; + } + ret -= iov[i].iov_len - j; + j = 0; + if (++i >= iovcnt) + break; + } + } + return 0; +} + +/** + * Read from a non-blocking file descriptor into a single buffer. + * + * \param fd The file descriptor to read from. + * \param buf The buffer to read data to. + * \param sz The size of \a buf. + * \param rfds \see \ref readv_nonblock(). + * \param num_bytes \see \ref readv_nonblock(). + * + * This is a simple wrapper for readv_nonblock() which uses an iovec with a single + * buffer. + * + * \return The return value of the underlying call to readv_nonblock(). + */ +int read_nonblock(int fd, void *buf, size_t sz, fd_set *rfds, size_t *num_bytes) +{ + struct iovec iov = {.iov_base = buf, .iov_len = sz}; + return readv_nonblock(fd, &iov, 1, rfds, num_bytes); +} + /** * Simple wrapper for readv(). * diff --git a/fd.h b/fd.h index 68092308..b9558820 100644 --- a/fd.h +++ b/fd.h @@ -27,6 +27,9 @@ int mmap_full_file(const char *filename, int open_mode, void **map, int para_munmap(void *start, size_t length); int write_ok(int fd); void valid_fd_012(void); +int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds, + size_t *num_bytes); +int read_nonblock(int fd, void *buf, size_t sz, fd_set *rfds, size_t *num_bytes); int write_nonblock(int fd, const char *buf, size_t len, size_t max_bytes_per_write); int for_each_file_in_dir(const char *dirname, -- 2.39.5