/*
 * Copyright (c) 2004-2009 Voltaire Inc.  All rights reserved.
 * Copyright (c) 2009 HNR Consulting.  All rights reserved.
 * Copyright (c) 2010,2011 Mellanox Technologies LTD.  All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <netinet/in.h>
#include <inttypes.h>

#include <infiniband/umad.h>
#include <infiniband/mad.h>
#include <util/node_name_map.h>

#include "ibdiag_common.h"

static struct ibmad_port *srcport;

#define MAXHOPS	63

static const char * const node_type_str[] = {
	"???",
	"ca",
	"switch",
	"router",
	"iwarp rnic"
};

static int timeout = 0;		/* ms */
static int force;
static FILE *f;
static FILE *ports_fd;

static char *node_name_map_file = NULL;
static char *ports_file = NULL;
static nn_map_t *node_name_map = NULL;

typedef struct Port Port;
typedef struct Switch Switch;
typedef struct Node Node;

struct Port {
	Port *next;
	Port *remoteport;
	uint64_t portguid;
	int portnum;
	int lid;
	int lmc;
	int state;
	int physstate;
	char portinfo[64];
};

struct Switch {
	int linearcap;
	int mccap;
	int linearFDBtop;
	int fdb_base;
	int enhsp0;
	uint8_t fdb[64];
	char switchinfo[64];
};

struct Node {
	Node *htnext;
	Node *dnext;
	Port *ports;
	ib_portid_t path;
	int type;
	int dist;
	int numports;
	int upport;
	Node *upnode;
	uint64_t nodeguid;	/* also portguid */
	char nodedesc[IB_SMP_DATA_SIZE + 1];
	char nodeinfo[64];
};

static Node *nodesdist[MAXHOPS];
static uint64_t target_portguid;

/*
 * is_port_inactive
 * Checks whether or not the port state is other than active.
 * The "sw" argument is only relevant when the port is on a
 * switch; for HCAs and routers, this argument is ignored.
 * Returns 1 when port is not active and 0 when active.
 * Base switch port 0 is considered always active.
 */
static int is_port_inactive(Node * node, Port * port, Switch * sw)
{
	int res = 0;
	if (port->state != 4 &&
	    (node->type != IB_NODE_SWITCH ||
	     (node->type == IB_NODE_SWITCH && sw->enhsp0)))
		res = 1;
	return res;
}

static int get_node(Node * node, Port * port, ib_portid_t * portid)
{
	void *pi = port->portinfo, *ni = node->nodeinfo, *nd = node->nodedesc;
	char *s, *e;

	memset(ni, 0, sizeof(node->nodeinfo));
	if (!smp_query_via(ni, portid, IB_ATTR_NODE_INFO, 0, timeout, srcport))
		return -1;

	memset(nd, 0, sizeof(node->nodedesc));
	if (!smp_query_via(nd, portid, IB_ATTR_NODE_DESC, 0, timeout, srcport))
		return -1;

	for (s = nd, e = s + 64; s < e; s++) {
		if (!*s)
			break;
		if (!isprint(*s))
			*s = ' ';
	}

	memset(pi, 0, sizeof(port->portinfo));
	if (!smp_query_via(pi, portid, IB_ATTR_PORT_INFO, 0, timeout, srcport))
		return -1;

	mad_decode_field(ni, IB_NODE_GUID_F, &node->nodeguid);
	mad_decode_field(ni, IB_NODE_TYPE_F, &node->type);
	mad_decode_field(ni, IB_NODE_NPORTS_F, &node->numports);

	mad_decode_field(ni, IB_NODE_PORT_GUID_F, &port->portguid);
	mad_decode_field(ni, IB_NODE_LOCAL_PORT_F, &port->portnum);
	mad_decode_field(pi, IB_PORT_LID_F, &port->lid);
	mad_decode_field(pi, IB_PORT_LMC_F, &port->lmc);
	mad_decode_field(pi, IB_PORT_STATE_F, &port->state);

	DEBUG("portid %s: got node %" PRIx64 " '%s'", portid2str(portid),
	      node->nodeguid, node->nodedesc);
	return 0;
}

