/*
 * nasd_srpc_linux_server.c
 *
 * Linux in-kernel 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>

/*
 * Don't enable this until we work out some way to
 * safely do a SIGNAL_COND in a bottom-half
 */
#define TRY_TO_DO_WORK_IN_BH 0

/*
 * Caller holds listener lock
 * Caller holds conn lock
 */
#define IDLE_CONN(_listener_,_conn_) { \
  struct sock *sk; \
  \
  NASD_ASSERT(!((_conn_)->state & NASD_SRPC_STATE_IDLE)); \
  NASD_ASSERT((_conn_)->handle_count == 0); \
  \
  /* 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; \
  \
  /* setup data_available callback */ \
  sk = (_conn_)->sock->sock.linux_sock->sk; \
  sk->state_change = nasd_srpc_sys_listener_state_change; \
  sk->data_ready = nasd_srpc_sys_listener_data_ready; \
  if (nasd_srpc_sys_sock_data_isavail((_conn_)->sock)) { \
    nasd_srpc_sys_listener_data_readable_worker(listener, conn); \
  } \
}

/*
 * Caller holds listener lock
 * Caller holds conn lock
 */
#define ACTIVATE_CONN(_listener_,_conn_) { \
  struct sock *sk; \
  \
  /* flag as not-idle */ \
  NASD_ASSERT((_conn_)->state&NASD_SRPC_STATE_IDLE); \
  (_conn_)->state &= ~NASD_SRPC_STATE_IDLE; \
  \
  /* take a handle_count ref */ \
  (_conn_)->handle_count++; \
  \
  /* 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; \
  \
  /* stop listening for new data */ \
  sk = (_conn_)->sock->sock.linux_sock->sk; \
  sk->data_ready = nasd_srpc_sys_listener_data_ready_ignore; \
  \
  /* notify listeners */ \
  NASD_SIGNAL_COND((_listener_)->sys_listener.conn_active_cond); \
}

/*
 * Call with listener locked
 */
#define WAKEUP_DEFER(_listener_) { \
  wake_up(&(_listener_)->sys_listener.defer_wq); \
}

NASD_INLINE int
nasd_srpc_sys_sock_data_isavail(
  nasd_srpc_sock_t  *sock)
{
  struct sk_buff *skb;
  struct sock *sk;

  sk = sock->sock.linux_sock->sk;

  lock_sock(sk);
  skb = skb_peek(&sk->receive_queue);
  release_sock(sk);

  if (skb)
    return(1);
  else
    return(0);
}

/*
 * Call with listener lock held
 */
NASD_INLINE void
nasd_srpc_sys_conn_defer_queue(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  if (conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_QUEUE) {
    /* already queued */
    return;
  }

  conn->sys_conn.defer_next = listener->sys_listener.defer_queue;
  if (listener->sys_listener.defer_queue_tail) {
    listener->sys_listener.defer_queue_tail->sys_conn.defer_next = conn;
  }
  else {
    NASD_ASSERT(listener->sys_listener.defer_queue == NULL);
    listener->sys_listener.defer_queue = conn;
  }
  listener->sys_listener.defer_queue_tail = conn;
  conn->sys_conn.defer_state |= NASD_SRPC_SYS_CONN_DEFER_QUEUE;

  WAKEUP_DEFER(listener);
}

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

  listener = (nasd_srpc_listener_t *)listener_arg;
  sock_release(listener->sys_listener.listen_sock);
  listener->sys_listener.listen_sock = NULL;
}

void
nasd_srpc_sys_listener_incoming_ignore(
  struct sock  *listener_sock_arg)
{
}

void
nasd_srpc_sys_listener_data_ready_ignore(
  struct sock  *conn_sock_arg,
  int           count)
{
}

void
nasd_srpc_sys_listener_state_change_ignore(
  struct sock  *conn_sock_arg)
{
}

/*
 * Caller holds listener lock
 * Caller holds conn lock
 *
 * Data is readable on conn
 */
