/*
 * nasd_srpc.c
 *
 * Basics for SRPC
 *
 * 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_srpc.h>
#include <nasd/nasd_rpcgen_glob_param.h>

/*
 * -1 == "don't set" for these options
 */

nasd_srpc_sockbuf_opts_t nasd_srpc_cli_sockbuf_opts = {
  131072,  /* snd buf */
  131072,  /* rcv buf */
#ifndef __linux__
  (-1),    /* snd lowat */
  (-1),    /* rcv lowat */
#endif /* !__linux__ */
  1,       /* nodelay */
  (-1),    /* nodelay_thresh */
  (-1),    /* keepalive */
  (-1),    /* reuseaddr */
#ifndef __linux__
  1        /* loopback */
#endif /* !__linux__ */
};

nasd_srpc_sockbuf_opts_t nasd_srpc_srv_sockbuf_opts = {
  131072,  /* snd buf */
  131072,  /* rcv buf */
#ifndef __linux__
  (-1),    /* snd lowat */
  (-1),    /* rcv lowat */
#endif /* !__linux__ */
  1,       /* nodelay */
  (-1),    /* nodelay_thresh */
  (-1),    /* keepalive */
  1,       /* reuseaddr */
#ifndef __linux__
  1        /* loopback */
#endif /* !__linux__ */
};

NASD_DECLARE_ONCE(nasd_srpc_init_once)
NASD_DECLARE_MUTEX(nasd_srpc_use_counter_lock)
int nasd_srpc_use_counter;
nasd_shutdown_list_t *nasd_srpc_shutdown_list = NULL;

nasd_srpc_stats_t nasd_srpc_stats;

void
nasd_srpc_reset_stats()
{
  bzero((char *)&nasd_srpc_stats, sizeof(nasd_srpc_stats));
}

void
nasd_srpc_dump_stats()
{
#define PRINT_TS(_field_) { \
  int _n; \
  _n = strlen("  " NASD_STRING(_field_) " "); \
  nasd_printf("  " NASD_STRING(_field_) " "); \
  for(;_n<25;_n++) \
    nasd_printf(" "); \
  nasd_printf("%d:%09d\n", \
    nasd_srpc_stats._field_.ts_sec, nasd_srpc_stats._field_.ts_nsec); \
}
#define PRINT_CNT(_field_) { \
  int _n; \
  _n = strlen("  " NASD_STRING(_field_) " "); \
  nasd_printf("  " NASD_STRING(_field_) " "); \
  for(;_n<25;_n++) \
    nasd_printf(" "); \
  nasd_printf("%" NASD_64u_FMT "\n", nasd_srpc_stats._field_); \
}

  nasd_printf("NASD SRPC stats:\n");

#if NASD_SRPC_COUNTERS > 0
  PRINT_CNT(sock_send_calls);
  PRINT_CNT(sock_write_calls);
  PRINT_CNT(sock_writev_calls);
  PRINT_CNT(sock_send_chgblock);
  PRINT_CNT(sock_send_errs);

  PRINT_CNT(sock_recv_calls);
  PRINT_CNT(sock_read_calls);
  PRINT_CNT(sock_recv_chgblock);
  PRINT_CNT(sock_recv_errs);
#endif /* NASD_SRPC_COUNTERS > 0 */

#if NASD_SRPC_TIMERS > 0
  PRINT_TS(pipe_push_wait_ts);
  PRINT_TS(pipe_push_send_ts);
  PRINT_TS(pipe_pull_recv_ts);
#endif /* NASD_SRPC_TIMERS > 0 */
}

nasd_status_t
nasd_srpc_conn_init(
  nasd_srpc_conn_t  *conn)
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&conn->lock);
  if (rc)
    return(rc);

  rc = nasd_cond_init(&conn->use_cond);
  if (rc) {
    nasd_mutex_destroy(&conn->lock);
    return(rc);
  }

  rc = nasd_cond_init(&conn->state_cond);
  if (rc) {
    nasd_cond_destroy(&conn->use_cond);
    nasd_mutex_destroy(&conn->lock);
    return(rc);
  }

  return(NASD_SUCCESS);
}

