/*
 * nasd_srpc_generic_server.c
 *
 * UNIX user-level SRPC server
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_shutdown.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_srpc.h>
#include <nasd/nasd_rpcgen_glob_param.h>
#include <nasd/nasd_timer.h>

#include <stdio.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/errno.h>
#include <sys/param.h>
#ifdef _IBMR2
#include <sys/select.h>
#endif /* _IBMR2 */
#include <arpa/inet.h>

#define RB_LOCK(_listener_) \
  NASD_LOCK_MUTEX(listener->sys_listener.readbits_mutex)
#define RB_UNLOCK(_listener_) \
  NASD_UNLOCK_MUTEX(listener->sys_listener.readbits_mutex)

/* XXX move into sys_listener struct */
fd_set nasd_srpc_generic_server_rdbm;

#define WAKEUP_MASTER(_listener_) { \
  int _ret; \
  char _c = 'x'; \
  if ((_listener_)->sys_listener.wr_master_wakeup >= 0) { \
    _ret = write((_listener_)->sys_listener.wr_master_wakeup, &_c, 1); \
    if (_ret != 1) { \
      NASD_PANIC(); \
    } \
 } \
}

/*
 * Call with listener locked
 */
nasd_srpc_conn_t *
nasd_srpc_sys_take_connection(
  nasd_srpc_listener_t  *listener)
{
  struct sockaddr_in addr;
  nasd_srpc_conn_t *conn;
  nasd_srpc_sock_t *sock;
  nasd_uint32 ipaddr;
  nasd_uint16 ipport;
  nasd_status_t rc;
  int addrlen, fd;

  addrlen = sizeof(addr);
  fd = accept(listener->sys_listener.listen_fd,
    (struct sockaddr *)&addr, &addrlen);
  if (fd < 0) {
    /*
     * Failed accepting client. Bummer, drag.
     */
    return(NULL);
  }

  ipaddr = ntohl(addr.sin_addr.s_addr);
  ipport = ntohs(addr.sin_port);

  rc = nasd_srpc_conn_get(&conn);
  if (rc) {
    close(fd);
    return(NULL);
  }

  rc = nasd_srpc_sock_get(&sock);
  if (rc) {
    nasd_srpc_conn_free(conn);
    close(fd);
  }

  sock->sock.fd = fd;

  rc = nasd_srpc_sys_sock_set_options(sock, &nasd_srpc_srv_sockbuf_opts);
  if (rc) {
    nasd_printf("SRPC: could not set some or all "
      "options on Ssock %d (0x%x/%s)\n",
      sock->sock.fd, rc, nasd_error_string(rc));
  }

  conn->sock = sock;
  conn->kind = NASD_SRPC_CONN_SERV;
  conn->state = NASD_SRPC_STATE_CONN;
  conn->use_count = 0;
  conn->handle_count = 0;
  conn->ipaddr = ipaddr;
  conn->ipport = ipport;
  conn->next_callid = 1;
  conn->active_next = NULL;
  conn->conn_number = listener->conn_number;
  listener->conn_number++;

  return(conn);
}

#define ACTIVATE_CONN(_conn_) { \
  \
  NASD_ASSERT(!((_conn_)->state&NASD_SRPC_STATE_OP_R)); \
  \
  /* flag as not-idle */ \
  NASD_ASSERT((_conn_)->state&NASD_SRPC_STATE_IDLE); \
  (_conn_)->state &= ~NASD_SRPC_STATE_IDLE; \
  \
  NASD_ASSERT((_conn_)->handle_count == 0); \
  \
  /* take a handle_count ref */ \
  (_conn_)->handle_count = 1; \
  \
  /* insert at tail of active list */ \
  NASD_ASSERT(!(conn->state&NASD_SRPC_STATE_AR)); \
  conn->active_next = &listener->conn_active_ring; \
  conn->active_prev = listener->conn_active_ring.active_prev; \
  conn->active_prev->active_next = conn; \
  conn->active_next->active_prev = conn; \
  conn->state |= NASD_SRPC_STATE_AR; \
  \
  /* remove from our listen list */ \
  RB_LOCK(listener); \
  FD_CLR((_conn_)->sock->sock.fd, &nasd_srpc_generic_server_rdbm); \
  RB_UNLOCK(listener); \
  \
  /* notify listeners */ \
  NASD_SRPC_SYS_LISTENER_SIGNAL_CONN_ACTIVE_COND(listener); \
}

