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); |
196e2fed |
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 */
|
f1a1ab2f |
/* 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; |
f1a1ab2f |
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 */
}
|
f1a1ab2f |
if (FD_ISSET(sock, &fds)) |
6df88f29 |
{ |
f1a1ab2f |
recvd = recv(sock, buf, len, flags);
if (recvd < 0) {
if (errno == EAGAIN)
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 */ |