void
nasd_srpc_conn_clean(
  nasd_srpc_conn_t  *conn)
{
  nasd_status_t rc;

  rc = nasd_mutex_destroy(&conn->lock);
  if (rc) {
    nasd_printf("WARNING: got 0x%x (%s) destroying conn lock\n",
      rc, nasd_error_string(rc));
  }

  rc = nasd_cond_destroy(&conn->use_cond);
  if (rc) {
    nasd_printf("WARNING: got 0x%x (%s) destroying conn use_cond\n",
      rc, nasd_error_string(rc));
  }

  rc = nasd_cond_destroy(&conn->state_cond);
  if (rc) {
    nasd_printf("WARNING: got 0x%x (%s) destroying conn state_cond\n",
      rc, nasd_error_string(rc));
  }
}

nasd_status_t
nasd_srpc_call_init(
  nasd_srpc_call_t  *call)
{
  nasd_status_t rc;

  rc = nasd_cond_init(&call->data_ready);
  if (rc) {
    return(rc);
  }

  return(NASD_SUCCESS);
}

void
nasd_srpc_call_clean(
  nasd_srpc_call_t  *call)
{
  nasd_status_t rc;

  rc = nasd_cond_destroy(&call->data_ready);
  if (rc) {
    nasd_printf("WARNING: got 0x%x (%s) destroying call data_ready cond\n",
      rc, nasd_error_string(rc));
  }
}

nasd_status_t
nasd_srpc_server_call_init(
  nasd_srpc_server_call_t  *call)
{
  nasd_status_t rc;

  rc = nasd_cond_init(&call->data_ready);
  if (rc) {
    return(rc);
  }

  return(NASD_SUCCESS);
}

void
nasd_srpc_server_call_clean(
  nasd_srpc_server_call_t  *call)
{
  nasd_status_t rc;

  rc = nasd_cond_destroy(&call->data_ready);
  if (rc) {
    nasd_printf("WARNING: got 0x%x (%s) destroying server_call data_ready cond\n",
      rc, nasd_error_string(rc));
  }
}

/*
 * nasd_srpc_real_init
 *
 * Called when the system comes into use after not being
 * used (start-of-day call to nasd_srpc_init(), or first
 * such call after the last call to nasd_srpc_shutdown()
 * which actually deactivated the system).
 */
nasd_status_t
nasd_srpc_real_init()
{
  nasd_status_t rc, rc2;

  NASD_ASSERT(nasd_srpc_use_counter == 1);

  /* already did threads_init() */

  rc = nasd_mem_init();
  if (rc) {
    nasd_threads_shutdown();
    return(rc);
  }

  rc = nasd_shutdown_sys_init();
  if (rc) {
    nasd_threads_shutdown();
    nasd_mem_shutdown();
    return(rc);
  }

  rc = nasd_shutdown_list_init(&nasd_srpc_shutdown_list);
  if (rc) {
    nasd_shutdown_cleanup();
    nasd_mem_shutdown();
    nasd_threads_shutdown();
    return(rc);
  }

  /*
   * This is where we really do srpc-specific initialization
   */

  rc = nasd_srpc_sys_init(nasd_srpc_shutdown_list);
  if (rc)
    goto bad;

  rc = nasd_srpc_server_init(nasd_srpc_shutdown_list);
  if (rc)
    goto bad;

  return(NASD_SUCCESS);

bad:
  rc2 = nasd_shutdown_list_shutdown(nasd_srpc_shutdown_list,
    NASD_SHUTDOWN_ANNOUNCE_NONE);
  if (rc2) {
    NASD_PANIC();
  }
  nasd_shutdown_cleanup();
  nasd_mem_shutdown();
  nasd_threads_shutdown();
  return(rc);
}

/*
 * nasd_srpc_real_shutdown
 *
 * Called when last user of the srpc system calls nasd_srpc_shutdown().
 * Clean up and deallocate resources.
 */
