/*
 * nasd_cheops_cl_switch.c
 *
 * RPC wrappers for Cheops RPCs, dispatch to correct binding type
 */
/*
 * Copyright (c) 1996,1997,1998,1999 Carnegie Mellon University.
 * All rights reserved.
 *
 * Author: Sean Levy
 *
 * [ Much of this code is cribbed from nasd_edrfs_client_switch.c ]
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND
 * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie Mellon the
 * rights to redistribute these changes.
 */

#include <nasd/nasd_options.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_cheops_client.h>

nasd_cheops_client_rpcmod_tab_t *nasd_cheops_client_rpcmod_tabs = NULL;
int nasd_cheops_client_rpcmod_max_specsize = 1;
nasd_cheops_handle_s_t nasd_cheops_client_handles;

NASD_DECLARE_ONCE(nasd_cheops_client_init_once);
NASD_DECLARE_MUTEX(nasd_cheops_client_use_counter_lock);
int nasd_cheops_client_use_counter = 0;
nasd_shutdown_list_t *nasd_cheops_client_shutdown_list = NULL;
nasd_cheops_default_bindings_t *nasd_cheops_default_bindings = NULL;

#define INIT_COUNTER_ONCE()                                             \
 nasd_once(&nasd_cheops_client_init_once, nasd_cheops_client_sys_init)
#define LOCK_COUNTER_LOCK()                                             \
 NASD_LOCK_MUTEX(nasd_cheops_client_use_counter_lock)
#define UNLOCK_COUNTER_LOCK()                                           \
 NASD_UNLOCK_MUTEX(nasd_cheops_client_use_counter_lock)

/*
 * Initialization
 */

nasd_status_t
nasd_cheops_client_real_init(void)
{
  nasd_status_t rc;

  rc = nasd_mem_init();
  if (rc)
    goto failure;
  rc = nasd_shutdown_sys_init();
  if (rc)
    goto failure;
  rc = nasd_shutdown_list_init(&nasd_cheops_client_shutdown_list);
  if (rc)
    goto failure_shutdown;
  nasd_cheops_client_handles.prev = &nasd_cheops_client_handles;
  nasd_cheops_client_handles.next = &nasd_cheops_client_handles;
  rc = nasd_cheops_client_mods_init(nasd_cheops_client_shutdown_list);
  if (rc)
    goto failure_mods;
  return rc;

  /* Failure's such a bitch. */
failure_mods:
  rc = nasd_shutdown_list_shutdown(nasd_cheops_client_shutdown_list,
                                   NASD_SHUTDOWN_ANNOUNCE_NONE);
  if (rc) {
    NASD_PANIC();
  }
failure_shutdown:
  nasd_shutdown_cleanup();
failure:
  nasd_mem_shutdown();
  nasd_threads_shutdown();
  return rc;
}

