/*****************************************************************************\
 *  topology.c - Topology plugin function setup.
 *****************************************************************************
 *  Copyright (C) 2009-2010 Lawrence Livermore National Security.
 *  Copyright (C) 2014 Silicon Graphics International Corp. All rights reserved.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Morris Jette <jette1@llnl.gov>
 *  CODE-OCEC-09-009. All rights reserved.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include <pthread.h>
#include <sys/stat.h>

#include "src/common/log.h"
#include "src/common/plugrack.h"
#include "src/common/read_config.h"
#include "src/common/sercli.h"
#include "src/common/slurm_protocol_api.h"
#include "src/common/timers.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "src/interfaces/data_parser.h"
#include "src/interfaces/serializer.h"
#include "src/interfaces/topology.h"

strong_alias(topology_g_build_config, slurm_topology_g_build_config);
strong_alias(topology_g_destroy_config, slurm_topology_g_detroy_config);

typedef struct slurm_topo_ops {
	uint32_t (*plugin_id);
	char(*plugin_type);
	bool(*supports_exclusive_topo);
	int (*add_rm_node)(node_record_t *node_ptr, char *addr,
			   topology_ctx_t *tctx);
	bool (*allow_one_node)(void *tctx);
	int (*build_config)(topology_ctx_t *tctx);
	int (*destroy_config)(topology_ctx_t *tctx);
	int (*eval_node)(topology_eval_t *topo_eval, int node_inx);
	int (*eval_nodes) (topology_eval_t *topo_eval);

	int (*whole_topo)(bitstr_t *node_mask, void *tctx);
	bitstr_t *(*get_bitmap)(char *name, void *tctx);
	bool (*node_ranking)(topology_ctx_t *tctx);
	int		(*get_node_addr)	( char* node_name,
						  char** addr,
						  char** pattern,
						  void *tctx
					       	);
	int (*split_hostlist)(hostlist_t *hl, hostlist_t ***sp_hl, int *count,
			      uint16_t tree_width, void *tctx);
	int (*get)(topology_data_t type, void *data, void *tctx);
	int (*topoinfo_free) (void *topoinfo_ptr);
	int (*topoinfo_pack) (void *topoinfo_ptr, buf_t *buffer,
			      uint16_t protocol_version);
	int (*topoinfo_print)(void *topoinfo_ptr, char *nodes_list, char *unit,
			      char **out);
	int (*topoinfo_unpack) (void **topoinfo_pptr, buf_t *buffer,
				uint16_t protocol_version);
	void (*jobinfo_free)(void *topo_jobinfo);
	void (*jobinfo_pack)(void *topo_jobinfo, buf_t *buffer,
			     uint16_t protocol_version);
	int (*jobinfo_unpack)(void **topo_jobinfo, buf_t *buffer,
			      uint16_t protocol_version);
	int (*jobinfo_get)(topology_jobinfo_type_t type, void *topo_jobinfo,
			   void *data);
	uint32_t (*get_fragmentation)(bitstr_t *node_mask, void *tctx);
	void (*get_topology_str)(node_record_t *node_ptr,
				 char **topology_str_ptr, topology_ctx_t *tctx);
	int (*get_rank)(bitstr_t *node_bitmap, uint32_t **node_rank,
			uint32_t *size, void *tctx);
} slurm_topo_ops_t;

/*
 * Must be synchronized with slurm_topo_ops_t above.
 */
static const char *syms[] = {
	"plugin_id",
	"plugin_type",
	"supports_exclusive_topo",
	"topology_p_add_rm_node",
	"topology_p_allow_one_node",
	"topology_p_build_config",
	"topology_p_destroy_config",
	"topology_p_eval_node",
	"topology_p_eval_nodes",
	"topology_p_whole_topo",
	"topology_p_get_bitmap",
	"topology_p_generate_node_ranking",
	"topology_p_get_node_addr",
	"topology_p_split_hostlist",
	"topology_p_get",
	"topology_p_topoinfo_free",
	"topology_p_topoinfo_pack",
	"topology_p_topoinfo_print",
	"topology_p_topoinfo_unpack",
	"topology_p_jobinfo_free",
	"topology_p_jobinfo_pack",
	"topology_p_jobinfo_unpack",
	"topology_p_jobinfo_get",
	"topology_p_get_fragmentation",
	"topology_p_get_topology_str",
	"topology_p_get_rank",
};

