/*
 * $Id: comm.c,v 1.18.6.4 2004/09/28 02:14:08 squidadm Exp $
 *
 * DEBUG: section 5     Socket Functions
 */
#include "squidDeclaration.h"

#include "squid.h"

#if defined(_SQUID_CYGWIN_)
#include <sys/ioctl.h>
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif

typedef struct {
    char *host;
    u_short port;
    struct sockaddr_in S;
    CNCB *callback;
    void *data;
    struct in_addr in_addr;
    int fd;
    int tries;
    int addrcount;
    int connstart;
} ConnectStateData;

/* STATIC */
static int commBind(int s, struct in_addr, u_short port);
static void commSetReuseAddr(int socket);
static void commSetNoLinger(int fd);
static void CommWriteStateCallbackAndFree(int fd, int code);
#ifdef TCP_NODELAY
static void commSetTcpNoDelay(int fd);
#endif
static void commSetTcpRcvbuf(int socket, int size);
static PF commConnectFree;
static PF commConnectHandle;
static PF commHandleWrite;
static IPH commConnectDnsHandle;
static void commConnectCallback(ConnectStateData * cs, int status);
static int commResetFD(ConnectStateData * cs);
static int commRetryConnect(ConnectStateData * cs);
CBDATA_TYPE(ConnectStateData);

static MemPool *comm_write_pool = NULL;
static MemPool *conn_close_pool = NULL;

static void
CommWriteStateCallbackAndFree(int fd, int code)
{
    CommWriteStateData *CommWriteState;
    CWCB *callback = NULL;
    void *data;
   squid_fd_table.check(fd, 13);
    CommWriteState = squid_fd_table.fd_table[fd].rwstate;

    squid_fd_table.fd_table[fd].rwstate = NULL;
    if (CommWriteState == NULL)
       return;
    if (CommWriteState->free_func) {
       FREE *free_func = CommWriteState->free_func;
       void *free_buf = CommWriteState->buf;
       CommWriteState->free_func = NULL;
       CommWriteState->buf = NULL;
       free_func(free_buf);
    }
    callback = CommWriteState->handler;
    data = CommWriteState->handler_data;
    CommWriteState->handler = NULL;
    if (callback && cbdataValid(data))
       callback(fd, CommWriteState->buf, CommWriteState->offset, code, data);
    cbdataUnlock(data);
    memPoolFree(comm_write_pool, CommWriteState);
}

/* Return the local port associated with fd. */
u_short
comm_local_port(int fd)
{
    struct sockaddr_in addr;
    int socket;
    socklen_t addr_len = 0;
   squid_fd_table.check(fd, 14);
    squid_fde *F = &squid_fd_table.fd_table[fd];

    /* If the fd is closed already, just return */
    if (!F->flags.open) {
       debug(5, 0) ("comm_local_port: FD %d has been closed.\n", fd);
       return 0;
    }
    if (F->local_port)
       return F->local_port;
    addr_len = sizeof(addr);
    socket = F->GetSocket();
    if(socket  < 0)
    {   debug(5, 0) ("comm_local_port: WARNING: socket=%d\n", socket );
        return 0;
    }

    if (getsockname(socket, (struct sockaddr *) &addr, &addr_len)) {
       debug(50, 1) ("comm_local_port: Failed to retrieve TCP/UDP port number for socket: FD %d(%d): %s\n",
                       fd, socket , xstrerror_type(FD_SOCKET));
       return 0;
    }
    F->local_port = ntohs(addr.sin_port);
    debug(5, 6) ("comm_local_port: FD %d: port %d\n", fd, (int) F->local_port);
    return F->local_port;
}

static int
commBind(int s, struct in_addr in_addr, u_short port)
{
    struct sockaddr_in S;

    memset(&S, '\0', sizeof(S));
    S.sin_family = AF_INET;
    S.sin_port = htons(port);
    S.sin_addr = in_addr;
    statCounter.syscalls.sock.binds++;
    if(s < 0)
    {   debug(5, 0) ("commBind: WARNING: socket=%d\n", s);
        return COMM_ERROR;
    }

    if (bind(s, (struct sockaddr *) &S, sizeof(S)) == 0)
       return COMM_OK;
    debug(50, 0) ("commBind: WARNING: Cannot bind socket FD %d to %s:%d: %s\n",
       s,
       S.sin_addr.s_addr == INADDR_ANY ? "*" : inet_ntoa(S.sin_addr),
       (int) port,
       xstrerror_type(FD_SOCKET));
    return COMM_ERROR;
}

/* Create a socket. Default is blocking, stream (TCP) socket.  IO_TYPE
 * is OR of flags specified in comm.h. Defaults TOS */
int
comm_open(int sock_type,
    int proto,
    struct in_addr addr,
    u_short port,
    int flags,
    const char *note)
{
    return comm_openex(sock_type, proto, addr, port, flags, 0, note);
}


