/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/*
 * Description: Socket/Naming manipulation functions
 * Based on:    Various Jserv files
 */
/**
 * @package jk_connect
 * @author      Gal Shachor <shachor@il.ibm.com>
 * @author      Mladen Turk <mturk@apache.org>
 * @version     $Revision: 1841818 $
 */


#include "jk_connect.h"
#include "jk_util.h"

#ifdef HAVE_APR
#include "apr_network_io.h"
#include "apr_errno.h"
#include "apr_general.h"
#include "apr_pools.h"
static apr_pool_t *jk_apr_pool = NULL;
#endif

#ifdef HAVE_SYS_FILIO_H
/* FIONREAD on Solaris et al. */
#include <sys/filio.h>
#endif
#ifdef HAVE_POLL_H
/* Use poll instead select */
#include <poll.h>
#endif

#if defined(WIN32)
#define JK_IS_SOCKET_ERROR(x) ((x) == SOCKET_ERROR)
#define JK_GET_SOCKET_ERRNO() errno = WSAGetLastError() - WSABASEERR
#else
#define JK_IS_SOCKET_ERROR(x) ((x) == -1)
#define JK_GET_SOCKET_ERRNO() ((void)0)
#endif /* WIN32 */

#ifndef INET6_ADDRSTRLEN
/* Maximum size of an IPv6 address in ASCII */
#define INET6_ADDRSTRLEN 46
#endif

/* 2 IPv6 adresses of length (INET6_ADDRSTRLEN-1)
 * each suffixed with a ":" and a port (5 digits)
 * plus " -> " plus terminating "\0"
 */
#define DUMP_SINFO_BUF_SZ (2 * (INET6_ADDRSTRLEN - 1 + 1 + 5) + 4 + 1)

#ifndef IN6ADDRSZ
#define IN6ADDRSZ   16
#endif

#ifndef INT16SZ
#define INT16SZ     sizeof(short)
#endif

#if !defined(EAFNOSUPPORT) && defined(WSAEAFNOSUPPORT)
#define EAFNOSUPPORT WSAEAFNOSUPPORT
#endif

/** Set socket to blocking
 * @param sd  socket to manipulate
 * @return    errno: fcntl returns -1 (!WIN32)
 *            pseudo errno: ioctlsocket returns SOCKET_ERROR (WIN32)
 *            0: success
 */
static int soblock(jk_sock_t sd)
{
/* BeOS uses setsockopt at present for non blocking... */
#if defined (WIN32)
    u_long on = 0;
    if (JK_IS_SOCKET_ERROR(ioctlsocket(sd, FIONBIO, &on))) {
        JK_GET_SOCKET_ERRNO();
        return errno;
    }
#else
    int fd_flags;

    fd_flags = fcntl(sd, F_GETFL, 0);
#if defined(O_NONBLOCK)
    fd_flags &= ~O_NONBLOCK;
#elif defined(O_NDELAY)
    fd_flags &= ~O_NDELAY;
#elif defined(FNDELAY)
    fd_flags &= ~FNDELAY;
#else
#error Please teach JK how to make sockets blocking on your platform.
#endif
    if (fcntl(sd, F_SETFL, fd_flags) == -1) {
        return errno;
    }
#endif /* WIN32 */
    return 0;
}

/** Set socket to non-blocking
 * @param sd  socket to manipulate
 * @return    errno: fcntl returns -1 (!WIN32)
 *            pseudo errno: ioctlsocket returns SOCKET_ERROR (WIN32)
 *            0: success
 */
static int sononblock(jk_sock_t sd)
{
#if defined (WIN32)
    u_long on = 1;
    if (JK_IS_SOCKET_ERROR(ioctlsocket(sd, FIONBIO, &on))) {
        JK_GET_SOCKET_ERRNO();
        return errno;
    }
#else
    int fd_flags;

    fd_flags = fcntl(sd, F_GETFL, 0);
#if defined(O_NONBLOCK)
    fd_flags |= O_NONBLOCK;
#elif defined(O_NDELAY)
    fd_flags |= O_NDELAY;
#elif defined(FNDELAY)
    fd_flags |= FNDELAY;
#else
#error Please teach JK how to make sockets non-blocking on your platform.
#endif
    if (fcntl(sd, F_SETFL, fd_flags) == -1) {
        return errno;
    }
#endif /* WIN32 */
    return 0;
}

#if defined (WIN32)
/* WIN32 implementation */
/** Non-blocking socket connect
 * @param sd       socket to connect
 * @param addr     address to connect to
 * @param source   optional source address
 * @param timeout  connect timeout in seconds
 *                 (<=0: no timeout=blocking)
 * @param l        logger
 * @return         -1: some kind of error occured
 *                 SOCKET_ERROR: no timeout given and error
 *                               during blocking connect
 *                 0: success
 */