#define IDLE_CONN(_conn_) { \
  \
  NASD_ASSERT(!((_conn_)->state&NASD_SRPC_STATE_OP_R)); \
  \
  NASD_ASSERT(!((_conn_)->state & NASD_SRPC_STATE_IDLE)); \
  NASD_ASSERT((_conn_)->handle_count == 0); \
  \
  /* add to select fd_set */ \
  RB_LOCK(listener); \
  FD_SET((_conn_)->sock->sock.fd, &nasd_srpc_generic_server_rdbm); \
  RB_UNLOCK(listener); \
  maxfd = NASD_MAX(maxfd, (_conn_)->sock->sock.fd); \
  \
  /* insert in idle list */ \
  (_conn_)->idle_next = &listener->conn_idle_ring; \
  (_conn_)->idle_prev = listener->conn_idle_ring.idle_prev; \
  (_conn_)->idle_prev->idle_next = (_conn_); \
  (_conn_)->idle_next->idle_prev = (_conn_); \
  (_conn_)->state |= NASD_SRPC_STATE_IDLE; \
}

/*
 * RPC controller thread. Does select() and stuff.
 */
void
nasd_srpc_sys_master_thread_proc(
  nasd_threadarg_t listener_arg)
{
  nasd_srpc_listener_t *listener;
  nasd_srpc_conn_t *conn, *next;
  int ret, maxfd, nread, badf;
  fd_set rdb;
  char c;

  listener = (nasd_srpc_listener_t *)listener_arg;

  NASD_THREADGROUP_RUNNING(&listener->sys_listener.master_group);

  NASD_SRPC_LOCK_LISTENER(listener);

  FD_ZERO(&nasd_srpc_generic_server_rdbm);
  FD_SET(listener->sys_listener.listen_fd, &nasd_srpc_generic_server_rdbm);
  FD_SET(listener->sys_listener.rd_master_wakeup,
    &nasd_srpc_generic_server_rdbm);
  maxfd = NASD_MAX(listener->sys_listener.listen_fd,
    listener->sys_listener.rd_master_wakeup);
  badf = 0;

  while(!NASD_THREADGROUP_SHUTDOWNP(&listener->sys_listener.master_group)) {
    RB_LOCK(listener);
    rdb = nasd_srpc_generic_server_rdbm;
    RB_UNLOCK(listener);
    NASD_SRPC_UNLOCK_LISTENER(listener);

    ret = select(maxfd+1, &rdb, NULL, NULL, NULL);
    NASD_SRPC_LOCK_LISTENER(listener);
    if (NASD_THREADGROUP_SHUTDOWNP(&listener->sys_listener.master_group)) {
      break;
    }
    if (ret < 0) {
      if (errno == EINTR)
        continue;
      if ((errno == EBADF) && (badf < 1)) {
        /*
         * Gives us a chance to spin around and recover when the
         * problem is a connection being killed from under us.
         */
        badf++;
        continue;
      }
perror("TELL_JIM_YOU_SAW_THIS select.S"); fflush(stderr);
      NASD_PANIC();
    }
    badf = 0;
    if (FD_ISSET(listener->sys_listener.listen_fd, &rdb)) {
      conn = nasd_srpc_sys_take_connection(listener);
      if (conn) {
        IDLE_CONN(conn);
      }
    }
    if (FD_ISSET(listener->sys_listener.rd_master_wakeup, &rdb)) {
      ret = read(listener->sys_listener.rd_master_wakeup, &c, 1);
      if (ret != 1) {
        NASD_PANIC();
      }
    }

    for(conn = listener->sys_listener.conn_reidle;
      conn != NULL;
      conn = next)
    {
      NASD_SRPC_LOCK_CONN(conn);
      NASD_ASSERT(conn->state & NASD_SRPC_STATE_REIDLE);
      NASD_ASSERT(!(conn->state & NASD_SRPC_STATE_IDLE));
      next = conn->idle_next;
      listener->sys_listener.conn_reidle = next;
      if (listener->sys_listener.conn_reidle == NULL) {
        NASD_ASSERT(conn == listener->sys_listener.conn_reidle_tail);
        listener->sys_listener.conn_reidle_tail = NULL;
      }
      conn->state &= ~NASD_SRPC_STATE_REIDLE;
      /* is the connection really active? */
      ret = ioctl(conn->sock->sock.fd, FIONREAD, &nread);
      if (ret) {
        /* connection is dead */
        RB_LOCK(listener);
        FD_CLR(conn->sock->sock.fd, &nasd_srpc_generic_server_rdbm);
        RB_UNLOCK(listener);
        NASD_SRPC_LOCK_CONN(conn);
        if (!(conn->state&NASD_SRPC_STATE_DEAD)) {
          NASD_SRPC_CONN_MARK_DEAD(listener, conn);
          NASD_SRPC_SYS_LISTENER_SIGNAL_DEAD_COND(listener);
        }
        NASD_SRPC_UNLOCK_CONN(conn);
      }
      if (nread > 0) {
        NASD_ASSERT(!(conn->state & NASD_SRPC_STATE_IDLE));
        conn->state |= NASD_SRPC_STATE_IDLE;
        ACTIVATE_CONN(conn);
      }
      else {
        IDLE_CONN(conn);
      }
      NASD_SRPC_UNLOCK_CONN(conn);
    }
    for(conn = listener->conn_idle_ring.idle_next;
      conn != &listener->conn_idle_ring;
      conn = next)
    {
      next = conn->idle_next;
      if (FD_ISSET(conn->sock->sock.fd, &rdb)) {
        ret = ioctl(conn->sock->sock.fd, FIONREAD, &nread);
        if (ret || (nread == 0)) {
          /*
           * Connection is dead
           */

          RB_LOCK(listener);
          FD_CLR(conn->sock->sock.fd, &nasd_srpc_generic_server_rdbm);
          RB_UNLOCK(listener);
          NASD_SRPC_LOCK_CONN(conn);

          /* remove from idle list */
          NASD_ASSERT(conn->state & NASD_SRPC_STATE_IDLE);
          conn->idle_prev->idle_next = conn->idle_next;
          conn->idle_next->idle_prev = conn->idle_prev;
          conn->idle_next = conn->idle_prev = NULL;
          conn->state &= ~NASD_SRPC_STATE_IDLE;

          if (!(conn->state&NASD_SRPC_STATE_DEAD)) {
            NASD_SRPC_CONN_MARK_DEAD(listener, conn);
            NASD_SRPC_SYS_LISTENER_SIGNAL_DEAD_COND(listener);
          }

          NASD_SRPC_UNLOCK_CONN(conn);
        }
        else {
          NASD_SRPC_LOCK_CONN(conn);

          /* remove from idle list */
          NASD_ASSERT(conn->state & NASD_SRPC_STATE_IDLE);
          conn->idle_prev->idle_next = conn->idle_next;
          conn->idle_next->idle_prev = conn->idle_prev;
          conn->idle_next = conn->idle_prev = NULL;

          ACTIVATE_CONN(conn);

          NASD_SRPC_UNLOCK_CONN(conn);
        }
      }
    }
  }

  NASD_SRPC_UNLOCK_LISTENER(listener);
  NASD_THREADGROUP_DONE(&listener->sys_listener.master_group);
}