/* Create a socket. Default is blocking, stream (TCP) socket.  IO_TYPE
 * is OR of flags specified in defines.h:COMM_*
  return fd index (for _SQUID_OS2VAC_)
*/
int
comm_openex(int sock_type,
    int proto,
    struct in_addr addr,
    u_short port,
    int flags,
    unsigned char TOS,
    const char *note)
{
    int new_socket, index_fd_new_socket=-1;
    int tos=0,rc;
    squid_fde *F = NULL;

    /* Create socket for accepting new connections. */
    statCounter.syscalls.sock.sockets++;
    if ((new_socket = socket(AF_INET, sock_type, proto)) < 0) {
       /* Increase the number of reserved fd's if calls to socket()
        * are failing because the open file table is full.  This
        * limits the number of simultaneous clients */
#ifdef _SQUID_OS2VAC_
     { int ierrno;
        ierrno = sock_errno();
       switch (ierrno) {
#ifndef TCPV40HDRS
       case SOCENFILE:
#endif
       case SOCEMFILE: /* EMFILE */
           debug(50, 1) ("comm_open: socket failure: %s\n", xstrerror_i(ierrno));
           fdAdjustReserved();
           break;
       default:
           debug(50, 0) ("comm_open: socket failure: %s\n",  xstrerror_i(ierrno));
       }
    }
#else
       switch (errno) {
       case ENFILE:
       case EMFILE:
           debug(50, 1) ("comm_open: socket failure: %s\n", xstrerror());
           fdAdjustReserved();
           break;
       default:
           debug(50, 0) ("comm_open: socket failure: %s\n", xstrerror());
       }
#endif //_SQUID_OS2VAC_
       return -1;
    }
// debug(91, 6)
//     ("comm_openex socket %d\n",new_socket);

    /* set TOS if needed */
    if (TOS) {
#ifdef IP_TOS
       tos = TOS;
       if (setsockopt(new_socket, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof(int)) < 0)
               debug(50, 1) ("comm_open: setsockopt(IP_TOS) on FD %d: %s\n",
               new_socket, xstrerror_type(FD_SOCKET));
#else
       debug(50, 0) ("comm_open: setsockopt(IP_TOS) not supported on this platform\n");
#endif
    }
    /* update fdstat */
    debug(5, 5) ("comm_open: FD %d is a new socket\n", new_socket);

    rc = squid_fd_table.open(index_fd_new_socket,new_socket, FD_SOCKET, note);
    if(rc < 0)
    {  debug(5, 0) ("WARNING:comm_open: squid_fd_table.open rc=%i, index=%i,socket=%i\n", rc,index_fd_new_socket,new_socket);
       return -1;
    }
   squid_fd_table.check(index_fd_new_socket, 15);

    F = &squid_fd_table.fd_table[index_fd_new_socket];
    F->local_addr = addr;
    F->tos = tos;
    if (!(flags & COMM_NOCLOEXEC))
       commSetCloseOnExec(new_socket);
    if ((flags & COMM_REUSEADDR))
       commSetReuseAddr(new_socket); /* socket */
    if (port > (u_short) 0) {
       commSetNoLinger(index_fd_new_socket);  /* index */
       if (opt_reuseaddr)
           commSetReuseAddr(new_socket);      /* socket */
    }
    if (addr.s_addr != no_addr.s_addr) {
       if (commBind(new_socket, addr, port) != COMM_OK) {
           comm_close(index_fd_new_socket);
           return -1;
       }
    }
    F->local_port = port;

    if (flags & COMM_NONBLOCKING)
       if (commSetNonBlocking(index_fd_new_socket) == COMM_ERROR)
           return -1;
#ifdef TCP_NODELAY
    if (sock_type == SOCK_STREAM)
          commSetTcpNoDelay(index_fd_new_socket);
#endif
    if (Config.tcpRcvBufsz > 0 && sock_type == SOCK_STREAM)
       commSetTcpRcvbuf(new_socket, Config.tcpRcvBufsz);  /* socket */
    return index_fd_new_socket;
}

/*
 * NOTE: set the listen queue to Squid_MaxFD/4 and rely on the kernel to
 * impose an upper limit.  Solaris' listen(3n) page says it has
 * no limit on this parameter, but sys/socket.h sets SOMAXCONN
 * to 5.  HP-UX currently has a limit of 20.  SunOS is 5 and
 * OSF 3.0 is 8.
 */
int
comm_listen(int fd)
{
    int x, sock;
   squid_fd_table.check(fd, 16);

    sock = squid_fd_table.fd_table[fd].GetSocket();
    if(sock < 0)
    {      debug(50, 0) ("WARNING:comm_listen: FD %d(%d)\n", fd, sock);
           return -1;
    }
    if ((x = listen(sock, Squid_MaxFD >> 2)) < 0) {
       debug(50, 0) ("comm_listen: listen(%d, %d): %s\n",
           Squid_MaxFD >> 2,
           sock, xstrerror_type(FD_SOCKET));
    }
    return x;  /* return 0 for Ok, -1 for Error  EK */
}

void
commConnectStart(int fd, const char *host, u_short port, CNCB * callback, void *data)
{
    ConnectStateData *cs;
    debug(5, 3) ("commConnectStart: FD %d, %s:%d\n", fd, host, (int) port);
    cs = cbdataAlloc(ConnectStateData);
    cs->fd = fd;
    cs->host = xstrdup(host);
    cs->port = port;
    cs->callback = callback;
    cs->data = data;
    cbdataLock(cs->data);
    comm_add_close_handler(fd, commConnectFree, cs);
    ipcache_nbgethostbyname(host, commConnectDnsHandle, cs);
}

