/*
 * Copyright (c) 2012 Linux Box Corporation.
 * Copyright (c) 2013-2018 Red Hat, Inc. and/or its affiliates.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include <sys/types.h>
#include <sys/poll.h>
#include <stdint.h>
#include <err.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <rpc/types.h>
#include <misc/portable.h>
#include <rpc/rpc.h>
#include "rpc_com.h"
#include "svc_internal.h"
#include "svc_xprt.h"
#ifdef USE_MONITORING
#include "metrics_libntirpc.h"
#endif /* USE_MONITORING */

/**
 * @file svc_xprt.c
 * @contributeur William Allen Simpson <bill@cohortfs.com>
 * @brief Service transports package
 *
 * @section DESCRIPTION
 *
 * Maintains a tree of all extant transports by fd.
 *
 * Each SVCXPRT has its own instance, however, so operations to
 * close and delete (for example) given an existing xprt handle
 * are O(1) without any ordered or hashed representation.
 *
 * @note currently static sizes
 *	partitions should be largish prime, relative to connections.
 *	no cache slots, as rpc_dplx_rec has fd_node for direct access.
 */

#define SVC_XPRT_PARTITIONS 193

static bool initialized;

struct svc_xprt_fd {
	mutex_t lock;
	struct rbtree_x xt;
	uint32_t connections;

#ifdef USE_RPC_RDMA
	uint32_t rdma_connections;
#endif

};

static struct svc_xprt_fd svc_xprt_fd = {
	MUTEX_INITIALIZER /* svc_xprt_lock */ ,
	{
	 SVC_XPRT_PARTITIONS,	/* npart */
	 RBT_X_FLAG_NONE,	/* flags */
	 0,			/* cachesz */
	 NULL			/* tree */
	}			/* xt */
};

static inline int
svc_xprt_fd_cmpf(const struct opr_rbtree_node *lhs,
		 const struct opr_rbtree_node *rhs)
{
	struct rpc_dplx_rec *lk, *rk;

	lk = opr_containerof(lhs, struct rpc_dplx_rec, fd_node);
	rk = opr_containerof(rhs, struct rpc_dplx_rec, fd_node);

	if (lk->xprt.xp_fd < rk->xprt.xp_fd)
		return (-1);

	if (lk->xprt.xp_fd == rk->xprt.xp_fd)
		return (0);

	return (1);
}

int
svc_xprt_init(void)
{
	int code = 0;

	mutex_lock(&svc_xprt_fd.lock);

	if (initialized)
		goto unlock;

	/* one of advantages of this RBT is convenience of external
	 * iteration, we'll go to that shortly */
	code =
	    rbtx_init(&svc_xprt_fd.xt, svc_xprt_fd_cmpf /* NULL (inline) */ ,
		      SVC_XPRT_PARTITIONS, RBT_X_FLAG_ALLOC);
	if (code)
		__warnx(TIRPC_DEBUG_FLAG_SVC_XPRT,
			"svc_xprt_init: rbtx_init failed");

	initialized = true;

 unlock:
	mutex_unlock(&svc_xprt_fd.lock);
	return (code);
}

static inline bool
svc_xprt_init_failure(void)
{
	if (initialized)
		return (false);
	return (svc_xprt_init() != 0);
}

/*
 * On success, returns with RPC_DPLX_LOCKED
 */