static slurm_topo_ops_t *ops = NULL;
static plugin_context_t **g_context = NULL;
static int g_context_num = 0;
static pthread_mutex_t g_context_lock = PTHREAD_MUTEX_INITIALIZER;
static plugin_init_t plugin_inited = PLUGIN_NOT_INITED;
static topology_ctx_t *tctx = NULL;
static int tctx_num = -1;

static void _free_topology_ctx_members(topology_ctx_t *tctx_ptr)
{
	if (tctx_ptr) {
		if (!xstrcmp(tctx_ptr->plugin, "topology/tree"))
			free_topology_tree_config(tctx_ptr->config);
		else if (!xstrcmp(tctx_ptr->plugin, "topology/block"))
			free_topology_block_config(tctx_ptr->config);
		else if (!xstrcmp(tctx_ptr->plugin, "topology/flat"))
			free_topology_flat_config(tctx_ptr->config);
		else if (!xstrcmp(tctx_ptr->plugin, "topology/ring"))
			free_topology_ring_config(tctx_ptr->config);
		else if (!xstrcmp(tctx_ptr->plugin, "topology/torus3d"))
			free_topology_torus3d_config(tctx_ptr->config);

		xfree(tctx_ptr->name);
		xfree(tctx_ptr->plugin);
		xfree(tctx_ptr->topo_conf);
	}
}

static void _free_tctx_array(void)
{
	if (tctx_num < 0)
		return;

	for (int i = 0; i < tctx_num; i++) {
		_free_topology_ctx_members(&tctx[i]);
	}
	xfree(tctx);
	tctx_num = -1;
}

static int _get_plugin_index(int plugin_id)
{
	xassert(ops);

	for (int i = 0; i < g_context_num; i++)
		if (plugin_id == *ops[i].plugin_id)
			return i;

	return -1;
}

static int _get_plugin_index_by_type(char *type)
{
	char *plugin_type = "topo";

	for (int i = 0; i < g_context_num; i++)
		if (!xstrcmp(plugin_type, ops[i].plugin_type))
			return i;

	xrecalloc(ops, g_context_num + 1, sizeof(slurm_topo_ops_t));
	xrecalloc(g_context, g_context_num + 1, sizeof(plugin_context_t *));

	g_context[g_context_num] =
		plugin_context_create(plugin_type, type,
				      (void **) &ops[g_context_num], syms,
				      sizeof(syms));
	if (!g_context[g_context_num]) {
		error("%s: cannot create %s context for %s",
		      __func__, plugin_type, type);
		return -1;
	}

	return g_context_num++;
}

static int _get_tctx_index_by_name(char *name)
{
	for (int i = 0; i < tctx_num; i++) {
		if (!xstrcmp(name, tctx[i].name))
			return i;
	}

	return -1;
}

static int _cmp_tctx(const void *x, const void *y)
{
	const topology_ctx_t *t1 = x, *t2 = y;

	if (t1->cluster_default && !t2->cluster_default)
		return -1;
	else if (!t1->cluster_default && t2->cluster_default)
		return 1;

	return 0;
}

/*
 * Sort a populated topology context array and resolve plugin indices.
 * IN/OUT tctx_array - topology context array; .tctx entries are sorted and
 *                     each .idx is populated on success
 * RET SLURM_SUCCESS or SLURM_ERROR
 */
