/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * Copyright (c) 2003-2008 Thomas Graf <tgraf@suug.ch>
 */

/**
 * @ingroup link
 * @defgroup link_API Link Modules API
 * @brief API for modules implementing specific link types/semantics.
 *
 * @par 1) Registering/Unregistering a new link info type
 * @code
 * static struct rtnl_link_info_ops vlan_info_ops = {
 * 	.io_name		= "vlan",
 * 	.io_alloc		= vlan_alloc,
 * 	.io_parse		= vlan_parse,
 * 	.io_dump[NL_DUMP_BRIEF]	= vlan_dump_brief,
 * 	.io_dump[NL_DUMP_FULL]	= vlan_dump_full,
 * 	.io_free		= vlan_free,
 * };
 *
 * static void __init vlan_init(void)
 * {
 * 	rtnl_link_register_info(&vlan_info_ops);
 * }
 *
 * static void __exit vlan_exit(void)
 * {
 * 	rtnl_link_unregister_info(&vlan_info_ops);
 * }
 * @endcode
 *
 * @{
 */

#include <netlink-private/netlink.h>
#include <netlink/netlink.h>
#include <netlink/utils.h>
#include <netlink/route/link.h>
#include <netlink-private/route/link/api.h>

static NL_LIST_HEAD(info_ops);

/* lock protecting info_ops and af_ops */
static NL_RW_LOCK(info_lock);

static struct rtnl_link_info_ops *__rtnl_link_info_ops_lookup(const char *name)
{
	struct rtnl_link_info_ops *ops;

	nl_list_for_each_entry(ops, &info_ops, io_list)
		if (!strcmp(ops->io_name, name))
			return ops;

	return NULL;
}

/**
 * @name Link Info Modules
 * @{
 */

/**
 * Return operations of a specific link info type
 * @arg name		Name of link info type.
 *
 * @note The returned pointer must be given back using rtnl_link_info_ops_put()
 *
 * @return Pointer to operations or NULL if unavailable.
 */
struct rtnl_link_info_ops *rtnl_link_info_ops_lookup(const char *name)
{
	struct rtnl_link_info_ops *ops;

	nl_write_lock(&info_lock);
	if ((ops = __rtnl_link_info_ops_lookup(name)))
		ops->io_refcnt++;
	nl_write_unlock(&info_lock);

	return ops;
}

/**
 * Take reference to a set of operations.
 * @arg ops		Link info operations.
 */
void rtnl_link_info_ops_get(struct rtnl_link_info_ops *ops)
{
	if (!ops)
		return;

	nl_write_lock(&info_lock);
	ops->io_refcnt++;
	nl_write_unlock(&info_lock);
}

/**
 * Give back reference to a set of operations.
 * @arg ops		Link info operations.
 */
void rtnl_link_info_ops_put(struct rtnl_link_info_ops *ops)
{
	if (!ops)
		return;

	nl_write_lock(&info_lock);
	_nl_assert(ops->io_refcnt > 0);
	ops->io_refcnt--;
	nl_write_unlock(&info_lock);
}

/**
 * Register operations for a link info type
 * @arg ops		Link info operations
 *
 * This function must be called by modules implementing a specific link
 * info type. It will make the operations implemented by the module
 * available for everyone else.
 *
 * @return 0 on success or a negative error code.
 * @return -NLE_INVAL Link info name not specified.
 * @return -NLE_EXIST Operations for address family already registered.
 */
int rtnl_link_register_info(struct rtnl_link_info_ops *ops)
{
	int err = 0;

	if (ops->io_name == NULL)
		return -NLE_INVAL;

	nl_write_lock(&info_lock);
	if (__rtnl_link_info_ops_lookup(ops->io_name)) {
		err = -NLE_EXIST;
		goto errout;
	}

	NL_DBG(1, "Registered link info operations %s\n", ops->io_name);

	nl_list_add_tail(&ops->io_list, &info_ops);
errout:
	nl_write_unlock(&info_lock);

	return err;
}