SVCXPRT *
svc_xprt_lookup(int fd, svc_xprt_setup_t setup)
{
	struct rpc_dplx_rec sk;
	struct rpc_dplx_rec *rec;
	struct rbtree_x_part *t;
	struct opr_rbtree_node *nv;
	SVCXPRT *xprt = NULL;
	uint16_t xp_flags;
	static uint32_t xprt_unique_id = 1;

	if (svc_xprt_init_failure())
		return (NULL);

	sk.xprt.xp_fd = fd;

#ifdef USE_RPC_RDMA
	sk.xprt.xp_rdma = false;
#endif

	t = rbtx_partition_of_scalar(&svc_xprt_fd.xt, fd);

	rwlock_rdlock(&t->lock);
	nv = opr_rbtree_lookup(&t->t, &sk.fd_node);
	if (!nv) {
		rwlock_unlock(&t->lock);
		if (!setup) {
			__warnx(TIRPC_DEBUG_FLAG_SVC_XPRT,
				"%s: fd %d not found - return NULL",
				__func__, fd);
			return (NULL);
		}

		rwlock_wrlock(&t->lock);
		nv = opr_rbtree_lookup(&t->t, &sk.fd_node);
		if (!nv) {
			if (atomic_inc_uint32_t(&svc_xprt_fd.connections)
			    > __svc_params->max_connections) {
				atomic_dec_uint32_t(&svc_xprt_fd.connections);
				rwlock_unlock(&t->lock);
				__warnx(TIRPC_DEBUG_FLAG_ERROR,
					"%s: fd %d max_connections %u exceeded",
					__func__, fd,
					__svc_params->max_connections);
				return (NULL);
			}
			(*setup)(&xprt); /* zalloc, xp_refcnt = 1 */
			xprt->xp_fd = fd;
			xprt->xp_fd_send = -1;
			xprt->xp_flags = SVC_XPRT_FLAG_INITIAL;
			xprt->xp_dispatch.remote_addr_set_cb = NULL;
			xprt->xp_unique_id =
					atomic_inc_uint32_t(&xprt_unique_id);
			xprt->recv_rearm_allowed = true;
			/* Get ref for caller */
			SVC_REF(xprt, SVC_REF_FLAG_NONE);

			rec = REC_XPRT(xprt);
			rpc_dplx_rli(rec);
			if (opr_rbtree_insert(&t->t, &rec->fd_node)) {
				/* cant happen */
				rpc_dplx_rui(rec);
				__warnx(TIRPC_DEBUG_FLAG_LOCK,
					"%s: collision inserting in locked rbtree partition",
					__func__);
				(*setup)(&xprt);	/* free, sets NULL */
				atomic_dec_uint32_t(&svc_xprt_fd.connections);
			}
			if (unlikely(!xprt)) {
				__warnx(TIRPC_DEBUG_FLAG_WARN,
					"%s: fd %d xprt WAS NOT created!",
					__func__, fd);
			} else {
				XPRT_AUTO_TRACEPOINT(xprt, xprt_created,
						TRACE_INFO,
						"xprt was created with xp_unique_id {}",
						xprt->xp_unique_id);
				__warnx(TIRPC_DEBUG_FLAG_SVC_XPRT,
					"%s: fd %d xprt %p created with xp_unique_id %" PRIu32,
					__func__, fd, xprt, xprt->xp_unique_id);
			}
			rwlock_unlock(&t->lock);
#ifdef USE_MONITORING
			metrics_libntirpc_update_tcp_connection_count(
				atomic_fetch_uint32_t(&svc_xprt_fd.connections));
#endif /* USE_MONITORING */
			return (xprt);
		}
		/* raced, fallthru */
	}
	rec = opr_containerof(nv, struct rpc_dplx_rec, fd_node);
	xprt = &rec->xprt;

	if (setup) {
		__warnx(TIRPC_DEBUG_FLAG_WARN,
			"%s: fd %d xprt %p found, though expected new",
			__func__, fd, xprt);
	}

	/* lookup reference before unlock ensures shutdown cannot release */
	SVC_REF(xprt, SVC_REF_FLAG_NONE);
	rwlock_unlock(&t->lock);

	/* unlocked window here permits shutdown to destroy without release;
	 * then duplex lock is required to match allocation return,
	 * ensuring SVC_XPRT_FLAG_INITIAL cleared in this thread only
	 * (obviating extra atomic fetch).
	 */
	rpc_dplx_rli(rec);

	/* We could end up here while xprt is not initialized yet,
	 * So clear SVC_XPRT_FLAG_INITIAL only if xprt is initialized */

	xp_flags = atomic_fetch_uint16_t(&xprt->xp_flags);

	if (xp_flags & SVC_XPRT_FLAG_READY) {
		xp_flags = atomic_clear_uint16_t_bits(&xprt->xp_flags,
		    SVC_XPRT_FLAG_INITIAL);
	}

	rpc_dplx_rui(rec);

	if (!(xp_flags & SVC_XPRT_FLAG_DESTROYED)) {
		/* do not return destroyed xprts */
		return (xprt);
	}

	/* unlock before release permits releasing here after destroy */
	__warnx(TIRPC_DEBUG_FLAG_ERROR,
		"%s: fd %d found DESTROYED xprt %p - calling RELEASE",
		__func__, fd, xprt);
	SVC_RELEASE(xprt, SVC_RELEASE_FLAG_NONE);
	return (NULL);
}