void
nasd_cheops_client_real_shutdown(void)
{
  nasd_status_t rc;

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

void
nasd_cheops_client_sys_init(void)
{
  nasd_status_t rc;

  rc = nasd_mutex_init(&nasd_cheops_client_use_counter_lock);
  if (rc) {
    NASD_PANIC();
  }
  nasd_cheops_client_use_counter = 0;
  LOCK_COUNTER_LOCK();
  nasd_cheops_default_bindings = NULL;
  nasd_cheops_client_mods_load();
  UNLOCK_COUNTER_LOCK();
}

nasd_status_t
nasd_cheops_client_init(void)
{
  nasd_status_t rc;

  rc = nasd_threads_init();
  if (!rc) {
    INIT_COUNTER_ONCE();
    LOCK_COUNTER_LOCK();
    nasd_cheops_client_use_counter++;
    if (nasd_cheops_client_use_counter == 1) {
      rc = nasd_cheops_client_real_init();
      if (rc)
        nasd_cheops_client_use_counter = 0;
    } else {
      nasd_threads_shutdown();
      rc = NASD_SUCCESS;
    }
    UNLOCK_COUNTER_LOCK();
  }
  return rc;
}

void
nasd_cheops_client_shutdown(void)
{
  NASD_ASSERT(nasd_cheops_client_use_counter != 0);
  LOCK_COUNTER_LOCK();
  nasd_cheops_client_use_counter--;
  if (nasd_cheops_client_use_counter == 0)
    nasd_cheops_client_real_shutdown();
  UNLOCK_COUNTER_LOCK();
}

/*
 * Registration of RPC Modules
 */

void
nasd_cheops_client_mod_register(
  nasd_cheops_client_rpcmod_tab_t *tab,
  int counter_lock_held)
{
  nasd_cheops_client_rpcmod_tab_t *r;

  if (!counter_lock_held) {
    INIT_COUNTER_ONCE();
    LOCK_COUNTER_LOCK();
  }
  for (r = nasd_cheops_client_rpcmod_tabs; r; r = r->next) {
    if (r == tab) {
      nasd_printf("NASD CHEOPS CLIENT: module 0x%lx (%s) already reg'd!\n",
                  (unsigned long)tab, tab->name);
      break;
    }
    if (strcmp(r->name, tab->name) == 0) {
      nasd_printf("NASD CHEOPS CLIENT: new module 0x%lx has same name as "
                  "module 0x%lx (%s), rejecting registration\n",
                  (unsigned long)tab, (unsigned long)r, r->name);
      break;
    }
    if (r->binding_type == tab->binding_type) {
      nasd_printf("NASD CHEOPS CLIENT: new module 0x%lx (%s) has same "
                  "binding type as module 0x%lx (%s) (type %d), "
                  "rejecting registration\n",
                  (unsigned long)tab, tab->name,
                  (unsigned long)r, r->name, r->binding_type);
      break;
    }
  }
  if (r == NULL) {
    tab->next = nasd_cheops_client_rpcmod_tabs;
    nasd_cheops_client_rpcmod_tabs = tab;
    if (tab->spec_size > nasd_cheops_client_rpcmod_max_specsize)
      nasd_cheops_client_rpcmod_max_specsize = tab->spec_size;
    if (nasd_cheops_client_use_counter) {
      if (tab->init_status) {
#if NASD_CHEOPS_CLIENT_SWITCH_STANDALONE == 0
        tab->init_status = tab->init(nasd_cheops_client_shutdown_list);
#else /* NASD_CHEOPS_CLIENT_SWITCH_STANDALONE == 0 */
        tab->init_status = tab->init(NULL);
#endif /* NASD_CHEOPS_CLIENT_SWITCH_STANDALONE == 0 */
      } else
        tab->init_status = NASD_SUCCESS;
    }
  }
  if (!counter_lock_held) {
    UNLOCK_COUNTER_LOCK();
  }
}

void
nasd_cheops_client_mod_unregister(
  nasd_cheops_client_rpcmod_tab_t *tab,
  int counter_lock_held)
{
  nasd_cheops_handle_t h;
  int n_handles = 0;

  if (!counter_lock_held) {
    INIT_COUNTER_ONCE();
    LOCK_COUNTER_LOCK();
  }
  for (h = nasd_cheops_client_handles.next; h != &nasd_cheops_client_handles;
       h = h->next) {
    if (h->rpc_tab == tab)
      n_handles++;
  }
  if (n_handles)
    nasd_printf("NASD CHEOPS CLIENT: attempted to unregister module 0x%lx"
                " (%s) with %d handles still using it -- unregistration"
                " rejected.\n", tab, tab->name, n_handles);
  else {
    nasd_cheops_client_rpcmod_tab_t *r;
    nasd_cheops_client_rpcmod_tab_t *p;

    for (p = NULL, r = nasd_cheops_client_rpcmod_tabs; r; p = r, r = r->next) {
      if (r == tab) {
        if (p)
          p->next = r->next;
        else
          nasd_cheops_client_rpcmod_tabs = r->next;
        r->next = NULL;
        break;
      }
    }
    if (r == NULL)
      nasd_printf("NASD CHEOPS CLIENT: failed unregistring for "
                  "module 0x%lx (%s), could not locate it!\n",
                  (unsigned long)tab, tab->name);
  }
  if (!counter_lock_held) {
    UNLOCK_COUNTER_LOCK();
  }
}

nasd_status_t
nasd_cheops_client_mods_init(nasd_shutdown_list_t *sl)
{
  nasd_status_t rc;
  nasd_cheops_client_rpcmod_tab_t *r;

  rc = NASD_SUCCESS;
  for (r = nasd_cheops_client_rpcmod_tabs; r; r = r->next) {
    if (r->init) {
      rc = r->init_status = r->init(sl);
      if (rc)
        break;
    } else
      rc = r->init_status = NASD_SUCCESS;
  }
  return rc;
}

/*
 * Handle Management
 */

static void
nasd_cheops_client_handle_free(nasd_cheops_handle_t handle)
{
  NASD_Free(handle->rpc_specific_handle, handle->rpc_specific_handle_size);
  NASD_Free(handle, sizeof(nasd_cheops_handle_s_t));
}

nasd_status_t
nasd_bind_to_cheops_mgr(
  char                  *mgr_name,
  char                  *portnum,
  int                    in_binding_type,
  void                  *binding_param,
  int                    binding_param_len,
  nasd_cheops_handle_t  *out_handle)
{
  int binding_type;
  int nd = 0;
  nasd_status_t rc;
  nasd_cheops_handle_t handle;
  nasd_cheops_client_rpcmod_tab_t *r;

  INIT_COUNTER_ONCE();

  if ((in_binding_type == NASD_BIND_DEFAULT) && binding_param_len) {
    return NASD_BAD_BINDING_PARAM_LEN;
  }

  LOCK_COUNTER_LOCK();
  if (nasd_cheops_client_use_counter == 0) {
    UNLOCK_COUNTER_LOCK();
    return NASD_SYS_NOT_INITIALIZED;
  }
  nasd_cheops_client_use_counter++;
  UNLOCK_COUNTER_LOCK();

  if (in_binding_type == NASD_BIND_DEFAULT) {
    if ((nasd_cheops_default_bindings == NULL) ||
        (nasd_cheops_default_bindings->nbindings == 0)) {
      nasd_cheops_client_shutdown();
      return NASD_NO_DEFAULT_BINDING;
    }
    nd = 0;
  }

  *out_handle = NULL;
  NASD_Malloc(handle, sizeof(nasd_cheops_handle_s_t),(nasd_cheops_handle_t));
  if (handle == NULL) {
    nasd_cheops_client_shutdown();
    return NASD_NO_MEM;
  }
  bzero((char *)handle, sizeof(nasd_cheops_handle_s_t));
  rc = NASD_SUCCESS;

  LOCK_COUNTER_LOCK();

  NASD_Malloc(handle->rpc_specific_handle,
              nasd_cheops_client_rpcmod_max_specsize, (void *));
  if (handle->rpc_specific_handle == NULL) {
    NASD_Free(handle, sizeof(nasd_cheops_handle_s_t));
    UNLOCK_COUNTER_LOCK();
    nasd_cheops_client_shutdown();
    return NASD_NO_MEM;
  }
  handle->rpc_specific_handle_size = nasd_cheops_client_rpcmod_max_specsize;
  bzero(handle->rpc_specific_handle, handle->rpc_specific_handle_size);
  do {
    if (in_binding_type == NASD_BIND_DEFAULT) {
      if (nd >= nasd_cheops_default_bindings->nbindings) {
        nasd_cheops_client_handle_free(handle);
        nasd_cheops_client_shutdown();
        if (!rc)
          rc = NASD_FAIL;
        break;
      }
      binding_type = nasd_cheops_default_bindings->bindings[nd++];
    } else {
      binding_type = in_binding_type;
    }
    for (r = nasd_cheops_client_rpcmod_tabs; r; r = r->next)
      if (r->binding_type == binding_type)
        break;
  } while (r == NULL);

  handle->rpc_tab = r;
  handle->type = binding_type;
  if (r == NULL)
    rc = NASD_BAD_HANDLE_TYPE;
  else if (r->init_status != NASD_SUCCESS)
    rc = NASD_RPCMOD_INIT_FAIL;
  else
    rc = r->bind(handle, mgr_name, portnum, binding_type, binding_param,
                 binding_param_len);
  if (rc == NASD_SUCCESS) {
    handle->next = nasd_cheops_client_handles.next;
    handle->prev = &nasd_cheops_client_handles;
    handle->prev->next = handle;
    handle->next->prev = handle;
    *out_handle = handle;
  }

  UNLOCK_COUNTER_LOCK();

  if (rc) {
    nasd_cheops_client_handle_free(handle);
    nasd_cheops_client_shutdown();
  }

  return rc;
}

nasd_status_t
nasd_unbind_cheops_server(nasd_cheops_handle_t *handlep)
{
  nasd_cheops_handle_t handle;
  nasd_status_t rc;

  INIT_COUNTER_ONCE();
  handle = *handlep;
  *handlep = NULL;
  if (handle == NULL)
    rc = NASD_BAD_HANDLE;
  else if (handle->rpc_tab)
    rc = handle->rpc_tab->unbind(handle);
  else
    rc = NASD_SUCCESS;

  LOCK_COUNTER_LOCK();
  handle->prev->next = handle->next;
  handle->next->prev = handle->prev;
  UNLOCK_COUNTER_LOCK();

  nasd_cheops_client_handle_free(handle);
  nasd_cheops_client_shutdown();

  return rc;
}

/*
 * RPC Wrappers
 */

void
nasd_cheops_client_null(
  nasd_cheops_handle_t handle,
  nasd_status_t *out_status,
  nasd_rpc_status_t *op_status)
{
  nasd_res_t res;

  if (handle == NULL) {
    *out_status = NASD_BAD_HANDLE;
    *op_status = 0;
  } else if (handle->rpc_tab == NULL) {
    *out_status = NASD_BINDING_INVALIDATED;
    *op_status = 0;
  } else if (handle->rpc_tab->null == NULL) {
    *out_status = NASD_BINDING_NOIMPL;
    *op_status = 0;
  } else {
    handle->rpc_tab->null(handle, &res, op_status);
    *out_status = res.nasd_status;
  }
}

void
nasd_cheops_client_bs_lookup(
  nasd_cheops_handle_t handle,
  nasd_cheops_bs_lu_args_t *in_args,
  nasd_cheops_bs_lu_res_t *out_res,
  nasd_rpc_status_t *out_status)
{
  if (handle == NULL) {
    out_res->out_nasd_status = NASD_BAD_HANDLE;
    *out_status = 0;
  } else if (handle->rpc_tab == NULL) {
    out_res->out_nasd_status = NASD_BINDING_INVALIDATED;
    *out_status = 0;
  } else if (handle->rpc_tab->bs_lookup == NULL) {
    out_res->out_nasd_status = NASD_BINDING_NOIMPL;
    *out_status = 0;
  } else {
    handle->rpc_tab->bs_lookup(handle, in_args, out_res, out_status);
  }
}

void
nasd_cheops_client_mgr_lookup(
  nasd_cheops_handle_t			handle,
  nasd_cheops_mgr_lu_args_t		*in_args,
  nasd_cheops_mgr_lu_res_t		*out_res,
  nasd_rpc_status_t			*out_status)
{
  if (handle == NULL) {
    out_res->out_nasd_status = NASD_BAD_HANDLE;
    *out_status = 0;
  } else if (handle->rpc_tab == NULL) {
    out_res->out_nasd_status = NASD_BINDING_INVALIDATED;
    *out_status = 0;
  } else if (handle->rpc_tab->mgr_lookup == NULL) {
    out_res->out_nasd_status = NASD_BINDING_NOIMPL;
    *out_status = 0;
  } else {
    handle->rpc_tab->mgr_lookup(handle, in_args, out_res, out_status);
  }
}

void
nasd_cheops_client_dr_lookup(
  nasd_cheops_handle_t			handle,
  nasd_cheops_dr_lu_args_t		*in_args,
  nasd_cheops_dr_lu_res_t		*out_res,
  nasd_rpc_status_t			*out_status)
{
  if (handle == NULL) {
    out_res->out_nasd_status = NASD_BAD_HANDLE;
    *out_status = 0;
  } else if (handle->rpc_tab == NULL) {
    out_res->out_nasd_status = NASD_BINDING_INVALIDATED;
    *out_status = 0;
  } else if (handle->rpc_tab->dr_lookup == NULL) {
    out_res->out_nasd_status = NASD_BINDING_NOIMPL;
    *out_status = 0;
  } else {
    handle->rpc_tab->dr_lookup(handle, in_args, out_res, out_status);
  }
}

void
nasd_cheops_client_bs_refresh(
  nasd_cheops_handle_t			handle,
  nasd_cheops_bs_re_args_t		*in_args,
  nasd_cheops_bs_re_res_t		*out_res,
  nasd_rpc_status_t			*out_status)
{
  if (handle == NULL) {
    out_res->out_nasd_status = NASD_BAD_HANDLE;
    *out_status = 0;
  } else if (handle->rpc_tab == NULL) {
    out_res->out_nasd_status = NASD_BINDING_INVALIDATED;
    *out_status = 0;
  } else if (handle->rpc_tab->bs_refresh == NULL) {
    out_res->out_nasd_status = NASD_BINDING_NOIMPL;
    *out_status = 0;
  } else {
    handle->rpc_tab->bs_refresh(handle, in_args, out_res, out_status);
  }
}

void
nasd_cheops_client_bs_qos_create(
  nasd_cheops_handle_t			handle,
  nasd_cheops_bs_qc_args_t	*in_args,
  nasd_cheops_bs_qc_res_t	*out_res,
  nasd_rpc_status_t			*out_status)
{
  if (handle == NULL) {
    out_res->out_nasd_status = NASD_BAD_HANDLE;
    *out_status = 0;
  } else if (handle->rpc_tab == NULL) {
    out_res->out_nasd_status = NASD_BINDING_INVALIDATED;
    *out_status = 0;
  } else if (handle->rpc_tab->bs_qos_create == NULL) {
    out_res->out_nasd_status = NASD_BINDING_NOIMPL;
    *out_status = 0;
  } else {
    handle->rpc_tab->bs_qos_create(handle, in_args, out_res, out_status);
  }
}

void
nasd_cheops_client_bs_create(
  nasd_cheops_handle_t			handle,
  nasd_cheops_bs_cr_args_t		*in_args,
  nasd_cheops_bs_cr_res_t		*out_res,
  nasd_rpc_status_t			*out_status)
{
  if (handle == NULL) {
    out_res->out_nasd_status = NASD_BAD_HANDLE;
    *out_status = 0;
  } else if (handle->rpc_tab == NULL) {
    out_res->out_nasd_status = NASD_BINDING_INVALIDATED;
    *out_status = 0;
  } else if (handle->rpc_tab->bs_create == NULL) {
    out_res->out_nasd_status = NASD_BINDING_NOIMPL;
    *out_status = 0;
  } else {
    handle->rpc_tab->bs_create(handle, in_args, out_res, out_status);
  }
}

void
nasd_cheops_client_bs_remove(
  nasd_cheops_handle_t			handle,
  nasd_cheops_bs_rm_args_t		*in_args,
  nasd_cheops_bs_rm_res_t		*out_res,
  nasd_rpc_status_t			*out_status)
{
  if (handle == NULL) {
    out_res->out_nasd_status = NASD_BAD_HANDLE;
    *out_status = 0;
  } else if (handle->rpc_tab == NULL) {
    out_res->out_nasd_status = NASD_BINDING_INVALIDATED;
    *out_status = 0;
  } else if (handle->rpc_tab->bs_remove == NULL) {
    out_res->out_nasd_status = NASD_BINDING_NOIMPL;
    *out_status = 0;
  } else {
    handle->rpc_tab->bs_remove(handle, in_args, out_res, out_status);
  }
}

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