static void
commConnectDnsHandle(const ipcache_addrs * ia, void *data)
{
    ConnectStateData *cs = (ConnectStateData *)data;
debug(5, 9) ("commConnectDnsHandle: FD:%i(%i) (type %i)\n", cs->fd,squid_fd_table.fd_table[cs->fd].GetSocket(),squid_fd_table.fd_table[cs->fd].type);
    if (ia == NULL) {
       debug(5, 3) ("commConnectDnsHandle: Unknown host: %s\n", cs->host);
       if (!dns_error_message) {
           dns_error_message = "Unknown DNS error";
           debug(5, 1) ("commConnectDnsHandle: Bad dns_error_message\n");
       }
       assert(dns_error_message != NULL);
       commConnectCallback(cs, COMM_ERR_DNS);
       return;
    }
    assert(ia->cur < ia->count);
    cs->in_addr = ia->in_addrs[ia->cur];
    if (Config.onoff.balance_on_multiple_ip)
	    ipcacheCycleAddr(cs->host, NULL);
    cs->addrcount = ia->count;
    cs->connstart = squid_curtime;
    commConnectHandle(cs->fd, cs);
}

static void
commConnectCallback(ConnectStateData * cs, int status)
{
    CNCB *callback = cs->callback;
    void *data = cs->data;
    int fd = cs->fd;
    comm_remove_close_handler(fd, commConnectFree, cs);
    cs->callback = NULL;
    cs->data = NULL;
    commSetTimeout(fd, -1, NULL, NULL);
    commConnectFree(fd, cs);
    if (cbdataValid(data))
       callback(fd, status, data);
    cbdataUnlock(data);
}

static void
commConnectFree(int fd, void *data)
{
    ConnectStateData *cs = (ConnectStateData *)data;
    debug(5, 3) ("commConnectFree: FD %d\n", fd);
    if (cs->data)
       cbdataUnlock(cs->data);
    safe_free(cs->host);
    cbdataFree(cs,ConnectStateData);
}

/* Reset FD so that we can connect() again */
static int
commResetFD(ConnectStateData * cs)
{
    int fd2sock,rc;
    squid_fde *F;
    if (!cbdataValid(cs->data))
       return 0;
    statCounter.syscalls.sock.sockets++;
    fd2sock = socket(AF_INET, SOCK_STREAM, 0);
// debug(91, 6)
//     ("commResetFD socket %d, cs->fd=%d\n",fd2sock,cs->fd);

    statCounter.syscalls.sock.sockets++;
    if (fd2sock < 0) {
       debug(5, 0) ("commResetFD: socket: %s\n", xstrerror_type(FD_SOCKET));
       if (ENFILE == errno || EMFILE == errno)
           fdAdjustReserved();
       return 0;
    }

/*  EK ??????? */
     { int fh;
       extern fd_set global_readfds;
       extern fd_set global_writefds;
   squid_fd_table.check(cs->fd, 17);

       fh = squid_fd_table.fd_table[cs->fd].GetSocket();
       if(fh < 0)
       {    debug(5, 0) ("WARNING:commResetFD: fh: %i\n", fh);
            return 0;  
       }
       if (FD_ISSET(fh, &global_readfds))
       {   FD_CLR(fh, &global_readfds);
       }
       if (FD_ISSET(fh, &global_writefds))
       {   FD_CLR(fh, &global_writefds);
       }
       rc = soclose(fh);
     }

    squid_fd_table.fd_table[cs->fd].ChangeSocket(fd2sock, cs->fd, squid_fd_table.SocketToIndex);

//    if (dup2(fd2, cs->fd) < 0) {
//       debug(5, 0) ("commResetFD: dup2: %s\n", xstrerror_type(FD_SOCKET) /* ?? */);
//       if (ENFILE == errno || EMFILE == errno)
//           fdAdjustReserved();
//       close(fd2);
//       return 0;
//    }
//    close(fd2);
     F = &squid_fd_table.fd_table[cs->fd];
     F->flags.called_connect = 0;
    /*
     * yuck, this has assumptions about comm_open() arguments for
     * the original socket
     */
    if (commBind(fd2sock, F->local_addr, F->local_port) != COMM_OK) {
       debug(5, 0) ("commResetFD: bind: %s\n", xstrerror_type(FD_SOCKET) /* ?? */);
       return 0;
    }
#ifdef IP_TOS
    if (F->tos) {
       int tos = F->tos;
       if (setsockopt(fd2sock, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof(int)) < 0)
               debug(50, 1) ("commResetFD: setsockopt(IP_TOS) on Socket %d: %s\n", fd2sock, xstrerror_type(FD_SOCKET));
    }
#endif
    if (F->flags.close_on_exec)
       commSetCloseOnExec(cs->fd);
    if (F->flags.nonblocking)
       commSetNonBlocking(cs->fd);
#ifdef TCP_NODELAY
    if (F->flags.nodelay)
       commSetTcpNoDelay(cs->fd);
#endif
    if (Config.tcpRcvBufsz > 0)
       commSetTcpRcvbuf(fd2sock, Config.tcpRcvBufsz);
    return 1;
}

static int
commRetryConnect(ConnectStateData * cs)
{
    assert(cs->addrcount > 0);
    if (cs->addrcount == 1) {
       if (cs->tries >= Config.retry.maxtries)
           return 0;
       if (squid_curtime - cs->connstart > Config.Timeout.connect)
           return 0;
    } else {
       if (cs->tries > cs->addrcount)
           return 0;
    }
    return commResetFD(cs);
}

static void
commReconnect(void *data)
{
    ConnectStateData *cs = (ConnectStateData *)data;
    ipcache_nbgethostbyname(cs->host, commConnectDnsHandle, cs);
}