static int nb_connect(jk_sock_t sd, jk_sockaddr_t *addr, jk_sockaddr_t *source,
                      int timeout, jk_logger_t *l)
{
    int rc;
    char buf[64];

    JK_TRACE_ENTER(l);

    if (source != NULL) {
        if (bind(sd, (const struct sockaddr *)&source->sa.sin, source->salen)) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                   "error during source bind on socket %d [%s] (errno=%d)",
                   sd, jk_dump_hinfo(source, buf, sizeof(buf)), errno);
        }
    }
    if (timeout <= 0) {
        rc = connect(sd, (const struct sockaddr *)&addr->sa.sin, addr->salen);
        JK_TRACE_EXIT(l);
        return rc;
    }

    if ((rc = sononblock(sd))) {
        JK_TRACE_EXIT(l);
        return -1;
    }
    if (JK_IS_SOCKET_ERROR(connect(sd, (const struct sockaddr *)&addr->sa.sin, addr->salen))) {
        struct timeval tv;
        fd_set wfdset, efdset;

        if ((rc = WSAGetLastError()) != WSAEWOULDBLOCK) {
            soblock(sd);
            WSASetLastError(rc);
            JK_TRACE_EXIT(l);
            return -1;
        }
        /* wait for the connect to complete or timeout */
        FD_ZERO(&wfdset);
        FD_SET(sd, &wfdset);
        FD_ZERO(&efdset);
        FD_SET(sd, &efdset);

        tv.tv_sec = timeout / 1000;
        tv.tv_usec = (timeout % 1000) * 1000;

        rc = select((int)sd + 1, NULL, &wfdset, &efdset, &tv);
        if (JK_IS_SOCKET_ERROR(rc) || rc == 0) {
            rc = WSAGetLastError();
            soblock(sd);
            WSASetLastError(rc);
            JK_TRACE_EXIT(l);
            return -1;
        }
        /* Evaluate the efdset */
        if (FD_ISSET(sd, &efdset)) {
            /* The connect failed. */
            int rclen = (int)sizeof(rc);
            if (getsockopt(sd, SOL_SOCKET, SO_ERROR, (char*) &rc, &rclen))
                rc = 0;
            soblock(sd);
            if (rc)
                WSASetLastError(rc);
            JK_TRACE_EXIT(l);
            return -1;
        }
    }
    soblock(sd);
    JK_TRACE_EXIT(l);
    return 0;
}
#elif defined(HAVE_POLL_H)
/* POSIX implementation using poll(2) */
/** Non-blocking socket connect
 * @param sd       socket to connect
 * @param addr     address to connect to
 * @param source   optional source address
 * @param timeout  connect timeout in seconds
 *                 (<=0: no timeout=blocking)
 * @param l        logger
 * @return         -1: some kind of error occured
 *                 0: success
 */
static int nb_connect(jk_sock_t sd, jk_sockaddr_t *addr, jk_sockaddr_t *source,
                      int timeout, jk_logger_t *l) {
    int rc = 0;
    char buf[64];

    JK_TRACE_ENTER(l);

    if (source != NULL) {
        if (bind(sd, (const struct sockaddr *)&source->sa.sin, source->salen)) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                   "error during source bind on socket %d [%s] (errno=%d)", sd,
                   jk_dump_hinfo(source, buf, sizeof(buf)), errno);
        }
    }
    if (timeout > 0) {
        if (sononblock(sd)) {
            JK_TRACE_EXIT(l);
            return -1;
        }
    }
    do {
        rc = connect(sd, (const struct sockaddr *)&addr->sa.sin, addr->salen);
    } while (rc == -1 && errno == EINTR);

    if ((rc == -1) && (errno == EINPROGRESS || errno == EALREADY)
                   && (timeout > 0)) {
        struct pollfd pfd;
        socklen_t rclen = (socklen_t)sizeof(rc);
        pfd.fd = sd;
        pfd.events = POLLOUT;
        rc = poll(&pfd, 1, timeout);
        if (rc <= 0) {
            /* Save errno */
            int err = errno;
            soblock(sd);
            errno = err;
            JK_TRACE_EXIT(l);
            return -1;
        }
        rc = 0;
#ifdef SO_ERROR
        if (getsockopt(sd, SOL_SOCKET, SO_ERROR,
                       (char *)&rc, &rclen) < 0 || rc) {
            if (rc)
                errno = rc;
            rc = -1;
        }
#endif
    }
    soblock(sd);
    JK_TRACE_EXIT(l);
    return rc;
}

#else
/* POSIX implementation using select(2) */
/** Non-blocking socket connect
 * @param sd       socket to connect
 * @param addr     address to connect to
 * @param source   optional source address
 * @param timeout  connect timeout in seconds
 *                 (<=0: no timeout=blocking)
 * @param l        logger
 * @return         -1: some kind of error occured
 *                 0: success
 */
static int nb_connect(jk_sock_t sd, jk_sockaddr_t *addr, jk_sockaddr_t *source,
                      int timeout, jk_logger_t *l)
{
    int rc = 0;
    char buf[64];

    JK_TRACE_ENTER(l);

    if (source != NULL) {
        if (bind(sd, (const struct sockaddr *)&source->sa.sin, source->salen)) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                   "error during source bind on socket %d [%s] (errno=%d)",
                   sd, jk_dump_hinfo(source, buf, sizeof(buf)), errno);
        }
    }
    if (timeout > 0) {
        if (sononblock(sd)) {
            JK_TRACE_EXIT(l);
            return -1;
        }
    }
    do {
        rc = connect(sd, (const struct sockaddr *)&addr->sa.sin, addr->salen);
    } while (rc == -1 && errno == EINTR);

    if ((rc == -1) && (errno == EINPROGRESS || errno == EALREADY)
                   && (timeout > 0)) {
        fd_set wfdset;
        struct timeval tv;
        socklen_t rclen = (socklen_t)sizeof(rc);

        FD_ZERO(&wfdset);
        FD_SET(sd, &wfdset);
        tv.tv_sec = timeout / 1000;
        tv.tv_usec = (timeout % 1000) * 1000;
        rc = select(sd + 1, NULL, &wfdset, NULL, &tv);
        if (rc <= 0) {
            /* Save errno */
            int err = errno;
            soblock(sd);
            errno = err;
            JK_TRACE_EXIT(l);
            return -1;
        }
        rc = 0;
#ifdef SO_ERROR
        if (!FD_ISSET(sd, &wfdset) ||
            (getsockopt(sd, SOL_SOCKET, SO_ERROR,
                        (char *)&rc, &rclen) < 0) || rc) {
            if (rc)
                errno = rc;
            rc = -1;
        }
#endif /* SO_ERROR */
    }
    /* Not sure we can be already connected */
    if (rc == -1 && errno == EISCONN)
        rc = 0;
    soblock(sd);
    JK_TRACE_EXIT(l);
    return rc;
}
#endif

/** Clone a jk_sockaddr_t
 * @param out     The source structure
 * @param in      The target structure
 */
void jk_clone_sockaddr(jk_sockaddr_t *out, jk_sockaddr_t *in)
{
    memcpy(out, in, sizeof(*in));
    /* The ipaddr_ptr member points to memory inside the struct.
     * Do not copy the pointer but use the same offset relative
     * to the struct start
     */
    out->ipaddr_ptr = (char *)out + ((char *)in->ipaddr_ptr - (char *)in);
}

