freshclam/nonblock.c
7d1c492d
 /*
  *  Copyright 2006 Everton da Silva Marques <everton.marques@gmail.com>
  *
  *  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 of the License, 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; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
17ad1c5b
 #ifdef	_MSC_VER
 #include <winsock.h>
 #endif
7d1c492d
 
e4aef950
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include "nonblock.h"
 
7d1c492d
 #include <stdio.h>
 #include <stdlib.h>
17ad1c5b
 #ifdef	HAVE_UNISTD_H
7d1c492d
 #include <unistd.h>
17ad1c5b
 #endif
7d1c492d
 #include <string.h>
 #include <ctype.h>
17ad1c5b
 #ifndef	C_WINDOWS
7d1c492d
 #include <netinet/in.h>
 #include <netdb.h>
17ad1c5b
 #endif
7d1c492d
 #include <sys/types.h>
17ad1c5b
 #ifndef	C_WINDOWS
c8e620d2
 #include <sys/socket.h>
7d1c492d
 #include <sys/time.h>
17ad1c5b
 #endif
7d1c492d
 #include <time.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <errno.h>
 
a889f40e
 #include "shared/output.h"
e48dcea9
 #include "libclamav/clamav.h"
7d1c492d
 
17ad1c5b
 #if	(!defined(EALREADY)) && (defined(WSAEALREADY))
 #define EALREADY	WSAEALREADY
 #endif
 #if	(!defined(EINPROGRESS)) && (defined(WSAEINPROGRESS))
 #define EINPROGRESS	WSAEINPROGRESS
 #endif
 #if	(!defined(EISCONN)) && (defined(WSAEISCONN))
 #define EISCONN	WSAEISCONN
 #endif
 
22b802b6
 #ifdef SO_ERROR
 
7d1c492d
 #ifndef timercmp
 # define timercmp(a, b, cmp)          \
   (((a)->tv_sec == (b)->tv_sec) ?     \
    ((a)->tv_usec cmp (b)->tv_usec) :  \
    ((a)->tv_sec cmp (b)->tv_sec))
 #endif /* timercmp */
 
 #ifndef timersub
 # define timersub(a, b, result)                       \
   do {                                                \
     (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;     \
     (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;  \
     if ((result)->tv_usec < 0) {                      \
       --(result)->tv_sec;                             \
       (result)->tv_usec += 1000000;                   \
     }                                                 \
   } while (0)
 #endif /* timersub */
 
 #define NONBLOCK_SELECT_MAX_FAILURES 3
 #define NONBLOCK_MAX_BOGUS_LOOPS     10
 #undef  NONBLOCK_DEBUG
 
 static int connect_error(int sock)
 {
 	int optval;
c8e620d2
 	socklen_t optlen;
7d1c492d
 
 	optlen = sizeof(optval);
 	getsockopt(sock, SOL_SOCKET, SO_ERROR, &optval, &optlen);
 
 	if (optval) {
e92c9f60
 		logg("connect_error: getsockopt(SO_ERROR): fd=%d error=%d: %s\n",
7d1c492d
 		     sock, optval, strerror(optval));
 	}
 
 	return optval ? -1 : 0;
 }
 
 static int nonblock_connect(int sock, const struct sockaddr *addr, socklen_t addrlen, int secs)
 {
 	/* Max. of unexpected select() failures */
 	int select_failures = NONBLOCK_SELECT_MAX_FAILURES;
 	/* Max. of useless loops */
 	int bogus_loops = NONBLOCK_MAX_BOGUS_LOOPS;
 	struct timeval timeout;  /* When we should time out */
 	int numfd;               /* Highest fdset fd plus 1 */
 
 	/* Calculate into 'timeout' when we should time out */
 	gettimeofday(&timeout, 0);
 	timeout.tv_sec += secs;
 
 	/* Launch (possibly) non-blocking connect() request */
 	if (connect(sock, addr, addrlen)) {
 		int e = errno;
 #ifdef NONBLOCK_DEBUG
e92c9f60
 		logg("DEBUG nonblock_connect: connect(): fd=%d errno=%d: %s\n",
7d1c492d
 		     sock, e, strerror(e));
 #endif
 		switch (e) {
 		case EALREADY:
 		case EINPROGRESS:
 			break; /* wait for connection */
 		case EISCONN:
 			return 0; /* connected */
 		default:
e92c9f60
 			logg("nonblock_connect: connect(): fd=%d errno=%d: %s\n",
7d1c492d
 			     sock, e, strerror(e));
 			return -1; /* failed */
 		}
 	}
 	else {
 		return connect_error(sock);
 	}
 
 	numfd = sock + 1; /* Highest fdset fd plus 1 */
 
 	for (;;) {
 		fd_set fds;
 		struct timeval now;
 		struct timeval wait;
 		int n;
 
 		/* Force timeout if we ran out of time */
 		gettimeofday(&now, 0);
 		if (timercmp(&now, &timeout, >)) {
e92c9f60
 			logg("nonblock_connect: connect timing out (%d secs)\n",
7d1c492d
 			     secs);
 			break; /* failed */
 		}
 
 		/* Calculate into 'wait' how long to wait */
 		timersub(&timeout, &now, &wait); /* wait = timeout - now */
 
 		/* Init fds with 'sock' as the only fd */
 		FD_ZERO(&fds);
 		FD_SET(sock, &fds);
 
 		n = select(numfd, 0, &fds, 0, &wait);
 		if (n < 0) {
e92c9f60
 			logg("nonblock_connect: select() failure %d: errno=%d: %s\n",
7d1c492d
 			     select_failures, errno, strerror(errno));
 			if (--select_failures >= 0)
 				continue; /* keep waiting */
 			break; /* failed */
 		}
 
 #ifdef NONBLOCK_DEBUG
e92c9f60
 		logg("DEBUG nonblock_connect: select = %d\n", n);
7d1c492d
 #endif
 
 		if (n) {
 			return connect_error(sock);
 		}
 
 		/* Select returned, but there is no work to do... */
 		if (--bogus_loops < 0) {
e92c9f60
 			logg("nonblock_connect: giving up due to excessive bogus loops\n");
7d1c492d
 			break; /* failed */
 		}
 
 	} /* for loop: keep waiting */
 
 	return -1; /* failed */
 }
 
 static ssize_t nonblock_recv(int sock, void *buf, size_t len, int flags, int secs)
 {
 	/* Max. of unexpected select() failures */
 	int select_failures = NONBLOCK_SELECT_MAX_FAILURES;
 	/* Max. of useless loops */
 	int bogus_loops = NONBLOCK_MAX_BOGUS_LOOPS;
 	struct timeval timeout;  /* When we should time out */
 	int numfd;               /* Highest fdset fd plus 1 */
 
 	/* Calculate into 'timeout' when we should time out */
 	gettimeofday(&timeout, 0);
 	timeout.tv_sec += secs;
 
 	numfd = sock + 1; /* Highest fdset fd plus 1 */
 
 	for (;;) {
 		fd_set fds;
 		struct timeval now;
 		struct timeval wait;
 		int n;
 
 		/* Force timeout if we ran out of time */
 		gettimeofday(&now, 0);
 		if (timercmp(&now, &timeout, >)) {
e92c9f60
 			logg("nonblock_recv: recv timing out (%d secs)\n", secs);
7d1c492d
 			break; /* failed */
 		}
 
 		/* Calculate into 'wait' how long to wait */
 		timersub(&timeout, &now, &wait); /* wait = timeout - now */
 
 		/* Init fds with 'sock' as the only fd */
 		FD_ZERO(&fds);
 		FD_SET(sock, &fds);
 
 		n = select(numfd, &fds, 0, 0, &wait);
 		if (n < 0) {
e92c9f60
 			logg("nonblock_recv: select() failure %d: errno=%d: %s\n",
7d1c492d
 			     select_failures, errno, strerror(errno));
 			if (--select_failures >= 0)
 				continue; /* keep waiting */
 			break; /* failed */
 		}
 
 		if (n) {
 			return recv(sock, buf, len, flags);
 		}
 
 		/* Select returned, but there is no work to do... */
 		if (--bogus_loops < 0) {
e92c9f60
 			logg("nonblock_recv: giving up due to excessive bogus loops\n");
7d1c492d
 			break; /* failed */
 		}
 
 	} /* for loop: keep waiting */
 
 	return -1; /* failed */
 }
 
 static long nonblock_fcntl(int sock)
 {
17ad1c5b
 #ifdef	F_GETFL
7d1c492d
 	long fcntl_flags; /* Save fcntl() flags */
 
 	fcntl_flags = fcntl(sock, F_GETFL, 0);
 	if (fcntl_flags == -1) {
e92c9f60
 		logg("nonblock_fcntl: saving: fcntl(%d, F_GETFL): errno=%d: %s\n",
7d1c492d
 		     sock, errno, strerror(errno));
 	}
 	else if (fcntl(sock, F_SETFL, fcntl_flags | O_NONBLOCK)) {
e92c9f60
 		logg("nonblock_fcntl: fcntl(%d, F_SETFL, O_NONBLOCK): errno=%d: %s\n",
7d1c492d
 		     sock, errno, strerror(errno));
 	}
 
 	return fcntl_flags;
17ad1c5b
 #else
 	return 0;
 #endif
7d1c492d
 }
 
 static void restore_fcntl(int sock, long fcntl_flags)
 {
17ad1c5b
 #ifdef	F_SETFL
7d1c492d
 	if (fcntl_flags != -1) {
 		if (fcntl(sock, F_SETFL, fcntl_flags)) {
e92c9f60
 			logg("restore_fcntl: restoring: fcntl(%d, F_SETFL): errno=%d: %s\n",
7d1c492d
 			     sock, errno, strerror(errno));
 		}
 	}
17ad1c5b
 #endif
7d1c492d
 }
 
 /*
 	wait_connect(): wrapper for connect(), with explicit 'secs' timeout
 */
 int wait_connect(int sock, const struct sockaddr *addr, socklen_t addrlen, int secs)
 {
 	long fcntl_flags; /* Save fcntl() flags */
 	int ret;
 
 	/* Temporarily set socket to non-blocking mode */
 	fcntl_flags = nonblock_fcntl(sock);
 
 	ret = nonblock_connect(sock, addr, addrlen, secs);
 
 	/* Restore socket's default blocking mode */
 	restore_fcntl(sock, fcntl_flags);
 
 	return ret;
 }
 
 /*
 	wait_recv(): wrapper for recv(), with explicit 'secs' timeout
 */
 ssize_t wait_recv(int sock, void *buf, size_t len, int flags, int secs)
 {
 	long fcntl_flags; /* Save fcntl() flags */
 	int ret;
 
 	/* Temporarily set socket to non-blocking mode */
 	fcntl_flags = nonblock_fcntl(sock);
 
 	ret = nonblock_recv(sock, buf, len, flags, secs);
 
 	/* Restore socket's default blocking mode */
 	restore_fcntl(sock, fcntl_flags);
 
 	return ret;
 }
 
22b802b6
 #endif /* SO_ERROR */