void
nasd_srpc_sys_listener_data_readable_worker(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  conn->sock->sock.linux_sock->sk->data_ready =
    nasd_srpc_sys_listener_data_ready_ignore;

  if (conn->state &
    (NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM|NASD_SRPC_STATE_DEAD))
  {
    return;
  }

  /* 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(listener, conn);
}

void
nasd_srpc_sys_listener_state_change(
  struct sock  *conn_sock_arg)
{
  nasd_srpc_listener_t *listener;
  nasd_srpc_conn_t *conn;

  conn = (nasd_srpc_conn_t *)conn_sock_arg->user_data;
  listener = conn->sys_conn.listener;

  conn->sock->sock.linux_sock->sk->data_ready =
    nasd_srpc_sys_listener_data_ready_ignore;

#if TRY_TO_DO_WORK_IN_BH > 0
  if (NASD_SRPC_TRY_LOCK_LISTENER(listener)) {

    if (NASD_SRPC_TRY_LOCK_CONN(conn)) {

      if (conn->state&NASD_SRPC_STATE_IDLE) {
        /* remove from idle list */
        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;
      }

      if (!(conn->state&NASD_SRPC_STATE_DEAD)) {
        /* put on dead list */
        NASD_SRPC_CONN_MARK_DEAD(listener, conn);
        NASD_SIGNAL_COND(listener->sys_listener.dead_cond);
      }

      conn->state |= (NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM);
      NASD_BROADCAST_COND(conn->state_cond);

      /* also, don't process deferred data */
      conn->sys_conn.defer_state &= ~NASD_SRPC_SYS_CONN_DEFER_DATAAVAIL;

      NASD_SRPC_UNLOCK_CONN(conn);
    }
    else {
      NASD_SRPC_UNLOCK_LISTENER(conn);
      goto defer;
    }

    NASD_SRPC_UNLOCK_LISTENER(listener);
  }
  else {
defer:
#endif /* TRY_TO_DO_WORK_IN_BH > 0 */
    /*
     * We need the conn lock to manipulate the conn's state bits.
     * Defer that work for later.
     *
     * MY KINGDOM FOR AN AST
     */
    if (!(conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_DEAD)) {
      conn->sys_conn.defer_state |= NASD_SRPC_SYS_CONN_DEFER_DEAD;
      nasd_srpc_sys_conn_defer_queue(listener, conn);
    }
#if TRY_TO_DO_WORK_IN_BH > 0
  }
#endif /* TRY_TO_DO_WORK_IN_BH > 0 */
}

/*
 * We have readable data.
 *
 * !!! We ignore count here !!!
 */
void
nasd_srpc_sys_listener_data_ready(
  struct sock  *conn_sock_arg,
  int           count)
{
  nasd_srpc_listener_t *listener;
  nasd_srpc_conn_t *conn;

  if (conn_sock_arg->state != TCP_ESTABLISHED) {
    /* This is really a disconnect */
    nasd_srpc_sys_listener_state_change(conn_sock_arg);
    return;
  }

  conn = (nasd_srpc_conn_t *)conn_sock_arg->user_data;
  listener = conn->sys_conn.listener;

#if TRY_TO_DO_WORK_IN_BH > 0
  if (NASD_SRPC_TRY_LOCK_LISTENER(listener)) {
    if (NASD_SRPC_TRY_LOCK_CONN(conn)) {
      nasd_srpc_sys_listener_data_readable_worker(listener, conn);
      NASD_SRPC_UNLOCK_CONN(conn);
    }
    else {
      NASD_SRPC_UNLOCK_LISTENER(listener);
      goto defer;
    }
  }
  else {
defer:
#endif /* TRY_TO_DO_WORK_IN_BH > 0 */
    /*
     * Couldn't get the conn lock in this (interrupt) context, so
     * queue the connection for the state change to happen
     * out of interrupt context.
     *
     * MY KINGDOM FOR AN AST
     */
    if (!(conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_DATAAVAIL)) {
      conn->sys_conn.defer_state |= NASD_SRPC_SYS_CONN_DEFER_DATAAVAIL;
      nasd_srpc_sys_conn_defer_queue(listener, conn);
    }
#if TRY_TO_DO_WORK_IN_BH > 0
  }
#endif /* TRY_TO_DO_WORK_IN_BH > 0 */
}

