/*
 * nasd_srpc_server.c
 *
 * SRPC server stuff
 *
 * 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>

#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_SRPC

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

extern int nasd_srpc_use_counter;

int nasd_srpc_listener_serial_num_next;

NASD_DECLARE_COND(nasd_srpc_listeners_changed)
NASD_DECLARE_MUTEX(nasd_srpc_listener_set_lock)
nasd_srpc_listener_set_t *nasd_srpc_listener_set;

/*
 * Caller holds nasd_srpc_listener_set_lock
 */
int
nasd_srpc_listener_in_set(
  nasd_srpc_listener_t  *listener,
  nasd_uint64            serial_num)
{
  nasd_srpc_listener_set_t *r;

  for(r=nasd_srpc_listener_set;r;r=r->next) {
    if (r->serial_num == serial_num) {
      NASD_ASSERT(r->listener == listener);
      return(1);
    }
  }

  return(0);
}

nasd_status_t
nasd_srpc_listener_wait(
  nasd_srpc_listener_t  *listener)
{
  nasd_uint64 sn;

  /*
   * Be careful, once the condition is signalled the
   * listener is probably deallocated already
   */
  sn = listener->serial_num;

  NASD_LOCK_MUTEX(nasd_srpc_listener_set_lock);
  while(nasd_srpc_listener_in_set(listener, sn)) {
    NASD_WAIT_COND(nasd_srpc_listeners_changed, nasd_srpc_listener_set_lock);
  }
  NASD_UNLOCK_MUTEX(nasd_srpc_listener_set_lock);

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_srpc_server_init(
  nasd_shutdown_list_t  *sl)
{
  nasd_status_t rc;

  nasd_srpc_listener_serial_num_next = 1;
  nasd_srpc_listener_set = NULL;

  rc = nasd_cond_init(&nasd_srpc_listeners_changed);
  if (rc)
    return(rc);
  rc = nasd_shutdown_cond(sl, &nasd_srpc_listeners_changed);
  if (rc)
    return(rc);

  rc = nasd_mutex_init(&nasd_srpc_listener_set_lock);
  if (rc)
    return(rc);
  rc = nasd_shutdown_mutex(sl, &nasd_srpc_listener_set_lock);
  if (rc)
    return(rc);

  return(NASD_SUCCESS);
}

/*
 * Call with conn lock held
 */
nasd_status_t
nasd_srpc_send_reply_header(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn,
  nasd_srpc_header_t    *header)
{
  nasd_srpc_header_otw_t header_otw;
  nasd_srpc_memvec_t vec;
  nasd_status_t rc;
  int sent;

  nasd_srpc_header_t_marshall(header, header_otw);

  vec.buf = header_otw;
  vec.len = sizeof(nasd_srpc_header_otw_t);
  vec.next = NULL;

  while((conn->state&NASD_SRPC_STATE_OP_S) &&
    (!(conn->state&NASD_SRPC_STATE_ODAM)))
  {
    NASD_SRPC_CONN_WAIT_STATE(conn);
  }

  if (conn->state&NASD_SRPC_STATE_ODAM) {
    return(NASD_SRPC_SENDSTATE_BAD);
  }

  conn->state |= NASD_SRPC_STATE_OP_S;

#if 0
  nasd_printf("reply %u\n", header->op);
#endif

  rc = nasd_srpc_sys_sock_send(conn->sock, &vec,
    vec.len, &sent);
  if (rc) {
    conn->state |= NASD_SRPC_STATE_ODAM;
    conn->state &= ~NASD_SRPC_STATE_OP_S;
    return(NASD_SRPC_SENDSTATE_BAD);
  }

  conn->state &= ~NASD_SRPC_STATE_OP_S;

  if (sent != vec.len) {
    conn->state |= NASD_SRPC_STATE_ODAM;
    return(NASD_SRPC_SENDSTATE_BAD);
  }

  return(NASD_SUCCESS);
}

/*
 * Caller does not hold conn lock
 */
nasd_status_t
nasd_srpc_call_enable_pipes(
  nasd_srpc_listener_t     *listener,
  nasd_srpc_conn_t         *conn,
  nasd_srpc_server_call_t  *call)
{
  nasd_srpc_header_t header;
  nasd_status_t rc;

  NASD_SRPC_LOCK_CONN(conn);

  header.callid = call->callid;
  header.len = 0;
  header.op = NASD_SRPC_OP_PIPEENAB;
  header.opstat = 0;

  rc = nasd_srpc_send_reply_header(listener, conn, &header);

  NASD_SRPC_UNLOCK_CONN(conn);

  return(rc);
}

/*
 * call with conn lock held
 */
static void NASD_INLINE
nasd_srpc_server_relock_change_handle(
  nasd_srpc_listener_t     *listener,
  nasd_srpc_conn_t         *conn,
  nasd_srpc_server_call_t  *call,
  int                       dir,
  int                       diddle_handle_count)
{
  NASD_SRPC_CONN_INC_USE(conn);
  NASD_SRPC_UNLOCK_CONN(conn);

  NASD_SRPC_LOCK_LISTENER(listener);
  NASD_SRPC_LOCK_CONN(conn);

  if (diddle_handle_count) {
    call->handle_count_held += dir;
  }
  if (dir == 1) {
    NASD_SRPC_CONN_INC_HANDLE(listener, conn);
  }
  else if (dir == (-1)) {
    NASD_SRPC_CONN_DEC_HANDLE(listener, conn);
  }
  else {
    NASD_PANIC();
  }

  NASD_SRPC_UNLOCK_LISTENER(listener);

  NASD_SRPC_CONN_DEC_USE(conn);
}

/*
 * Call with connection lock held.
 * Caller has incremented conn->use_count for us.
 */
nasd_status_t
nasd_srpc_listener_handle_conn(
  nasd_srpc_listener_t      *listener,
  nasd_srpc_conn_t          *conn,
  nasd_srpc_server_call_t  **callp,
  nasd_srpc_server_call_t   *call_active)
{
  nasd_srpc_header_t header, rep_header;
  nasd_srpc_server_pipestate_t *pipe;
  nasd_srpc_header_otw_t header_otw;
  nasd_srpc_server_call_t *call;
  nasd_srpc_req_otw_t req_otw;
  nasd_status_t rc, rc2;
  int recvd, h;

  *callp = NULL;

handle_conn_grab_data:
  NASD_ASSERT(!(conn->state&NASD_SRPC_STATE_IDLE));
  NASD_ASSERT(!(conn->state&NASD_SRPC_STATE_IDAM));
  NASD_ASSERT(conn->handle_count > 0);

  if (call_active) {
    NASD_ASSERT(conn->state&NASD_SRPC_STATE_OP_R);
  }
  else {
    while((conn->state&NASD_SRPC_STATE_OP_R) &&
      (!(conn->state&NASD_SRPC_STATE_IDAM)))
    {
      NASD_SRPC_CONN_WAIT_STATE(conn);
    }
  }

  if (conn->state&NASD_SRPC_STATE_IDAM) {
    NASD_SRPC_CONN_DEC_USE(conn);
    return(NASD_SRPC_RECVSTATE_BAD);
  }

  if (call_active == NULL) {
    conn->state |= NASD_SRPC_STATE_OP_R;
  }

  NASD_SRPC_UNLOCK_CONN(conn);

  if (call_active) {
    rc = nasd_srpc_sys_sock_recv(conn->sock, header_otw,
      sizeof(nasd_srpc_header_otw_t),
      &recvd, NASD_SRPC_RECV_FILL);
  }
  else {
    rc = nasd_srpc_sys_sock_recv(conn->sock, header_otw,
      sizeof(nasd_srpc_header_otw_t),
      &recvd, NASD_SRPC_RECV_FILL|NASD_SRPC_RECV_NOWAIT);
  }

  NASD_SRPC_LOCK_CONN(conn);

  if (rc) {
    conn->state |= NASD_SRPC_STATE_IDAM;
    if (call_active == NULL) {
      conn->state &= ~NASD_SRPC_STATE_OP_R;
    }
    NASD_BROADCAST_COND(conn->state_cond);
    NASD_SRPC_CONN_DEC_USE(conn);
    return(rc);
  }

  if (recvd == 0) {
    /*
     * Nothing to do, so bail out.
     */
    if (call_active == NULL) {
      conn->state &= ~NASD_SRPC_STATE_OP_R;
    }
    NASD_SRPC_CONN_DEC_USE(conn);
    return(NASD_SUCCESS);
  }

  NASD_ASSERT(recvd == sizeof(nasd_srpc_header_otw_t));

  nasd_srpc_header_t_unmarshall(header_otw, &header);

#if 0
  switch(header.op) {
    case NASD_SRPC_OP_NOOP:
      nasd_printf("OP NOOP\n");
      break;
    case NASD_SRPC_OP_PIPEDATA:
      nasd_printf("OP PIPEDATA %u %u\n", header.callid, header.len);
      break;
    case NASD_SRPC_OP_PIPETERM:
      nasd_printf("OP PIPETERM %u\n", header.callid);
      break;
    case NASD_SRPC_OP_REQ:
      nasd_printf("OP REQ %u\n", header.callid);
      break;
    case NASD_SRPC_OP_SETSEQ:
      nasd_printf("OP SETSEQ\n");
      break;
    case NASD_SRPC_OP_REP:
      nasd_printf("OP REP\n");
      break;
    default:
      nasd_printf("OP unknown %d\n", (int)header.op);
  }
#endif

  if (header.op == NASD_SRPC_OP_SETSEQ) {
    /*
     * Special case: advance our sequence number (callid)
     */
    if (header.callid < conn->next_callid) {
      /*
       * We don't allow backtracking.
       * We're not going to actually send a reply,
       * because the client is probably equally confused
       * about callids at this point, and we don't know
       * how to address it properly. Hopefully the ensuing
       * bad callid errors will encourage the client to reset.
       */
      if (call_active == NULL) {
        conn->state &= ~NASD_SRPC_STATE_OP_R;
      }
      goto handle_conn_grab_data;
    }
    conn->next_callid = header.callid;
    rc = nasd_srpc_slurp_extra(conn, header.len);
    if (rc) {
      conn->state |= NASD_SRPC_STATE_IDAM;
      if (call_active == NULL) {
        conn->state &= ~NASD_SRPC_STATE_OP_R;
      }
      NASD_BROADCAST_COND(conn->state_cond);
      NASD_SRPC_CONN_DEC_USE(conn);
      return(rc);
    }
    if (call_active == NULL) {
      conn->state &= ~NASD_SRPC_STATE_OP_R;
    }
    goto handle_conn_grab_data;
  }

  if (header.callid > conn->next_callid) {
    /*
     * Bad call id.
     */
    rep_header.callid = header.callid;
    rep_header.len = 0;
    rep_header.op = NASD_SRPC_OP_REP;
    rep_header.opstat = NASD_SRPC_S_BAD_CALLID;
    rc2 = nasd_srpc_slurp_extra(conn, header.len);
    if (rc2) {
      conn->state |= NASD_SRPC_STATE_IDAM;
      if (call_active == NULL) {
        conn->state &= ~NASD_SRPC_STATE_OP_R;
      }
      NASD_BROADCAST_COND(conn->state_cond);
      NASD_SRPC_CONN_DEC_USE(conn);
      return(rc);
    }
    rc = nasd_srpc_send_reply_header(listener, conn, &rep_header);
    if (rc) {
      conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
      if (call_active == NULL) {
        conn->state &= ~NASD_SRPC_STATE_OP_R;
      }
      NASD_BROADCAST_COND(conn->state_cond);
      NASD_SRPC_CONN_DEC_USE(conn);
      return(rc);
    }
    /*
     * Nothing more to send.
     */
    if (call_active == NULL) {
      conn->state &= ~NASD_SRPC_STATE_OP_R;
    }
    goto handle_conn_grab_data;
  }

  /*
   * Refers to an outstanding operation.
   */
  switch(header.op) {
    case NASD_SRPC_OP_NOOP:
      /*
       * Okay, we no-opd. (Should we allow a noop
       * with a used callid? This does.)
       */
      rc = nasd_srpc_slurp_extra(conn, header.len);
      if (rc) {
        conn->state |= NASD_SRPC_STATE_IDAM;
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
        }
        NASD_BROADCAST_COND(conn->state_cond);
        NASD_SRPC_CONN_DEC_USE(conn);
        return(rc);
      }
      if (call_active == NULL) {
        conn->state &= ~NASD_SRPC_STATE_OP_R;
      }
      goto handle_conn_grab_data;
      /* NOTREACHED */
      break;
    case NASD_SRPC_OP_PIPEDATA:
    case NASD_SRPC_OP_PIPETERM:
      if (header.callid >= conn->next_callid) {
        rc2 = nasd_srpc_slurp_extra(conn, header.len);
        if (rc2) {
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        /*
         * We don't recognize this callid
         */
        rep_header.callid = header.callid;
        rep_header.len = 0;
        rep_header.op = NASD_SRPC_OP_REP;
        rep_header.opstat = NASD_SRPC_S_BAD_CALLID;
        rc = nasd_srpc_send_reply_header(listener, conn, &rep_header);
        if (rc) {
          conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        /*
         * Nothing more to send.
         */
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          goto handle_conn_grab_data;
        }
        NASD_SRPC_CONN_DEC_USE(conn);
        return(NASD_SUCCESS);
      }
      /*
       * We are not the droids this op is looking for.
       * Locate the call that is being referred to.
       */
      h = NASD_SRPC_CALL_HASH(header.callid);
      for(call = conn->s_call_buckets[h].next;
          call != &conn->s_call_buckets[h];
          call = call->next)
      {
        if (call->callid == header.callid)
          break;
      }
      if (call == &conn->s_call_buckets[h]) {
        /*
         * We don't recognize this callid
         */
        rc2 = nasd_srpc_slurp_extra(conn, header.len);
        if (rc2) {
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
#if 0
        rep_header.callid = header.callid;
        rep_header.len = 0;
        rep_header.op = NASD_SRPC_OP_REP;
        rep_header.opstat = NASD_SRPC_S_BAD_CALLID;
        rc = nasd_srpc_send_reply_header(listener, conn, &rep_header);
        if (rc) {
          conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        /*
         * Nothing more to send.
         */
#endif
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          goto handle_conn_grab_data;
        }
        NASD_SRPC_CONN_DEC_USE(conn);
        return(NASD_SUCCESS);
      }
      if (header.opstat >= call->npipes) {
        /* bad pipe number */
        rc2 = nasd_srpc_slurp_extra(conn, header.len);
        if (rc2) {
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        rep_header.callid = header.callid;
        rep_header.len = 0;
        rep_header.op = NASD_SRPC_OP_REP;
        rep_header.opstat = NASD_SRPC_S_BAD_PIPENO;
        rc = nasd_srpc_send_reply_header(listener, conn, &rep_header);
        if (rc) {
          conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        /*
         * Nothing more to send.
         */
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          goto handle_conn_grab_data;
        }
        NASD_SRPC_CONN_DEC_USE(conn);
        return(NASD_SUCCESS);
      }
      pipe = &call->pipes[header.opstat];
      if (header.op == NASD_SRPC_OP_PIPETERM) {
        pipe->state = NASD_SRPC_PIPESTATE_TERM;
        if (call->cur_pipe == pipe) {
          call->cur_pipe = NULL;
          NASD_ASSERT(call->pull_remain == 0);
        }
        NASD_SIGNAL_COND(call->data_ready);
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          goto handle_conn_grab_data;
        }
        NASD_SRPC_CONN_DEC_USE(conn);
        return(NASD_SUCCESS);
      }
      if ((pipe->state == NASD_SRPC_PIPESTATE_RTERM)
        || (pipe->state == NASD_SRPC_PIPESTATE_TERM))
      {
        /* receiver doesn't care, save trouble */
        rc2 = nasd_srpc_slurp_extra(conn, header.len);
        if (rc2) {
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          goto handle_conn_grab_data;
        }
        NASD_SRPC_CONN_DEC_USE(conn);
        return(NASD_SUCCESS);
      }
      if (call->cur_pipe && (header.opstat != call->cur_pipe->pipenum)) {
        /* wrong pipe */
        rc2 = nasd_srpc_slurp_extra(conn, header.len);
        if (rc2) {
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        rep_header.callid = header.callid;
        rep_header.len = 0;
        rep_header.op = NASD_SRPC_OP_REP;
        rep_header.opstat = NASD_SRPC_S_BAD_PIPENO;
        rc = nasd_srpc_send_reply_header(listener, conn, &rep_header);
        if (rc) {
          conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        /*
         * Nothing more to send.
         */
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          goto handle_conn_grab_data;
        }
        NASD_SRPC_CONN_DEC_USE(conn);
        return(NASD_SUCCESS);
      }

      if (header.op == NASD_SRPC_OP_PIPEDATA) {
        /*
         * We already hold a conn ref, so we can just jump all over
         * the locks here to do the dance to get the listener lock.
         * We'll give up the extra ref when we're done, since the
         * pipe user already holds a ref. We want to flag this as
         * being handled by the thread doing the pipe stuff.
         */
        NASD_SRPC_UNLOCK_CONN(conn);
        NASD_SRPC_LOCK_LISTENER(listener);
        NASD_SRPC_LOCK_CONN(conn);
        NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
        call->handle_count_held++;
        NASD_SRPC_CONN_INC_HANDLE(listener, conn);
        NASD_SRPC_UNLOCK_LISTENER(listener);
      }

      /*
       * We have pipe stuff for an in-progress call.
       * Hand it off.
       */
      NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
      call->pipe_header = header;
      call->pull_remain = header.len;
      NASD_SIGNAL_COND(call->data_ready);

      NASD_SRPC_CONN_DEC_USE(conn);
      return(NASD_SUCCESS);
      /* NOTREACHED */
      break;
    case NASD_SRPC_OP_REQ:
      if (header.callid != conn->next_callid) {
        /*
         * Unexpected callid- out of sequence.
         */
        rc2 = nasd_srpc_slurp_extra(conn, header.len);
        if (rc2) {
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        rep_header.callid = header.callid;
        rep_header.len = 0;
        rep_header.op = NASD_SRPC_OP_REP;
        rep_header.opstat = NASD_SRPC_S_BAD_CALLID;
        rc = nasd_srpc_send_reply_header(listener, conn, &rep_header);
        if (rc) {
          conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        /*
         * Nothing more to send.
         */
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
        }
        goto handle_conn_grab_data;
      }

      /*
       * Call is at least legit enough to be recognized as a new call
       * on the connection.
       */
      conn->next_callid++;

      if (header.len >
        ((NASD_SRPC_MAX_NARGS*NASD_SRPC_MAX_ARGSIZE)+sizeof(nasd_srpc_req_t)))
      {
        /*
         * Not enough memory in argument buffer, which
         * should never happen unless the client is speaking
         * a different protocol version than the header.
         */
        rc = nasd_srpc_slurp_extra(conn, header.len);
        if (rc) {
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        rep_header.callid = header.callid;
        rep_header.len = 0;
        rep_header.op = NASD_SRPC_OP_REP;
        rep_header.opstat = NASD_SRPC_S_BAD_OPLEN;
        rc = nasd_srpc_send_reply_header(listener, conn, &rep_header);
        if (rc) {
          conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
        }
        goto handle_conn_grab_data;
      }
      rc = nasd_srpc_server_call_get(&call);
      if (rc) {
        /*
         * Not enough memory to process- so sad.
         */
        rc2 = nasd_srpc_slurp_extra(conn, header.len);
        if (rc2) {
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        rep_header.callid = header.callid;
        rep_header.len = 0;
        rep_header.op = NASD_SRPC_OP_REP;
        rep_header.opstat = NASD_SRPC_S_FAIL;
        rc2 = nasd_srpc_send_reply_header(listener, conn, &rep_header);
        if (rc2) {
          conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
        }
        NASD_SRPC_CONN_DEC_USE(conn);
        return(rc);
      }

      /*
       * Now we have a call pending on the connection, and
       * a call structure to stuff everything into.
       * Get the req structure, then the args.
       */
      rc = nasd_srpc_sys_sock_recv(conn->sock, req_otw,
        sizeof(nasd_srpc_req_otw_t), &recvd, NASD_SRPC_RECV_FILL);
      if (rc) {
        nasd_srpc_server_call_free(call);
        conn->state |= NASD_SRPC_STATE_IDAM;
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
        }
        NASD_BROADCAST_COND(conn->state_cond);
        NASD_SRPC_CONN_DEC_USE(conn);
        return(rc);
      }
      nasd_srpc_req_t_unmarshall(req_otw, &call->req);
      if (header.len > sizeof(nasd_srpc_req_otw_t)) {
        rc = nasd_srpc_sys_sock_recv(conn->sock, call->argmem,
          header.len-sizeof(nasd_srpc_req_otw_t), &recvd,
          NASD_SRPC_RECV_FILL);
        if (rc) {
          nasd_srpc_server_call_free(call);
          conn->state |= NASD_SRPC_STATE_IDAM;
          if (call_active == NULL) {
            conn->state &= ~NASD_SRPC_STATE_OP_R;
          }
          NASD_BROADCAST_COND(conn->state_cond);
          NASD_SRPC_CONN_DEC_USE(conn);
          return(rc);
        }
        NASD_ASSERT(recvd == (header.len-sizeof(nasd_srpc_req_otw_t)));
        call->arglen = recvd;
      }
      else {
        call->arglen = 0;
      }

      call->call_header = header;
      call->callid = call->call_header.callid;

      call->handle_count_held = 0;
      call->handle_count_held_opt = 0;

      call->pipe_header.opstat = NASD_SRPC_PIPENUM_NOPIPES;

      h = NASD_SRPC_CALL_HASH(header.callid);
      call->next = &conn->s_call_buckets[h];
      call->prev = conn->s_call_buckets[h].prev;
      call->prev->next = call;
      call->next->prev = call;
      call->ishashed = 1;
      call->pull_remain = 0;
      call->cur_pipe = NULL;

      *callp = call;

      NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
      if (call_active == NULL) {
        conn->state &= ~NASD_SRPC_STATE_OP_R;
        NASD_SIGNAL_COND(conn->state_cond);
      }

      /* keep the use_count ref */
      return(NASD_SUCCESS);
      /* NOTREACHED */
      break;
    case NASD_SRPC_OP_SETSEQ:
      /*
       * This case is handled above.
       */
      NASD_PANIC();
      break;
    case NASD_SRPC_OP_REP:
    default:
      /*
       * Unrecognized or bad op
       * (Example of bad: a reply. We only take requests.)
       */
      rc2 = nasd_srpc_slurp_extra(conn, header.len);
      if (rc2) {
        conn->state |= NASD_SRPC_STATE_IDAM;
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
        }
        NASD_BROADCAST_COND(conn->state_cond);
        NASD_SRPC_CONN_DEC_USE(conn);
        return(rc);
      }
      rep_header.callid = header.callid;
      rep_header.len = 0;
      rep_header.op = NASD_SRPC_OP_REP;
      rep_header.opstat = NASD_SRPC_S_BAD_OP;
      rc = nasd_srpc_send_reply_header(listener, conn, &rep_header);
      if (rc) {
        conn->state |= NASD_SRPC_STATE_IDAM|NASD_SRPC_STATE_ODAM;
        if (call_active == NULL) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
        }
        NASD_BROADCAST_COND(conn->state_cond);
        NASD_SRPC_CONN_DEC_USE(conn);
        return(rc);
      }
      /*
       * Nothing more to send.
       */
      if (call_active == NULL) {
        conn->state &= ~NASD_SRPC_STATE_OP_R;
      }
      goto handle_conn_grab_data;
  }

  /* NOTREACHED */
  NASD_PANIC();
}

/*
 * RPC "service" thread. Master thread dispatches
 * work to it, it in turn switches through the demux
 * into server code.
 */
void
nasd_srpc_listener_proc(
  void  *listener_arg)
{
  nasd_srpc_header_otw_t rep_header_otw;
  nasd_srpc_listener_t *listener;
  nasd_srpc_header_t rep_header;
  nasd_srpc_server_call_t *call;
  nasd_srpc_memvec_t vec[2];
  nasd_srpc_conn_t *conn;
  nasd_status_t rc, rc2;
  int sent, total_len;

  listener = (nasd_srpc_listener_t *)listener_arg;

  NASD_THREADGROUP_RUNNING(&listener->listener_group);

  NASD_SRPC_LOCK_LISTENER(listener);

  while(!NASD_THREADGROUP_SHUTDOWNP(&listener->listener_group)) {
    conn = listener->conn_active_ring.active_next;
    while(conn == &listener->conn_active_ring) {
      NASD_SRPC_SYS_LISTENER_WAIT_ACTIVE_CONN(listener, conn);
      if (NASD_THREADGROUP_SHUTDOWNP(&listener->listener_group)) {
        goto done;
      }
      conn = listener->conn_active_ring.active_next;
    }
    /* remove connection from active ring */
    NASD_ASSERT(conn->state&NASD_SRPC_STATE_AR);
    conn->active_prev->active_next = conn->active_next;
    conn->active_next->active_prev = conn->active_prev;
    conn->active_next = NULL;
    conn->active_prev = NULL;
    conn->state &= ~NASD_SRPC_STATE_AR;

    /*
     * Here, we implicitly "take over" a handle_count ref
     * that was created when the conn was transited to the
     * active ring.
     */

    NASD_SRPC_CONN_INC_USE(conn);
    NASD_SRPC_UNLOCK_LISTENER(listener);

    NASD_SRPC_LOCK_CONN(conn);

    if (conn->pend_calls) {
      /*
       * Someone has already read our next call for us.
       */
      call = conn->pend_calls;
      conn->pend_calls = call->pend_next;
      if (conn->pend_calls == NULL) {
        NASD_ASSERT(conn->pend_calls_tail == call);
        conn->pend_calls_tail = NULL;
      }
    }
    else {
      /* consumes the use_count reference we took */
      if (conn->state & (NASD_SRPC_STATE_OP_R|NASD_SRPC_STATE_IDAM)) {
        /*
         * Either someone else is already receiving,
         * or the connection is failing/has failed.
         * Bail.
         */
        NASD_SRPC_UNLOCK_CONN(conn);
        NASD_SRPC_LOCK_LISTENER(listener);
        NASD_SRPC_LOCK_CONN(conn);
        NASD_SRPC_CONN_DEC_HANDLE(listener, conn);
        NASD_SRPC_CONN_DEC_USE(conn);
        NASD_SRPC_UNLOCK_CONN(conn);
        continue;
      }
      rc = nasd_srpc_listener_handle_conn(listener, conn, &call, NULL);
      if (rc) {
        if (!(conn->state&NASD_SRPC_STATE_DEAD)) {
          /*
           * We currently have no ref, so we take one
           * temporarily to release the conn lock so
           * we can take the listener lock and put the
           * conn on the listener dead list. Whew!
           */
          NASD_SRPC_CONN_INC_USE(conn);
          NASD_SRPC_UNLOCK_CONN(conn);

          NASD_SRPC_LOCK_LISTENER(listener);
          NASD_SRPC_LOCK_CONN(conn);

          NASD_SRPC_CONN_MARK_DEAD(listener, conn);

          NASD_SRPC_CONN_DEC_USE(conn);
          NASD_SRPC_CONN_DEC_HANDLE(listener, conn);

          NASD_SRPC_UNLOCK_CONN(conn);
          NASD_SRPC_UNLOCK_LISTENER(listener);

          NASD_SRPC_SYS_LISTENER_SIGNAL_DEAD_COND(listener);
        }

        NASD_SRPC_LOCK_LISTENER(listener);
        continue;
      }
    }

    /*
     * Here, we have a use_count ref, but no conn lock
     *
     * Note that in both cases of this if(), we release
     * the conn lock first. We don't move this outside
     * the conditional because we need that lock to test it.
     */

    if (conn->pend_calls) {
      /*
       * Still more to process. Throw it back into the
       * active list for another thread to take a crack.
       * Tail-enqueue to help avoid denial-of-service.
       * We "give" our handle_count ref to the
       * next thing to come and pick this up (see
       * comment below)
       */
      NASD_SRPC_UNLOCK_CONN(conn);
      /* okay to do this, because we hold a use_count ref */
      NASD_SRPC_LOCK_LISTENER(listener);
      NASD_SRPC_LOCK_CONN(conn);
      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;
      NASD_SRPC_UNLOCK_CONN(conn);
      NASD_SRPC_UNLOCK_LISTENER(listener);
    }
    else {
      NASD_SRPC_UNLOCK_CONN(conn);
      /* okay to do this, because we hold a use_count ref */
      NASD_SRPC_LOCK_LISTENER(listener);

      /*
       * We're surrendering our handle_count ref here.
       *
       * If there's pending calls, it's "kept" for the
       * next thing to come along and dequeue a call
       * (see above).
       */
      NASD_SRPC_LOCK_CONN(conn);
      NASD_SRPC_CONN_DEC_HANDLE(listener, conn);
      NASD_SRPC_UNLOCK_CONN(conn);

      NASD_SRPC_UNLOCK_LISTENER(listener);
    }

    /*
     * Here, we have a use_count ref, but no conn lock
     * We have surrendered our handle_count ref.
     */

    if (call) {
      /*
       * We have a new call. Whopee! Now we become
       * the service thread for the call. SERVICE ME BABY.
       * We are the holder of the use_count ref.
       */
      rc = listener->demux_proc(listener, conn, call);

      /*
       * Input pipes that wanted to handle the connection
       * themselves rather than let the master thread see it
       * too handle_count refereces. Clear any remaining such
       * out (could be left-over from error conditions).
       */
      if (call->handle_count_held) {
        NASD_SRPC_LOCK_LISTENER(listener);
        NASD_SRPC_LOCK_CONN(conn);
        while (call->handle_count_held) {
          call->handle_count_held--;
          NASD_SRPC_CONN_DEC_HANDLE(listener, conn);
        }
        if (call->ishashed) {
          call->prev->next = call->next;
          call->next->prev = call->prev;
          call->ishashed = 0;
        }
        NASD_SRPC_UNLOCK_CONN(conn);
        NASD_SRPC_UNLOCK_LISTENER(listener);
      }

      if (call->ishashed) {
        NASD_SRPC_LOCK_CONN(conn);
        call->prev->next = call->next;
        call->next->prev = call->prev;
        call->ishashed = 0;
        NASD_SRPC_UNLOCK_CONN(conn);
      }

      if (call->pipe_header.opstat != NASD_SRPC_PIPENUM_NOPIPES) {
        /*
         * There's data pending on the connection for a pipe from
         * this call. Flush it.
         */
        NASD_SRPC_LOCK_CONN(conn);
        if (!(conn->state & NASD_SRPC_STATE_IDAM)) {
          NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
          rc2 = nasd_srpc_slurp_extra(conn, call->pipe_header.len);
          if (rc2) {
            NASD_SRPC_LOCK_CONN(conn);
            conn->state |= NASD_SRPC_STATE_IDAM;
            NASD_SRPC_UNLOCK_CONN(conn);
          }
          if (rc == NASD_SUCCESS)
            rc = rc2;
        }
        conn->state &= ~NASD_SRPC_STATE_OP_R;
        NASD_SRPC_UNLOCK_CONN(conn);
        /* changed this to signal from broadcast */
        NASD_SIGNAL_COND(conn->state_cond);
      }

      if (rc) {
badjob:
        /*
         * We still have that use_count ref, but no locks
         */
        NASD_SRPC_LOCK_CONN(conn);

        conn->state |= NASD_SRPC_STATE_ODAM|NASD_SRPC_STATE_IDAM;

        if (call->ishashed) {
          call->prev->next = call->next;
          call->next->prev = call->prev;
          call->ishashed = 0;
        }
        nasd_srpc_server_call_free(call);

        NASD_SRPC_UNLOCK_CONN(conn);

        NASD_SRPC_LOCK_LISTENER(listener);

        NASD_SRPC_LOCK_CONN(conn);

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

        NASD_SRPC_CONN_DEC_USE(conn);

        NASD_SRPC_UNLOCK_CONN(conn);
        NASD_SRPC_UNLOCK_LISTENER(listener);


        NASD_SRPC_SYS_LISTENER_SIGNAL_DEAD_COND(listener);

        NASD_SRPC_LOCK_LISTENER(listener);
        continue;
      }

      if (call->cur_pipe) {
        NASD_ASSERT(call->pull_remain > 0);
        NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
        rc = nasd_srpc_slurp_extra(conn, call->pull_remain);
        if (rc) {
          goto badjob;
        }
        call->pull_remain = 0;
        call->cur_pipe = NULL;
        NASD_SRPC_LOCK_CONN(conn);
        conn->state &= ~NASD_SRPC_STATE_OP_R;
        NASD_SRPC_UNLOCK_CONN(conn);
        NASD_SIGNAL_COND(conn->state_cond);
      }

      rep_header.callid = call->callid;
      rep_header.len = call->reslen;
      rep_header.op = NASD_SRPC_OP_REP;
      rep_header.opstat = call->result;

      if (NASD_THREADGROUP_SHUTDOWNP(&listener->listener_group)) {
        nasd_srpc_server_call_free(call);
        NASD_SRPC_LOCK_CONN(conn);
        NASD_SRPC_CONN_DEC_USE(conn);
        NASD_SRPC_UNLOCK_CONN(conn);
        NASD_SRPC_LOCK_LISTENER(listener);
        goto done;
      }

      NASD_SRPC_LOCK_CONN(conn);

      while((conn->state&NASD_SRPC_STATE_OP_S) &&
        (!(conn->state&NASD_SRPC_STATE_ODAM)))
      {
        NASD_SRPC_CONN_WAIT_STATE(conn);
      }

      if (conn->state & NASD_SRPC_STATE_ODAM) {
        goto barf;
      }

      conn->state |= NASD_SRPC_STATE_OP_S;

      nasd_srpc_header_t_marshall(&rep_header, rep_header_otw);
      vec[0].buf = rep_header_otw;
      vec[0].len = sizeof(nasd_srpc_header_otw_t);
      vec[0].next = &vec[1];
      vec[1].buf = call->resmem;
      vec[1].len = call->reslen;
      vec[1].next = NULL;

      total_len = vec[0].len + vec[1].len;

      NASD_SRPC_UNLOCK_CONN(conn);

#if 0
      nasd_printf("listener_proc reply %u\n", rep_header.op);
#endif

      rc = nasd_srpc_sys_sock_send(conn->sock, vec, total_len, &sent);

      NASD_SRPC_LOCK_CONN(conn);

      conn->state &= ~NASD_SRPC_STATE_OP_S;

      if (rc || (sent != total_len)) {
barf:
        nasd_srpc_server_call_free(call);

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

        NASD_SRPC_LOCK_LISTENER(listener);
        NASD_SRPC_LOCK_CONN(conn);

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

        NASD_SRPC_CONN_DEC_USE(conn);

        NASD_SRPC_UNLOCK_CONN(conn);
        NASD_SRPC_UNLOCK_LISTENER(listener);


        NASD_SRPC_SYS_LISTENER_SIGNAL_DEAD_COND(listener);

        NASD_SRPC_LOCK_LISTENER(listener);
        continue;
      }

      nasd_srpc_server_call_free(call);

      NASD_BROADCAST_COND(conn->state_cond);
      NASD_SRPC_CONN_DEC_USE(conn);

      NASD_SRPC_UNLOCK_CONN(conn);

      /*
       * We fallthrough here to go and get another call.
       * We might ignore another call on this connection
       * to service a different one that way, but that's
       * our "fairness" algorithm at work. "Fairness"
       * looks a lot like token-passing.
       */
    }

    NASD_SRPC_LOCK_LISTENER(listener);
  }

done:
  NASD_SRPC_UNLOCK_LISTENER(listener);
  NASD_THREADGROUP_DONE(&listener->listener_group);
}

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

  listener = (nasd_srpc_listener_t *)listener_arg;

  NASD_THREADGROUP_INDICATE_SHUTDOWN(&listener->listener_group);
  NASD_SRPC_SYS_LISTENER_BROADCAST_CONN_ACTIVE_COND(listener);
  NASD_THREADGROUP_WAIT_STOP(&listener->listener_group);
}

void
nasd_srpc_listener_killgroup(
  void  *group_arg)
{
  nasd_threadgroup_t *group;

  group = (nasd_threadgroup_t *)group_arg;

  nasd_destroy_threadgroup(group);
}

void
nasd_srpc_listener_kill_conn(
  nasd_srpc_listener_t  *listener,
  nasd_srpc_conn_t      *conn)
{
  nasd_srpc_server_call_t *call, *next;
  nasd_status_t rc;
  int i;

  NASD_ASSERT(conn->sock != NULL);

  NASD_SRPC_LOCK_LISTENER(listener);
  NASD_SRPC_LOCK_CONN(conn);
  conn->state |=
    NASD_SRPC_STATE_DISC|NASD_SRPC_STATE_ODAM|NASD_SRPC_STATE_IDAM;
  if (conn->state&NASD_SRPC_STATE_IDLE) {
    NASD_ASSERT(!(conn->state&NASD_SRPC_STATE_REIDLE));
    conn->idle_prev->idle_next = conn->idle_next;
    conn->idle_next->idle_prev = conn->idle_prev;
    conn->state &= ~NASD_SRPC_STATE_IDLE;
  }
  NASD_SRPC_UNLOCK_LISTENER(listener);

  /*
   * Get rid of pending RPCs.
   */
  while ((call = conn->pend_calls)) {
    conn->pend_calls = call->pend_next;
    if (conn->pend_calls == NULL) {
      NASD_ASSERT(conn->pend_calls_tail == call);
      conn->pend_calls_tail = NULL;
    }
    nasd_srpc_server_call_free(call);
    NASD_SRPC_CONN_DEC_USE(conn);
  }
  NASD_SRPC_UNLOCK_CONN(conn);

  NASD_SRPC_LOCK_CONN(conn);
  NASD_BROADCAST_COND(conn->state_cond);
  /* wait for RPCs to end */
  while(conn->use_count) {
    NASD_BROADCAST_COND(conn->state_cond);
    for(i=0;i<NASD_SRPC_CALLHASH_BUCKETS;i++) {
      for(call = conn->s_call_buckets[i].next;
        call != &conn->s_call_buckets[i];
        call = next)
      {
        next = call->next;
        NASD_BROADCAST_COND(call->data_ready);
      }
    }
    NASD_SRPC_CONN_WAIT_USE(conn);
  }
  NASD_SRPC_UNLOCK_CONN(conn);

  /* conn has no outstanding RPCs */
  NASD_ASSERT(conn->use_count == 0);

  rc = nasd_srpc_sys_listener_conn_dead(listener, conn);
  if (rc) {
    nasd_printf("SRPC SERVER WARNING: got 0x%x (%s) informing sys listener "
      "0x%lx of dead connection 0x%lx\n",
      rc, nasd_error_string(rc),
      (unsigned long)listener, (unsigned long)conn);
  }

  rc = nasd_srpc_sys_sock_destroy(conn->sock);
  if (rc) {
    nasd_printf("SRPC SERVER WARNING: got 0x%x (%s) destroying sys socket\n",
      rc, nasd_error_string(rc));
  }
  nasd_srpc_sock_free(conn->sock);
  conn->sock = NULL;
  nasd_srpc_conn_free(conn);
}

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

  listener = (nasd_srpc_listener_t *)listener_arg;

  /*
   * Kill off dead-collector thread.
   */
  NASD_THREADGROUP_INDICATE_SHUTDOWN(&listener->dead_group);
  NASD_SRPC_SYS_LISTENER_BROADCAST_DEAD_COND(listener);
  NASD_THREADGROUP_WAIT_STOP(&listener->dead_group);
}

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

  listener = (nasd_srpc_listener_t *)listener_arg;

  nasd_srpc_sys_listener_stop(listener);
}

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

  listener = (nasd_srpc_listener_t *)listener_arg;

  nasd_srpc_free_mem(listener->threads,
    listener->nthreads*sizeof(nasd_thread_t));
}