/* Connect SOCK to specified DEST_PORT at DEST_HOST. */
static void
commConnectHandle(int fd, void *data)
{
    ConnectStateData *cs = (ConnectStateData *)data;
   squid_fd_table.check(fd, 18);

 debug(5, 5) ("commConnectHandle: in FD %d(%i)(type %i)\n",
                       fd,squid_fd_table.fd_table[fd].GetSocket(),squid_fd_table.fd_table[fd].type);
    if (cs->S.sin_addr.s_addr == 0) {
       cs->S.sin_family = AF_INET;
       cs->S.sin_addr = cs->in_addr;
       cs->S.sin_port = htons(cs->port);
       if (Config.onoff.log_fqdn)
           fqdncache_gethostbyaddr(cs->S.sin_addr, FQDN_LOOKUP_IF_MISS);
    }
    switch (comm_connect_addr(fd, &cs->S)) {
    case COMM_INPROGRESS:
       debug(5, 5) ("commConnectHandle: FD %d(type %i): COMM_INPROGRESS\n", fd,squid_fd_table.fd_table[fd].type);
       commSetSelect(fd, COMM_SELECT_WRITE, commConnectHandle, cs, 0);
       break;
    case COMM_OK:
debug(5, 9) ("commConnectHandle: FD %d: COMM_OK\n", fd);
       ipcacheMarkGoodAddr(cs->host, cs->S.sin_addr);
       commConnectCallback(cs, COMM_OK);
       break;
    default:
debug(5, 9) ("commConnectHandle: FD %d(%i): default\n",fd,squid_fd_table.fd_table[fd].GetSocket());
       cs->tries++;
       ipcacheMarkBadAddr(cs->host, cs->S.sin_addr);
       if (Config.onoff.test_reachability)
           netdbDeleteAddrNetwork(cs->S.sin_addr);
       if (commRetryConnect(cs)) {
           eventAdd("commReconnect", commReconnect, cs, cs->addrcount == 1 ? 0.05 : 0.0, 0);
       } else {
           commConnectCallback(cs, COMM_ERR_CONNECT);
       }
       break;
    }
}

int
commSetTimeout(int fd, int timeout, PF * handler, void *data)
{
    squid_fde *F;
    debug(5, 3) ("commSetTimeout: FD %d timeout %d\n", fd, timeout);
    assert(fd >= 0);
    assert(fd < Squid_MaxFD);
   squid_fd_table.check(fd, 19);
    F = &squid_fd_table.fd_table[fd];
if(F->type == FD_FILE)
    debug(5, 0) ("commSetTimeout:WARNING:shit2!! FD %i at %s:%d\n", fd, __FILE__,__LINE__);

    assert(F->flags.open);
    if (timeout < 0) {
       F->timeout_handler = NULL;
       F->timeout_data = NULL;
        return F->timeout = 0;
    }
    assert(handler || F->timeout_handler);
       if (handler || data) {
           F->timeout_handler = handler;
           F->timeout_data = data;
       }
    F->timeout = squid_curtime + (time_t) timeout;
    return F->timeout;
}

int
comm_connect_addr(int fd, const struct sockaddr_in *address)
{
    int status = COMM_OK;
    int sock;
    squid_fde *F = &squid_fd_table.fd_table[fd];
    int x;
    int err = 0;
#ifdef _SQUID_OS2VAC_
    int ierr;
#endif
    socklen_t errlen;
    assert(ntohs(address->sin_port) != 0);
   squid_fd_table.check(fd, 20);

    sock = F->GetSocket();
    if(sock < 0)
    {      debug(50, 0) ("WARNING:comm_connect_addr: FD %d(%d)\n", fd, sock);
           return COMM_ERROR;
    }

 debug(5, 5) ("comm_connect_addr: FD %d(%d) flags.called_connect %i\n",
              fd, sock, F->flags.called_connect);
    /* Establish connection. */
#ifdef _SQUID_OS2VAC_
    ierr = 0;
#else
    errno = 0;
#endif
    if (!F->flags.called_connect) {
       F->flags.called_connect = 1;
       statCounter.syscalls.sock.connects++;

       x = connect(sock, (struct sockaddr *) address, sizeof(struct sockaddr_in));
#ifdef _SQUID_OS2VAC_
       if (x < 0)
       {    ierr = sock_errno();
            debug(5, 9) ("connect FD %d(%i, type %i): %s\n",
                  fd,sock,squid_fd_table.fd_table[fd].type , xstrerror_i(ierr));
       }
#else
       if (x < 0)
           debug(5, 9) ("connect FD %d: %s\n", sock, xstrerror_type(FD_SOCKET));
#endif
    } else {
#if defined(_SQUID_NEWSOS6_)
       /* Makoto MATSUSHITA <matusita@ics.es.osaka-u.ac.jp> */
       connect(sock, (struct sockaddr *) address, sizeof(sockaddr_in));
       if (errno == EINVAL) {
           errlen = sizeof(err);
           x = getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &errlen);
           if (x >= 0)
               errno = x;
       }
#else
       errlen = sizeof(err);
       x = getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen);
#ifndef _SQUID_OS2VAC_
       if (x == 0)
           errno = err;
#else
       if (x == 0)
           ierr = err;
       else
           ierr = sock_errno();
#endif
#if defined(_SQUID_SOLARIS_)
       /*
        * Solaris 2.4's socket emulation doesn't allow you
        * to determine the error from a failed non-blocking
        * connect and just returns EPIPE.  Create a fake
        * error message for connect.   -- fenner@parc.xerox.com
        */
       if (x < 0 && errno == EPIPE)
           errno = ENOTCONN;
#endif
#endif
    }