static int switch_lookup(Switch * sw, ib_portid_t * portid, int lid)
{
	void *si = sw->switchinfo, *fdb = sw->fdb;

	memset(si, 0, sizeof(sw->switchinfo));
	if (!smp_query_via(si, portid, IB_ATTR_SWITCH_INFO, 0, timeout,
			   srcport))
		return -1;

	mad_decode_field(si, IB_SW_LINEAR_FDB_CAP_F, &sw->linearcap);
	mad_decode_field(si, IB_SW_LINEAR_FDB_TOP_F, &sw->linearFDBtop);
	mad_decode_field(si, IB_SW_ENHANCED_PORT0_F, &sw->enhsp0);

	if (lid >= sw->linearcap && lid > sw->linearFDBtop)
		return -1;

	memset(fdb, 0, sizeof(sw->fdb));
	if (!smp_query_via(fdb, portid, IB_ATTR_LINEARFORWTBL, lid / 64,
			   timeout, srcport))
		return -1;

	DEBUG("portid %s: forward lid %d to port %d",
	      portid2str(portid), lid, sw->fdb[lid % 64]);
	return sw->fdb[lid % 64];
}

static int sameport(Port * a, Port * b)
{
	return a->portguid == b->portguid || (force && a->lid == b->lid);
}

static int extend_dpath(ib_dr_path_t * path, int nextport)
{
	if (path->cnt + 2 >= sizeof(path->p))
		return -1;
	++path->cnt;
	path->p[path->cnt] = (uint8_t) nextport;
	return path->cnt;
}

static void dump_endnode(int dump, const char *prompt, Node *node, Port *port)
{
	char *nodename = NULL;

	if (!dump)
		return;
	if (dump == 1) {
		fprintf(f, "%s {0x%016" PRIx64 "}[%d]\n",
			prompt, node->nodeguid,
			node->type == IB_NODE_SWITCH ? 0 : port->portnum);
		return;
	}

	nodename =
	    remap_node_name(node_name_map, node->nodeguid, node->nodedesc);

	fprintf(f, "%s %s {0x%016" PRIx64 "} portnum %d lid %u-%u \"%s\"\n",
		prompt,
		(node->type <= IB_NODE_MAX ? node_type_str[node->type] : "???"),
		node->nodeguid,
		node->type == IB_NODE_SWITCH ? 0 : port->portnum, port->lid,
		port->lid + (1 << port->lmc) - 1, nodename);

	free(nodename);
}

static void dump_route(int dump, Node * node, int outport, Port * port)
{
	char *nodename = NULL;

	if (!dump && !ibverbose)
		return;

	nodename =
	    remap_node_name(node_name_map, node->nodeguid, node->nodedesc);

	if (dump == 1)
		fprintf(f, "[%d] -> {0x%016" PRIx64 "}[%d]\n",
			outport, port->portguid, port->portnum);
	else
		fprintf(f, "[%d] -> %s port {0x%016" PRIx64
			"}[%d] lid %u-%u \"%s\"\n", outport,
			(node->type <=
			 IB_NODE_MAX ? node_type_str[node->type] : "???"),
			port->portguid, port->portnum, port->lid,
			port->lid + (1 << port->lmc) - 1, nodename);

	free(nodename);
}