/** Resolve the host IP
 * @param host     host or ip address
 * @param port     port
 * @param rc       return value pointer
 * @param l        logger
 * @return         JK_FALSE: some kind of error occured
 *                 JK_TRUE: success
 */
int jk_resolve(const char *host, int port, jk_sockaddr_t *saddr,
               void *pool, int prefer_ipv6, jk_logger_t *l)
{
    int family = JK_INET;
    struct in_addr iaddr;

    JK_TRACE_ENTER(l);

    memset(saddr, 0, sizeof(jk_sockaddr_t));
    if (*host >= '0' && *host <= '9' && strspn(host, "0123456789.") == strlen(host)) {

        /* If we found only digits we use inet_addr() */
        iaddr.s_addr = jk_inet_addr(host);
        memcpy(&(saddr->sa.sin.sin_addr), &iaddr, sizeof(struct in_addr));
    }
    else {
#ifdef HAVE_APR
        apr_sockaddr_t *remote_sa, *temp_sa;

        if (!jk_apr_pool) {
            if (apr_pool_create(&jk_apr_pool, (apr_pool_t *)pool) != APR_SUCCESS) {
                JK_TRACE_EXIT(l);
                return JK_FALSE;
            }
        }
        apr_pool_clear(jk_apr_pool);
        if (apr_sockaddr_info_get(&remote_sa, host, APR_UNSPEC, (apr_port_t)port,
                                  0, jk_apr_pool) != APR_SUCCESS) {
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        /* Check if we have multiple address matches
         */
        if (remote_sa->next) {
            /* Since we are only handling JK_INET (IPV4) address (in_addr_t) */
            /* make sure we find one of those.                               */
            temp_sa = remote_sa;
#if APR_HAVE_IPV6
            if (prefer_ipv6) {
                while ((NULL != temp_sa) && (APR_INET6 != temp_sa->family))
                    temp_sa = temp_sa->next;
            }
#endif
            if (NULL != temp_sa) {
                remote_sa = temp_sa;
            }
            else {
                while ((NULL != temp_sa) && (APR_INET != temp_sa->family))
                    temp_sa = temp_sa->next;
#if APR_HAVE_IPV6
                if (NULL == temp_sa) {
                    temp_sa = remote_sa;
                    while ((NULL != temp_sa) && (APR_INET6 != temp_sa->family))
                        temp_sa = temp_sa->next;
                }
#endif
            }
            /* if temp_sa is set, we have a valid address otherwise, just return */
            if (NULL != temp_sa) {
                remote_sa = temp_sa;
            }
            else {
                JK_TRACE_EXIT(l);
                return JK_FALSE;
            }
        }
        if (remote_sa->family == APR_INET) {
            saddr->sa.sin = remote_sa->sa.sin;
            family = JK_INET;
        }
#if APR_HAVE_IPV6
        else {
            saddr->sa.sin6 = remote_sa->sa.sin6;
            family = JK_INET6;
        }
#endif
#else /* HAVE_APR */
        /* Without APR go the classic way.
         */
#if defined(HAVE_GETADDRINFO)
        /* TODO:
         * 1. Check for numeric IPV6 addresses
         * 2. Do we need to set service name for getaddrinfo?
         */
        struct addrinfo hints, *ai_list, *ai = NULL;
        int error;
        char  pbuf[12];
        char *pbufptr = NULL;

        memset(&hints, 0, sizeof(hints));
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        
#if JK_HAVE_IPV6
        if (strchr(host, ':')) {
            /* If host name contains colon this must be IPV6 address.
             * Set prefer_ipv6 flag in this case if it wasn't set already
             */
            prefer_ipv6 = 1;            
        }
        if (prefer_ipv6)
            hints.ai_family = JK_INET6;
        else
#endif
            hints.ai_family = JK_INET;
        if (port > 0) {
            snprintf(pbuf, sizeof(pbuf), "%d", port);
            pbufptr = pbuf;
        }
        error = getaddrinfo(host, pbufptr, &hints, &ai_list);
#if JK_HAVE_IPV6
        /* XXX:
         * Is the check for EAI_FAMILY/WSAEAFNOSUPPORT correct
         * way to retry the IPv4 address?
         */
        if (error == EAI_FAMILY && prefer_ipv6) {
            hints.ai_family = JK_INET;
            error = getaddrinfo(host, pbufptr, &hints, &ai_list);
        }
#endif
        if (error) {
            JK_TRACE_EXIT(l);
            errno = error;
            return JK_FALSE;
        }
#if JK_HAVE_IPV6
        if (prefer_ipv6) {
            ai = ai_list;
            while (ai) {
                if (ai->ai_family == JK_INET6) {
                    /* ignore elements without required address info */
                    if((ai->ai_addr != NULL) && (ai->ai_addrlen > 0)) {                        
                        family = JK_INET6;
                        break;
                    }
                }
                ai = ai->ai_next;
            }
        }
#endif
        if (ai == NULL) {
            ai = ai_list;
            while (ai) {
                if (ai->ai_family == JK_INET) {
                    /* ignore elements without required address info */
                    if((ai->ai_addr != NULL) && (ai->ai_addrlen > 0)) {                        
                        family = JK_INET;
                        break;
                    }
                }
                ai = ai->ai_next;
            }
        }
        if (ai == NULL) {
            /* No address found
             * XXX: Use better error code?
             */
            freeaddrinfo(ai_list);
            JK_TRACE_EXIT(l);
            errno = ENOENT;
            return JK_FALSE;
        }
        memcpy(&(saddr->sa), ai->ai_addr, ai->ai_addrlen);
        freeaddrinfo(ai_list);
#else /* HAVE_GETADDRINFO */
        struct hostent *hoste;

        /* XXX : WARNING : We should really use gethostbyname_r in multi-threaded env */
        /* Fortunatly when APR is available, ie under Apache 2.0, we use it */
        hoste = gethostbyname(host);
        if (!hoste) {
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }
        iaddr = *((struct in_addr *)hoste->h_addr_list[0]);
        memcpy(&(saddr->sa.sin.sin_addr), &iaddr, sizeof(struct in_addr));
#endif /* HAVE_GETADDRINFO */
#endif /* HAVE_APR */
    }

    if (family == JK_INET) {
        saddr->ipaddr_ptr = &(saddr->sa.sin.sin_addr);
        saddr->ipaddr_len = (int)sizeof(struct in_addr);
        saddr->salen      = (int)sizeof(struct sockaddr_in);
    }
#if JK_HAVE_IPV6
    else {
        saddr->ipaddr_ptr = &(saddr->sa.sin6.sin6_addr);
        saddr->ipaddr_len = (int)sizeof(struct in6_addr);
        saddr->salen      = (int)sizeof(struct sockaddr_in6);
    }
#endif
    saddr->sa.sin.sin_family = family;
    /* XXX IPv6: assumes sin_port and sin6_port at same offset */
    saddr->sa.sin.sin_port = htons(port);
    saddr->port   = port;
    saddr->family = family;

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

/** Connect to Tomcat
 * @param addr      address to connect to
 * @param keepalive should we set SO_KEEPALIVE (if !=0)
 * @param timeout   connect timeout in seconds
 *                  (<=0: no timeout=blocking)
 * @param sock_buf  size of send and recv buffer
 *                  (<=0: use default)
 * @param l         logger
 * @return          JK_INVALID_SOCKET: some kind of error occured
 *                  created socket: success
 * @remark          Cares about errno
 */
jk_sock_t jk_open_socket(jk_sockaddr_t *addr, jk_sockaddr_t *source,
                         int keepalive,
                         int timeout, int connect_timeout,
                         int sock_buf, jk_logger_t *l)
{
    char buf[DUMP_SINFO_BUF_SZ];
    jk_sock_t sd;
    int set = 1;
    int ret = 0;
    int flags = 0;
#ifdef SO_LINGER
    struct linger li;
#endif

    JK_TRACE_ENTER(l);

    errno = 0;
#if defined(SOCK_CLOEXEC) && defined(USE_SOCK_CLOEXEC)
    flags |= SOCK_CLOEXEC;
#endif
    sd = socket(addr->family, SOCK_STREAM | flags, 0);
    if (!IS_VALID_SOCKET(sd)) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
               "socket() failed (errno=%d)", errno);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
#if defined(FD_CLOEXEC) && !defined(USE_SOCK_CLOEXEC)
    if ((flags = fcntl(sd, F_GETFD)) == -1) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
               "fcntl() failed (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
    flags |= FD_CLOEXEC;
    if (fcntl(sd, F_SETFD, flags) == -1) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
               "fcntl() failed (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
#endif

    /* Disable Nagle algorithm */
    if (setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, (const char*)&set,
                   sizeof(set))) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
                "failed setting TCP_NODELAY (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
    if (JK_IS_DEBUG_LEVEL(l))
        jk_log(l, JK_LOG_DEBUG,
               "socket TCP_NODELAY set to On");
    if (keepalive) {
#if defined(WIN32)
        DWORD dw;
        struct tcp_keepalive ka = { 0 }, ks = { 0 };
        if (timeout)
            ka.keepalivetime = timeout * 10000;
        else
            ka.keepalivetime = 60 * 10000; /* 10 minutes */
        ka.keepaliveinterval = 1000;
        ka.onoff = 1;
        if (WSAIoctl(sd, SIO_KEEPALIVE_VALS, &ka, sizeof(ka),
                     &ks, sizeof(ks), &dw, NULL, NULL)) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                   "failed setting SIO_KEEPALIVE_VALS (errno=%d)", errno);
            jk_close_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_INVALID_SOCKET;
        }
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "socket SO_KEEPALIVE set to %d seconds",
                   ka.keepalivetime / 1000);