void
nasd_srpc_real_shutdown()
{
  nasd_status_t rc;

  NASD_ASSERT(nasd_srpc_use_counter == 0);

  rc = nasd_shutdown_list_shutdown(nasd_srpc_shutdown_list,
    NASD_SHUTDOWN_ANNOUNCE_NONE);
  if (rc) {
    NASD_PANIC();
  }
  nasd_srpc_shutdown_list = NULL;
  nasd_shutdown_cleanup();
  nasd_mem_shutdown();
  nasd_threads_shutdown();
}

/*
 * nasd_srpcsys_init
 *
 * Executed exactly once, the first time nasd_srpc_init() is
 * called. Initialize counter tracking number of times system
 * is initted.
 */
void
nasd_srpcsys_init()
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&nasd_srpc_use_counter_lock);
  if (rc) {
    NASD_PANIC();
  }

  nasd_srpc_use_counter = 0;
}

/*
 * nasd_srpc_init
 *
 * Keep a counter of the number of times we're initted and
 * shutdown. When the last shutdown arrives, really deallocate.
 * This lets multiple subsystems use us without knowing about
 * one another.
 */
nasd_status_t
nasd_srpc_init()
{
  nasd_status_t rc;

  rc = nasd_threads_init();
  if (rc)
    return(rc);
  nasd_once(&nasd_srpc_init_once, nasd_srpcsys_init);
  NASD_LOCK_MUTEX(nasd_srpc_use_counter_lock);
  nasd_srpc_use_counter++;
  if (nasd_srpc_use_counter == 1) {
    rc = nasd_srpc_real_init();
    if (rc) {
      nasd_srpc_use_counter = 0;
    }
  }
  else {
    /* get rid of extra threads init above */
    nasd_threads_shutdown();
    rc = NASD_SUCCESS;
  }
  NASD_UNLOCK_MUTEX(nasd_srpc_use_counter_lock);

  return(rc);
}

/*
 * nasd_srpc_shutdown
 *
 * Previous caller of nasd_srpc_init() not using the srpc
 * subsystem any more. Deallocate and cleanup iff necessary.
 */
void
nasd_srpc_shutdown()
{
  NASD_ASSERT(nasd_srpc_use_counter != 0);

  NASD_LOCK_MUTEX(nasd_srpc_use_counter_lock);
  nasd_srpc_use_counter--;
  if (nasd_srpc_use_counter == 0) {
    nasd_srpc_real_shutdown();
  }
  NASD_UNLOCK_MUTEX(nasd_srpc_use_counter_lock);
}

/*
 * Call with conn lock held
 * Pull extra bytes off of the wire. Call with extra = 0 to just
 *   throw away garbage from the wire.
 */
nasd_status_t
nasd_srpc_slurp_extra(
  nasd_srpc_conn_t      *conn,
  int                    extra)
{
  nasd_byte_t buf[NASD_SRPC_EXTRABUF_SIZE];
  int l, remain, recv;
  nasd_status_t rc = NASD_SUCCESS;

  NASD_ASSERT(conn->state & NASD_SRPC_STATE_OP_R);

  /* XXX -- eventually we don't want to do this */
  if (conn->bytes_outstanding > 0) {
    /* handle the outstanding bytes first */

    while (conn->bytes_outstanding && (rc == NASD_SUCCESS)) {
      l = NASD_MIN(sizeof(buf), conn->bytes_outstanding);
      rc = nasd_srpc_sys_sock_recv(conn->sock, buf, l, &recv,
                                   NASD_SRPC_RECV_FILL);
      if (rc == NASD_AGAIN) { return rc; } /* fuck. we have to do it AGAIN. */
      else if (rc) { break; }

      NASD_ASSERT(l == recv);
      conn->bytes_outstanding -= l;
    }

    /* if we haven't gotten everything, something's seriously wrong. */
    NASD_ASSERT(conn->bytes_outstanding == 0);
  }

  if (extra == 0) { return(NASD_SUCCESS); }
  if (extra < 0)  { return(NASD_BAD_LEN); }

  rc = NASD_SUCCESS;
  remain = extra;

  while(remain && (rc == NASD_SUCCESS)) {
    l = NASD_MIN(sizeof(buf), remain);
    rc = nasd_srpc_sys_sock_recv(conn->sock, buf, l, &recv,
                                 NASD_SRPC_RECV_FILL);
    if (rc) { break; }
    NASD_ASSERT(l == recv);
    remain -= l;
  }

  return(rc);
}