#ifndef _SQUID_OS2VAC_
    if (errno == 0 || errno == EISCONN)
       status = COMM_OK;
    else if (ignoreErrnoSocket(errno))
       status = COMM_INPROGRESS;
    else
       return COMM_ERROR;
#else
    if (ierr == 0 || ierr == EISCONN)
       status = COMM_OK;
    else if (ignoreErrnoSocket(ierr))
       status = COMM_INPROGRESS;
    else
       return COMM_ERROR;
#endif

    xstrncpy(F->ipaddr, inet_ntoa(address->sin_addr), 16);
    F->remote_port = ntohs(address->sin_port);
    if (status == COMM_OK) {
       debug(5, 10) ("comm_connect_addr: FD %d(Socket %d) connected to %s:%d\n",
           fd, sock, F->ipaddr, F->remote_port);
    } else if (status == COMM_INPROGRESS) {
       debug(5, 10) ("comm_connect_addr: FD %d(Socket %d) connection pending\n", fd,sock);
    }
    return status;
}

/* Wait for an incoming connection on FD.  FD should be a socket returned
 * from comm_listen. */
int
comm_accept(int fd, struct sockaddr_in *pn, struct sockaddr_in *me)
{
    int sock,sockindex, fdsock;
    struct sockaddr_in P;
    struct sockaddr_in M;
    socklen_t Slen;
    squid_fde *F = NULL;
    Slen = sizeof(P);
    statCounter.syscalls.sock.accepts++;

    squid_fd_table.check(fd, 21);

    fdsock = squid_fd_table.fd_table[fd].GetSocket();
    if(fdsock < 0)
    {      debug(50, 0) ("WARNING:comm_accept: FD %d(%d)\n", fd, fdsock);
           return COMM_ERROR;
    }
    if ((sock = accept(fdsock , (struct sockaddr *) &P, &Slen)) < 0)
    {
       int ierr;
       ierr =sock_errno();
       if (ignoreErrnoSocket(ierr)) {
           debug(50, 5) ("comm_accept: FD %d(%d): %s\n", fd, fdsock, xstrerror_i(ierr) );
           return COMM_NOMESSAGE;
       } else if (ENFILE == ierr) {
           debug(50, 3) ("comm_accept: FD %d(%d): %s\n", fd, fdsock, xstrerror_i(ierr));
           return COMM_ERROR;
       } else {
           debug(50, 1) ("comm_accept: FD %d(%d): %s\n", fd, fdsock, xstrerror_i(ierr));
           return COMM_ERROR;
       }
    }
    if (pn)
       *pn = P;
    Slen = sizeof(M);
    memset(&M, '\0', Slen);
    getsockname(sock, (struct sockaddr *) &M, &Slen);
    if (me)
       *me = M;
    commSetCloseOnExec(sock);
    /* fdstat update */
    squid_fd_table.open(sockindex,sock, FD_SOCKET, "HTTP Request");

//    fd_open(sock, FD_SOCKET, "HTTP Request");
    F = &squid_fd_table.fd_table[sockindex];
    xstrncpy(F->ipaddr, inet_ntoa(P.sin_addr), 16);
    F->remote_port = htons(P.sin_port);
    F->local_port = htons(M.sin_port);
    commSetNonBlocking(sockindex);
    return sockindex;
}

void
commCallCloseHandlers(int fd)
{
    squid_fde *F = &squid_fd_table.fd_table[fd];
    close_handler *ch;
   squid_fd_table.check(fd, 22);
    debug(5, 5) ("commCallCloseHandlers: FD %d\n", fd);
    while ((ch = F->close_handler) != NULL) {
       F->close_handler = ch->next;
       debug(5, 5) ("commCallCloseHandlers: ch->handler=%p\n", ch->handler);
       if (cbdataValid(ch->data))
           ch->handler(fd, ch->data);
       cbdataUnlock(ch->data);
       memPoolFree(conn_close_pool, ch);       /* AAA */
    }
}

#if LINGERING_CLOSE
static void
commLingerClose(int fd, void *unused)
{
    LOCAL_ARRAY(char, buf, 1024);
    int n;
    n = fd_table[fd].read_method(fd, buf, 1024);
    if (n < 0)
       debug(5, 3) ("commLingerClose: FD %d read: %s\n", fd, xstrerror());
    comm_close(fd);
}

static void
commLingerTimeout(int fd, void *unused)
{
    debug(5, 3) ("commLingerTimeout: FD %d\n", fd);
    comm_close(fd);
}

/*
 * Inspired by apache
 */
void
comm_lingering_close(int fd)
{
#if USE_SSL
    if (fd_table[fd].ssl)
       ssl_shutdown_method(fd);
#endif
    if (shutdown(fd, 1) < 0) {
       comm_close(fd);
       return;
    }
    fd_note(fd, "lingering close");
    commSetTimeout(fd, 10, commLingerTimeout, NULL);
    commSetSelect(fd, COMM_SELECT_READ, commLingerClose, NULL, 0);
}
#endif

/*
 * enable linger with time of 0 so that when the socket is
 * closed, TCP generates a RESET
 */
void
comm_reset_close(int fd)
{
    struct linger L;
    int  socket;
    L.l_onoff = 1;
    L.l_linger = 0;
   squid_fd_table.check(fd, 23);

    socket = squid_fd_table.fd_table[fd].GetSocket();
    if(socket >= 0)
    {  if (setsockopt(socket, SOL_SOCKET, SO_LINGER, (char *) &L, sizeof(L)) < 0)
            debug(50, 0) ("commResetTCPClose: FD %d(%d): %s\n", fd, socket, xstrerror_type(FD_SOCKET));
    } else {
       debug(5, 0) ("comm_reset_close: WARNING: FD %d socket=%d\n", fd,socket);
    }
    comm_close(fd);
}