static int _process_conf(topology_ctx_array_t *tctx_array)
{
	qsort(tctx_array->tctx, tctx_array->tctx_num, sizeof(topology_ctx_t),
	      _cmp_tctx);
	for (int i = 0; i < tctx_array->tctx_num; i++) {
		debug("Plugin: %s, Topology Name:%s",
		      tctx_array->tctx[i].plugin, tctx_array->tctx[i].name);
		if ((tctx_array->tctx[i].idx =
			     _get_plugin_index_by_type(tctx_array->tctx[i]
							       .plugin)) < 0) {
			return SLURM_ERROR;
		}
	}

	if (get_log_level() > LOG_LEVEL_DEBUG2) {
		char *dump_str = NULL;

		if (!(SERCLI_DUMP_STR(TOPOLOGY_CONF_ARRAY, NULL, (*tctx_array),
				      dump_str, MIME_TYPE_YAML,
				      SER_FLAGS_NO_TAG)))
			debug2("%s", dump_str);

		xfree(dump_str);
	}

	return SLURM_SUCCESS;
}

/*
 * The topology plugin can not be changed via reconfiguration
 * due to background threads, job priorities, etc. slurmctld must
 * be restarted and job priority changes may be required to change
 * the topology type.
 */
extern int topology_g_init(void)
{
	int retval = SLURM_SUCCESS;
	topology_ctx_array_t tctx_array = {
		.tctx_num = -1,
	};

	slurm_mutex_lock(&g_context_lock);

	if (plugin_inited)
		goto done;

	retval = CONF_PARSE(TOPOLOGY_CONF_ARRAY, "topology.yaml", tctx_array);
	if (!retval) {
		if (!(retval = _process_conf(&tctx_array))) {
			tctx_num = tctx_array.tctx_num;
			tctx = tctx_array.tctx;
		}
		plugin_inited = PLUGIN_INITED;
		goto done;
	}
	if (retval != ENOENT) {
		error("Failed to load topology.yaml: %s",
		      slurm_strerror(retval));
		plugin_inited = PLUGIN_NOT_INITED;
		goto done;
	}

	/* ENOENT: topology.yaml absent, fall through to plugrack */
	retval = SLURM_SUCCESS;

	xassert(slurm_conf.topology_plugin);

	tctx = xcalloc(1, sizeof(topology_ctx_t));
	tctx[0].name = xstrdup("default");
	tctx[0].topo_conf = get_extra_conf_path("topology.conf");

	if ((tctx[0].idx =
	     _get_plugin_index_by_type(slurm_conf.topology_plugin)) < 0) {
		retval = SLURM_ERROR;
		plugin_inited = PLUGIN_NOT_INITED;
		goto done;
	}

	plugin_inited = PLUGIN_INITED;
	tctx_num = 1;
done:

	slurm_mutex_unlock(&g_context_lock);
	return retval;
}

extern int topology_g_fini(void)
{
	int rc = SLURM_SUCCESS;

	slurm_mutex_lock(&g_context_lock);
	_free_tctx_array();
	for (int i = 0; i < g_context_num; i++) {
		int rc2 = plugin_context_destroy(g_context[i]);
		if (rc2) {
			debug("%s: %s: %s",
			      __func__, g_context[i]->type,
			      slurm_strerror(rc2));
			rc = SLURM_ERROR;
		}
	}

	xfree(ops);
	xfree(g_context);
	g_context_num = 0;

	slurm_mutex_unlock(&g_context_lock);

	return rc;
}

extern int topology_get_plugin_id(void)
{
	xassert(plugin_inited != PLUGIN_NOT_INITED);

	return *(ops[0].plugin_id);
}

extern int topology_g_build_config(void)
{
	int rc = SLURM_SUCCESS;
	DEF_TIMERS;

	slurm_mutex_lock(&g_context_lock);
	xassert(plugin_inited != PLUGIN_NOT_INITED);

	START_TIMER;
	for (int i = 0; i < tctx_num; i++) {
		int rc2 = (*(ops[tctx[i].idx].build_config))(&(tctx[i]));
		if (rc2) {
			debug("%s: %s: %s",
			      __func__, g_context[i]->type,
			      slurm_strerror(rc2));
			rc = SLURM_ERROR;
		}
	}
	END_TIMER3(__func__, 20000);

	slurm_mutex_unlock(&g_context_lock);

	return rc;
}