static int find_route(ib_portid_t * from, ib_portid_t * to, int dump)
{
	Node *node, fromnode, tonode, nextnode;
	Port *port, fromport, toport, nextport;
	Switch sw;
	int maxhops = MAXHOPS;
	int portnum, outport = 255, next_sw_outport = 255;

	memset(&fromnode,0,sizeof(Node));
	memset(&tonode,0,sizeof(Node));
	memset(&nextnode,0,sizeof(Node));
	memset(&fromport,0,sizeof(Port));
	memset(&toport,0,sizeof(Port));
	memset(&nextport,0,sizeof(Port));

	DEBUG("from %s", portid2str(from));

	if (get_node(&fromnode, &fromport, from) < 0 ||
	    get_node(&tonode, &toport, to) < 0) {
		IBWARN("can't reach to/from ports");
		if (!force)
			return -1;
		if (to->lid > 0)
			toport.lid = to->lid;
		IBWARN("Force: look for lid %d", to->lid);
	}

	node = &fromnode;
	port = &fromport;
	portnum = port->portnum;

	dump_endnode(dump, "From", node, port);
	if (node->type == IB_NODE_SWITCH) {
		next_sw_outport = switch_lookup(&sw, from, to->lid);
		if (next_sw_outport < 0 || next_sw_outport > node->numports) {
			/* needed to print the port in badtbl */
			outport = next_sw_outport;
			goto badtbl;
		}
	}

	while (maxhops--) {
		if (is_port_inactive(node, port, &sw))
			goto badport;

		if (sameport(port, &toport))
			break;	/* found */

		if (node->type == IB_NODE_SWITCH) {
			DEBUG("switch node");
			outport = next_sw_outport;

			if (extend_dpath(&from->drpath, outport) < 0)
				goto badpath;

			if (get_node(&nextnode, &nextport, from) < 0) {
				IBWARN("can't reach port at %s",
				       portid2str(from));
				return -1;
			}
			if (outport == 0) {
				if (!sameport(&nextport, &toport))
					goto badtbl;
				else
					break;	/* found SMA port */
			}
		} else if ((node->type == IB_NODE_CA) ||
			   (node->type == IB_NODE_ROUTER)) {
			int ca_src = 0;

			outport = portnum;
			DEBUG("ca or router node");
			if (!sameport(port, &fromport)) {
				IBWARN
				    ("can't continue: reached CA or router port %"
				     PRIx64 ", lid %d", port->portguid,
				     port->lid);
				return -1;
			}
			/* we are at CA or router "from" - go one hop back to (hopefully) a switch */
			if (from->drpath.cnt > 0) {
				DEBUG("ca or router node - return back 1 hop");
				from->drpath.cnt--;
			} else {
				ca_src = 1;
				if (portnum
				    && extend_dpath(&from->drpath, portnum) < 0)
					goto badpath;
			}
			if (get_node(&nextnode, &nextport, from) < 0) {
				IBWARN("can't reach port at %s",
				       portid2str(from));
				return -1;
			}
			/* fix port num to be seen from the CA or router side */
			if (!ca_src)
				nextport.portnum =
				    from->drpath.p[from->drpath.cnt + 1];
		}
		/* only if the next node is a switch, get switch info */
		if (nextnode.type == IB_NODE_SWITCH) {
			next_sw_outport = switch_lookup(&sw, from, to->lid);
			if (next_sw_outport < 0 ||
			    next_sw_outport > nextnode.numports) {
				/* needed to print the port in badtbl */
				outport = next_sw_outport;
				goto badtbl;
			}
		}

		port = &nextport;
		if (is_port_inactive(&nextnode, port, &sw))
			goto badoutport;
		node = &nextnode;
		portnum = port->portnum;
		dump_route(dump, node, outport, port);
	}

	if (maxhops <= 0) {
		IBWARN("no route found after %d hops", MAXHOPS);
		return -1;
	}
	dump_endnode(dump, "To", node, port);
	return 0;

badport:
	IBWARN("Bad port state found: node \"%s\" port %d state %d",
	       clean_nodedesc(node->nodedesc), portnum, port->state);
	return -1;
badoutport:
	IBWARN("Bad out port state found: node \"%s\" outport %d state %d",
	       clean_nodedesc(node->nodedesc), outport, port->state);
	return -1;
badtbl:
	IBWARN
	    ("Bad forwarding table entry found at: node \"%s\" lid entry %d is %d (top %d)",
	     clean_nodedesc(node->nodedesc), to->lid, outport, sw.linearFDBtop);
	return -1;
badpath:
	IBWARN("Direct path too long!");
	return -1;
}

/**************************
 * MC span part
 */

#define HASHGUID(guid)		((uint32_t)(((uint32_t)(guid) * 101) ^ ((uint32_t)((guid) >> 32) * 103)))
#define HTSZ 137