#else
        set = 1;
        if (setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&set,
                       sizeof(set))) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                   "failed setting SO_KEEPALIVE (errno=%d)", errno);
            jk_close_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_INVALID_SOCKET;
        }
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "socket SO_KEEPALIVE set to On");
#endif
    }

    if (sock_buf > 0) {
        set = sock_buf;
        /* Set socket send buffer size */
        if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (const char*)&set,
                        sizeof(set))) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                    "failed setting SO_SNDBUF (errno=%d)", errno);
            jk_close_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_INVALID_SOCKET;
        }
        set = sock_buf;
        /* Set socket receive buffer size */
        if (setsockopt(sd, SOL_SOCKET, SO_RCVBUF, (const char*)&set,
                                sizeof(set))) {
            JK_GET_SOCKET_ERRNO();
            jk_log(l, JK_LOG_ERROR,
                    "failed setting SO_RCVBUF (errno=%d)", errno);
            jk_close_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_INVALID_SOCKET;
        }
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "socket SO_SNDBUF and SO_RCVBUF set to %d",
                   sock_buf);
    }

    if (timeout > 0) {
#if defined(WIN32)
        int tmout = timeout * 1000;
        setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
                   (const char *) &tmout, sizeof(int));
        setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
                   (const char *) &tmout, sizeof(int));
        JK_GET_SOCKET_ERRNO();
#elif defined(SO_RCVTIMEO) && defined(USE_SO_RCVTIMEO) && defined(SO_SNDTIMEO) && defined(USE_SO_SNDTIMEO)
        struct timeval tv;
        tv.tv_sec  = timeout;
        tv.tv_usec = 0;
        setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
                   (const void *) &tv, sizeof(tv));
        setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
                   (const void *) &tv, sizeof(tv));
#endif
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "timeout %d set for socket=%d",
                   timeout, sd);
    }
#ifdef SO_NOSIGPIPE
    /* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
     * sending data to a dead peer. Possibly also existing and in use on other BSD
     * systems?
    */
    set = 1;
    if (setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&set,
                   sizeof(int))) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
                "failed setting SO_NOSIGPIPE (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
