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.
  */
 
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>
4cd80898
 #ifndef	_WIN32
7d1c492d
 #include <netinet/in.h>
 #include <netdb.h>
c8e620d2
 #include <sys/socket.h>
7d1c492d
 #include <sys/time.h>
17ad1c5b
 #endif
4cd80898
 #include <sys/types.h>
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
 
22b802b6
 #ifdef SO_ERROR
 
7d1c492d
 #ifndef timercmp
6df88f29
 #define timercmp(a, b, cmp)          \
7d1c492d
   (((a)->tv_sec == (b)->tv_sec) ?     \
    ((a)->tv_usec cmp (b)->tv_usec) :  \
    ((a)->tv_sec cmp (b)->tv_sec))
 #endif /* timercmp */
 
 #ifndef timersub
6df88f29
 #define timersub(a, b, result)                       \
7d1c492d
   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
 
6df88f29
 static int
 connect_error (int sock)
7d1c492d
 {
6df88f29
     int optval;
     socklen_t optlen;
7d1c492d
 
6df88f29
     optlen = sizeof (optval);
138b5aba
     getsockopt (sock, SOL_SOCKET, SO_ERROR, &optval, (socklen_t *)&optlen);
7d1c492d
 
6df88f29
     if (optval)
     {
         logg ("connect_error: getsockopt(SO_ERROR): fd=%d error=%d: %s\n",
               sock, optval, strerror (optval));
     }
7d1c492d
 
6df88f29
     return optval ? -1 : 0;
7d1c492d
 }
 
6df88f29
 static int
 nonblock_connect (int sock, const struct sockaddr *addr, socklen_t addrlen,
                   int secs)
7d1c492d
 {
6df88f29
     /* 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;
7d1c492d
 #ifdef NONBLOCK_DEBUG
6df88f29
         logg ("DEBUG nonblock_connect: connect(): fd=%d errno=%d: %s\n",
               sock, e, strerror (e));
7d1c492d
 #endif
6df88f29
         switch (e)
         {
         case EALREADY:
         case EINPROGRESS:
         case EAGAIN:
             break;              /* wait for connection */
         case EISCONN:
             return 0;           /* connected */
         default:
             logg ("nonblock_connect: connect(): fd=%d errno=%d: %s\n",
                   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, >))
         {
             logg ("nonblock_connect: connect timing out (%d secs)\n", 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)
         {
             logg ("nonblock_connect: select() failure %d: errno=%d: %s\n",
                   select_failures, errno, strerror (errno));
             if (--select_failures >= 0)
                 continue;       /* keep waiting */
             break;              /* failed */
         }
7d1c492d
 
 #ifdef NONBLOCK_DEBUG
6df88f29
         logg ("DEBUG nonblock_connect: select = %d\n", n);
7d1c492d
 #endif
 
6df88f29
         if (n)
         {
             return connect_error (sock);
         }
7d1c492d
 
6df88f29
         /* Select returned, but there is no work to do... */
         if (--bogus_loops < 0)
         {
             logg ("nonblock_connect: giving up due to excessive bogus loops\n");
             break;              /* failed */
         }
7d1c492d
 
6df88f29
     }                           /* for loop: keep waiting */
7d1c492d
 
6df88f29
     return -1;                  /* failed */
7d1c492d
 }
 
6df88f29
 static ssize_t
 nonblock_recv (int sock, void *buf, size_t len, int flags, int secs)