static int insert_node(Node * new)
{
	static Node *nodestbl[HTSZ];
	int hash = HASHGUID(new->nodeguid) % HTSZ;
	Node *node;

	for (node = nodestbl[hash]; node; node = node->htnext)
		if (node->nodeguid == new->nodeguid) {
			DEBUG("node %" PRIx64 " already exists", new->nodeguid);
			return -1;
		}

	new->htnext = nodestbl[hash];
	nodestbl[hash] = new;

	return 0;
}

static int get_port(Port * port, int portnum, ib_portid_t * portid)
{
	char portinfo[64] = { 0 };
	void *pi = portinfo;

	port->portnum = portnum;

	if (!smp_query_via(pi, portid, IB_ATTR_PORT_INFO, portnum, timeout,
			   srcport))
		return -1;

	mad_decode_field(pi, IB_PORT_LID_F, &port->lid);
	mad_decode_field(pi, IB_PORT_LMC_F, &port->lmc);
	mad_decode_field(pi, IB_PORT_STATE_F, &port->state);
	mad_decode_field(pi, IB_PORT_PHYS_STATE_F, &port->physstate);

	VERBOSE("portid %s portnum %d: lid %d state %d physstate %d",
		portid2str(portid), portnum, port->lid, port->state,
		port->physstate);
	return 1;
}

static void link_port(Port * port, Node * node)
{
	port->next = node->ports;
	node->ports = port;
}

static int new_node(Node * node, Port * port, ib_portid_t * path, int dist)
{
	if (port->portguid == target_portguid) {
		node->dist = -1;	/* tag as target */
		link_port(port, node);
		dump_endnode(ibverbose, "found target", node, port);
		return 1;	/* found; */
	}

	/* BFS search start with my self */
	if (insert_node(node) < 0)
		return -1;	/* known switch */

	VERBOSE("insert dist %d node %p port %d lid %d", dist, node,
		port->portnum, port->lid);

	link_port(port, node);

	node->dist = dist;
	node->path = *path;
	node->dnext = nodesdist[dist];
	nodesdist[dist] = node;

	return 0;
}

static int switch_mclookup(Node * node, ib_portid_t * portid, int mlid,
			   char *map)
{
	Switch sw;
	char mdb[64];
	void *si = sw.switchinfo;
	__be16 *msets = (__be16 *) mdb;
	int maxsets, block, i, set;

	memset(map, 0, 256);

	memset(si, 0, sizeof(sw.switchinfo));
	if (!smp_query_via(si, portid, IB_ATTR_SWITCH_INFO, 0, timeout,
			   srcport))
		return -1;

	mlid -= 0xc000;

	mad_decode_field(si, IB_SW_MCAST_FDB_CAP_F, &sw.mccap);

	if (mlid >= sw.mccap)
		return -1;

	block = mlid / 32;
	maxsets = (node->numports + 15) / 16;	/* round up */

	for (set = 0; set < maxsets; set++) {
		memset(mdb, 0, sizeof(mdb));
		if (!smp_query_via(mdb, portid, IB_ATTR_MULTICASTFORWTBL,
				   block | (set << 28), timeout, srcport))
			return -1;

		for (i = 0; i < 16; i++, map++) {
			uint16_t mask = ntohs(msets[mlid % 32]);
			if (mask & (1 << i))
				*map = 1;
			else
				continue;
			VERBOSE("Switch guid 0x%" PRIx64
				": mlid 0x%x is forwarded to port %d",
				node->nodeguid, mlid + 0xc000, i + set * 16);
		}
	}

	return 0;
}

/*
 * Return 1 if found, 0 if not, -1 on errors.
 */