void
nasd_srpc_sys_listener_killpipe(
  void  *listener_arg)
{
  nasd_srpc_listener_t *listener;

  listener = (nasd_srpc_listener_t *)listener_arg;
  /* If I were a better person, I'd check return values here */
  close(listener->sys_listener.rd_master_wakeup);
  close(listener->sys_listener.wr_master_wakeup);
  listener->sys_listener.rd_master_wakeup = (-1);
  listener->sys_listener.wr_master_wakeup = (-1);
}

void
nasd_srpc_sys_listener_kill_insocket(
  void  *listener_arg)
{
  nasd_srpc_listener_t *listener;

  listener = (nasd_srpc_listener_t *)listener_arg;
  /* If I were a better person, I'd check return values here */
  close(listener->sys_listener.listen_fd);
  listener->sys_listener.listen_fd = (-1);
}

nasd_status_t
nasd_srpc_sys_listener_init(
  nasd_srpc_listener_t  *listener)
{
  nasd_status_t rc;

  bzero((char *)&listener->sys_listener, sizeof(listener->sys_listener));

  rc = nasd_mutex_init(&listener->sys_listener.lock);
  if (rc) {
    return(rc);
  }
  rc = nasd_shutdown_mutex(listener->shutdown_list,
    &listener->sys_listener.lock);
  if (rc) {
    return(rc);
  }

  rc = nasd_cond_init(&listener->sys_listener.conn_active_cond);
  if (rc) {
    return(rc);
  }
  rc = nasd_shutdown_cond(listener->shutdown_list,
    &listener->sys_listener.conn_active_cond);
  if (rc) {
    return(rc);
  }

  rc = nasd_cond_init(&listener->sys_listener.dead_cond);
  if (rc) {
    return(rc);
  }
  rc = nasd_shutdown_cond(listener->shutdown_list,
    &listener->sys_listener.dead_cond);
  if (rc) {
    return(rc);
  }

  rc = nasd_mutex_init(&listener->sys_listener.readbits_mutex);
  if (rc)
    return(rc);
  rc = nasd_shutdown_mutex(listener->shutdown_list,
    &listener->sys_listener.readbits_mutex);
  if (rc)
    return(rc);

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_srpc_sys_listener_start(
  nasd_srpc_listener_t  *listener)
{
  struct sockaddr_in sockaddr;
  int fd, ret, mpipe[2];
  nasd_status_t rc;
  int reuse;

  listener->sys_listener.conn_reidle = NULL;
  listener->sys_listener.conn_reidle_tail = NULL;

  fd = socket(AF_INET, SOCK_STREAM, 0);
  if (fd < 0) {
    return(NASD_FAIL);
  }

  bzero((char *)&sockaddr, sizeof(sockaddr));
  sockaddr.sin_family = AF_INET;
  sockaddr.sin_addr.s_addr = INADDR_ANY;
  sockaddr.sin_port = htons(listener->ipport);

  if (nasd_srpc_srv_sockbuf_opts.reuseaddr >= 0) {
    reuse = nasd_srpc_srv_sockbuf_opts.reuseaddr;
    ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
      (char *)&reuse, sizeof(int));
    if (ret < 0) {
      close(fd);
      return(NASD_SRPC_CANNOT_BIND);
    }
  }

  ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
  if (ret < 0) {
    close(fd);
    return(NASD_SRPC_CANNOT_BIND);
  }

  ret = listen(fd, listener->nthreads);
  if (ret) {
    close(fd);
    return(NASD_SRPC_CANNOT_BIND);
  }

  listener->sys_listener.listen_fd = fd;

  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_sys_listener_kill_insocket, listener);
  if (rc) {
    nasd_srpc_sys_listener_kill_insocket(listener);
    return(rc);
  }

  ret = pipe(mpipe);
  if (ret) {
    return(NASD_FAIL);
  }
  listener->sys_listener.rd_master_wakeup = mpipe[0];
  listener->sys_listener.wr_master_wakeup = mpipe[1];
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_sys_listener_killpipe, listener);
  if (rc) {
    nasd_srpc_sys_listener_killpipe(listener);
    return(rc);
  }

  rc = nasd_init_threadgroup(&listener->sys_listener.master_group);
  if (rc) {
    return(rc);
  }

  rc = nasd_thread_create(&listener->sys_listener.master_thread,
    nasd_srpc_sys_master_thread_proc, listener);
  if (rc) {
    nasd_destroy_threadgroup(&listener->sys_listener.master_group);
    return(rc);
  }
  NASD_THREADGROUP_STARTED(&listener->sys_listener.master_group);
  NASD_THREADGROUP_WAIT_START(&listener->sys_listener.master_group);

  return(NASD_SUCCESS);
}