#endif
#ifdef SO_LINGER
    /* Make hard closesocket by disabling lingering */
    li.l_linger = li.l_onoff = 0;
    if (setsockopt(sd, SOL_SOCKET, SO_LINGER, (const char*)&li,
                   sizeof(li))) {
        JK_GET_SOCKET_ERRNO();
        jk_log(l, JK_LOG_ERROR,
                "failed setting SO_LINGER (errno=%d)", errno);
        jk_close_socket(sd, l);
        JK_TRACE_EXIT(l);
        return JK_INVALID_SOCKET;
    }
#endif
    /* Tries to connect to Tomcat (continues trying while error is EINTR) */
    if (JK_IS_DEBUG_LEVEL(l))
        jk_log(l, JK_LOG_DEBUG,
                "trying to connect socket %d to %s", sd,
                jk_dump_hinfo(addr, buf, sizeof(buf)));

/* Need more infos for BSD 4.4 and Unix 98 defines, for now only
iSeries when Unix98 is required at compile time */
#if (_XOPEN_SOURCE >= 520) && defined(AS400)
    ((struct sockaddr *)addr)->sa.sin.sa_len = sizeof(struct sockaddr_in);
#endif
    ret = nb_connect(sd, addr, source, connect_timeout, l);
#if defined(WIN32)
    if (JK_IS_SOCKET_ERROR(ret)) {
        JK_GET_SOCKET_ERRNO();
    }
#endif /* WIN32 */

    /* Check if we are connected */
    if (ret) {
        jk_log(l, JK_LOG_INFO,
               "connect to %s failed (errno=%d)",
               jk_dump_hinfo(addr, buf, sizeof(buf)), errno);
        jk_close_socket(sd, l);
        sd = JK_INVALID_SOCKET;
    }
    else {
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG, "socket %d [%s] connected",
                   sd, jk_dump_sinfo(sd, buf, sizeof(buf)));
    }
    JK_TRACE_EXIT(l);
    return sd;
}

/** Close the socket
 * @param sd        socket to close
 * @param l         logger
 * @return          -1: some kind of error occured (!WIN32)
 *                  SOCKET_ERROR: some kind of error occured  (WIN32)
 *                  0: success
 * @remark          Does not change errno
 */
int jk_close_socket(jk_sock_t sd, jk_logger_t *l)
{
    int rc;
    int save_errno;

    JK_TRACE_ENTER(l);

    if (!IS_VALID_SOCKET(sd)) {
        JK_TRACE_EXIT(l);
        return -1;
    }

    save_errno = errno;
#if defined(WIN32)
    rc = closesocket(sd) ? -1 : 0;
#else
    do {
        rc = close(sd);
    } while (JK_IS_SOCKET_ERROR(rc) && (errno == EINTR || errno == EAGAIN));
#endif
    JK_TRACE_EXIT(l);
    errno = save_errno;
    return rc;
}

#ifndef MAX_SECS_TO_LINGER
#define MAX_SECS_TO_LINGER 2
#endif

#ifndef MS_TO_LINGER
#define MS_TO_LINGER  100
#endif

#ifndef MS_TO_LINGER_LAST
#define MS_TO_LINGER_LAST 20
#endif

#ifndef MAX_READ_RETRY
#define MAX_READ_RETRY 10
#endif

#ifndef MAX_LINGER_BYTES
#define MAX_LINGER_BYTES 32768
#endif

#ifndef SHUT_WR
#ifdef SD_SEND
#define SHUT_WR SD_SEND
#else
#define SHUT_WR 0x01
#endif
#endif

#ifndef SHUT_RD
#ifdef SD_RECEIVE
#define SHUT_RD SD_RECEIVE
#else
#define SHUT_RD 0x00
#endif
#endif

/** Drain and close the socket
 * @param sd        socket to close
 * @param l         logger
 * @return          -1: socket to close is invalid
 *                  -1: some kind of error occured (!WIN32)
 *                  SOCKET_ERROR: some kind of error occured  (WIN32)
 *                  0: success
 * @remark          Does not change errno
 */
int jk_shutdown_socket(jk_sock_t sd, jk_logger_t *l)
{
    char dummy[512];
    char buf[DUMP_SINFO_BUF_SZ];
    char *sb = NULL;
    int rc = 0;
    size_t rd = 0;
    size_t rp = 0;
    int save_errno;
    int timeout = MS_TO_LINGER;
    time_t start = time(NULL);

    JK_TRACE_ENTER(l);

    if (!IS_VALID_SOCKET(sd)) {
        JK_TRACE_EXIT(l);
        return -1;
    }

    save_errno = errno;
    if (JK_IS_DEBUG_LEVEL(l)) {
        sb = jk_dump_sinfo(sd, buf, sizeof(buf));
        jk_log(l, JK_LOG_DEBUG, "About to shutdown socket %d [%s]",
               sd, sb);
    }
    /* Shut down the socket for write, which will send a FIN
     * to the peer.
     */
    if (shutdown(sd, SHUT_WR)) {
        rc = jk_close_socket(sd, l);
        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "Failed sending SHUT_WR for socket %d [%s]",
                   sd, sb);
        errno = save_errno;
        JK_TRACE_EXIT(l);
        return rc;
    }

    do {
        rp = 0;
        if (jk_is_input_event(sd, timeout, l)) {
            /* Do a restartable read on the socket
             * draining out all the data currently in the socket buffer.
             */
            int num = 0;
            do {
                num++;
#if defined(WIN32)
                rc = recv(sd, &dummy[0], sizeof(dummy), 0);
                if (JK_IS_SOCKET_ERROR(rc))
                    JK_GET_SOCKET_ERRNO();
#else
                rc = read(sd, &dummy[0], sizeof(dummy));
#endif
                if (rc > 0)
                    rp += rc;
            } while (JK_IS_SOCKET_ERROR(rc) && (errno == EINTR || errno == EAGAIN) && num < MAX_READ_RETRY);

            if (rc < 0) {
                /* Read failed.
                 * Bail out from the loop.
                 */
                break;
            }
        }
        else {
            /* Error or timeout (reason is logged within jk_is_input_event)
             * Exit the drain loop
             */
            break;
        }
        rd += rp;
        if (rp < sizeof(dummy)) {
            if (timeout > MS_TO_LINGER_LAST) {
                /* Try one last time with a short timeout
                */
                timeout = MS_TO_LINGER_LAST;
                continue;
            }
            /* We have read less then size of buffer
             * It's a good chance there will be no more data
             * to read.
             */
            if ((rc = sononblock(sd))) {
                rc = jk_close_socket(sd, l);
                if (JK_IS_DEBUG_LEVEL(l))
                    jk_log(l, JK_LOG_DEBUG,
                           "error setting socket %d [%s] to nonblocking",
                           sd, sb);
                errno = save_errno;
                JK_TRACE_EXIT(l);
                return rc;
            }
            if (JK_IS_DEBUG_LEVEL(l))
                jk_log(l, JK_LOG_DEBUG,
                       "shutting down the read side of socket %d [%s]",
                       sd, sb);
            shutdown(sd, SHUT_RD);
            break;
        }
        timeout = MS_TO_LINGER;
    } while ((rd < MAX_LINGER_BYTES) && (difftime(time(NULL), start) < MAX_SECS_TO_LINGER));

    rc = jk_close_socket(sd, l);
    if (JK_IS_DEBUG_LEVEL(l))
        jk_log(l, JK_LOG_DEBUG,
               "Shutdown socket %d [%s] and read %d lingering bytes in %d sec.",
               sd, sb, rd, (int)difftime(time(NULL), start));
    errno = save_errno;
    JK_TRACE_EXIT(l);
    return rc;
}