static Node *find_mcpath(ib_portid_t * from, int mlid)
{
	Node *node, *remotenode;
	Port *port, *remoteport;
	char map[256];
	int r, i;
	int dist = 0, leafport = 0;
	ib_portid_t *path;

	DEBUG("from %s", portid2str(from));

	node = calloc(1, sizeof(Node));
	if (!node)
		IBEXIT("out of memory");

	port = calloc(1, sizeof(Port));
	if (!port) {
		free(node);
		IBEXIT("out of memory");
	}

	if (get_node(node, port, from) < 0) {
		IBWARN("can't reach node %s", portid2str(from));
		free(node);
		free(port);
		return NULL;
	}

	node->upnode = NULL;	/* root */
	if ((r = new_node(node, port, from, 0)) > 0) {
		if (node->type != IB_NODE_SWITCH) {
			IBWARN("ibtracert from CA to CA is unsupported");
			free(node);
			free(port);
			return NULL;	/* ibtracert from host to itself is unsupported */
		}

		if (switch_mclookup(node, from, mlid, map) < 0 || !map[0]) {
			free(node);
			free(port);
			return NULL;
		}
		return node;
	}

	for (dist = 0; dist < MAXHOPS; dist++) {

		for (node = nodesdist[dist]; node; node = node->dnext) {

			path = &node->path;

			VERBOSE("dist %d node %p", dist, node);
			dump_endnode(ibverbose, "processing", node,
				     node->ports);

			memset(map, 0, sizeof(map));

			if (node->type != IB_NODE_SWITCH) {
				if (dist)
					continue;
				leafport = path->drpath.p[path->drpath.cnt];
				map[port->portnum] = 1;
				node->upport = 0;	/* starting here */
				DEBUG("Starting from CA 0x%" PRIx64
				      " lid %d port %d (leafport %d)",
				      node->nodeguid, port->lid, port->portnum,
				      leafport);
			} else {	/* switch */

				/* if starting from a leaf port fix up port (up port) */
				if (dist == 1 && leafport)
					node->upport = leafport;

				if (switch_mclookup(node, path, mlid, map) < 0) {
					IBWARN("skipping bad Switch 0x%" PRIx64
					       "", node->nodeguid);
					continue;
				}
			}

			for (i = 1; i <= node->numports; i++) {
				if (!map[i] || i == node->upport)
					continue;

				if (dist == 0 && leafport) {
					if (from->drpath.cnt > 0)
						path->drpath.cnt--;
				} else {
					port = calloc(1, sizeof(Port));
					if (!port)
						IBEXIT("out of memory");

					if (get_port(port, i, path) < 0) {
						IBWARN
						    ("can't reach node %s port %d",
						     portid2str(path), i);
						free(port);
						return NULL;
					}

					if (port->physstate != 5) {	/* LinkUP */
						free(port);
						continue;
					}
#if 0
					link_port(port, node);
#endif

					if (extend_dpath(&path->drpath, i) < 0) {
						free(port);
						return NULL;
					}
				}

				remotenode = calloc(1, sizeof(Node));
				if (!remotenode) {
					free(port);
					IBEXIT("out of memory");
				}

				remoteport = calloc(1, sizeof(Port));
				if (!remoteport) {
					free(port);
					free(remotenode);
					IBEXIT("out of memory");
				}

				if (get_node(remotenode, remoteport, path) < 0) {
					IBWARN
					    ("NodeInfo on %s port %d failed, skipping port",
					     portid2str(path), i);
					path->drpath.cnt--;	/* restore path */
					free(remotenode);
					free(remoteport);
					continue;
				}

				remotenode->upnode = node;
				remotenode->upport = remoteport->portnum;
				remoteport->remoteport = port;

				if ((r = new_node(remotenode, remoteport, path,
						  dist + 1)) > 0)
					return remotenode;

				if (r == 0)
					dump_endnode(ibverbose, "new remote",
						     remotenode, remoteport);
				else if (remotenode->type == IB_NODE_SWITCH)
					dump_endnode(2,
						     "ERR: circle discovered at",
						     remotenode, remoteport);

				path->drpath.cnt--;	/* restore path */
				free(port);
				free(remotenode);
				free(remoteport);
			}
		}
	}

	return NULL;		/* not found */
}

static uint64_t find_target_portguid(ib_portid_t * to)
{
	Node tonode;
	Port toport;

	if (get_node(&tonode, &toport, to) < 0) {
		IBWARN("can't find to port\n");
		return -1;
	}

	return toport.portguid;
}

