/* Copyright Red Hat, Inc. 2002-2003 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** @file * Wrapper functions around read/write/select to retry in the event * of interrupts. */ #include "config.h" #include #include #include #include #include "fdops.h" /** * This is a wrapper around select which will retry in the case we receive * EINTR. This is necessary for _read_retry, since it wouldn't make sense * to have _read_retry terminate if and only if two EINTRs were received * in a row - one during the read() call, one during the select call... * * See select(2) for description of parameters. */ int _select_retry(int fdmax, fd_set * rfds, fd_set * wfds, fd_set * xfds, struct timeval *timeout) { int rv; while (1) { rv = select(fdmax, rfds, wfds, xfds, timeout); if (rv == -1) { /* return on EBADF/EINVAL/ENOMEM; continue on EINTR/EAGAIN/ENOMEM */ if (errno == EINTR || errno == EAGAIN || errno == ENOMEM) continue; } return rv; } } /** * Retries a write in the event of a non-blocked interrupt signal. * * @param fd File descriptor to which we are writing. * @param buf Data buffer to send. * @param count Number of bytes in buf to send. * @param timeout (struct timeval) telling us how long we should retry. * @return The number of bytes written to the file descriptor, * or -1 on error (with errno set appropriately). */ ssize_t _write_retry(int fd, void *buf, int count, struct timeval * timeout) { int n, total = 0, remain = count, rv = 0; fd_set wfds, xfds; char *tmp_buf = (char *)buf; while (total < count) { /* Create the write FD set of 1... */ FD_ZERO(&wfds); FD_SET(fd, &wfds); FD_ZERO(&xfds); FD_SET(fd, &xfds); /* wait for the fd to be available for writing */ rv = _select_retry(fd + 1, NULL, &wfds, &xfds, timeout); if (rv == -1) return -1; else if (rv == 0) { errno = ETIMEDOUT; return -1; } if (FD_ISSET(fd, &xfds)) { errno = EPIPE; return -1; } /* * Attempt to write to fd */ n = write(fd, tmp_buf + total, remain); /* * When we know our fd was select()ed and we receive 0 bytes * when we write, the fd was closed. */ if ((n == 0) && (rv == 1)) { errno = EPIPE; return -1; } if (n == -1) { if ((errno == EAGAIN) || (errno == EINTR)) { /* * Not ready? */ continue; } /* Other errors: EIO, EINVAL, etc */ return -1; } total += n; remain -= n; } return total; } /** * Retry reads until we (a) time out or (b) get our data. Of course, if * timeout is NULL, it'll wait forever. * * @param sockfd File descriptor we want to read from. * @param buf Preallocated buffer into which we will read data. * @param count Number of bytes to read. * @param timeout (struct timeval) describing how long we should retry. * @return The number of bytes read on success, or -1 on failure. Note that we will always return (count) or (-1). */ ssize_t _read_retry(int sockfd, void *buf, int count, struct timeval * timeout) { int n, total = 0, remain = count, rv = 0; fd_set rfds, xfds; char *tmp_buf = (char *)buf; while (total < count) { FD_ZERO(&rfds); FD_SET(sockfd, &rfds); FD_ZERO(&xfds); FD_SET(sockfd, &xfds); /* * Select on the socket, in case it closes while we're not * looking... */ rv = _select_retry(sockfd + 1, &rfds, NULL, &xfds, timeout); if (rv == -1) return -1; else if (rv == 0) { errno = ETIMEDOUT; return -1; } if (FD_ISSET(sockfd, &xfds)) { errno = EPIPE; return -1; } /* * Attempt to read off the socket */ n = read(sockfd, tmp_buf + total, remain); /* * When we know our socket was select()ed and we receive 0 bytes * when we read, the socket was closed. */ if ((n == 0) && (rv == 1)) { errno = EPIPE; return -1; } if (n == -1) { if ((errno == EAGAIN) || (errno == EINTR)) { /* * Not ready? Wait for data to become available */ continue; } /* Other errors: EPIPE, EINVAL, etc */ return -1; } total += n; remain -= n; } return total; }