/*
 * Someone is trying to connect
 * Registered as state_change op for listener socket
 */
void
nasd_srpc_sys_listener_incoming(
  struct sock  *listener_sock_arg)
{
  nasd_srpc_listener_t *listener;
  struct sockaddr_in sockaddr;
  nasd_srpc_conn_t *conn;
  struct socket *socket;
  nasd_uint32 ipaddr;
  nasd_uint16 ipport;
  nasd_status_t rc;
  int ret, len;

  listener = (nasd_srpc_listener_t *)listener_sock_arg->user_data;

  if (listener_sock_arg->state != TCP_ESTABLISHED) {
    /*
     * XXX Do we need to do anything here?
     */
    return;
  }

  if (listener->sys_listener.listen_sock->ops == NULL) {
    /*
     * XXX How to clear out the pending connection?
     */
    nasd_printf("SRPC error: no ops structure for listener sock!\n");
    return;
  }

  socket = sock_alloc();
  if (socket == NULL) {
    /*
     * XXX How to clear out the pending connection?
     */
    return;
  }

  socket->type = listener_sock_arg->type;
  if (listener->sys_listener.listen_sock->ops->dup == NULL) {
    /*
     * XXX How to clear out the pending connection?
     */
    sock_release(socket);
    nasd_printf("SRPC error: no dup operation for listener sock!\n");
    return;
  }
  ret = listener->sys_listener.listen_sock->ops->dup(socket,
    listener->sys_listener.listen_sock);
  if (ret < 0) {
    /*
     * XXX How to clear out the pending connection?
     */
    sock_release(socket);
    return;
  }

  if (socket->ops->accept == NULL) {
    /*
     * XXX How to clear out the pending connection?
     */
    nasd_printf("SRPC error: no accept operation for listener sock!\n");
    sock_release(socket);
    return;
  }

  ret = socket->ops->accept(listener->sys_listener.listen_sock,
    socket, O_NONBLOCK);
  if (ret < 0) {
    sock_release(socket);
    return;
  }

  if (socket->ops->getname == NULL) {
    nasd_printf("SRPC error: no getname operation for listener sock!\n");
    sock_release(socket);
    return;
  }

  len = sizeof(sockaddr);
  ret = socket->ops->getname(socket, (struct sockaddr *)&sockaddr, &len, 1);
  if (ret < 0) {
    sock_release(socket);
    return;
  }

  rc = nasd_srpc_conn_get(&conn);
  if (rc) {
    sock_release(socket);
    return;
  }

  rc = nasd_srpc_sock_get(&conn->sock);
  if (rc) {
    nasd_srpc_conn_free(conn);
    sock_release(socket);
    return;
  }

  conn->sock->sock.linux_sock = socket;
  conn->sock->sock.peer_addr = sockaddr;

  rc = nasd_srpc_sys_sock_set_options(conn->sock,
    &nasd_srpc_srv_sockbuf_opts);
  if (rc) {
#if 0
    /*
     * I'm turning this message off, because it's always
     * printed for localhost connections. No idea why,
     * but it's really bugging me.  --jimz
     */
    nasd_printf("SRPC: could not set some or all options on Ssock 0x%lx (0x%x/%s)\n",
      conn->sock, rc, nasd_error_string(rc));
#endif
  }

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

  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;

  /*
   * This appears to be a race condition, but it's not.
   * Since this is the only place that conn_number gets
   * incremented, and we're already protected against
   * multiple entry, we're safe. Amazing, eh?
   */
  conn->conn_number = listener->conn_number;
  listener->conn_number++;

  conn->sys_conn.listener = listener;
  conn->sys_conn.defer_state = 0;
  conn->sys_conn.defer_next = NULL;

  socket->sk->user_data = conn;