void
nasd_srpc_sys_listener_stop(
  nasd_srpc_listener_t  *listener)
{
  nasd_srpc_conn_t *conn, *next;

  /*
   * Kill off listener. No new connections will
   * be accepted.
   */
  NASD_THREADGROUP_INDICATE_SHUTDOWN(&listener->sys_listener.master_group);

  WAKEUP_MASTER(listener);

  NASD_THREADGROUP_WAIT_STOP(&listener->sys_listener.master_group);

  NASD_SRPC_LOCK_LISTENER(listener);

  nasd_destroy_threadgroup(&listener->sys_listener.master_group);

  /* mark all "reidle" connections dead */
  for(conn = listener->sys_listener.conn_reidle;
    conn != NULL;
    conn = next)
  {
    NASD_ASSERT(conn->state & NASD_SRPC_STATE_REIDLE);
    next = conn->idle_next;
    listener->sys_listener.conn_reidle = next;
    if (listener->sys_listener.conn_reidle == NULL) {
      NASD_ASSERT(conn == listener->sys_listener.conn_reidle_tail);
       listener->sys_listener.conn_reidle_tail = NULL;
    }
    conn->state &= ~NASD_SRPC_STATE_REIDLE;

    NASD_SRPC_LOCK_CONN(conn);
    if (!(conn->state&NASD_SRPC_STATE_DEAD)) {
      NASD_SRPC_CONN_MARK_DEAD(listener, conn);
      NASD_SRPC_SYS_LISTENER_SIGNAL_DEAD_COND(listener);
    }
    NASD_SRPC_UNLOCK_CONN(conn);
  }

  NASD_SRPC_UNLOCK_LISTENER(listener);
}