extern int topology_g_destroy_config(void)
{
	int rc = SLURM_SUCCESS;
	DEF_TIMERS;

	slurm_mutex_lock(&g_context_lock);
	xassert(plugin_inited != PLUGIN_NOT_INITED);

	START_TIMER;
	for (int i = 0; i < tctx_num; i++) {
		int rc2 = (*(ops[tctx[i].idx].destroy_config))(&(tctx[i]));
		if (rc2) {
			debug("%s: %s: %s",
			      __func__, g_context[i]->type,
			      slurm_strerror(rc2));
			rc = SLURM_ERROR;
		}
	}
	END_TIMER3(__func__, 20000);

	slurm_mutex_unlock(&g_context_lock);

	return rc;
}

extern char *topology_g_get_config(void)
{
	char *dump_str = NULL;
	topology_ctx_array_t tctx_array = {
		.tctx = tctx,
		.tctx_num = tctx_num,
	};

	(void) SERCLI_DUMP_STR(TOPOLOGY_CONF_ARRAY, NULL, tctx_array, dump_str,
			       MIME_TYPE_YAML, SER_FLAGS_NO_TAG);

	return dump_str;
}

extern int topology_g_eval_nodes(topology_eval_t *topo_eval)
{
	int idx = topo_eval->job_ptr->part_ptr->topology_idx;

	xassert(plugin_inited != PLUGIN_NOT_INITED);
	xassert((idx >= 0) && (idx < tctx_num));

	/*
	 * topo_jobinfo needs to be reset in interface before entering plugin in
	 * case it was set by a different topology plugin.
	 */
	topology_g_jobinfo_free(topo_eval->job_ptr->topo_jobinfo);
	topo_eval->job_ptr->topo_jobinfo = NULL;

	topo_eval->tctx = &(tctx[idx]);

	return (*(ops[tctx[idx].idx].eval_nodes))(topo_eval);
}

extern int topology_g_eval_node(topology_eval_t *topo_eval, int node_inx)
{
	int idx = topo_eval->job_ptr->part_ptr->topology_idx;

	xassert(plugin_inited != PLUGIN_NOT_INITED);
	xassert((idx >= 0) && (idx < tctx_num));

	/*
	 * topo_jobinfo needs to be reset in interface before entering plugin in
	 * case it was set by a different topology plugin.
	 */
	topology_g_jobinfo_free(topo_eval->job_ptr->topo_jobinfo);
	topo_eval->job_ptr->topo_jobinfo = NULL;

	topo_eval->tctx = &(tctx[idx]);

	return (*(ops[tctx[idx].idx].eval_node))(topo_eval, node_inx);
}

extern int topology_g_whole_topo(bitstr_t *node_mask, int idx)
{
	xassert(plugin_inited);
	xassert((idx >= 0) && (idx < tctx_num));

	return (*(ops[tctx[idx].idx].whole_topo))(node_mask,
						  tctx[idx].plugin_ctx);
}

extern bool topology_g_whole_topo_enabled(int idx)
{
	xassert(plugin_inited);
	xassert((idx >= 0) && (idx < tctx_num));

	return (*(ops[tctx[idx].idx].supports_exclusive_topo));
}

