/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * rpcb_stat.c
 * Allows for gathering of statistics
 *
 * Copyright (c) 1990 by Sun Microsystems, Inc.
 */
/*
 * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
 */

#include <stdio.h>
#include <netconfig.h>
#include <rpc/rpc.h>
#include <rpc/rpcb_prot.h>
#include <sys/stat.h>
#ifdef PORTMAP
#include <rpc/pmap_prot.h>
#endif
#include <stdlib.h>
#include <atomic.h>
#include <assert.h>
#include <thread.h>
#include <synch.h>
#include <string.h>
#include "rpcbind.h"

static rpcb_stat_byvers inf;
static rwlock_t inf_lock = DEFAULTRWLOCK;

void
rpcbs_procinfo(int rtype, rpcproc_t proc)
{
	assert(rtype >= 0 && rtype < RPCBVERS_STAT);

#ifdef PORTMAP
	if ((rtype == RPCBVERS_2_STAT) && (proc > rpcb_highproc_2))
		return;
#else
	assert(rtype != RPCBVERS_2_STAT);
#endif

	if ((rtype == RPCBVERS_3_STAT) && (proc > rpcb_highproc_3))
		return;

	if ((rtype == RPCBVERS_4_STAT) && (proc > rpcb_highproc_4))
		return;

	atomic_add_int((uint_t *)&inf[rtype].info[proc], 1);
}

void
rpcbs_set(int rtype, bool_t success)
{
	assert(rtype >= 0 && rtype < RPCBVERS_STAT);

	if (success == FALSE)
		return;

	atomic_add_int((uint_t *)&inf[rtype].setinfo, 1);
}

void
rpcbs_unset(int rtype, bool_t success)
{
	assert(rtype >= 0 && rtype < RPCBVERS_STAT);

	if (success == FALSE)
		return;

	atomic_add_int((uint_t *)&inf[rtype].unsetinfo, 1);
}

void
rpcbs_getaddr(int rtype, rpcprog_t prog, rpcvers_t vers, char *netid,
    char *uaddr)
{
	rpcbs_addrlist *al;
	rpcbs_addrlist *s;
	rpcbs_addrlist *wal;
	struct netconfig *nconf;

	assert(rtype >= 0 && rtype < RPCBVERS_STAT);

	/*
	 * First try with read lock only.
	 */
	(void) rw_rdlock(&inf_lock);
	for (s = al = inf[rtype].addrinfo; al; al = al->next) {
		if ((al->prog == prog) && (al->vers == vers) &&
		    (strcmp(al->netid, netid) == 0)) {
			(void) rw_unlock(&inf_lock);

			if ((uaddr == NULL) || (uaddr[0] == NULL))
				atomic_add_int((uint_t *)&al->failure, 1);
			else
				atomic_add_int((uint_t *)&al->success, 1);

			return;
		}
	}
	(void) rw_unlock(&inf_lock);

	/*
	 * If not found, we will likely need to add a new entry,
	 * so pre-allocate it, and then try to search again with write lock.
	 */
	nconf = rpcbind_get_conf(netid);
	if (nconf == NULL) {
		return;
	}

	al = (rpcbs_addrlist *) malloc(sizeof (rpcbs_addrlist));
	if (al == NULL) {
		return;
	}

	al->prog = prog;
	al->vers = vers;
	al->netid = nconf->nc_netid;
	if ((uaddr == NULL) || (uaddr[0] == NULL)) {
		al->failure = 1;
		al->success = 0;
	} else {
		al->failure = 0;
		al->success = 1;
	}

	(void) rw_wrlock(&inf_lock);
	for (wal = inf[rtype].addrinfo; wal != s; wal = wal->next) {
		if ((wal->prog == prog) && (wal->vers == vers) &&
		    (strcmp(wal->netid, netid) == 0)) {
			(void) rw_unlock(&inf_lock);

			free(al);

			if ((uaddr == NULL) || (uaddr[0] == NULL))
				atomic_add_int((uint_t *)&wal->failure, 1);
			else
				atomic_add_int((uint_t *)&wal->success, 1);

			return;
		}
	}

	al->next = inf[rtype].addrinfo;
	inf[rtype].addrinfo = al;
	(void) rw_unlock(&inf_lock);
}