void
nasd_srpc_conn_reinit(
  nasd_srpc_conn_t  *conn)
{
  int i;

  for(i=0;i<NASD_SRPC_CALLHASH_BUCKETS;i++) {
    conn->c_call_buckets[i].next = &conn->c_call_buckets[i];
    conn->c_call_buckets[i].prev = &conn->c_call_buckets[i];
    conn->s_call_buckets[i].next = &conn->s_call_buckets[i];
    conn->s_call_buckets[i].prev = &conn->s_call_buckets[i];
  }
  conn->next = NULL;
  conn->active_next = NULL;
  conn->active_prev = NULL;
  conn->idle_next = NULL;
  conn->idle_prev = NULL;
  conn->pend_calls = NULL;
  conn->pend_calls_tail = NULL;
}

void
nasd_srpc_call_reinit(
  nasd_srpc_call_t  *call)
{
  call->state = 0;
  call->callid = 0;
  call->ishashed = 0;
  call->pipe_header.opstat = NASD_SRPC_PIPENUM_NOPIPES;
  call->pipe_header.callid = 0;
  call->pipe_enabled = 0;
}

void
nasd_srpc_server_call_reinit(
  nasd_srpc_server_call_t  *call)
{
  call->callid = 0;
  call->handle_count_held = 0;
  call->ishashed = 0;
  call->cur_pipenum = NASD_SRPC_PIPENUM_NOPIPES;
  call->pipe_header.opstat = NASD_SRPC_PIPENUM_NOPIPES;
  call->cur_pipe = NULL;
}

char *
nasd_srpc_error_string(
  nasd_srpc_status_t    status,
  char                 *str,
  int                   len,
  char                 *file,
  int                   line)
{
  switch(status) {
    case NASD_SRPC_S_SUCCESS:
      strcpy(str, "Success");
      break;
    case NASD_SRPC_S_BAD_CALLID:
      strcpy(str, "bad callid");
      break;
    case NASD_SRPC_S_BAD_RPCID:
      strcpy(str, "bad rpcid");
      break;
    case NASD_SRPC_S_BAD_PIPENO:
      strcpy(str, "bad pipe number");
      break;
    case NASD_SRPC_S_BAD_REQLEN:
      strcpy(str, "bad request length");
      break;
    case NASD_SRPC_S_BAD_REPLEN:
      strcpy(str, "bad reply length");
      break;
    case NASD_SRPC_S_PIPE_TERMD:
      strcpy(str, "pipe terminated");
      break;
    case NASD_SRPC_S_BAD_OP:
      strcpy(str, "bad operation type");
      break;
    case NASD_SRPC_S_FAIL:
      strcpy(str, "failed");
      break;
    case NASD_SRPC_S_BAD_OPLEN:
      strcpy(str, "bad operation length");
      break;
    case NASD_SRPC_S_GENERR:
      strcpy(str, "error in generated code");
      break;
    case NASD_SRPC_S_ABORTED:
      strcpy(str, "call aborted");
      break;
    case NASD_SRPC_S_CALL_NOIMPL:
      strcpy(str, "call not implemented");
      break;
    case NASD_SRPC_S_OFFLINE:
      strcpy(str, "unit offline");
      break;
    case NASD_SRPC_S_BAD_PIPEWORD:
      strcpy(str, "pipes mismatch");
      break;
    case NASD_SRPC_S_PIPE_NOSTART:
      strcpy(str, "pipe not started");
      break;
    case NASD_SRPC_S_PIPEORD:
      strcpy(str, "bad pipe misordering");
      break;
    case NASD_SRPC_S_ENABFAIL:
      strcpy(str, "server could not enable incoming pipes");
      break;
    case NASD_SRPC_S_BAD_PIPE:
      strcpy(str, "bad operation received during pipe stream");
      break;
    default:
      sprintf(str, "unknown srpc error %u at %s:%d",
        (unsigned)status, file, line);
      break;
  }

  return(str);
}

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