  socket->sk->state_change = nasd_srpc_sys_listener_state_change;
  socket->sk->data_ready = nasd_srpc_sys_listener_data_ready_ignore;

#if TRY_TO_DO_WORK_IN_BH > 0
  if (NASD_SRPC_TRY_LOCK_LISTENER(listener)) {
    if (NASD_SRPC_TRY_LOCK_CONN(conn)) {
      IDLE_CONN(listener, conn);
      NASD_SRPC_UNLOCK_CONN(conn);
    }
    else {
      /*
       * Who could be holding that lock?!
       */
      NASD_PANIC();
    }
    NASD_SRPC_UNLOCK_LISTENER(listener);
  }
  else {
#endif /* TRY_TO_DO_WORK_IN_BH > 0 */
    conn->sys_conn.defer_state |= NASD_SRPC_SYS_CONN_DEFER_MARKIDLE;
    nasd_srpc_sys_conn_defer_queue(listener, conn);
#if TRY_TO_DO_WORK_IN_BH > 0
  }
#endif /* TRY_TO_DO_WORK_IN_BH > 0 */
}

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));

  init_waitqueue(&listener->sys_listener.defer_wq);

  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);
  }

  return(NASD_SUCCESS);
}

void
nasd_srpc_sys_listener_kill_defer_group(
  void  *listener_arg)
{
  nasd_srpc_listener_t *listener;
  nasd_status_t rc;

  listener = (nasd_srpc_listener_t *)listener_arg;

  rc = nasd_destroy_threadgroup(&listener->sys_listener.defer_group);
  if (rc) {
    nasd_printf("SRPC: got 0x%x (%s) shutting down defer group for 0x%lx\n",
      rc, nasd_error_string(rc), (unsigned long)listener);
  }
}

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

  listener = (nasd_srpc_listener_t *)listener_arg;

  NASD_THREADGROUP_INDICATE_SHUTDOWN(&listener->sys_listener.defer_group);
  NASD_SRPC_LOCK_LISTENER(listener);
  WAKEUP_DEFER(listener);
  NASD_SRPC_UNLOCK_LISTENER(listener);
  NASD_THREADGROUP_WAIT_STOP(&listener->sys_listener.defer_group);
}