/*
 * rpcbproc - rpcbind proc number on which this was called
 */
void
rpcbs_rmtcall(int rtype, rpcproc_t rpcbproc, rpcprog_t prog, rpcvers_t vers,
    rpcproc_t proc, char *netid, rpcblist_ptr rbl)
{
	rpcbs_rmtcalllist *rl;
	rpcbs_rmtcalllist *s;
	rpcbs_rmtcalllist *wrl;
	struct netconfig *nconf;

	assert(rtype >= 0 && rtype < RPCBVERS_STAT);

	/*
	 * First try with read lock only.
	 */
	(void) rw_rdlock(&inf_lock);
	for (s = rl = inf[rtype].rmtinfo; rl; rl = rl->next) {
		if ((rl->prog == prog) && (rl->vers == vers) &&
		    (rl->proc == proc) && (strcmp(rl->netid, netid) == 0)) {
			(void) rw_unlock(&inf_lock);

			if ((rbl == NULL) || (rbl->rpcb_map.r_vers != vers))
				atomic_add_int((uint_t *)&rl->failure, 1);
			else
				atomic_add_int((uint_t *)&rl->success, 1);
			if (rpcbproc == RPCBPROC_INDIRECT)
				atomic_add_int((uint_t *)&rl->indirect, 1);

			return;
		}
	}
	(void) rw_unlock(&inf_lock);

	/*
	 * If not found, we will likely need to add a new entry,
	 * so pre-allocate it, and then try to search again with write lock.
	 */
	nconf = rpcbind_get_conf(netid);
	if (nconf == NULL) {
		return;
	}

	rl = (rpcbs_rmtcalllist *) malloc(sizeof (rpcbs_rmtcalllist));
	if (rl == NULL) {
		return;
	}

	rl->prog = prog;
	rl->vers = vers;
	rl->proc = proc;
	rl->netid = nconf->nc_netid;
	if ((rbl == NULL) || (rbl->rpcb_map.r_vers != vers)) {
		rl->failure = 1;
		rl->success = 0;
	} else {
		rl->failure = 0;
		rl->success = 1;
	}
	rl->indirect = rpcbproc == RPCBPROC_INDIRECT ? 1 : 0;

	(void) rw_wrlock(&inf_lock);
	for (wrl = inf[rtype].rmtinfo; wrl != s; wrl = wrl->next) {
		if ((wrl->prog == prog) && (wrl->vers == vers) &&
		    (wrl->proc == proc) && (strcmp(wrl->netid, netid) == 0)) {
			(void) rw_unlock(&inf_lock);

			free(rl);

			if ((rbl == NULL) || (rbl->rpcb_map.r_vers != vers))
				atomic_add_int((uint_t *)&wrl->failure, 1);
			else
				atomic_add_int((uint_t *)&wrl->success, 1);
			if (rpcbproc == RPCBPROC_INDIRECT)
				atomic_add_int((uint_t *)&wrl->indirect, 1);

			return;
		}
	}

	rl->next = inf[rtype].rmtinfo;
	inf[rtype].rmtinfo = rl;
	(void) rw_unlock(&inf_lock);
}

/* ARGSUSED */
bool_t
rpcbproc_getstat(void *argp, rpcb_stat_byvers **result)
{
	/*
	 * inf_lock is unlocked in xdr_rpcb_stat_byvers_ptr()
	 */
	(void) rw_rdlock(&inf_lock);
	*result = &inf;
	return (TRUE);
}

bool_t
xdr_rpcb_stat_byvers_ptr(XDR *xdrs, rpcb_stat_byvers **objp)
{
	if (xdrs->x_op == XDR_FREE) {
		/*
		 * inf_lock is locked in rpcbproc_getstat()
		 */
		(void) rw_unlock(&inf_lock);
		return (TRUE);
	}

	return (xdr_rpcb_stat_byvers(xdrs, (rpcb_stat *)*objp));
}