/** send a message
 * @param sd  socket to use
 * @param b   buffer containing the data
 * @param len length to send
 * @param l   logger
 * @return    negative errno: write returns a fatal -1 (!WIN32)
 *            negative pseudo errno: send returns SOCKET_ERROR (WIN32)
 *            JK_SOCKET_EOF: no bytes could be sent
 *            >0: success, provided number of bytes send
 * @remark    Always closes socket in case of error
 * @remark    Cares about errno
 * @bug       this fails on Unixes if len is too big for the underlying
 *            protocol
 */
int jk_tcp_socket_sendfull(jk_sock_t sd, const unsigned char *b, int len, jk_logger_t *l)
{
    int sent = 0;
    int wr;

    JK_TRACE_ENTER(l);

    errno = 0;
    while (sent < len) {
        do {
#if defined(WIN32)
            wr = send(sd, (const char*)(b + sent),
                      len - sent, 0);
            if (JK_IS_SOCKET_ERROR(wr))
                JK_GET_SOCKET_ERRNO();
#else
            wr = write(sd, b + sent, len - sent);
#endif
        } while (JK_IS_SOCKET_ERROR(wr) && (errno == EINTR || errno == EAGAIN));

        if (JK_IS_SOCKET_ERROR(wr)) {
            int err;
            jk_shutdown_socket(sd, l);
            err = (errno > 0) ? -errno : errno;
            JK_TRACE_EXIT(l);
            return err;
        }
        else if (wr == 0) {
            jk_shutdown_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_SOCKET_EOF;
        }
        sent += wr;
    }

    JK_TRACE_EXIT(l);
    return sent;
}

/** receive a message
 * @param sd  socket to use
 * @param b   buffer to store the data
 * @param len length to receive
 * @param l   logger
 * @return    negative errno: read returns a fatal -1 (!WIN32)
 *            negative pseudo errno: recv returns SOCKET_ERROR (WIN32)
 *            JK_SOCKET_EOF: no bytes could be read
 *            >0: success, requested number of bytes received
 * @remark    Always closes socket in case of error
 * @remark    Cares about errno
 */
int jk_tcp_socket_recvfull(jk_sock_t sd, unsigned char *b, int len, jk_logger_t *l)
{
    int rdlen = 0;
    int rd;

    JK_TRACE_ENTER(l);

    errno = 0;
    while (rdlen < len) {
        do {
#if defined(WIN32)
            rd = recv(sd, (char *)b + rdlen,
                      len - rdlen, 0);
            if (JK_IS_SOCKET_ERROR(rd))
                JK_GET_SOCKET_ERRNO();
#else
            rd = read(sd, (char *)b + rdlen, len - rdlen);
#endif
        } while (JK_IS_SOCKET_ERROR(rd) && errno == EINTR);

        if (JK_IS_SOCKET_ERROR(rd)) {
            int err = (errno > 0) ? -errno : errno;
            jk_shutdown_socket(sd, l);
            JK_TRACE_EXIT(l);
            return (err == 0) ? JK_SOCKET_EOF : err;
        }
        else if (rd == 0) {
            jk_shutdown_socket(sd, l);
            JK_TRACE_EXIT(l);
            return JK_SOCKET_EOF;
        }
        rdlen += rd;
    }

    JK_TRACE_EXIT(l);
    return rdlen;
}

/* const char *
 * inet_ntop4(src, dst, size)
 *  format an IPv4 address, more or less like inet_ntoa()
 * return:
 *  `dst' (as a const)
 * notes:
 *  (1) uses no statics
 *  (2) takes a u_char* not an in_addr as input
 * author:
 *  Paul Vixie, 1996.
 */
static const char *inet_ntop4(const unsigned char *src, char *dst, size_t size)
{
    const size_t MIN_SIZE = 16; /* space for 255.255.255.255\0 */
    int n = 0;
    char *next = dst;

    if (size < MIN_SIZE) {
        errno = ENOSPC;
        return NULL;
    }
    do {
        unsigned char u = *src++;
        if (u > 99) {
            *next++ = '0' + u/100;
             u %= 100;
            *next++ = '0' + u/10;
             u %= 10;
        }
        else if (u > 9) {
            *next++ = '0' + u/10;
             u %= 10;
        }
        *next++ = '0' + u;
        *next++ = '.';
        n++;
    } while (n < 4);
    *--next = '\0';
    return dst;
}