extern int topology_g_add_rm_node(node_record_t *node_ptr)
{
	int rc = SLURM_SUCCESS;
	char *topology_str, *token, *save_ptr = NULL;
	bool *set_tctx = NULL;

	xassert(plugin_inited);

	if (!node_ptr->topology_str || !node_ptr->topology_str[0]) {
		for (int i = 0; i < tctx_num; i++) {
			rc = (*(ops[tctx[i].idx].add_rm_node))(node_ptr, NULL,
							       &(tctx[i]));
			if (rc)
				break;
		}
		return rc;
	}

	topology_str = xstrdup(node_ptr->topology_str);
	token = strtok_r(topology_str, ",", &save_ptr);

	set_tctx = xcalloc(tctx_num, sizeof(*set_tctx));
	while (token) {
		char *name, *unit = NULL;
		int tctx_idx;

		name = strtok_r(token, ":", &unit);

		tctx_idx = _get_tctx_index_by_name(name);

		if (tctx_idx < 0) {
			rc = SLURM_ERROR;
			break;
		}
		rc = (*(ops[tctx[tctx_idx].idx]
				.add_rm_node))(node_ptr, unit,
					       &(tctx[tctx_idx]));
		if (rc)
			break;

		set_tctx[tctx_idx] = true;
		token = strtok_r(NULL, ",", &save_ptr);
	}

	if (!rc) {
		/* clear the node from any topology not in topology_str */
		for (int i = 0; i < tctx_num; i++) {
			if (set_tctx[i])
				continue;
			rc = (*(ops[tctx[i].idx].add_rm_node))(node_ptr, NULL,
							       &(tctx[i]));
			if (rc)
				break;
		}
	}

	xfree(set_tctx);
	xfree(topology_str);

	return rc;
}

extern bool topology_g_allow_one_node(int idx)
{
	xassert(plugin_inited);
	xassert((idx >= 0) && (idx < tctx_num));

	return (*(ops[tctx[idx].idx].allow_one_node))(tctx[idx].plugin_ctx);
}

extern char *topology_g_get_topology_str(node_record_t *node_ptr)
{
	char *topology_str = NULL;

	for (int i = 0; i < tctx_num; i++) {
		(*(ops[tctx[i].idx].get_topology_str))(node_ptr, &topology_str,
						       &(tctx[i]));
	}
	return topology_str;
}

/*
 * topology_g_get_bitmap - Get bitmap of nodes in topo group
 *
 * IN name of topo group
 * RET bitmap of nodes from _record_table (do not free)
 */
extern bitstr_t *topology_g_get_bitmap(char *name)
{
	xassert(plugin_inited);

	return (*(ops[tctx[0].idx].get_bitmap))(name, tctx[0].plugin_ctx);
}

/*
 * This operation is only supported by those topology plugins for
 * which the node ordering between slurmd and slurmctld is invariant.
 */
extern bool topology_g_generate_node_ranking(void)
{
	xassert(plugin_inited != PLUGIN_NOT_INITED);

	return (*(ops[tctx[0].idx].node_ranking))(&(tctx[0]));
}

extern int topology_g_get_node_addr(char *node_name, char **addr,
				    char **pattern)
{
	xassert(plugin_inited != PLUGIN_NOT_INITED);

	return (*(ops[tctx[0].idx].get_node_addr))(node_name, addr, pattern,
						   tctx[0].plugin_ctx);
}

extern int topology_g_split_hostlist(hostlist_t *hl,
				     hostlist_t ***sp_hl,
				     int *count,
				     uint16_t tree_width)
{
	int depth, j, nnodes, nnodex;
	char *buf;

	nnodes = nnodex = 0;
	xassert(g_context);

	if (!tree_width)
		tree_width = slurm_conf.tree_width;

	if (slurm_conf.debug_flags & DEBUG_FLAG_ROUTE) {
		/* nnodes has to be set here as the hl is empty after the
		 * split_hostlise call.  */
		nnodes = hostlist_count(hl);
		buf = hostlist_ranged_string_xmalloc(hl);
		info("ROUTE: split_hostlist: hl=%s tree_width %u",
		     buf, tree_width);
		xfree(buf);
	}

	if (hostlist_count(hl) == 1) {
		/* No need to split a list of 1. */
		char *name = hostlist_shift(hl);
		*sp_hl = xcalloc(1, sizeof(hostlist_t *));
		(*sp_hl)[0] = hostlist_create(name);
		free(name);
		*count = depth = 1;

		goto end;
	}

	depth = (*(ops[tctx[0].idx].split_hostlist))(hl, sp_hl, count,
						     tree_width,
						     tctx[0].plugin_ctx);
	if (!depth && !(*count))
		goto end;

	if (slurm_conf.debug_flags & DEBUG_FLAG_ROUTE) {
		/* Sanity check to make sure all nodes in msg list are in
		 * a child list */
		nnodex = 0;
		for (j = 0; j < *count; j++) {
			nnodex += hostlist_count((*sp_hl)[j]);
		}
		if (nnodex != nnodes) {	/* CLANG false positive */
			info("ROUTE: number of nodes in split lists (%d)"
			     " is not equal to number in input list (%d)",
			     nnodex, nnodes);
		}
	}

end:
	return depth;
}