/**
 * Unregister operations for a link info type
 * @arg ops		Link info operations
 *
 * This function must be called if a module implementing a specific link
 * info type is unloaded or becomes unavailable. It must provide a
 * set of operations which have previously been registered using
 * rtnl_link_register_info().
 *
 * @return 0 on success or a negative error code
 * @return _NLE_OPNOTSUPP Link info operations not registered.
 * @return -NLE_BUSY Link info operations still in use.
 */
int rtnl_link_unregister_info(struct rtnl_link_info_ops *ops)
{
	struct rtnl_link_info_ops *t;
	int err = -NLE_OPNOTSUPP;

	nl_write_lock(&info_lock);

	nl_list_for_each_entry(t, &info_ops, io_list) {
		if (t == ops) {
			_nl_assert(t->io_refcnt >= 0);
			if (t->io_refcnt > 0) {
				err = -NLE_BUSY;
				goto errout;
			}

			nl_list_del(&t->io_list);

			NL_DBG(1, "Unregistered link info operations %s\n",
				ops->io_name);
			err = 0;
			goto errout;
		}
	}

errout:
	nl_write_unlock(&info_lock);

	return err;
}

/** @} */

/**
 * @name Link Address Family Modules
 * @{
 */

static struct rtnl_link_af_ops *af_ops[AF_MAX];

/**
 * Return operations of a specific link address family
 * @arg family		Address family
 *
 * @note The returned pointer must be given back using rtnl_link_af_ops_put()
 *
 * @return Pointer to operations or NULL if unavailable.
 */
struct rtnl_link_af_ops *rtnl_link_af_ops_lookup(const unsigned int family)
{
	if (family == AF_UNSPEC || family >= AF_MAX)
		return NULL;

	nl_write_lock(&info_lock);
	if (af_ops[family])
		af_ops[family]->ao_refcnt++;
	nl_write_unlock(&info_lock);

	return af_ops[family];
}

/**
 * Give back reference to a set of operations.
 * @arg ops		Address family operations.
 */
void rtnl_link_af_ops_put(struct rtnl_link_af_ops *ops)
{
	if (ops) {
		nl_write_lock(&info_lock);
		ops->ao_refcnt--;
		nl_write_unlock(&info_lock);
	}
}

/**
 * Allocate and return data buffer for link address family modules
 * @arg link		Link object
 * @arg ops		Address family operations
 *
 * This function must be called by link address family modules in all
 * cases where the API does not provide the data buffer as argument
 * already. This typically includes set functions the module provides.
 * Calling this function is strictly required to ensure proper allocation
 * of the buffer upon first use. Link objects will NOT proactively
 * allocate a data buffer for each registered link address family.
 *
 * @return Pointer to data buffer or NULL on error.
 */
void *rtnl_link_af_alloc(struct rtnl_link *link,
			 const struct rtnl_link_af_ops *ops)
{
	int family;

	if (!link || !ops)
		BUG();

	family = ops->ao_family;

	if (!link->l_af_data[family]) {
		if (!ops->ao_alloc)
			BUG();
		
		link->l_af_data[family] = ops->ao_alloc(link);
		if (!link->l_af_data[family])
			return NULL;
	}

	return link->l_af_data[family];
}

/**
 * Return data buffer for link address family modules
 * @arg link		Link object
 * @arg ops		Address family operations
 *
 * This function returns a pointer to the data buffer for the specified link
 * address family module or NULL if the buffer was not allocated yet. This
 * function is typically used by get functions of modules which are not
 * interested in having the data buffer allocated if no values have been set
 * yet.
 *
 * @return Pointer to data buffer or NULL on error.
 */
void *rtnl_link_af_data(const struct rtnl_link *link,
			const struct rtnl_link_af_ops *ops)
{
	if (!link || !ops)
		BUG();

	return link->l_af_data[ops->ao_family];
}