#if JK_HAVE_IPV6
/* const char *
 * inet_ntop6(src, dst, size)
 *  convert IPv6 binary address into presentation (printable) format
 * author:
 *  Paul Vixie, 1996.
 */
static const char *inet_ntop6(const unsigned char *src, char *dst, size_t size)
{
    /*
     * Note that int32_t and int16_t need only be "at least" large enough
     * to contain a value of the specified size.  On some systems, like
     * Crays, there is no such thing as an integer variable with 16 bits.
     * Keep this in mind if you think this function should have been coded
     * to use pointer overlays.  All the world's not a VAX.
     */
    char tmp[INET6_ADDRSTRLEN], *tp;
    struct { int base, len; } best = {-1, 0}, cur = {-1, 0};
    unsigned int words[IN6ADDRSZ / INT16SZ];
    int i;
    const unsigned char *next_src, *src_end;
    unsigned int *next_dest;

    /*
     * Preprocess:
     *  Copy the input (bytewise) array into a wordwise array.
     *  Find the longest run of 0x00's in src[] for :: shorthanding.
     */
    next_src = src;
    src_end = src + IN6ADDRSZ;
    next_dest = words;
    i = 0;
    do {
        unsigned int next_word = (unsigned int)*next_src++;
        next_word <<= 8;
        next_word |= (unsigned int)*next_src++;
        *next_dest++ = next_word;

        if (next_word == 0) {
            if (cur.base == -1) {
                cur.base = i;
                cur.len = 1;
            }
            else {
                cur.len++;
            }
        } else {
            if (cur.base != -1) {
                if (best.base == -1 || cur.len > best.len) {
                    best = cur;
                }
                cur.base = -1;
            }
        }

        i++;
    } while (next_src < src_end);

    if (cur.base != -1) {
        if (best.base == -1 || cur.len > best.len) {
            best = cur;
        }
    }
    if (best.base != -1 && best.len < 2) {
        best.base = -1;
    }

    /*
     * Format the result.
     */
    tp = tmp;
    for (i = 0; i < (IN6ADDRSZ / INT16SZ);) {
        /* Are we inside the best run of 0x00's? */
        if (i == best.base) {
            *tp++ = ':';
            i += best.len;
            continue;
        }
        /* Are we following an initial run of 0x00s or any real hex? */
        if (i != 0) {
            *tp++ = ':';
        }
        /* Is this address an encapsulated IPv4? */
        if (i == 6 && best.base == 0 &&
            (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) {
            if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp))) {
                return (NULL);
            }
            tp += strlen(tp);
            break;
        }
        tp += sprintf(tp, "%x", words[i]);
        i++;
    }
    /* Was it a trailing run of 0x00's? */
    if (best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ)) {
        *tp++ = ':';
    }
    *tp++ = '\0';

    /*
     * Check for overflow, copy, and we're done.
     */
    if ((size_t)(tp - tmp) > size) {
        errno = ENOSPC;
        return NULL;
    }
    strcpy(dst, tmp);
    return dst;
}
#endif

/**
 * dump a jk_sockaddr_t in A.B.C.D:P in ASCII buffer
 *
 */
char *jk_dump_hinfo(jk_sockaddr_t *saddr, char *buf, size_t size)
{
    char pb[8];

    if (saddr->ipaddr_ptr == NULL) {
        strcpy(buf, "UnresolvedIP");
    } else {
        if (saddr->family == JK_INET) {
            inet_ntop4(saddr->ipaddr_ptr, buf, size);
        }
#if JK_HAVE_IPV6
        else {
            inet_ntop6(saddr->ipaddr_ptr, buf, size);
        }
#endif
    }
 
    sprintf(pb, ":%d", saddr->port);
    strncat(buf, pb, size - strlen(buf) - 1);

    return buf;
}

char *jk_dump_sinfo(jk_sock_t sd, char *buf, size_t size)
{
    struct sockaddr rsaddr;
    struct sockaddr lsaddr;
    socklen_t       salen;

    salen = sizeof(struct sockaddr);
    if (getsockname(sd, &lsaddr, &salen) == 0) {
        salen = sizeof(struct sockaddr);
        if (getpeername(sd, &rsaddr, &salen) == 0) {
            char   pb[8];
            size_t ps;
            if (lsaddr.sa_family == JK_INET) {
                struct sockaddr_in  *sa = (struct sockaddr_in  *)&lsaddr;
                inet_ntop4((unsigned char *)&sa->sin_addr, buf, size);
                sprintf(pb, ":%d", (unsigned int)htons(sa->sin_port));
            }
#if JK_HAVE_IPV6
            else {
                struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&lsaddr;
                inet_ntop6((unsigned char *)&sa->sin6_addr, buf, size);
                sprintf(pb, ":%d", (unsigned int)htons(sa->sin6_port));
            }
#endif
            ps = strlen(buf);
            strncat(buf, pb, size - ps - 1);
            ps = strlen(buf);
            strncat(buf, " -> ", size - ps - 1);
            ps = strlen(buf);
            if (rsaddr.sa_family == JK_INET) {
                struct sockaddr_in  *sa = (struct sockaddr_in  *)&rsaddr;
                inet_ntop4((unsigned char *)&sa->sin_addr,  buf + ps, size - ps);
                sprintf(pb, ":%d", (unsigned int)htons(sa->sin_port));
            }
#if JK_HAVE_IPV6
            else {
                struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&rsaddr;
                inet_ntop6((unsigned char *)&sa->sin6_addr, buf + ps, size - ps);
                sprintf(pb, ":%d", (unsigned int)htons(sa->sin6_port));
            }
#endif
            strncat(buf, pb, size - strlen(buf) - 1);
            return buf;
        }
    }
    snprintf(buf, size, "errno=%d", errno);
    return buf;
}