void
comm_close(int fd)
{
    squid_fde *F = NULL;

//EK DEBUG
 if(fd < 0)
 {   debug(5, 0) ("comm_close: WARNING: FD %d\n", fd);
     return;
 }
/* проверим - действительно ли fd есть правильный индекс */
/* ***************************************************** */
   squid_fd_table.check(fd, 24);

   if(squid_fd_table.fd_table[fd].type ==  FD_NONE)
                   fatalf("comm_close: Attempt to close with FD_NONE type handle");

/* ***************************************************** */
    debug(5, 5) ("comm_close: FD %d\n", fd);
    assert(fd >= 0);
    assert(fd < Squid_MaxFD);
    F = &squid_fd_table.fd_table[fd];

    if (F->flags.closing)
       return;
    if (shutting_down && (!F->flags.open || F->type == FD_FILE))
       return;

    assert(F->flags.open);
//EK?? if(!F->flags.open) return; //EK
if(F->type == FD_FILE)
    printf("shit!! FD=%i at %s:%d\n",fd,  __FILE__,__LINE__);

    assert(F->type != FD_FILE);
    F->flags.closing = 1;
#if USE_SSL
    if (F->ssl)
       ssl_shutdown_method(fd);
#endif
    CommWriteStateCallbackAndFree(fd, COMM_ERR_CLOSING);
    commCallCloseHandlers(fd);
    if (F->uses)               /* assume persistent connect count */
       pconnHistCount(1, F->uses);
#if USE_SSL
    if (F->ssl) {
       SSL_free(F->ssl);
       F->ssl = NULL;
    }
#endif
    squid_fd_table.close(fd);
//  fd_close(fd);              /* update fdstat */
    statCounter.syscalls.sock.closes++;
}

/* Send a udp datagram to specified TO_ADDR. */
int
comm_udp_sendto(int fd,
    const struct sockaddr_in *to_addr,
    int addr_len,
    const void *buf,
    int len)
{
    int x, socket;
    statCounter.syscalls.sock.sendtos++;
   squid_fd_table.check(fd, 25);
    socket = squid_fd_table.fd_table[fd].GetSocket();
    if(socket < 0)
    {   debug(5, 0) ("comm_udp_sendto: WARNING: fd=%d, socket=%d\n", fd,socket);
        return COMM_ERROR;
    }
    x = sendto(socket, (char *)buf, len, 0, (struct sockaddr *) to_addr, addr_len);
    if (x < 0) {
#ifdef _SQUID_LINUX_
       if (ECONNREFUSED != errno)
#endif
           debug(50, 1) ("comm_udp_sendto: FD %d(%d), %s, port %d: %s\n",
               fd,socket,
               inet_ntoa(to_addr->sin_addr),
               (int) htons(to_addr->sin_port),
               xstrerror_type(FD_SOCKET));
       return COMM_ERROR;
    }
    return x;
}

void
commSetDefer(int fd, DEFER * func, void *data)
{
    squid_fde *F = &squid_fd_table.fd_table[fd];
   squid_fd_table.check(fd, 26);
    F->defer_check = func;
    F->defer_data = data;
}

void
commSetSelect(int fd, unsigned int type, PF * handler, void *client_data,  time_t timeout)
{
    squid_fde *F = &squid_fd_table.fd_table[fd];
    assert(fd >= 0);
    assert(F->flags.open);
   squid_fd_table.check(fd, 27);

    debug(5, 5) ("commSetSelect: FD %d SelectType %d\n", fd, type);
    if (type & COMM_SELECT_READ) {
        F->read_handler = handler;
        F->read_data = client_data;
        commUpdateReadBits(fd, handler);
    }
    if (type & COMM_SELECT_WRITE) {
        F->write_handler = handler;
        F->write_data = client_data;
        commUpdateWriteBits(fd, handler);
    }
    if (timeout)
        F->timeout = squid_curtime + timeout;
}

void
comm_add_close_handler(int fd, PF * handler, void *data)
{
    close_handler *newh = (close_handler *)memPoolAlloc(conn_close_pool);                /* AAA */
    close_handler *c;
   squid_fd_table.check(fd, 28);

    debug(5, 5) ("comm_add_close_handler: FD %d (type%i), handler=%p, data=%p\n",
       fd,squid_fd_table.fd_table[fd].type ,handler, data);
    for (c = squid_fd_table.fd_table[fd].close_handler; c; c = c->next)
       assert(c->handler != handler || c->data != data);
    newh->handler = handler;
    newh->data = data;
    newh->next = squid_fd_table.fd_table[fd].close_handler;
    squid_fd_table.fd_table[fd].close_handler = newh;
    cbdataLock(data);
}

void
comm_remove_close_handler(int fd, PF * handler, void *data)
{
    close_handler *p;
    close_handler *last = NULL;
   squid_fd_table.check(fd, 29);

    /* Find handler in list */
    debug(5, 5) ("comm_remove_close_handler: FD %d, handler=%p, data=%p\n",
       fd, handler, data);
    for (p = squid_fd_table.fd_table[fd].close_handler; p != NULL; last = p, p = p->next)
       if (p->handler == handler && p->data == data)
           break;              /* This is our handler */
    assert(p != NULL);
    /* Remove list entry */
    if (last)
       last->next = p->next;
    else
       squid_fd_table.fd_table[fd].close_handler = p->next;
    cbdataUnlock(p->data);
    memPoolFree(conn_close_pool, p);   /* AAA */
}