static void dump_mcpath(Node * node, int dumplevel)
{
	char *nodename = NULL;

	if (node->upnode)
		dump_mcpath(node->upnode, dumplevel);

	nodename =
	    remap_node_name(node_name_map, node->nodeguid, node->nodedesc);

	if (!node->dist) {
		printf("From %s 0x%" PRIx64 " port %d lid %u-%u \"%s\"\n",
		       (node->type <=
			IB_NODE_MAX ? node_type_str[node->type] : "???"),
		       node->nodeguid, node->ports->portnum, node->ports->lid,
		       node->ports->lid + (1 << node->ports->lmc) - 1,
		       nodename);
		goto free_name;
	}

	if (node->dist) {
		if (dumplevel == 1)
			printf("[%d] -> %s {0x%016" PRIx64 "}[%d]\n",
			       node->ports->remoteport->portnum,
			       (node->type <=
				IB_NODE_MAX ? node_type_str[node->type] :
				"???"), node->nodeguid, node->upport);
		else
			printf("[%d] -> %s 0x%" PRIx64 "[%d] lid %u \"%s\"\n",
			       node->ports->remoteport->portnum,
			       (node->type <=
				IB_NODE_MAX ? node_type_str[node->type] :
				"???"), node->nodeguid, node->upport,
			       node->ports->lid, nodename);
	}

	if (node->dist < 0)
		/* target node */
		printf("To %s 0x%" PRIx64 " port %d lid %u-%u \"%s\"\n",
		       (node->type <=
			IB_NODE_MAX ? node_type_str[node->type] : "???"),
		       node->nodeguid, node->ports->portnum, node->ports->lid,
		       node->ports->lid + (1 << node->ports->lmc) - 1,
		       nodename);

free_name:
	free(nodename);
}

static int resolve_lid(ib_portid_t *portid)
{
	uint8_t portinfo[64] = { 0 };
	uint16_t lid;

	if (!smp_query_via(portinfo, portid, IB_ATTR_PORT_INFO, 0, 0, NULL))
		return -1;
	mad_decode_field(portinfo, IB_PORT_LID_F, &lid);

	ib_portid_set(portid, lid, 0, 0);

	return 0;
}

static int dumplevel = 2, multicast, mlid;

static int process_opt(void *context, int ch)
{
	switch (ch) {
	case 1:
		node_name_map_file = strdup(optarg);
		if (node_name_map_file == NULL)
			IBEXIT("out of memory, strdup for node_name_map_file name failed");
		break;
	case 2:
		ports_file = strdup(optarg);
		if (ports_file == NULL)
			IBEXIT("out of memory, strdup for ports_file name failed");
		break;
	case 'm':
		multicast++;
		mlid = strtoul(optarg, NULL, 0);
		break;
	case 'f':
		force++;
		break;
	case 'n':
		dumplevel = 1;
		break;
	default:
		return -1;
	}
	return 0;
}

static int get_route(char *srcid, char *dstid) {
	ib_portid_t my_portid = { 0 };
	ib_portid_t src_portid = { 0 };
	ib_portid_t dest_portid = { 0 };
	Node *endnode;

	if (resolve_portid_str(ibd_ca, ibd_ca_port, &src_portid, srcid,
			       ibd_dest_type, ibd_sm_id, srcport) < 0) {
		IBWARN("can't resolve source port %s", srcid);
		return -1;
	}

	if (resolve_portid_str(ibd_ca, ibd_ca_port, &dest_portid, dstid,
			       ibd_dest_type, ibd_sm_id, srcport) < 0) {
		IBWARN("can't resolve destination port %s", dstid);
		return -1;
	}

	if (ibd_dest_type == IB_DEST_DRPATH) {
		if (resolve_lid(&src_portid) < 0) {
			IBWARN("cannot resolve lid for port \'%s\'",
				portid2str(&src_portid));
			return -1;
		}
		if (resolve_lid(&dest_portid) < 0) {
			IBWARN("cannot resolve lid for port \'%s\'",
				portid2str(&dest_portid));
			return -1;
		}
	}

	if (dest_portid.lid == 0 || src_portid.lid == 0) {
		IBWARN("bad src/dest lid");
		ibdiag_show_usage();
	}

	if (ibd_dest_type != IB_DEST_DRPATH) {
		/* first find a direct path to the src port */
		if (find_route(&my_portid, &src_portid, 0) < 0) {
			IBWARN("can't find a route to the src port");
			return -1;
		}

		src_portid = my_portid;
	}

	if (!multicast) {
		if (find_route(&src_portid, &dest_portid, dumplevel) < 0) {
			IBWARN("can't find a route from src to dest");
			return -1;
		}
		return 0;
	} else {
		if (mlid < 0xc000)
			IBWARN("invalid MLID; must be 0xc000 or larger");
	}

	if (!(target_portguid = find_target_portguid(&dest_portid))) {
		IBWARN("can't reach target lid %d", dest_portid.lid);
		return -1;
	}

	if (!(endnode = find_mcpath(&src_portid, mlid))) {
		IBWARN("can't find a multicast route from src to dest");
		return -1;
	}

	/* dump multicast path */
	dump_mcpath(endnode, dumplevel);

	return 0;
}