/**
 * Clear an xprt
 *
 * @note Locking
 * - xprt is locked
 *   returned locked
 */
void
svc_xprt_clear(SVCXPRT *xprt)
{
	struct rbtree_x_part *t;

	if (svc_xprt_init_failure())
		return;

	/* xprt lock ensures only one active thread here */
	if (opr_rbtree_node_valid(&REC_XPRT(xprt)->fd_node)) {
		t = rbtx_partition_of_scalar(&svc_xprt_fd.xt, xprt->xp_fd);

		/* if another thread passes test during svc_xprt_shutdown(),
		 * this lock (and generation test) prevents repeats.
		 */
#ifdef USE_RPC_RDMA
			if (xprt->xp_rdma)
				atomic_dec_uint32_t(&svc_xprt_fd.rdma_connections);
			else
				atomic_dec_uint32_t(&svc_xprt_fd.connections);
#else
			atomic_dec_uint32_t(&svc_xprt_fd.connections);
#endif

		XPRT_AUTO_TRACEPOINT(xprt, xprt_cleared, TRACE_INFO,
				"xprt with xp_unique_id {} is cleared",
				xprt->xp_unique_id);
		__warnx(TIRPC_DEBUG_FLAG_SVC_XPRT,
			"Clearing xprts at %p: size %d and unique_id %" PRIu32,
			xprt, t->t.size, xprt->xp_unique_id);

		uint16_t xp_flags = atomic_postclear_uint16_t_bits(
			&xprt->xp_flags, SVC_XPRT_TREE_LOCKED);

		if (xp_flags & SVC_XPRT_TREE_LOCKED) {
			opr_rbtree_remove(&t->t, &REC_XPRT(xprt)->fd_node);
		} else {
			rwlock_wrlock(&t->lock);
			opr_rbtree_remove(&t->t, &REC_XPRT(xprt)->fd_node);
			rwlock_unlock(&t->lock);
		}
#ifdef USE_MONITORING
		metrics_libntirpc_update_tcp_connection_count(
			atomic_fetch_uint32_t(&svc_xprt_fd.connections));
#endif /* USE_MONITORING */
	}
}

/**
 * Perform custom task for each xprt
 *
 * @note Locking
 * - Callback is called with the tree write locked
 */