static void
commSetNoLinger(int fd)
{
    struct linger L;
    int socket;
    L.l_onoff = 0;             /* off */
    L.l_linger = 0;
   squid_fd_table.check(fd, 30);
    socket = squid_fd_table.fd_table[fd].GetSocket();
    if(socket >= 0)
    {     if (setsockopt(socket, SOL_SOCKET, SO_LINGER, (char *) &L, sizeof(L)) < 0)
                 debug(50, 0) ("commSetNoLinger: FD %d(%d): %s\n",
                       fd, socket,xstrerror_type(FD_SOCKET));
    } else {
        debug(5, 0) ("commSetNoLinger: WARNING: FD %d socket=%d\n", fd,socket);
    }

    squid_fd_table.fd_table[fd].flags.nolinger = 1;
}

static void
commSetReuseAddr(int fdsocket)
{
    int on = 1;
    if(fdsocket < 0)
    {   debug(5, 0) ("commSetReuseAddr: WARNING: socket=%d\n", fdsocket);
    } else {
       if (setsockopt(fdsocket, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0)
            debug(50, 1) ("commSetReuseAddr: FD %d: %s\n", fdsocket, xstrerror_type(FD_SOCKET));
    }
}

static void
commSetTcpRcvbuf(int socket, int size)
{
    if(socket < 0)
    {   debug(5, 0) ("commSetTcpRcvbuf: WARNING: socket=%d\n", socket);
    } else {
       if (setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *) &size, sizeof(size)) < 0)
           debug(50, 1) ("commSetTcpRcvbuf: Socket %d, SIZE %d: %s\n",
               socket, size, xstrerror_type(FD_SOCKET));
   }
}

#include <sys/ioctl.h>

int
commSetNonBlocking(int fd)
{
    int flags, socket;
    int nonblocking = TRUE;
   squid_fd_table.check(fd, 31);

    if (squid_fd_table.fd_table[fd].type != FD_PIPE)
    {  socket = squid_fd_table.fd_table[fd].GetSocket();
       if(socket >= 0)
       {   if (ioctl(socket, FIONBIO, (char *)&nonblocking,  sizeof(nonblocking)) < 0) {
              debug(50, 0) ("commSetNonBlocking: FD %d(%d): %s %D\n",
                   fd,socket, xstrerror_type(squid_fd_table.fd_table[fd].type), squid_fd_table.fd_table[fd].type);
              return COMM_ERROR;
            }
       } else {
          debug(5, 0) ("commSetNonBlocking: WARNING: FD %d socket=%d\n", fd,socket);
       }
    } else {
       debug(50, 0) ("commSetNonBlocking call for FD_PIPE!! : FD %d\n", fd);
//       fatal("commSetNonBlocking call for FD_PIPE");
    }
    squid_fd_table.fd_table[fd].flags.nonblocking = 1;

    return 0;
}

int
commUnsetNonBlocking(int fd)
{
    int flags, socket;
    int nonblocking = FALSE;
   squid_fd_table.check(fd, 32);
    if (squid_fd_table.fd_table[fd].type != FD_PIPE)
    {  socket = squid_fd_table.fd_table[fd].GetSocket();
       if(socket >= 0)
       {        if (ioctl(socket, FIONBIO, (char *)&nonblocking, sizeof(nonblocking)) < 0) {
                  debug(50, 0) ("commUnsetNonBlocking: FD %d(%d): %s %D\n",
                      fd,socket, xstrerror_type(squid_fd_table.fd_table[fd].type), squid_fd_table.fd_table[fd].type);
                  return COMM_ERROR;
              }
       } else {
          debug(5, 0) ("commUnsetNonBlocking: WARNING: FD %d socket=%d\n", fd,socket);
       }
    } else {
       debug(50, 0) ("commUnsetSetNonBlocking call for FD_PIPE!! : FD %d\n", fd);
//       fatal("commUnsetSetNonBlocking call for FD_PIPE");
    }
    squid_fd_table.fd_table[fd].flags.nonblocking = 0;
    return 0;
}

void
commSetCloseOnExec(int fd)
{
#ifdef FD_CLOEXEC
not used in VAC
    int flags;
    int dummy = 0;
    if ((flags = fcntl(fd, F_GETFL, dummy)) < 0) {
       debug(50, 0) ("FD %d: fcntl F_GETFL: %s\n", fd, xstrerror());
       return;
    }
    if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
       debug(50, 0) ("FD %d: set close-on-exec failed: %s\n", fd, xstrerror());
    fd_table[fd].flags.close_on_exec = 1;
#endif
}

#ifdef TCP_NODELAY
static void
commSetTcpNoDelay(int fd)
{
    int on = 1;
    int socket = squid_fd_table.fd_table[fd].GetSocket();
    if(socket >= 0)
    {     if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) < 0)
          debug(50, 1) ("commSetTcpNoDelay: Socket %d: %s\n", socket, xstrerror_type(FD_SOCKET));
    } else {
       debug(5, 0) ("commSetTcpNoDelay: WARNING: FD %d socket=%d\n", fd,socket);
    }

    squid_fd_table.fd_table[fd].flags.nodelay = 1;
}
#endif