int main(int argc, char **argv)
{
	char dstbuf[21];
	char srcbuf[21];
	char portsbuf[80];
	char *p_first;
	int len, i;
	int line_count = 0;
	int num_port_pairs = 0;
	int mgmt_classes[3] =
	    { IB_SMI_CLASS, IB_SMI_DIRECT_CLASS, IB_SA_CLASS };

	const struct ibdiag_opt opts[] = {
		{"force", 'f', 0, NULL, "force"},
		{"no_info", 'n', 0, NULL, "simple format"},
		{"mlid", 'm', 1, "<mlid>", "multicast trace of the mlid"},
		{"node-name-map", 1, 1, "<file>", "node name map file"},
		{"ports-file", 2, 1, "<file>", "port pairs file"},
		{}
	};
	char usage_args[] = "<src-addr> <dest-addr>";
	const char *usage_examples[] = {
		"- Unicast examples:",
		"4 16\t\t\t# show path between lids 4 and 16",
		"-n 4 16\t\t# same, but using simple output format",
		"-G 0x8f1040396522d 0x002c9000100d051\t# use guid addresses",

		" - Multicast examples:",
		"-m 0xc000 4 16\t# show multicast path of mlid 0xc000 between lids 4 and 16",
		NULL,
	};

	ibdiag_process_opts(argc, argv, NULL, "DK", opts, process_opt,
			    usage_args, usage_examples);

	f = stdout;
	argc -= optind;
	argv += optind;

	if (argc < 2 && ports_file == NULL)
		ibdiag_show_usage();

	if (ibd_timeout)
		timeout = ibd_timeout;

	srcport = mad_rpc_open_port(ibd_ca, ibd_ca_port, mgmt_classes, 3);
	if (!srcport)
		IBEXIT("Failed to open '%s' port '%d'", ibd_ca, ibd_ca_port);

	smp_mkey_set(srcport, ibd_mkey);

	node_name_map = open_node_name_map(node_name_map_file);

	if (ports_file == NULL) {
		/* single get_route call when lids/guids on command line */
		if (get_route(argv[0], argv[1]) != 0)
			IBEXIT("Failed to get route information");
	} else {
		/* multiple get_route calls when reading lids/guids from a file */
		ports_fd = fopen(ports_file, "r");
		if (!ports_fd)
			IBEXIT("cannot open ports-file %s", ports_file);

		while (fgets(portsbuf, sizeof(portsbuf), ports_fd) != NULL) {
			line_count++;
			p_first = strtok(portsbuf, "\n");
			if (!p_first)
				continue;	/* ignore blank lines */

			len = (int) strlen(p_first);
			for (i = 0; i < len; i++) {
				if (!isspace(p_first[i]))
					break;
			}
			if (i == len)		/* ignore all spaces */
				continue;
			if (p_first[i] == '#')
				continue;	/* ignore comment lines */

			if (sscanf(portsbuf, "%20s %20s", srcbuf, dstbuf) != 2)
				IBEXIT("ports-file, %s, at line %i contains bad data",
					ports_file, line_count);
			num_port_pairs++;
			if (get_route(srcbuf, dstbuf) != 0)
				IBEXIT("Failed to get route information at line %i",
					line_count);
		}
		printf("%i lid/guid pairs processed from %s\n",
		       num_port_pairs, ports_file);
        }
	close_node_name_map(node_name_map);

	mad_rpc_close_port(srcport);

	exit(0);
}