void
nasd_srpc_sys_listener_defer_proc(
  nasd_threadarg_t  listener_arg)
{
  nasd_srpc_listener_t *listener;
  nasd_srpc_conn_t *conn;
  struct wait_queue wait;

  listener = (nasd_srpc_listener_t *)listener_arg;

  NASD_THREADGROUP_RUNNING(&listener->sys_listener.defer_group);

  NASD_SRPC_LOCK_LISTENER(listener);

  while(!NASD_THREADGROUP_SHUTDOWNP(&listener->sys_listener.defer_group)) {

    start_bh_atomic();

    while(listener->sys_listener.defer_queue == NULL) {

      if (NASD_THREADGROUP_SHUTDOWNP(&listener->sys_listener.defer_group)) {
        goto done;
      }

      wait.task = current;
      add_wait_queue(&listener->sys_listener.defer_wq, &wait);
      current->state = TASK_INTERRUPTIBLE;
      if ((!NASD_THREADGROUP_SHUTDOWNP(&listener->sys_listener.defer_group))
        && (listener->sys_listener.defer_queue == NULL))
      {
        end_bh_atomic();
        NASD_SRPC_UNLOCK_LISTENER(listener);
        schedule();
        NASD_SRPC_LOCK_LISTENER(listener);
        start_bh_atomic();
      }
      current->state = TASK_RUNNING;
      remove_wait_queue(&listener->sys_listener.defer_wq, &wait);

      if (NASD_THREADGROUP_SHUTDOWNP(&listener->sys_listener.defer_group)) {
        end_bh_atomic();
        goto done;
      }

    }

    conn = listener->sys_listener.defer_queue;
    listener->sys_listener.defer_queue = conn->sys_conn.defer_next;
    if (listener->sys_listener.defer_queue == NULL) {
      NASD_ASSERT(listener->sys_listener.defer_queue_tail == conn);
      listener->sys_listener.defer_queue_tail = NULL;
    }

    end_bh_atomic();

    NASD_SRPC_LOCK_CONN(conn);

    if (NASD_THREADGROUP_SHUTDOWNP(&listener->sys_listener.defer_group)) {
      NASD_SRPC_UNLOCK_CONN(conn);
      goto done;
    }

    /*
     * May have been removed from the queue while we were grabbing
     * the connection lock.
     */
    if (conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_QUEUE) {
      conn->sys_conn.defer_state &= ~NASD_SRPC_SYS_CONN_DEFER_QUEUE;

      if (conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_MARKIDLE) {
        conn->sys_conn.defer_state &= ~NASD_SRPC_SYS_CONN_DEFER_MARKIDLE;
        IDLE_CONN(listener, conn);
      }

      if (conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_DEAD) {
        if (conn->state&NASD_SRPC_STATE_IDLE) {
          /* remove from idle list */
          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;
        }

        if (!(conn->state&NASD_SRPC_STATE_DEAD)) {
          /* put on dead list */
          NASD_SRPC_CONN_MARK_DEAD(listener, conn);
          NASD_SIGNAL_COND(listener->sys_listener.dead_cond);
        }

        conn->state |= (NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM);
        NASD_BROADCAST_COND(conn->state_cond);

        /* also, don't process deferred data */
        conn->sys_conn.defer_state &= ~NASD_SRPC_SYS_CONN_DEFER_DATAAVAIL;

        conn->sys_conn.defer_state &= ~NASD_SRPC_SYS_CONN_DEFER_DEAD;
      }

      if (conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_DATAAVAIL) {
        conn->sys_conn.defer_state &= ~NASD_SRPC_SYS_CONN_DEFER_DATAAVAIL;
        if (nasd_srpc_sys_sock_data_isavail(conn->sock)) {
          nasd_srpc_sys_listener_data_readable_worker(listener, conn);
        }
      }
    }

    NASD_ASSERT(conn->sys_conn.defer_state == 0);

    NASD_SRPC_UNLOCK_CONN(conn);
  }

done:
  NASD_SRPC_UNLOCK_LISTENER(listener);
  NASD_THREADGROUP_DONE(&listener->sys_listener.defer_group);
}

nasd_status_t
nasd_srpc_sys_listener_start(
  nasd_srpc_listener_t  *listener)
{
  struct sockaddr_in sockaddr;
  nasd_status_t rc;
  int ret;

  ret = sock_create(AF_INET, SOCK_STREAM, PF_UNSPEC,
    &listener->sys_listener.listen_sock);
  if (ret) {
    listener->sys_listener.listen_sock = NULL;
    return(NASD_FAIL);
  }

  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_sys_listener_kill_insocket, listener);

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

  ret = listener->sys_listener.listen_sock->ops->bind(
    listener->sys_listener.listen_sock,
    (struct sockaddr *)&sockaddr, sizeof(sockaddr));
  if (ret) {
    nasd_printf("SRPC: cannot bind to port %d\n", (int)listener->ipport);
    return(NASD_FAIL);
  }

  ret = listener->sys_listener.listen_sock->ops->listen(
    listener->sys_listener.listen_sock, listener->nthreads);
  if (ret) {
    return(NASD_FAIL);
  }

  listener->sys_listener.listen_sock->sk->user_data = listener;
  listener->sys_listener.listen_sock->flags |= SO_ACCEPTCON;
  listener->sys_listener.listen_sock->sk->state_change =
    nasd_srpc_sys_listener_incoming;

  rc = nasd_init_threadgroup(&listener->sys_listener.defer_group);
  if (rc) {
    return(rc);
  }
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_sys_listener_kill_defer_group, listener);
  if (rc) {
    nasd_srpc_sys_listener_kill_defer_group(listener);
    return(rc);
  }

  rc = nasd_thread_create_w_name(&listener->sys_listener.defer_thread,
    nasd_srpc_sys_listener_defer_proc, listener,
    "SRPC listener defer thread");
  if (rc) {
    return(rc);
  }
  NASD_THREADGROUP_STARTED(&listener->sys_listener.defer_group);
  NASD_THREADGROUP_WAIT_START(&listener->sys_listener.defer_group);
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_sys_listener_kill_defer_thread, listener);
  if (rc) {
    nasd_srpc_sys_listener_kill_defer_thread(listener);
    return(rc);
  }

  return(NASD_SUCCESS);
}