nasd_status_t
nasd_srpc_listener_start(
  nasd_srpc_listener_t  **listenerp,
  int                     service_threads,
  nasd_uint16             ipport,
  nasd_status_t         (*demux_proc)())
{
  nasd_srpc_listener_set_t *setmem;
  nasd_srpc_listener_t *listener;
  nasd_status_t rc;
  void *mem;
  int i;

  if (nasd_srpc_use_counter == 0) {
    return(NASD_SYS_NOT_INITIALIZED);
  }

  *listenerp = NULL;

  rc = nasd_srpc_allocate_mem(&mem, sizeof(nasd_srpc_listener_t));
  if (rc)
    return(rc);
  listener = mem;
  bzero((char *)listener, sizeof(nasd_srpc_listener_t));
  listener->conn_idle_ring.idle_next = &listener->conn_idle_ring;
  listener->conn_idle_ring.idle_prev = &listener->conn_idle_ring;

  listener->nthreads = service_threads;

  rc = nasd_shutdown_list_init(&listener->shutdown_list);
  if (rc) {
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  rc = nasd_srpc_allocate_mem(&mem, service_threads*sizeof(nasd_thread_t));
  if (rc) {
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(NASD_NO_MEM);
  }
  listener->threads = mem;
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_free_listener_threads, listener);
  if (rc) {
    nasd_srpc_free_listener_threads(listener);
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  rc = nasd_srpc_sys_listener_init(listener);
  if (rc) {
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  listener->conn_active_ring.active_prev = &listener->conn_active_ring;
  listener->conn_active_ring.active_next = &listener->conn_active_ring;

  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_listener_do_shutdown, listener);
  if (rc) {
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  listener->demux_proc = demux_proc;
  listener->ipport = ipport;

  rc = nasd_init_threadgroup(&listener->dead_group);
  if (rc) {
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_listener_killgroup, &listener->dead_group);
  if (rc) {
    nasd_srpc_listener_killgroup(&listener->dead_group);
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  rc = nasd_thread_create_w_name(&listener->dead_thread,
    nasd_srpc_listener_dead_proc, listener,
    "SRPC listener dead thread");
  if (rc) {
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }
  NASD_THREADGROUP_STARTED(&listener->dead_group);
  NASD_THREADGROUP_WAIT_START(&listener->dead_group);
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_listener_stop_deadthread, listener);
  if (rc) {
    nasd_srpc_listener_stop_deadthread(listener);
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  rc = nasd_init_threadgroup(&listener->listener_group);
  if (rc) {
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_listener_killgroup, &listener->listener_group);
  if (rc) {
    nasd_srpc_listener_killgroup(&listener->listener_group);
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  for(i=0;i<listener->nthreads;i++) {
    rc = nasd_thread_create_w_name(&listener->threads[i],
      nasd_srpc_listener_proc, listener,
      "SRPC listener worker thread");
    if (rc) {
      NASD_THREADGROUP_WAIT_START(&listener->listener_group);
      nasd_srpc_listener_proc_shutdown(listener);
      nasd_shutdown_list_shutdown(listener->shutdown_list,
        NASD_SHUTDOWN_ANNOUNCE_NONE);
      nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
      return(rc);
    }
    NASD_THREADGROUP_STARTED(&listener->listener_group);
  }
  NASD_THREADGROUP_WAIT_START(&listener->listener_group);
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_listener_proc_shutdown, listener);
  if (rc) {
    nasd_srpc_listener_proc_shutdown(listener);
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  rc = nasd_srpc_sys_listener_start(listener);
  if (rc) {
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }
  rc = nasd_shutdown_proc(listener->shutdown_list,
    nasd_srpc_listener_stop_syslistener, listener);
  if (rc) {
    nasd_srpc_listener_stop_syslistener(listener);
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(rc);
  }

  rc = nasd_srpc_allocate_mem(&mem, sizeof(nasd_srpc_listener_set_t));
  if (rc) {
    nasd_shutdown_list_shutdown(listener->shutdown_list,
      NASD_SHUTDOWN_ANNOUNCE_NONE);
    nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));
    return(NASD_NO_MEM);
  }
  setmem = mem;

  NASD_LOCK_MUTEX(nasd_srpc_listener_set_lock);

  listener->serial_num = nasd_srpc_listener_serial_num_next;
  nasd_srpc_listener_serial_num_next++;

  setmem->listener = listener;
  setmem->serial_num = listener->serial_num;
  setmem->next = nasd_srpc_listener_set;
  nasd_srpc_listener_set = setmem;

  NASD_UNLOCK_MUTEX(nasd_srpc_listener_set_lock);

  *listenerp = listener;

  return(NASD_SUCCESS);
}

/*
 * Caller does not hold listener lock
 * Caller has removed conn from any lists
 */
void
nasd_srpc_listener_dead_proc(
  void  *listener_arg)
{
  nasd_srpc_listener_t *listener;
  nasd_srpc_conn_t *conn;

  listener = (nasd_srpc_listener_t *)listener_arg;

  NASD_THREADGROUP_RUNNING(&listener->dead_group);

  while(!NASD_THREADGROUP_SHUTDOWNP(&listener->dead_group)) {
    if (NASD_THREADGROUP_SHUTDOWNP(&listener->dead_group)) {
      goto done;
    }

    NASD_SRPC_LOCK_LISTENER(listener);

    if (NASD_THREADGROUP_SHUTDOWNP(&listener->dead_group)) {
      NASD_SRPC_UNLOCK_LISTENER(listener);
      goto done;
    }

    while(listener->dead_list == NULL) {
      NASD_SRPC_SYS_LISTENER_WAIT_FOR_DEAD(listener);
      if (NASD_THREADGROUP_SHUTDOWNP(&listener->dead_group)) {
        NASD_SRPC_UNLOCK_LISTENER(listener);
        goto done;
      }
    }

    conn = listener->dead_list;
    listener->dead_list = conn->next;

    NASD_SRPC_UNLOCK_LISTENER(listener);

    NASD_ASSERT(conn->state&NASD_SRPC_STATE_DEAD);
    nasd_srpc_listener_kill_conn(listener, conn);
  }

done:
  NASD_THREADGROUP_DONE(&listener->dead_group);
}

nasd_status_t
nasd_srpc_listener_destroy(
  nasd_srpc_listener_t  *listener)
{
  nasd_srpc_listener_set_t *r, *p;
  nasd_status_t rc;
  nasd_uint64 sn;

  sn = listener->serial_num;

  rc = NASD_SUCCESS;

  nasd_shutdown_list_shutdown(listener->shutdown_list,
    NASD_SHUTDOWN_ANNOUNCE_NONE);

  nasd_srpc_free_mem(listener, sizeof(nasd_srpc_listener_t));

  NASD_LOCK_MUTEX(nasd_srpc_listener_set_lock);

  for(p=NULL,r=nasd_srpc_listener_set;r;p=r,r=r->next) {
    if (r->serial_num == sn) {
      NASD_ASSERT(r->listener == listener);
      break;
    }
  }
  if (r) {
    if (p) {
      p->next = r->next;
    }
    else {
      nasd_srpc_listener_set = r->next;
      nasd_srpc_free_mem(r, sizeof(nasd_srpc_listener_set_t));
    }
  }
  else {
    /* Where did it go ?! */
    NASD_PANIC();
  }

  NASD_UNLOCK_MUTEX(nasd_srpc_listener_set_lock);

  NASD_BROADCAST_COND(nasd_srpc_listeners_changed);

  return(NASD_SUCCESS);
}

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

  listener = (nasd_srpc_listener_t *)listener_arg;

  /*
   * The dead thread is no longer running.
   * Service threads are no longer running.
   * We can run through and kill at will.
   */

  for(conn = listener->dead_list;
      conn != NULL;
      conn = listener->dead_list)
  {
    listener->dead_list = conn->next;
    conn->next = NULL;

    NASD_SRPC_LOCK_LISTENER(listener);
    NASD_ASSERT(conn->state&NASD_SRPC_STATE_DEAD);
    nasd_srpc_listener_kill_conn(listener, conn);
    NASD_SRPC_UNLOCK_LISTENER(listener);
  }

  for(conn = listener->conn_active_ring.active_next;
      conn != &listener->conn_active_ring;
      conn = next)
  {
    next = conn->active_next;
    NASD_ASSERT(conn->state&NASD_SRPC_STATE_AR);
    conn->active_prev->active_next = conn->active_next;
    conn->active_next->active_prev = conn->active_prev;
    conn->active_next = NULL;
    conn->active_prev = NULL;
    conn->state &= ~NASD_SRPC_STATE_AR;
    nasd_srpc_listener_kill_conn(listener, conn);
  }

  for(conn = listener->conn_idle_ring.idle_next;
      conn != &listener->conn_idle_ring;
      conn = next)
  {
    next = conn->idle_next;
    NASD_ASSERT(conn->use_count == 0);
    NASD_ASSERT(conn->state & NASD_SRPC_STATE_IDLE);
    nasd_srpc_listener_kill_conn(listener, conn);
  }
}

nasd_status_t
nasd_srpc_server_pipe_push(
  void        *pipe_arg,
  void        *buf,
  nasd_len_t   len)
{
  nasd_srpc_server_pipestate_t *pipe;
  nasd_srpc_header_otw_t header_otw;
  nasd_srpc_memvec_t vec[2];
  nasd_srpc_header_t header;
  nasd_srpc_conn_t *conn;
  int send_len, sent;
  nasd_status_t rc;

#if NASD_SRPC_TIMERS > 0
  nasd_timer_t tm;
  nasd_timespec_t ts;
#endif /* NASD_SRPC_TIMERS > 0 */

  pipe = (nasd_srpc_server_pipestate_t *)pipe_arg;

  conn = pipe->conn;
  NASD_SRPC_LOCK_CONN(conn);

  if (pipe->state == NASD_SRPC_PIPESTATE_TERM) {
    rc = NASD_SRPC_PIPE_TERMINATED;
    goto done;
  }

  if (pipe->state == NASD_SRPC_PIPESTATE_PEND) {
    pipe->state = NASD_SRPC_PIPESTATE_RUNNING;
  }

  header.callid = pipe->call->callid;
  header.len = len;
  header.op = NASD_SRPC_OP_PIPEDATA;
  header.opstat = pipe->pipenum;

  vec[0].buf = header_otw;
  vec[0].len = sizeof(nasd_srpc_header_otw_t);
  vec[0].next = &vec[1];
  vec[1].buf = buf;
  vec[1].len = len;
  vec[1].next = NULL;

  send_len = vec[0].len + len;

  if (len == 0) {
    /*
     * Caller is terminating pipe
     */
    pipe->state = NASD_SRPC_PIPESTATE_TERM;
    header.op = NASD_SRPC_OP_PIPETERM;
    vec[0].next = NULL;
  }

  nasd_srpc_header_t_marshall(&header, header_otw);

  NASD_SRPC_START_TIMER(&tm, &ts);
  while((conn->state&NASD_SRPC_STATE_OP_S) &&
    (!(conn->state&NASD_SRPC_STATE_ODAM)))
  {
    NASD_SRPC_CONN_WAIT_STATE(conn);
  }
  NASD_SRPC_FINISH_TIMER(&tm, &ts, &nasd_srpc_stats.pipe_push_wait_ts);

  if (conn->state&NASD_SRPC_STATE_ODAM) {
    rc = NASD_SRPC_SENDSTATE_BAD;
    goto done;
  }

  conn->state |= NASD_SRPC_STATE_OP_S;

  NASD_SRPC_START_TIMER(&tm, &ts);
  rc = nasd_srpc_sys_sock_send(conn->sock, vec,
    send_len, &sent);
  NASD_SRPC_FINISH_TIMER(&tm, &ts, &nasd_srpc_stats.pipe_push_send_ts);
  if (rc) {
    conn->state |= NASD_SRPC_STATE_ODAM;
    conn->state &= ~NASD_SRPC_STATE_OP_S;
    rc = NASD_SRPC_SENDSTATE_BAD;
    goto done;
  }

  conn->state &= ~NASD_SRPC_STATE_OP_S;

  if (sent != send_len) {
    conn->state |= NASD_SRPC_STATE_ODAM;
    rc = NASD_SRPC_SENDSTATE_BAD;
    goto done;
  }

  rc = NASD_SUCCESS;

done:
  NASD_BROADCAST_COND(conn->state_cond);
  NASD_SRPC_UNLOCK_CONN(conn);

  return(rc);
}

nasd_status_t
nasd_srpc_server_procpipe_push(
  void        *pipe_arg,
  void        *buf,
  nasd_len_t   len,
  nasd_byte_t *ign1,
  nasd_byte_t *ign2,
  int         *ign3)
{
  return nasd_srpc_server_pipe_push(pipe_arg, buf, len);
}

nasd_status_t
nasd_srpc_server_pipe_pull(
  void        *pipe_arg,
  void        *buf,
  nasd_len_t   len,
  nasd_len_t  *bytes_pulled)
{
  nasd_srpc_server_call_t *call, *newcall;
  nasd_srpc_server_pipestate_t *pipe;
  nasd_srpc_listener_t *listener;
  nasd_srpc_conn_t *conn;
  int recvd, didrcv;
  nasd_status_t rc;

#if NASD_SRPC_TIMERS > 0
  nasd_timer_t tm;
  nasd_timespec_t ts;
#endif /* NASD_SRPC_TIMERS > 0 */

  pipe = (nasd_srpc_server_pipestate_t *)pipe_arg;

  listener = pipe->listener;
  conn = pipe->conn;
  call = pipe->call;

  *bytes_pulled = 0;

  NASD_SRPC_LOCK_CONN(conn);

  if (len == 0) {
    if (pipe->state != NASD_SRPC_PIPESTATE_TERM) {
      if (call->pull_remain) {
        if (call->cur_pipe == NULL) {
          if (call->pipe_header.opstat == pipe->pipenum) {
            call->cur_pipe = pipe;
          }
          else {
            /*
             * This is data for another pipe. Don't mess it up.
             */
            goto this_pipe_pull_termd;
          }
        }
        else {
          NASD_ASSERT(call->handle_count_held > 0);
        }
        NASD_ASSERT(call->cur_pipe != NULL);
        if (!(conn->state & NASD_SRPC_STATE_IDAM)) {
          NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
          rc = nasd_srpc_slurp_extra(conn, call->pull_remain);
          if (rc) {
            conn->state |= NASD_SRPC_STATE_IDAM;
            conn->state &= ~NASD_SRPC_STATE_OP_R;
            NASD_BROADCAST_COND(conn->state_cond);
            goto done;
          }
        }
        call->pipe_header.opstat = NASD_SRPC_PIPENUM_NOPIPES;
        call->pull_remain = 0;
        call->cur_pipe = NULL;
        conn->state &= ~NASD_SRPC_STATE_OP_R;
        NASD_SIGNAL_COND(conn->state_cond);
      }
this_pipe_pull_termd:
      pipe->state = NASD_SRPC_PIPESTATE_RTERM;
    }
    rc = NASD_SUCCESS;
    goto done;
  }

  if (pipe->state == NASD_SRPC_PIPESTATE_TERM) {
    rc = NASD_SRPC_PIPE_TERMINATED;
    goto done;
  }

  if (call->cur_pipe && (call->cur_pipe != pipe)) {
    /*
     * Caller has gotten ordering wrong!
     */
    rc = NASD_SRPC_BAD_PIPEORDER;
    goto done;
  }

  /*
   * Caller has given us a buffer and wants some data
   */

  if (call->cur_pipe == NULL) {
    /* make our pipe "run" */

    didrcv = 0;

    while ((!(conn->state&NASD_SRPC_STATE_IDAM))
      && (call->pipe_header.opstat == NASD_SRPC_PIPENUM_NOPIPES)
      && (pipe->state != NASD_SRPC_PIPESTATE_TERM))
    {
      if ((!(conn->state&NASD_SRPC_STATE_OP_R))
        && (!(conn->state&NASD_SRPC_STATE_IDAM)))
      {
        /*
         * Crazy dancing to get locking order (listener then conn)
         * right.
         */
        NASD_SRPC_CONN_INC_USE(conn);
        NASD_SRPC_UNLOCK_CONN(conn);

        NASD_SRPC_LOCK_LISTENER(listener);
        NASD_SRPC_LOCK_CONN(conn);

        if ((conn->state&NASD_SRPC_STATE_OP_R)
          || (conn->state&NASD_SRPC_STATE_IDAM))
        {
          NASD_SRPC_CONN_DEC_USE(conn);
          NASD_SRPC_UNLOCK_LISTENER(listener);
          continue;
        }

        if ((pipe->state == NASD_SRPC_PIPESTATE_PEND)
          && (call->handle_count_held_opt == 0))
        {
          /*
           * Performance optimization: grab a handle_count ref while we
           * run the pipe. Advantage: when we're streaming data, we don't
           * contend with other worker threads that might be trying to
           * service the incoming PIPEDATA ops. Disadvantage: if the
           * streaming op above us is slow/inefficient, and we're the only
           * handle_count holder on this connection, other ops may be slow
           * to start on this connection. The code is structured so that
           * this chunk may simply be disabled and everything continues
           * to work, but without this optimization.
           */
          call->handle_count_held_opt++;
          call->handle_count_held++;
          NASD_SRPC_CONN_INC_HANDLE(listener, conn);
        }

        call->handle_count_held++;
        NASD_SRPC_CONN_INC_HANDLE(listener, conn);
        NASD_SRPC_UNLOCK_LISTENER(listener);

        conn->state |= NASD_SRPC_STATE_OP_R;
        didrcv = 1;
        rc = nasd_srpc_listener_handle_conn(listener, conn, &newcall, call);
        if (rc) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          conn->state |= NASD_SRPC_STATE_IDAM;
          NASD_SRPC_CONN_INC_USE(conn);
          NASD_SRPC_UNLOCK_CONN(conn);

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

          NASD_SRPC_LOCK_CONN(conn);
          NASD_SRPC_CONN_DEC_USE(conn);

          NASD_SRPC_SYS_LISTENER_SIGNAL_DEAD_COND(listener);
          rc = NASD_SRPC_RECVSTATE_BAD;
          break;
        }
        if (newcall) {
          /* we're holding the conn lock */
          newcall->pend_next = NULL;
          if (conn->pend_calls_tail) {
            NASD_ASSERT(conn->pend_calls != NULL);
            conn->pend_calls_tail->pend_next = newcall;
          }
          else {
            NASD_ASSERT(conn->pend_calls == NULL);
            conn->pend_calls = newcall;
          }
          conn->pend_calls_tail = newcall;
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          NASD_SRPC_CONN_DEC_USE(conn);
          continue;
        }
        if (call->pipe_header.opstat == NASD_SRPC_PIPENUM_NOPIPES) {
          conn->state &= ~NASD_SRPC_STATE_OP_R;
          NASD_SIGNAL_COND(conn->state_cond);
          if (call->handle_count_held > 1) {
            /* fastpath- avoid lock dance */
            NASD_ASSERT(conn->handle_count > 1);
            call->handle_count_held--;
            NASD_SRPC_CONN_DEC_HANDLE(listener, conn);
          }
          else {
            /* We have to do the whole lock thing. Sigh. */
            nasd_srpc_server_relock_change_handle(listener, conn, call, -1, 1);
          }
        }
      }
      else {
        NASD_WAIT_COND(call->data_ready, conn->lock);
      }
    }
    if (didrcv) {
      NASD_SIGNAL_COND(conn->state_cond);
    }

    if (conn->state & NASD_SRPC_STATE_IDAM) {
      rc = NASD_SRPC_RECVSTATE_BAD;
      goto done;
    }

    if (pipe->state == NASD_SRPC_PIPESTATE_TERM) {
      rc = NASD_SUCCESS;
      goto done;
    }

    if (call->pipe_header.opstat != pipe->pipenum) {
      rc = NASD_SRPC_BAD_PIPEORDER;
      goto done;
    }

    call->cur_pipe = pipe;
    pipe->state = NASD_SRPC_PIPESTATE_RUNNING;
    NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
  }

  if (call->cur_pipe != pipe) {
    rc = NASD_SRPC_BAD_PIPEORDER;
    goto done;
  }

  NASD_ASSERT(call->pipe_header.op == NASD_SRPC_OP_PIPEDATA);
  NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
  NASD_ASSERT(conn->handle_count > 0);
  NASD_ASSERT(call->handle_count_held > 0);

  if (call->pull_remain) {
    NASD_ASSERT(call->cur_pipe != NULL);
    NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
    NASD_SRPC_START_TIMER(&tm, &ts);
    rc = nasd_srpc_sys_sock_recv(conn->sock, buf,
      NASD_MIN(call->pull_remain, len), &recvd, NASD_SRPC_RECV_FILL);
    NASD_SRPC_FINISH_TIMER(&tm, &ts, &nasd_srpc_stats.pipe_pull_recv_ts);
    if (rc) {
      conn->state |= NASD_SRPC_STATE_IDAM;
      conn->state &= ~NASD_SRPC_STATE_OP_R;
      NASD_BROADCAST_COND(conn->state_cond);
      rc = NASD_SRPC_RECVSTATE_BAD;
      goto done;
    }
    call->pull_remain -= recvd;
    *bytes_pulled += recvd;
  }

  if (call->pull_remain == 0) {
    NASD_ASSERT(call->handle_count_held > 0);
    conn->state &= ~NASD_SRPC_STATE_OP_R;
    call->pipe_header.opstat = NASD_SRPC_PIPENUM_NOPIPES;
    call->cur_pipe = NULL;
    NASD_SIGNAL_COND(conn->state_cond);
    nasd_srpc_server_relock_change_handle(listener, conn, call, -1, 1);
  }

  rc = NASD_SUCCESS;

done:
  if ((call->pull_remain != 0) && (rc == NASD_SUCCESS)) {
    NASD_ASSERT(call->cur_pipe != NULL);
    NASD_ASSERT(conn->handle_count > 0);
    NASD_ASSERT(call->handle_count_held > 0);
    NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);
  }

  NASD_SRPC_UNLOCK_CONN(conn);

  return(rc);
}

#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_SRPC */

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