extern int topology_g_get(topology_data_t type, char *name, void *data)
{
	int tctx_idx = 0;
	xassert(plugin_inited != PLUGIN_NOT_INITED);

	if (type == TOPO_DATA_TCTX_IDX) {
		int tmp_idx;
		if (!name || ((tmp_idx = _get_tctx_index_by_name(name)) < 0))
			return ESLURM_REQUESTED_TOPO_CONFIG_UNAVAILABLE;
		else {
			int *tctx_idx_ptr = data;
			*tctx_idx_ptr = tmp_idx;
			return SLURM_SUCCESS;
		}
	}

	if (type == TOPO_DATA_EXCLUSIVE_TOPO && !name) {
		int *exclusive_topo = data;
		*exclusive_topo = 0;
		for (int i = 0; i < g_context_num; i++) {
			if (*ops[i].supports_exclusive_topo) {
				*exclusive_topo = 1;
				break;
			}
		}
		return SLURM_SUCCESS;
	}

	if (name) {
		tctx_idx = _get_tctx_index_by_name(name);
		if (tctx_idx < 0) {
			error("%s: topology %s not active", __func__, name);
			return ESLURM_REQUESTED_TOPO_CONFIG_UNAVAILABLE;
		}
	}

	return (*(ops[tctx[tctx_idx].idx].get))(type, data,
						tctx[tctx_idx].plugin_ctx);
}

extern int topology_g_topoinfo_free(dynamic_plugin_data_t *topoinfo)
{
	int rc = SLURM_SUCCESS;

	xassert(plugin_inited != PLUGIN_NOT_INITED);

	if (topoinfo) {
		int plugin_inx = _get_plugin_index(topoinfo->plugin_id);

		if (topoinfo->data)
			rc = (*(ops[plugin_inx].topoinfo_free))(topoinfo->data);
		xfree(topoinfo);
	}
	return rc;
}

extern int topology_g_topoinfo_pack(dynamic_plugin_data_t *topoinfo,
				    buf_t *buffer, uint16_t protocol_version)
{
	int plugin_inx;

	xassert(plugin_inited != PLUGIN_NOT_INITED);
	xassert(topoinfo);

	/* Always pack the plugin_id */
	pack32(topoinfo->plugin_id, buffer);

	plugin_inx = _get_plugin_index(topoinfo->plugin_id);
	if (plugin_inx < 0)
		return SLURM_ERROR;

	return (*(ops[plugin_inx].topoinfo_pack))(topoinfo->data, buffer,
						  protocol_version);
}

extern int topology_g_topoinfo_print(dynamic_plugin_data_t *topoinfo,
				     char *nodes_list, char *unit, char **out)
{
	int plugin_inx = _get_plugin_index(topoinfo->plugin_id);
	xassert(plugin_inited != PLUGIN_NOT_INITED);
	xassert(topoinfo);
	if (plugin_inx < 0)
		return SLURM_ERROR;

	return (*(ops[tctx[plugin_inx].idx].topoinfo_print))(topoinfo->data,
							     nodes_list, unit,
							     out);
}