/*
 * Caller holds conn lock
 * Caller holds listener lock
 *
 * This is called when someone starts handling an
 * unhandled connection. The connection in question
 * should either be idle or marked for reidle.
 */
nasd_status_t
nasd_srpc_sys_mark_conn_active(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  nasd_srpc_conn_t *cn, *pcn;

  NASD_ASSERT(conn->state&(NASD_SRPC_STATE_IDLE|NASD_SRPC_STATE_REIDLE));

  if (conn->state & NASD_SRPC_STATE_IDLE) {
    conn->state &= ~NASD_SRPC_STATE_IDLE;
    conn->idle_prev->idle_next = conn->idle_next;
    conn->idle_next->idle_prev = conn->idle_prev;
    conn->idle_next = conn->idle_prev = NULL;
    RB_LOCK(listener);
    FD_CLR(conn->sock->sock.fd, &nasd_srpc_generic_server_rdbm);
    RB_UNLOCK(listener);
  }
  else if (conn->state & NASD_SRPC_STATE_REIDLE) {
    for(pcn=NULL,cn=listener->sys_listener.conn_reidle;
      cn;
      pcn=cn,cn=cn->idle_next)
    {
      if (cn == conn) {
        if (pcn == NULL) {
          NASD_ASSERT(listener->sys_listener.conn_reidle == conn);
          listener->sys_listener.conn_reidle = conn->idle_next;
        }
        else {
          pcn->idle_next = cn->idle_next;
        }
        if (listener->sys_listener.conn_reidle_tail == conn) {
          listener->sys_listener.conn_reidle_tail = pcn;
        }
        cn->idle_next = NULL;
        break;
      }
    }
    NASD_ASSERT(cn == conn);
    conn->state &= ~NASD_SRPC_STATE_REIDLE;
  }

  return(NASD_SUCCESS);
}

/*
 * Caller holds listener lock
 * Caller holds conn lock
 */
nasd_status_t
nasd_srpc_sys_mark_conn_idle(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  NASD_ASSERT(!(conn->state & (NASD_SRPC_STATE_REIDLE|NASD_SRPC_STATE_IDLE)));

  conn->idle_next = NULL;
  if (listener->sys_listener.conn_reidle_tail) {
    listener->sys_listener.conn_reidle_tail->idle_next = conn;
  }
  else {
    NASD_ASSERT(listener->sys_listener.conn_reidle == NULL);
    listener->sys_listener.conn_reidle = conn;
  }
  listener->sys_listener.conn_reidle_tail = conn;

  conn->state |= NASD_SRPC_STATE_REIDLE;

  NASD_BROADCAST_COND(conn->state_cond); /* ??? necessary ? */

  WAKEUP_MASTER(listener);

  return(NASD_SUCCESS);
}

/*
 * caller does not hold listener lock
 * caller does not hold conn lock
 */
nasd_status_t
nasd_srpc_sys_listener_conn_dead(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  nasd_srpc_conn_t *cn, *pcn;

  NASD_SRPC_LOCK_LISTENER(listener);
  NASD_SRPC_LOCK_CONN(conn);
  if (conn->state & NASD_SRPC_STATE_REIDLE) {
    for(pcn=NULL,cn=listener->sys_listener.conn_reidle;
      cn;
      pcn=cn,cn=cn->idle_next)
    {
      if (cn == conn) {
        if (pcn == NULL) {
          NASD_ASSERT(listener->sys_listener.conn_reidle == conn);
          listener->sys_listener.conn_reidle = conn->idle_next;
        }
        else {
          pcn->idle_next = cn->idle_next;
        }
        if (listener->sys_listener.conn_reidle_tail == conn) {
          listener->sys_listener.conn_reidle_tail = pcn;
        }
        cn->idle_next = NULL;
        break;
      }
    }
    NASD_ASSERT(cn == conn);
    conn->state &= ~NASD_SRPC_STATE_REIDLE;
  }
  NASD_SRPC_UNLOCK_CONN(conn);
  NASD_SRPC_UNLOCK_LISTENER(listener);

  RB_LOCK(listener);
  FD_CLR(conn->sock->sock.fd, &nasd_srpc_generic_server_rdbm);
  RB_UNLOCK(listener);

  WAKEUP_MASTER(listener);

  return(NASD_SUCCESS);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