/**
 * Register operations for a link address family
 * @arg ops		Address family operations
 *
 * This function must be called by modules implementing a specific link
 * address family. It will make the operations implemented by the module
 * available for everyone else.
 *
 * @return 0 on success or a negative error code.
 * @return -NLE_INVAL Address family is out of range (0..AF_MAX)
 * @return -NLE_EXIST Operations for address family already registered.
 */
int rtnl_link_af_register(struct rtnl_link_af_ops *ops)
{
	int err = 0;

	if (ops->ao_family == AF_UNSPEC || ops->ao_family >= AF_MAX)
		return -NLE_INVAL;

	nl_write_lock(&info_lock);
	if (af_ops[ops->ao_family]) {
		err = -NLE_EXIST;
		goto errout;
	}

	ops->ao_refcnt = 0;
	af_ops[ops->ao_family] = ops;

	NL_DBG(1, "Registered link address family operations %u\n",
		ops->ao_family);

errout:
	nl_write_unlock(&info_lock);

	return err;
}

/**
 * Unregister operations for a link address family
 * @arg ops		Address family operations
 *
 * This function must be called if a module implementing a specific link
 * address family is unloaded or becomes unavailable. It must provide a
 * set of operations which have previously been registered using
 * rtnl_link_af_register().
 *
 * @return 0 on success or a negative error code
 * @return -NLE_INVAL ops is NULL
 * @return -NLE_OBJ_NOTFOUND Address family operations not registered.
 * @return -NLE_BUSY Address family operations still in use.
 */
int rtnl_link_af_unregister(struct rtnl_link_af_ops *ops)
{
	int err = -NLE_INVAL;

	if (!ops)
		return err;

	nl_write_lock(&info_lock);
	if (!af_ops[ops->ao_family]) {
		err = -NLE_OBJ_NOTFOUND;
		goto errout;
	}

	if (ops->ao_refcnt > 0) {
		err = -NLE_BUSY;
		goto errout;
	}

	af_ops[ops->ao_family] = NULL;

	NL_DBG(1, "Unregistered link address family operations %u\n",
		ops->ao_family);

errout:
	nl_write_unlock(&info_lock);

	return err;
}

/**
 * Compare af data for a link address family
 * @arg a		Link object a
 * @arg b		Link object b
 * @arg family		af data family
 *
 * This function will compare af_data between two links
 * a and b of family given by arg family
 *
 * @return 0 if address family specific data matches or is not present
 * or != 0 if it mismatches.
 */
int rtnl_link_af_data_compare(struct rtnl_link *a, struct rtnl_link *b,
			      int family)
{
	struct rtnl_link_af_ops *af_ops;
	int ret = 0;

	if (!a->l_af_data[family] && !b->l_af_data[family])
		return 0;

	if (!a->l_af_data[family] || !b->l_af_data[family])
		return ~0;

	af_ops = rtnl_link_af_ops_lookup(family);
	if (!af_ops)
		return ~0;

	if (af_ops->ao_compare == NULL) {
		ret = ~0;
		goto out;
	}

	ret = af_ops->ao_compare(a, b, family, ~0, 0);

out:
	rtnl_link_af_ops_put(af_ops);

	return ret;
}

/**
 * Compare link info data
 * @arg a              Link object a
 * @arg b              Link object b
 *
 * This function will compare link_info data between two links
 * a and b
 *
 * @return 0 if link_info data matches or is not present
 * or != 0 if it mismatches.
 */
int rtnl_link_info_data_compare(struct rtnl_link *a, struct rtnl_link *b, int flags)
{
	if (a->l_info_ops != b->l_info_ops)
		return ~0;

	if (!a->l_info_ops || !a->l_info_ops->io_compare)
		return 0;

	return a->l_info_ops->io_compare(a, b, flags);
}

/** @} */

/** @} */