7d1c492d
 {
6df88f29
     /* 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 */
 
ec905ef8
     /* Zero buffer to maintain sanity in case we're dealing with strings */
     memset(buf, 0x00, len);
 
6df88f29
     /* 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;
ec905ef8
         ssize_t recvd;
6df88f29
 
         /* Force timeout if we ran out of time */
         gettimeofday (&now, 0);
         if (timercmp (&now, &timeout, >))
         {
             logg ("nonblock_recv: recv timing out (%d secs)\n", 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, &fds, 0, 0, &wait);
         if (n < 0)
         {
             logg ("nonblock_recv: select() failure %d: errno=%d: %s\n",
                   select_failures, errno, strerror (errno));
             if (--select_failures >= 0)
                 continue;       /* keep waiting */
             break;              /* failed */
         }
 
ec905ef8
         if (FD_ISSET(sock, &fds))
6df88f29
         {
ec905ef8
             recvd = recv(sock, buf, len, flags);
             if (recvd < 0) {
e1f1bc62
                 if (errno == EINTR)
ec905ef8
                     continue;
 
                 return -1;
             }
 
             return recvd;
6df88f29
         }
 
         /* Select returned, but there is no work to do... */
         if (--bogus_loops < 0)
         {
             logg ("nonblock_recv: giving up due to excessive bogus loops\n");
             break;              /* failed */
         }
 
     }                           /* for loop: keep waiting */
 
     return -1;                  /* failed */
7d1c492d
 }
 
6df88f29
 static long
 nonblock_fcntl (int sock)
7d1c492d
 {
17ad1c5b
 #ifdef	F_GETFL
6df88f29
     long fcntl_flags;           /* Save fcntl() flags */
 
     fcntl_flags = fcntl (sock, F_GETFL, 0);
     if (fcntl_flags == -1)
     {
         logg ("nonblock_fcntl: saving: fcntl(%d, F_GETFL): errno=%d: %s\n",
               sock, errno, strerror (errno));
     }
     else if (fcntl (sock, F_SETFL, fcntl_flags | O_NONBLOCK))
     {
         logg ("nonblock_fcntl: fcntl(%d, F_SETFL, O_NONBLOCK): errno=%d: %s\n", sock, errno, strerror (errno));
     }
 
     return fcntl_flags;
17ad1c5b
 #else
6df88f29
     return 0;
17ad1c5b
 #endif
7d1c492d
 }
 
6df88f29
 static void
 restore_fcntl (int sock, long fcntl_flags)
7d1c492d
 {
17ad1c5b
 #ifdef	F_SETFL
6df88f29
     if (fcntl_flags != -1)
     {
         if (fcntl (sock, F_SETFL, fcntl_flags))
         {
             logg ("restore_fcntl: restoring: fcntl(%d, F_SETFL): errno=%d: %s\n", sock, errno, strerror (errno));
         }
     }
17ad1c5b
 #endif
7d1c492d
 }
 
 /*
 	wait_connect(): wrapper for connect(), with explicit 'secs' timeout
 */
6df88f29
 int
 wait_connect (int sock, const struct sockaddr *addr, socklen_t addrlen,
               int secs)
7d1c492d
 {
6df88f29
     long fcntl_flags;           /* Save fcntl() flags */
     int ret;
7d1c492d
 
6df88f29
     /* Temporarily set socket to non-blocking mode */
     fcntl_flags = nonblock_fcntl (sock);
7d1c492d
 
6df88f29
     ret = nonblock_connect (sock, addr, addrlen, secs);
7d1c492d
 
6df88f29
     /* Restore socket's default blocking mode */
     restore_fcntl (sock, fcntl_flags);
7d1c492d
 
6df88f29
     return ret;
7d1c492d
 }
 
 /*
 	wait_recv(): wrapper for recv(), with explicit 'secs' timeout
 */
6df88f29
 ssize_t
 wait_recv (int sock, void *buf, size_t len, int flags, int secs)
7d1c492d
 {
6df88f29
     long fcntl_flags;           /* Save fcntl() flags */
     int ret;
7d1c492d
 
6df88f29
     /* Temporarily set socket to non-blocking mode */
     fcntl_flags = nonblock_fcntl (sock);
7d1c492d
 
6df88f29
     ret = nonblock_recv (sock, buf, len, flags, secs);
7d1c492d
 
6df88f29
     /* Restore socket's default blocking mode */
     restore_fcntl (sock, fcntl_flags);
7d1c492d
 
6df88f29
     return ret;
7d1c492d
 }
 
22b802b6
 #endif /* SO_ERROR */