extern int topology_g_topoinfo_unpack(dynamic_plugin_data_t **topoinfo,
				      buf_t *buffer, uint16_t protocol_version)
{
	dynamic_plugin_data_t *topoinfo_ptr = NULL;
	int plugin_inx;

	xassert(plugin_inited != PLUGIN_NOT_INITED);

	topoinfo_ptr = xmalloc(sizeof(dynamic_plugin_data_t));
	*topoinfo = topoinfo_ptr;

	if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		uint32_t plugin_id;
		safe_unpack32(&plugin_id, buffer);

		plugin_inx = _get_plugin_index(plugin_id);
		if (plugin_inx < 0) {
			error("%s: topology plugin %u not active",
			      __func__, plugin_id);
			goto unpack_error;
		} else {
			topoinfo_ptr->plugin_id = plugin_id;
		}
	} else {
		error("%s: protocol_version %hu not supported", __func__,
		      protocol_version);
		goto unpack_error;
	}

	if ((*(ops[tctx[plugin_inx].idx].topoinfo_unpack))(&topoinfo_ptr->data,
							   buffer,
							   protocol_version) !=
	    SLURM_SUCCESS)
		goto unpack_error;

	return SLURM_SUCCESS;

unpack_error:
	topology_g_topoinfo_free(topoinfo_ptr);
	*topoinfo = NULL;
	error("%s: unpack error", __func__);
	return SLURM_ERROR;
}

extern void topology_g_jobinfo_free(
	dynamic_plugin_data_t *jobinfo_plugin_data)
{
	int plugin_index;

	xassert(plugin_inited != PLUGIN_NOT_INITED);

	if (!jobinfo_plugin_data)
		return;

	plugin_index = _get_plugin_index(jobinfo_plugin_data->plugin_id);
	if (plugin_index < 0)
		return;

	(*(ops[plugin_index].jobinfo_free))(jobinfo_plugin_data->data);

	xfree(jobinfo_plugin_data);

	return;
}

extern void topology_g_jobinfo_pack(
	dynamic_plugin_data_t *jobinfo_plugin_data,
	buf_t *buffer,
	uint16_t protocol_version)
{
	int plugin_index;

	xassert(plugin_inited != PLUGIN_NOT_INITED);

	if (!jobinfo_plugin_data)
		goto pack_no_plugin;

	plugin_index = _get_plugin_index(jobinfo_plugin_data->plugin_id);
	if (plugin_index < 0)
		goto pack_no_plugin;

	dynamic_plugin_data_pack(jobinfo_plugin_data,
				 *(ops[plugin_index].jobinfo_pack), buffer,
				 protocol_version);
	return;

pack_no_plugin:
	dynamic_plugin_data_pack(NULL, NULL, buffer, protocol_version);
	return;
}

static dynamic_plugin_data_unpack_func _get_unpack_func(
	uint32_t plugin_id)
{
	int plugin_index = _get_plugin_index(plugin_id);

	if (plugin_index < 0)
		return NULL;

	return *(ops[plugin_index].jobinfo_unpack);
}

extern int topology_g_jobinfo_unpack(
	dynamic_plugin_data_t **jobinfo_plugin_data,
	buf_t *buffer,
	uint16_t protocol_version)
{
	xassert(jobinfo_plugin_data);

	if (plugin_inited != PLUGIN_INITED) {
		return dynamic_plugin_data_unpack(NULL, NULL, buffer,
						  protocol_version);
	}

	return dynamic_plugin_data_unpack(jobinfo_plugin_data, _get_unpack_func,
					  buffer, protocol_version);
}

extern int topology_g_jobinfo_get(
	topology_jobinfo_type_t type,
	dynamic_plugin_data_t *jobinfo_plugin_data,
	void *data)
{
	int plugin_index;

	xassert(plugin_inited != PLUGIN_NOT_INITED);

	if (!jobinfo_plugin_data)
		return SLURM_ERROR;

	plugin_index = _get_plugin_index(jobinfo_plugin_data->plugin_id);
	if (plugin_index < 0)
		return SLURM_ERROR;

	return (*(ops[plugin_index].jobinfo_get))(type,
						  jobinfo_plugin_data->data,
						  data);
}