int
svc_xprt_foreach(svc_xprt_each_func_t each_f, void *arg)
{
	struct rpc_dplx_rec sk;
	struct rpc_dplx_rec *rec;
	struct rbtree_x_part *t;
	struct opr_rbtree_node *n;
	uint64_t tgen;
	int p_ix;
	int x_ix;
	int restarts;

	if (svc_xprt_init_failure())
		return (-1);

	/* concurrent, restartable iteration over t */
	p_ix = 0;
	while (p_ix < SVC_XPRT_PARTITIONS) {
		t = &svc_xprt_fd.xt.tree[p_ix];
		restarts = 0;
		/* TI-RPC __svc_clean_idle held global svc_fd_lock
		 * exclusive locked for a full scan of the legacy svc_xprts
		 * array.  We avoid this via tree partitioning and by
		 * operating mostly unlocked. */
 restart:
		if (++restarts > 5)
			return (1);

		rwlock_wrlock(&t->lock);	/* t WLOCKED */
		tgen = t->t.gen;
		x_ix = 0;
		n = opr_rbtree_first(&t->t);
		while (n != NULL) {
			++x_ix;	/* diagnostic, index into logical srec
				 * sequence */
			rec = opr_containerof(n, struct rpc_dplx_rec, fd_node);
			sk.xprt.xp_fd = rec->xprt.xp_fd;

			/* Take ref since we want to clear SVC_XPRT_TREE_LOCKED
			 * which will be done after calling svc_rqst_clean_func,
			 * so there is chance of xprt getting already destroyed
			 * before we get chance to clear xp_flags, which will be
			 * use after free */
			SVC_REF(&rec->xprt, SVC_REF_FLAG_NONE);
			/* Intimate the inner APIs that tree is locked */
			atomic_set_uint16_t_bits(
				&rec->xprt.xp_flags, SVC_XPRT_TREE_LOCKED);

			/* restart if each_f disposed xprt */
			if (each_f(&rec->xprt, arg)) {
				/* If exits earlier,
				* clear the flag explicitly */
				atomic_clear_uint16_t_bits(
					&rec->xprt.xp_flags,
					SVC_XPRT_TREE_LOCKED);
				SVC_RELEASE(&rec->xprt, SVC_RELEASE_FLAG_NONE);
				/* already cleaned */
				rwlock_unlock(&t->lock);
				/* t !LOCKED */
				goto restart;
			}

			if (tgen != t->t.gen) {
				n = opr_rbtree_lookup(&t->t, &sk.fd_node);
				if (!n) {
					/* If exits earlier,
					* clear the flag explicitly */
					atomic_clear_uint16_t_bits(
						&rec->xprt.xp_flags,
						SVC_XPRT_TREE_LOCKED);
					SVC_RELEASE(&rec->xprt, SVC_RELEASE_FLAG_NONE);
					/* invalidated, try harder */
					rwlock_unlock(&t->lock);
							/* t !LOCKED */
					goto restart;
				}
			}
			/* If exits earlier, clear the flag explicitly */
			atomic_clear_uint16_t_bits(
				&rec->xprt.xp_flags, SVC_XPRT_TREE_LOCKED);
			SVC_RELEASE(&rec->xprt, SVC_RELEASE_FLAG_NONE);
			n = opr_rbtree_next(n);
		}		/* curr partition */
		rwlock_unlock(&t->lock); /* t !LOCKED */
		p_ix++;
	}			/* SVC_XPRT_PARTITIONS */

	return (0);
}

void
svc_xprt_dump_xprts(const char *tag)
{
	struct rbtree_x_part *t = NULL;
	struct opr_rbtree_node *n;
	struct rpc_dplx_rec *rec;
	int p_ix;

	if (!initialized)
		goto out;

	p_ix = 0;
	while (p_ix < SVC_XPRT_PARTITIONS) {
		t = &svc_xprt_fd.xt.tree[p_ix];
		rwlock_rdlock(&t->lock);	/* t RLOCKED */
		__warnx(TIRPC_DEBUG_FLAG_SVC_XPRT,
			"xprts at %s: tree %d size %d", tag, p_ix, t->t.size);
		n = opr_rbtree_first(&t->t);
		while (n != NULL) {
			rec = opr_containerof(n, struct rpc_dplx_rec, fd_node);
			__warnx(TIRPC_DEBUG_FLAG_SVC_XPRT,
				"xprts at %s: %p xp_fd %d",
				tag, &rec->xprt, rec->xprt.xp_fd);
			n = opr_rbtree_next(n);
		}		/* curr partition */
		rwlock_unlock(&t->lock);	/* t !LOCKED */
		p_ix++;
	}			/* SVC_XPRT_PARTITIONS */
 out:
	return;
}