/*
 * Nothing much to do here- everything we need
 * for shutdown is on the shutdown list. Make sure
 * we stop taking new connections.
 */
void
nasd_srpc_sys_listener_stop(
  nasd_srpc_listener_t  *listener)
{
  start_bh_atomic();

  listener->sys_listener.stopped = 1;

  /*
   * Disable incoming
   */
  listener->sys_listener.listen_sock->sk->state_change =
    nasd_srpc_sys_listener_incoming_ignore;

  end_bh_atomic();
}

/*
 * Caller holds listener lock
 * Caller holds conn lock
 * Caller holds bh_atomic
 *
 * Remove conn from listener's defer queue
 */
NASD_INLINE void
nasd_srpc_sys_listener_conn_undefer(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  nasd_srpc_conn_t *pcn, *cn;

  /*
   * Remove from defer queue
   */
  for(pcn=NULL,cn=listener->sys_listener.defer_queue;
    cn;
    pcn=cn,cn=cn->sys_conn.defer_next)
  {
    if (cn == conn) {
      if (pcn == NULL) {
        NASD_ASSERT(listener->sys_listener.defer_queue == conn);
        listener->sys_listener.defer_queue = conn->sys_conn.defer_next;
      }
      else {
        pcn->sys_conn.defer_next = cn->sys_conn.defer_next;
      }
      if (listener->sys_listener.defer_queue_tail == conn) {
        listener->sys_listener.defer_queue_tail = pcn;
      }
      cn->sys_conn.defer_next = NULL;
      break;
    }
  }
  NASD_ASSERT(cn == conn);

  conn->sys_conn.defer_state = 0;
}

/*
 * Caller holds conn lock
 * Caller holds listener lock
 *
 * This is called when someone starts handling an
 * unhandled connection. The connection in question
 * should be idle. This means that we should stop
 * notifying anyone else when there's data available.
 */
nasd_status_t
nasd_srpc_sys_mark_conn_active(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  start_bh_atomic();

  conn->sock->sock.linux_sock->sk->data_ready =
    nasd_srpc_sys_listener_data_ready_ignore;

  if (conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_QUEUE) {
    nasd_srpc_sys_listener_conn_undefer(listener, conn);
  }
  NASD_ASSERT(conn->sys_conn.defer_state == 0);

  end_bh_atomic();

  NASD_ASSERT(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_prev = conn->idle_next = NULL;

  return(NASD_SUCCESS);
}

/*
 * Caller holds listener lock
 * Caller holds conn lock
 *
 * This is called to indicate that no one is handling
 * the connection. We should generate appropriate notification
 * when there's data available.
 */
nasd_status_t
nasd_srpc_sys_mark_conn_idle(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  IDLE_CONN(listener, conn);

  return(NASD_SUCCESS);
}

/*
 * caller does not hold listener lock
 * caller does not hold conn lock
 *
 * Connection is dead- clean up any state.
 */
nasd_status_t
nasd_srpc_sys_listener_conn_dead(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  struct sock *sk;

  NASD_SRPC_LOCK_LISTENER(listener);
  NASD_SRPC_LOCK_CONN(conn);
  start_bh_atomic();

  sk = conn->sock->sock.linux_sock->sk;
  sk->state_change = nasd_srpc_sys_listener_state_change_ignore;
  sk->data_ready = nasd_srpc_sys_listener_data_ready_ignore;

  if (conn->sys_conn.defer_state&NASD_SRPC_SYS_CONN_DEFER_QUEUE) {
    nasd_srpc_sys_listener_conn_undefer(listener, conn);
  }
  NASD_ASSERT(conn->sys_conn.defer_state == 0);

  end_bh_atomic();
  NASD_SRPC_UNLOCK_CONN(conn);
  NASD_SRPC_UNLOCK_LISTENER(listener);

  return(NASD_SUCCESS);
}

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