void
comm_init(void)
{
//??    fd_table = (squid_fde *)xcalloc(Squid_MaxFD, sizeof(squid_fde));
//       debug(50, 0) ("sizeof(fde)=%i\n",sizeof(squid_fde));

    /* XXX account fd_table */
    /* Keep a few file descriptors free so that we don't run out of FD's
     * after accepting a client but before it opens a socket or a file.
     * Since Squid_MaxFD can be as high as several thousand, don't waste them */
    RESERVED_FD = XMIN(100, Squid_MaxFD / 4);
    CBDATA_INIT_TYPE(ConnectStateData);
    comm_write_pool = memPoolCreate("CommWriteStateData", sizeof(CommWriteStateData));
    conn_close_pool = memPoolCreate("close_handler", sizeof(close_handler));
}

/* Write to FD. */
static void
commHandleWrite(int fd, void *data)
{
    CommWriteStateData *state = (CommWriteStateData *)data;
    int len = 0;
    int nleft;
   squid_fd_table.check(fd, 33);

    debug(5, 5) ("commHandleWrite: FD %d: off %ld, sz %ld.\n",
       fd, (long int) state->offset, (long int) state->size);

    nleft = state->size - state->offset;
    len = squid_fd_table.fd_table[fd].write(state->buf + state->offset, nleft);
    debug(5, 5) ("commHandleWrite: write() returns %d\n", len);
//    fd_bytes(fd, len, FD_WRITE);
    statCounter.syscalls.sock.writes++;

    if (len == 0) {
       /* Note we even call write if nleft == 0 */
       /* We're done */
       if (nleft != 0)
           debug(5, 1) ("commHandleWrite: FD %d: write failure: connection closed with %d bytes remaining.\n", fd, nleft);
       CommWriteStateCallbackAndFree(fd, nleft ? COMM_ERROR : COMM_OK);
    } else if (len < 0) {
       /* An error */
       if (squid_fd_table.fd_table[fd].flags.socket_eof) {
           debug(50, 2) ("commHandleWrite: FD %d (type %d): write failure: %s.\n",
               fd,squid_fd_table.fd_table[fd].type ,xstrerror_type(squid_fd_table.fd_table[fd].type));
           CommWriteStateCallbackAndFree(fd, COMM_ERROR);
       } else if (ignoreErrno(errno,squid_fd_table.fd_table[fd].type)) {
           debug(50, 10) ("commHandleWrite: FD %d: write failure: %s.\n",
               fd, xstrerror_type(squid_fd_table.fd_table[fd].type));
           commSetSelect(fd,
               COMM_SELECT_WRITE,
               commHandleWrite,
               state,
               0);
       } else {
           debug(50, 2) ("commHandleWrite: FD %d: write failure: %s.\n",
               fd, xstrerror_type(squid_fd_table.fd_table[fd].type));
           CommWriteStateCallbackAndFree(fd, COMM_ERROR);
       }
    } else {
       /* A successful write, continue */
       state->offset += len;
       if (state->offset < state->size) {
           /* Not done, reinstall the write handler and write some more */
           commSetSelect(fd,
               COMM_SELECT_WRITE,
               commHandleWrite,
               state,
               0);
       } else {
           CommWriteStateCallbackAndFree(fd, COMM_OK);
       }
    }
}



/* Select for Writing on FD, until SIZE bytes are sent.  Call
 * *HANDLER when complete. */
void
comm_write(int fd, const char *buf, int size, CWCB * handler, void *handler_data, FREE * free_func)
{
    CommWriteStateData *state;
    if(fd < 0)
            return;
   squid_fd_table.check(fd, 34);
    state = squid_fd_table.fd_table[fd].rwstate;
    debug(5, 5) ("comm_write: FD %d: sz %d: hndl %p: data %p.\n",
       fd, size, handler, handler_data);
    if (NULL != state) {
       debug(5, 1) ("comm_write: fd_table[%d].rwstate != NULL\n", fd);
       memPoolFree(comm_write_pool, state);
       squid_fd_table.fd_table[fd].rwstate = NULL;
    }
    squid_fd_table.fd_table[fd].rwstate = state = (CommWriteStateData *)memPoolAlloc(comm_write_pool);
    state->buf = (char *) buf;
    state->size = size;
    state->offset = 0;
    state->handler = handler;
    state->handler_data = handler_data;
    state->free_func = free_func;
    cbdataLock(handler_data);
    commSetSelect(fd, COMM_SELECT_WRITE, commHandleWrite, state, 0);
}

/* a wrapper around comm_write to allow for MemBuf to be comm_written in a snap */
void
comm_write_mbuf(int fd, MemBuf mb, CWCB * handler, void *handler_data)
{
    comm_write(fd, mb.buf, mb.size, handler, handler_data, memBufFreeFunc(&mb));
}


void
commCloseAllSockets(void)
{
    int fd;
    squid_fde *F = NULL;
    PF *callback;
//    for (fd = 0; fd <= Biggest_FD; fd++) {
    for (fd = 0; fd <= squid_fd_table.n; fd++) {
       F = &squid_fd_table.fd_table[fd];
       if (!F->flags.open)
           continue;
       if (F->type != FD_SOCKET)
           continue;
       if (F->flags.ipc)       /* don't close inter-process sockets */
           continue;
       if (F->timeout_handler) {
           debug(5, 5) ("commCloseAllSockets: FD %d: Calling timeout handler\n",
               fd);
           callback = F->timeout_handler;
           F->timeout_handler = NULL;
           callback(fd, F->timeout_data);
       } else {
           debug(5, 5) ("commCloseAllSockets: FD %d: calling comm_close()\n", fd);
           comm_close(fd);
       }
    }
}