extern uint32_t topology_g_get_fragmentation(bitstr_t *node_mask)
{
	uint32_t fragmentation = 0;
	xassert(plugin_inited);

	for (int i = 0; i < tctx_num; i++) {
		fragmentation +=
			(*(ops[tctx[i].idx]
				   .get_fragmentation))(node_mask,
							tctx[i].plugin_ctx);
	}

	return fragmentation;
}

extern int topology_g_get_rank(bitstr_t *node_bitmap, uint32_t **node_rank,
			       uint32_t *size, int idx)
{
	xassert(plugin_inited);
	xassert((idx >= 0) && (idx < tctx_num));

	return (*(ops[tctx[idx].idx].get_rank))(node_bitmap, node_rank, size,
						tctx[idx].plugin_ctx);
}

extern void free_topology_ctx(topology_ctx_t *tctx_ptr)
{
	if (tctx_ptr) {
		_free_topology_ctx_members(tctx_ptr);
		xfree(tctx_ptr);
	}
}

static void _free_block_conf_members(slurm_conf_block_t *config)
{
	if (config) {
		xfree(config->block_name);
		xfree(config->nodes);
	}
}

extern void free_block_conf(slurm_conf_block_t *config)
{
	if (config) {
		_free_block_conf_members(config);
		xfree(config);
	}
}

extern void free_topology_block_config(topology_block_config_t *config)
{
	if (config) {
		for (int i = 0; i < config->config_cnt; i++)
			_free_block_conf_members(&config->block_configs[i]);
		xfree(config->block_configs);
		FREE_NULL_LIST(config->block_sizes);
		xfree(config);
	}
}

extern void free_topology_flat_config(topology_flat_config_t *config)
{
	xfree(config);
}

static void _free_ring_conf_members(slurm_conf_ring_t *config)
{
	if (config) {
		xfree(config->ring_name);
		xfree(config->nodes);
	}
}

extern void free_ring_conf(slurm_conf_ring_t *config)
{
	if (config) {
		_free_ring_conf_members(config);
		xfree(config);
	}
}

extern void free_topology_ring_config(topology_ring_config_t *config)
{
	if (config) {
		for (int i = 0; i < config->config_cnt; i++)
			_free_ring_conf_members(&config->ring_configs[i]);
		xfree(config->ring_configs);
		xfree(config);
	}
}

static void _free_torus3d_conf_members(slurm_conf_torus3d_t *config)
{
	if (config) {
		xfree(config->name);
		xfree(config->nodes);
		xfree(config->placements);
		if (config->regions) {
			for (int i = 0; i < config->region_count; i++)
				xfree(config->regions[i].nodes);
		}
		xfree(config->regions);
	}
}

extern void free_torus3d_conf(slurm_conf_torus3d_t *config)
{
	if (config) {
		_free_torus3d_conf_members(config);
		xfree(config);
	}
}

extern void free_topology_torus3d_config(topology_torus3d_config_t *config)
{
	if (config) {
		for (int i = 0; i < config->config_cnt; i++)
			_free_torus3d_conf_members(&config->torus3d_configs[i]);
		xfree(config->torus3d_configs);
		xfree(config);
	}
}

static void _free_switch_conf_members(slurm_conf_switches_t *config)
{
	if (config) {
		xfree(config->nodes);
		xfree(config->switch_name);
		xfree(config->switches);
	}
}

extern void free_switch_conf(slurm_conf_switches_t *config)
{
	if (config) {
		_free_switch_conf_members(config);
		xfree(config);
	}
}

extern void free_topology_tree_config(topology_tree_config_t *config)
{
	if (config) {
		for (int i = 0; i < config->config_cnt; i++)
			_free_switch_conf_members(&config->switch_configs[i]);
		xfree(config->switch_configs);
		xfree(config);
	}
}