/** Wait for input event on socket until timeout
 * @param sd      socket to use
 * @param timeout wait timeout in milliseconds
 * @param l       logger
 * @return        JK_FALSE: Timeout expired without something to read
 *                JK_FALSE: Error during waiting
 *                JK_TRUE: success
 * @remark        Does not close socket in case of error
 *                to allow for iterative waiting
 * @remark        Cares about errno
 */
#ifdef HAVE_POLL
int jk_is_input_event(jk_sock_t sd, int timeout, jk_logger_t *l)
{
    struct pollfd fds;
    int rc;
    int save_errno;
    char buf[DUMP_SINFO_BUF_SZ];

    JK_TRACE_ENTER(l);

    errno = 0;
    fds.fd = sd;
    fds.events = POLLIN;
    fds.revents = 0;

    do {
        rc = poll(&fds, 1, timeout);
    } while (rc < 0 && errno == EINTR);

    if (rc == 0) {
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "timeout during poll on socket %d [%s] (timeout=%d)",
                   sd, jk_dump_sinfo(sd, buf, sizeof(buf)), timeout);
        }
        /* Timeout. Set the errno to timeout */
        errno = ETIMEDOUT;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    else if (rc < 0) {
        save_errno = errno;
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "error during poll on socket %d [%s] (errno=%d)",
                   sd, jk_dump_sinfo(sd, buf, sizeof(buf)), errno);
        }
        errno = save_errno;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    if ((fds.revents & (POLLERR | POLLHUP))) {
        save_errno = fds.revents & (POLLERR | POLLHUP);
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "error event during poll on socket %d [%s] (event=%d)",
                   sd, jk_dump_sinfo(sd, buf, sizeof(buf)), save_errno);
        }
        errno = save_errno;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    errno = 0;
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}
#else
int jk_is_input_event(jk_sock_t sd, int timeout, jk_logger_t *l)
{
    fd_set rset;
    struct timeval tv;
    int rc;
    int save_errno;
    char buf[DUMP_SINFO_BUF_SZ];

    JK_TRACE_ENTER(l);

    errno = 0;
    FD_ZERO(&rset);
    FD_SET(sd, &rset);
    tv.tv_sec = timeout / 1000;
    tv.tv_usec = (timeout % 1000) * 1000;

    do {
        rc = select((int)sd + 1, &rset, NULL, NULL, &tv);
    } while (rc < 0 && errno == EINTR);

    if (rc == 0) {
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "timeout during select on socket %d [%s] (timeout=%d)",
                   sd, jk_dump_sinfo(sd, buf, sizeof(buf)), timeout);
        }
        /* Timeout. Set the errno to timeout */
#if defined(WIN32)
        errno = WSAETIMEDOUT - WSABASEERR;
#else
        errno = ETIMEDOUT;
#endif
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    else if (rc < 0) {
        save_errno = errno;
        if (JK_IS_DEBUG_LEVEL(l)) {
            jk_log(l, JK_LOG_DEBUG,
                   "error during select on socket %d [%s] (errno=%d)",
                   sd, jk_dump_sinfo(sd, buf, sizeof(buf)), errno);
        }
        errno = save_errno;
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    errno = 0;
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}
#endif

/** Test if a socket is still connected
 * @param sd   socket to use
 * @param l    logger
 * @return     JK_FALSE: failure
 *             JK_TRUE: success
 * @remark     Always closes socket in case of error
 * @remark     Cares about errno
 */
#ifdef HAVE_POLL
int jk_is_socket_connected(jk_sock_t sd, jk_logger_t *l)
{
    struct pollfd fds;
    int rc;

    JK_TRACE_ENTER(l);

    errno = 0;
    fds.fd = sd;
    fds.events = POLLIN;

    do {
        rc = poll(&fds, 1, 0);
    } while (rc < 0 && errno == EINTR);

    if (rc == 0) {
        /* If we get a timeout, then we are still connected */
        JK_TRACE_EXIT(l);
        return JK_TRUE;
    }
    else if (rc == 1 && fds.revents == POLLIN) {
        char buf;
        do {
            rc = (int)recvfrom(sd, &buf, 1, MSG_PEEK, NULL, NULL);
        } while (rc < 0 && errno == EINTR);
        if (rc == 1) {
            /* There is at least one byte to read. */
            JK_TRACE_EXIT(l);
            return JK_TRUE;
        }
    }
    jk_shutdown_socket(sd, l);

    JK_TRACE_EXIT(l);
    return JK_FALSE;
}

#else
int jk_is_socket_connected(jk_sock_t sd, jk_logger_t *l)
{
    fd_set fd;
    struct timeval tv;
    int rc;

    JK_TRACE_ENTER(l);

    errno = 0;
    FD_ZERO(&fd);
    FD_SET(sd, &fd);

    /* Initially test the socket without any blocking.
     */
    tv.tv_sec  = 0;
    tv.tv_usec = 0;

    do {
        rc = select((int)sd + 1, &fd, NULL, NULL, &tv);
        JK_GET_SOCKET_ERRNO();
        /* Wait one microsecond on next select, if EINTR */
        tv.tv_sec  = 0;
        tv.tv_usec = 1;
    } while (JK_IS_SOCKET_ERROR(rc) && errno == EINTR);

    errno = 0;
    if (rc == 0) {
        /* If we get a timeout, then we are still connected */
        JK_TRACE_EXIT(l);
        return JK_TRUE;
    }
    else if (rc == 1) {
#if defined(WIN32)
        u_long nr;
        rc = ioctlsocket(sd, FIONREAD, &nr);
        if (rc == 0) {
            if (WSAGetLastError() == 0)
                errno = 0;
            else
                JK_GET_SOCKET_ERRNO();
        }
#else
        int nr;
        rc = ioctl(sd, FIONREAD, (void*)&nr);
#endif
        if (rc == 0 && nr != 0) {
            JK_TRACE_EXIT(l);
            return JK_TRUE;
        }
        JK_GET_SOCKET_ERRNO();
    }
    jk_shutdown_socket(sd, l);

    JK_TRACE_EXIT(l);
    return JK_FALSE;
}
#endif