void
svc_xprt_shutdown(void)
{
	struct rbtree_x_part *t;
	struct opr_rbtree_node *n;
	struct rpc_dplx_rec *rec;
	int p_ix;

	if (!initialized)
		return;

	p_ix = 0;
	while (p_ix < SVC_XPRT_PARTITIONS) {
		t = &svc_xprt_fd.xt.tree[p_ix];

		rwlock_wrlock(&t->lock);	/* t WLOCKED */
		while ((n = opr_rbtree_first(&t->t))) {
			rec = opr_containerof(n, struct rpc_dplx_rec, fd_node);

			/* prevent repeats, see svc_xprt_clear() */
			opr_rbtree_remove(&t->t, &rec->fd_node);

			/* fd_node is counted by initial xp_refcnt = 1,
			 * SVC_DESTROY() decrements that reference.
			 */
			rwlock_unlock(&t->lock);
			SVC_DESTROY(&rec->xprt);
			rwlock_wrlock(&t->lock);
		}		/* curr partition */
		rwlock_unlock(&t->lock);	/* t !LOCKED */
		rwlock_destroy(&t->lock);
		p_ix++;
	}			/* SVC_XPRT_PARTITIONS */

	/* free tree */
	mem_free(svc_xprt_fd.xt.tree,
		 SVC_XPRT_PARTITIONS * sizeof(struct rbtree_x_part));
}

#ifdef USE_RPC_RDMA
int
svc_rdma_add_xprt_fd(SVCXPRT *xprt)
{
	struct rpc_dplx_rec sk;
	struct rpc_dplx_rec *rec;
	struct rbtree_x_part *t;
	struct opr_rbtree_node *nv;

	RDMAXPRT *rdma_xprt = RDMA_DR(REC_XPRT(xprt));

	sk.xprt.xp_fd = rdma_xprt->sm_dr.xprt.xp_fd;
	sk.xprt.xp_rdma = rdma_xprt->sm_dr.xprt.xp_rdma;
	t = rbtx_partition_of_scalar(&svc_xprt_fd.xt, sk.xprt.xp_fd);

	rwlock_wrlock(&t->lock);
	nv = opr_rbtree_lookup(&t->t, &sk.fd_node);
	if (!nv) {
		if (atomic_fetch_uint32_t(&svc_xprt_fd.rdma_connections)
			>= __svc_params->max_rdma_connections) {
			rwlock_unlock(&t->lock);
			__warnx(TIRPC_DEBUG_FLAG_ERROR,
			    "%s: fd %d max_rdma_connections %u exceeded\n",
			    __func__, sk.xprt.xp_fd,
			    __svc_params->max_rdma_connections);
			SVC_DESTROY(&rdma_xprt->sm_dr.xprt);
			return -1;
		}

		/* Get ref */
		SVC_REF(&rdma_xprt->sm_dr.xprt, SVC_REF_FLAG_NONE);

		rec = REC_XPRT(&rdma_xprt->sm_dr.xprt);

		rpc_dplx_rli(rec);

		if (opr_rbtree_insert(&t->t, &rec->fd_node)) {
			/* cant happen */
			__warnx(TIRPC_DEBUG_FLAG_ERROR,
				"%s: collision inserting in locked rbtree partition",
				__func__);

			rpc_dplx_rui(rec);
			rwlock_unlock(&t->lock);
			SVC_DESTROY(&rdma_xprt->sm_dr.xprt);
			SVC_RELEASE(&rdma_xprt->sm_dr.xprt,
			            SVC_RELEASE_FLAG_NONE);
			return -1;
		}

		rpc_dplx_rui(rec);

		atomic_inc_uint32_t(&svc_xprt_fd.rdma_connections);

		SVC_RELEASE(&rdma_xprt->sm_dr.xprt, SVC_RELEASE_FLAG_NONE);
	}
	rwlock_unlock(&t->lock);
	return 0;
}
#endif

void
svc_xprt_trace(SVCXPRT *xprt, const char *func, const char *tag, const int line)
{
	__warnx(TIRPC_DEBUG_FLAG_REFCNT,
		"%s() %p fd %d fd_send %d xp_refcnt %" PRId32
		" af %u port %u @%s:%d",
		func, xprt, xprt->xp_fd, xprt->xp_fd_send, xprt->xp_refcnt,
		xprt->xp_remote.ss.ss_family,
		__rpc_address_port(&xprt->xp_remote),
		tag, line);
}
