/*
 * Copyright (c) 2004-2009 Voltaire Inc.  All rights reserved.
 * Copyright (c) 2007 Xsigo Systems Inc.  All rights reserved.
 * Copyright (c) 2008 Lawrence Livermore National Lab.  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 <stdarg.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>

#include <util/node_name_map.h>
#include <infiniband/ibnetdisc.h>

#include "ibdiag_common.h"

#define DIFF_FLAG_PORT_CONNECTION  0x01
#define DIFF_FLAG_PORT_STATE       0x02
#define DIFF_FLAG_LID              0x04
#define DIFF_FLAG_NODE_DESCRIPTION 0x08

#define DIFF_FLAG_DEFAULT (DIFF_FLAG_PORT_CONNECTION | DIFF_FLAG_PORT_STATE)

static char *node_name_map_file = NULL;
static nn_map_t *node_name_map = NULL;
static char *load_cache_file = NULL;
static char *diff_cache_file = NULL;
static unsigned diffcheck_flags = DIFF_FLAG_DEFAULT;
static char *filterdownports_cache_file = NULL;
static ibnd_fabric_t *filterdownports_fabric = NULL;

static struct {
	uint64_t guid;
	char *guid_str;
} node_label;
static char *dr_path = NULL;
static int all = 0;

static int down_links_only = 0;
static int line_mode = 0;
static int add_sw_settings = 0;
static int only_flag = 0;
static int only_type = 0;

static int filterdownport_check(ibnd_node_t *node, ibnd_port_t *port)
{
	ibnd_node_t *fsw;
	ibnd_port_t *fport;
	int fistate;

	fsw = ibnd_find_node_guid(filterdownports_fabric, node->guid);

	if (!fsw)
		return 0;

	if (port->portnum > fsw->numports)
		return 0;

	fport = fsw->ports[port->portnum];

	if (!fport)
		return 0;

	fistate = mad_get_field(fport->info, 0, IB_PORT_STATE_F);

	return (fistate == IB_LINK_DOWN) ? 1 : 0;
}

static void print_port(ibnd_node_t *node, ibnd_port_t *port,
		       const char *out_prefix)
{
	char width[64], speed[64], state[64], physstate[64];
	char remote_guid_str[256];
	char remote_str[256];
	char link_str[256];
	char width_msg[256];
	char speed_msg[256];
	char ext_port_str[256];
	int iwidth, ispeed, fdr10, espeed, istate, iphystate, cap_mask;
	int n = 0;
	uint8_t *info = NULL;
	int rc;

	if (!port)
		return;

	iwidth = mad_get_field(port->info, 0, IB_PORT_LINK_WIDTH_ACTIVE_F);
	ispeed = mad_get_field(port->info, 0, IB_PORT_LINK_SPEED_ACTIVE_F);
	fdr10 = mad_get_field(port->ext_info, 0,
			      IB_MLNX_EXT_PORT_LINK_SPEED_ACTIVE_F) & FDR10;

	if (port->node->type == IB_NODE_SWITCH) {
		if (port->node->ports[0])
			info = (uint8_t *)&port->node->ports[0]->info;
	}
	else
		info = (uint8_t *)&port->info;

	if (info) {
		cap_mask = mad_get_field(info, 0, IB_PORT_CAPMASK_F);
		if (cap_mask & be32toh(IB_PORT_CAP_HAS_EXT_SPEEDS))
			espeed = mad_get_field(port->info, 0,
					       IB_PORT_LINK_SPEED_EXT_ACTIVE_F);
		else
			espeed = 0;
	} else {
		ispeed = 0;
		iwidth = 0;
		espeed = 0;
	}

	istate = mad_get_field(port->info, 0, IB_PORT_STATE_F);
	iphystate = mad_get_field(port->info, 0, IB_PORT_PHYS_STATE_F);

	remote_guid_str[0] = '\0';
	remote_str[0] = '\0';
	link_str[0] = '\0';
	width_msg[0] = '\0';
	speed_msg[0] = '\0';

	if (istate == IB_LINK_DOWN
	    && filterdownports_fabric
	    && filterdownport_check(node, port))
		return;

	/* C14-24.2.1 states that a down port allows for invalid data to be
	 * returned for all PortInfo components except PortState and
	 * PortPhysicalState */
	if (istate != IB_LINK_DOWN) {
		if (!espeed) {
			if (fdr10)
				sprintf(speed, "10.0 Gbps (FDR10)");
			else
				mad_dump_val(IB_PORT_LINK_SPEED_ACTIVE_F, speed,
					     64, &ispeed);
		} else
			mad_dump_val(IB_PORT_LINK_SPEED_EXT_ACTIVE_F, speed,
				     64, &espeed);

		n = snprintf(link_str, 256, "(%3s %18s %6s/%8s)",
		     mad_dump_val(IB_PORT_LINK_WIDTH_ACTIVE_F, width, 64,
				  &iwidth),
		     speed,
		     mad_dump_val(IB_PORT_STATE_F, state, 64, &istate),
		     mad_dump_val(IB_PORT_PHYS_STATE_F, physstate, 64,
				  &iphystate));
	} else {
		n = snprintf(link_str, 256, "(              %6s/%8s)",
		     mad_dump_val(IB_PORT_STATE_F, state, 64, &istate),
		     mad_dump_val(IB_PORT_PHYS_STATE_F, physstate, 64,
				  &iphystate));
	}

	/* again default values due to C14-24.2.1 */
	if (add_sw_settings && istate != IB_LINK_DOWN) {
		snprintf(link_str + n, 256 - n,
			" (HOQ:%d VL_Stall:%d)",
			mad_get_field(port->info, 0,
				IB_PORT_HOQ_LIFE_F),
			mad_get_field(port->info, 0,
				IB_PORT_VL_STALL_COUNT_F));
	}

	if (port->remoteport) {
		char *remap =
		    remap_node_name(node_name_map, port->remoteport->node->guid,
				    port->remoteport->node->nodedesc);

		if (port->remoteport->ext_portnum)
			snprintf(ext_port_str, 256, "%d",
				 port->remoteport->ext_portnum);
		else
			ext_port_str[0] = '\0';

		get_max_msg(width_msg, speed_msg, 256, port);

		if (line_mode) {
			snprintf(remote_guid_str, 256,
				 "0x%016" PRIx64 " ",
				 port->remoteport->guid);
		}

		rc = snprintf(remote_str, sizeof(remote_str),
			      "%s%6d %4d[%2s] \"%s\" (%s %s)\n",
			      remote_guid_str, port->remoteport->base_lid ?
			      port->remoteport->base_lid :
			      port->remoteport->node->smalid,
			      port->remoteport->portnum, ext_port_str, remap,
			      width_msg, speed_msg);
		if (rc > sizeof(remote_str))
			fprintf(stderr, "WARN: string buffer overflow\n");

		free(remap);
	} else {
		if (istate == IB_LINK_DOWN)
			snprintf(remote_str, 256, "           [  ] \"\" ( )\n");
		else
			snprintf(remote_str, 256, "    \"Port not available\"\n");
	}

	if (port->ext_portnum)
		snprintf(ext_port_str, 256, "%d", port->ext_portnum);
	else
		ext_port_str[0] = '\0';

	if (line_mode) {
		char *remap = remap_node_name(node_name_map, node->guid,
					      node->nodedesc);
		printf("%s0x%016" PRIx64 " \"%30s\" ",
		       out_prefix ? out_prefix : "",
		       port->guid, remap);
		free(remap);
	} else
		printf("%s      ", out_prefix ? out_prefix : "");

	if (port->node->type != IB_NODE_SWITCH) {
		if (!line_mode)
			printf("0x%016" PRIx64 " ", port->guid);

		printf("%6d %4d[%2s] ==%s==>  %s",
			port->base_lid,
			port->portnum, ext_port_str, link_str, remote_str);
	} else
		printf("%6d %4d[%2s] ==%s==>  %s",
			node->smalid, port->portnum, ext_port_str,
			link_str, remote_str);
}

static inline const char *nodetype_str(ibnd_node_t * node)
{
	switch (node->type) {
	case IB_NODE_SWITCH:
		return "Switch";
	case IB_NODE_CA:
		return "CA";
	case IB_NODE_ROUTER:
		return "Router";
	}
	return "??";
}

static void print_node_header(ibnd_node_t *node, int *out_header_flag,
		       const char *out_prefix)
{
	uint64_t guid = 0;
	if ((!out_header_flag || !(*out_header_flag)) && !line_mode) {
		char *remap =
			remap_node_name(node_name_map, node->guid, node->nodedesc);
		if (node->type == IB_NODE_SWITCH) {
			if (node->ports[0])
				guid = node->ports[0]->guid;
			else
				guid = mad_get_field64(node->info, 0, IB_NODE_PORT_GUID_F);

			printf("%s%s: 0x%016" PRIx64 " %s:\n",
				out_prefix ? out_prefix : "",
				nodetype_str(node),
				guid,
				remap);
		} else
			printf("%s%s: %s:\n",
				out_prefix ? out_prefix : "",
				nodetype_str(node), remap);
		(*out_header_flag)++;
		free(remap);
	}
}

static void print_node(ibnd_node_t *node, void *user_data)
{
	int i = 0;
	int head_print = 0;
	char *out_prefix = (char *)user_data;

	for (i = 1; i <= node->numports; i++) {
		ibnd_port_t *port = node->ports[i];
		if (!port)
			continue;
		if (!down_links_only ||
		    mad_get_field(port->info, 0,
				  IB_PORT_STATE_F) == IB_LINK_DOWN) {
			print_node_header(node, &head_print, out_prefix);
			print_port(node, port, out_prefix);
		}
	}
}

struct iter_diff_data {
        uint32_t diff_flags;
        ibnd_fabric_t *fabric1;
        ibnd_fabric_t *fabric2;
        const char *fabric1_prefix;
        const char *fabric2_prefix;
};

static void diff_node_ports(ibnd_node_t *fabric1_node,
			    ibnd_node_t *fabric2_node, int *head_print,
			    struct iter_diff_data *data)
{
	int i = 0;

	for (i = 1; i <= fabric1_node->numports; i++) {
		ibnd_port_t *fabric1_port, *fabric2_port;
		int output_diff = 0;

		fabric1_port = fabric1_node->ports[i];
		fabric2_port = fabric2_node->ports[i];

		if (!fabric1_port && !fabric2_port)
			continue;

		if (data->diff_flags & DIFF_FLAG_PORT_CONNECTION) {
			if ((fabric1_port && !fabric2_port)
			    || (!fabric1_port && fabric2_port)
			    || (fabric1_port->remoteport
				&& !fabric2_port->remoteport)
			    || (!fabric1_port->remoteport
				&& fabric2_port->remoteport)
			    || (fabric1_port->remoteport
				&& fabric2_port->remoteport
				&& fabric1_port->remoteport->guid !=
				fabric2_port->remoteport->guid))
				output_diff++;
		}

		/* if either fabric1_port or fabric2_port NULL, should be
		 * handled by port connection diff code
		 */
		if (data->diff_flags & DIFF_FLAG_PORT_STATE
		    && fabric1_port
		    && fabric2_port) {
			int state1, state2;

			state1 = mad_get_field(fabric1_port->info, 0,
					       IB_PORT_STATE_F);
			state2 = mad_get_field(fabric2_port->info, 0,
					       IB_PORT_STATE_F);

			if (state1 != state2)
				output_diff++;
		}

		if (data->diff_flags & DIFF_FLAG_PORT_CONNECTION
		    && data->diff_flags & DIFF_FLAG_LID
		    && fabric1_port && fabric2_port
		    && fabric1_port->remoteport && fabric2_port->remoteport
		    && fabric1_port->remoteport->base_lid != fabric2_port->remoteport->base_lid)
			output_diff++;

		if (data->diff_flags & DIFF_FLAG_PORT_CONNECTION
		    && data->diff_flags & DIFF_FLAG_NODE_DESCRIPTION
		    && fabric1_port && fabric2_port
		    && fabric1_port->remoteport && fabric2_port->remoteport
		    && memcmp(fabric1_port->remoteport->node->nodedesc,
			      fabric2_port->remoteport->node->nodedesc,
			      IB_SMP_DATA_SIZE))
			output_diff++;

		if (output_diff && fabric1_port) {
			print_node_header(fabric1_node,
					    head_print,
					    NULL);
			print_port(fabric1_node,
				   fabric1_port,
				   data->fabric1_prefix);
		}

		if (output_diff && fabric2_port) {
			print_node_header(fabric1_node,
					    head_print,
					    NULL);
			print_port(fabric2_node,
				   fabric2_port,
				   data->fabric2_prefix);
		}
	}
}

static void diff_node_iter(ibnd_node_t *fabric1_node, void *iter_user_data)
{
	struct iter_diff_data *data = iter_user_data;
	ibnd_node_t *fabric2_node;
	int head_print = 0;

	DEBUG("DEBUG: fabric1_node %p\n", fabric1_node);

	fabric2_node = ibnd_find_node_guid(data->fabric2, fabric1_node->guid);
	if (!fabric2_node)
		print_node(fabric1_node, (void *)data->fabric1_prefix);
	else if (data->diff_flags &
		 (DIFF_FLAG_PORT_CONNECTION | DIFF_FLAG_PORT_STATE
		  | DIFF_FLAG_LID | DIFF_FLAG_NODE_DESCRIPTION)) {

		if ((fabric1_node->type == IB_NODE_SWITCH
		     && data->diff_flags & DIFF_FLAG_LID
		     && fabric1_node->smalid != fabric2_node->smalid) ||
		    (data->diff_flags & DIFF_FLAG_NODE_DESCRIPTION
		     && memcmp(fabric1_node->nodedesc, fabric2_node->nodedesc,
			       IB_SMP_DATA_SIZE))) {
			print_node_header(fabric1_node,
					    NULL,
					    data->fabric1_prefix);
			print_node_header(fabric2_node,
					    NULL,
					    data->fabric2_prefix);
			head_print++;
		}

		if (fabric1_node->numports != fabric2_node->numports) {
			print_node_header(fabric1_node,
					    &head_print,
					    NULL);
			printf("%snumports = %d\n", data->fabric1_prefix,
			       fabric1_node->numports);
			printf("%snumports = %d\n", data->fabric2_prefix,
			       fabric2_node->numports);
			return;
		}

		diff_node_ports(fabric1_node, fabric2_node,
				  &head_print, data);
	}
}

static int diff_node(ibnd_node_t *node, ibnd_fabric_t *orig_fabric,
		     ibnd_fabric_t *new_fabric)
{
	struct iter_diff_data iter_diff_data;

	iter_diff_data.diff_flags = diffcheck_flags;
	iter_diff_data.fabric1 = orig_fabric;
	iter_diff_data.fabric2 = new_fabric;
	iter_diff_data.fabric1_prefix = "< ";
	iter_diff_data.fabric2_prefix = "> ";
	if (node)
		diff_node_iter(node, &iter_diff_data);
	else {
		if (only_flag)
			ibnd_iter_nodes_type(orig_fabric, diff_node_iter,
					     only_type, &iter_diff_data);
		else
			ibnd_iter_nodes(orig_fabric, diff_node_iter,
					&iter_diff_data);
	}

	/* Do opposite diff to find existence of node types
	 * in new_fabric but not in orig_fabric.
	 *
	 * In this diff, we don't need to check port connections,
	 * port state, lids, or node descriptions since it has already
	 * been done (i.e. checks are only done when guid exists on both
	 * orig and new).
	 */
	iter_diff_data.diff_flags = diffcheck_flags & ~DIFF_FLAG_PORT_CONNECTION;
	iter_diff_data.diff_flags &= ~DIFF_FLAG_PORT_STATE;
	iter_diff_data.diff_flags &= ~DIFF_FLAG_LID;
	iter_diff_data.diff_flags &= ~DIFF_FLAG_NODE_DESCRIPTION;
	iter_diff_data.fabric1 = new_fabric;
	iter_diff_data.fabric2 = orig_fabric;
	iter_diff_data.fabric1_prefix = "> ";
	iter_diff_data.fabric2_prefix = "< ";
	if (node)
		diff_node_iter(node, &iter_diff_data);
	else {
		if (only_flag)
			ibnd_iter_nodes_type(new_fabric, diff_node_iter,
					     only_type, &iter_diff_data);
		else
			ibnd_iter_nodes(new_fabric, diff_node_iter,
					&iter_diff_data);
	}

	return 0;
}

static int process_opt(void *context, int ch)
{
	struct ibnd_config *cfg = context;
	char *p;

	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:
		load_cache_file = strdup(optarg);
		break;
	case 3:
		diff_cache_file = strdup(optarg);
		break;
	case 4:
		diffcheck_flags = 0;
		p = strtok(optarg, ",");
		while (p) {
			if (!strcasecmp(p, "port"))
				diffcheck_flags |= DIFF_FLAG_PORT_CONNECTION;
			else if (!strcasecmp(p, "state"))
				diffcheck_flags |= DIFF_FLAG_PORT_STATE;
			else if (!strcasecmp(p, "lid"))
				diffcheck_flags |= DIFF_FLAG_LID;
			else if (!strcasecmp(p, "nodedesc"))
				diffcheck_flags |= DIFF_FLAG_NODE_DESCRIPTION;
			else {
				fprintf(stderr, "invalid diff check key: %s\n",
					p);
				return -1;
			}
			p = strtok(NULL, ",");
		}
		break;
	case 5:
		filterdownports_cache_file = strdup(optarg);
		break;
	case 6:
		only_flag = 1;
		only_type = IB_NODE_SWITCH;
		break;
	case 7:
		only_flag = 1;
		only_type = IB_NODE_CA;
		break;
	case 'S':
	case 'G':
		node_label.guid_str = optarg;
		node_label.guid = (uint64_t)strtoull(node_label.guid_str, NULL, 0);
		break;
	case 'D':
		dr_path = strdup(optarg);
		break;
	case 'a':
		all = 1;
		break;
	case 'n':
		cfg->max_hops = strtoul(optarg, NULL, 0);
		break;
	case 'd':
		down_links_only = 1;
		break;
	case 'l':
		line_mode = 1;
		break;
	case 'p':
		add_sw_settings = 1;
		break;
	case 'R':		/* nop */
		break;
	case 'o':
		cfg->max_smps = strtoul(optarg, NULL, 0);
		break;
	default:
		return -1;
	}

	return 0;
}

int main(int argc, char **argv)
{
	struct ibnd_config config = { 0 };
	int rc = 0;
	int resolved = -1;
	ibnd_fabric_t *fabric = NULL;
	ibnd_fabric_t *diff_fabric = NULL;
	struct ibmad_port *ibmad_port;
	ib_portid_t port_id = { 0 };
	uint8_t ni[IB_SMP_DATA_SIZE] = { 0 };
	int mgmt_classes[3] =
	    { IB_SMI_CLASS, IB_SMI_DIRECT_CLASS, IB_SA_CLASS };

	const struct ibdiag_opt opts[] = {
		{"node-name-map", 1, 1, "<file>", "node name map file"},
		{"switch", 'S', 1, "<port_guid>",
		 "start partial scan at the port specified by <port_guid> (hex format)"},
		{"port-guid", 'G', 1, "<port_guid>",
		 "(same as -S)"},
		{"Direct", 'D', 1, "<dr_path>",
		 "start partial scan at the port specified by <dr_path>"},
		{"all", 'a', 0, NULL,
		 "print all nodes found in a partial fabric scan"},
		{"hops", 'n', 1, "<hops>",
		 "Number of hops to include away from specified node"},
		{"down", 'd', 0, NULL, "print only down links"},
		{"line", 'l', 0, NULL,
		 "(line mode) print all information for each link on a single line"},
		{"additional", 'p', 0, NULL,
		 "print additional port settings (PktLifeTime, HoqLife, VLStallCount)"},
		{"load-cache", 2, 1, "<file>",
		 "filename of ibnetdiscover cache to load"},
		{"diff", 3, 1, "<file>",
		 "filename of ibnetdiscover cache to diff"},
		{"diffcheck", 4, 1, "<key(s)>",
		 "specify checks to execute for --diff"},
		{"filterdownports", 5, 1, "<file>",
		 "filename of ibnetdiscover cache to filter downports"},
		{"outstanding_smps", 'o', 1, NULL,
		 "specify the number of outstanding SMP's which should be "
		 "issued during the scan"},
		{"switches-only", 6, 0, NULL,
		 "Output only switches"},
		{"cas-only", 7, 0, NULL,
		 "Output only CAs"},
		{}
	};
	char usage_args[] = "";

	ibdiag_process_opts(argc, argv, &config, "aDdGgKLlnpRS", opts,
			    process_opt, usage_args, NULL);

	argc -= optind;
	argv += optind;

	ibmad_port = mad_rpc_open_port(ibd_ca, ibd_ca_port, mgmt_classes, 3);
	if (!ibmad_port) {
		fprintf(stderr, "Failed to open %s port %d\n", ibd_ca,
			ibd_ca_port);
		exit(1);
	}

	smp_mkey_set(ibmad_port, ibd_mkey);

	if (ibd_timeout) {
		mad_rpc_set_timeout(ibmad_port, ibd_timeout);
		config.timeout_ms = ibd_timeout;
	}

	config.flags = ibd_ibnetdisc_flags;
	config.mkey = ibd_mkey;

	node_name_map = open_node_name_map(node_name_map_file);

	if (dr_path && load_cache_file) {
		mad_rpc_close_port(ibmad_port);
		fprintf(stderr, "Cannot specify cache and direct route path\n");
		exit(1);
	}

	if (dr_path) {
		/* only scan part of the fabric */
		if ((resolved =
		     resolve_portid_str(ibd_ca, ibd_ca_port, &port_id, dr_path,
					IB_DEST_DRPATH, NULL, ibmad_port)) < 0)
			IBWARN("Failed to resolve %s; attempting full scan",
			       dr_path);
	} else if (node_label.guid_str) {
		if ((resolved = resolve_portid_str(
			     ibd_ca, ibd_ca_port, &port_id, node_label.guid_str,
			     IB_DEST_GUID, NULL, ibmad_port)) < 0)
			IBWARN("Failed to resolve %s; attempting full scan\n",
			       node_label.guid_str);
	}

	if (!all && dr_path) {
		if (!smp_query_via(ni, &port_id, IB_ATTR_NODE_INFO, 0,
				   ibd_timeout, ibmad_port)){
			mad_rpc_close_port(ibmad_port);
			fprintf(stderr, "Failed to get local Node Info\n");
			exit(1);
		}
	}
	mad_rpc_close_port(ibmad_port);

	if (diff_cache_file &&
	    !(diff_fabric = ibnd_load_fabric(diff_cache_file, 0)))
		IBEXIT("loading cached fabric for diff failed\n");

	if (filterdownports_cache_file &&
	    !(filterdownports_fabric = ibnd_load_fabric(filterdownports_cache_file, 0)))
		IBEXIT("loading cached fabric for filterdownports failed\n");

	if (load_cache_file) {
		if ((fabric = ibnd_load_fabric(load_cache_file, 0)) == NULL) {
			fprintf(stderr, "loading cached fabric failed\n");
			exit(1);
		}
	} else {
		if (resolved >= 0) {
			if (!config.max_hops)
				config.max_hops = 1;
			if (!(fabric =
			    ibnd_discover_fabric(ibd_ca, ibd_ca_port, &port_id, &config)))
				IBWARN("Partial fabric scan failed;"
				       " attempting full scan\n");
		}

		if (!fabric &&
		    !(fabric = ibnd_discover_fabric(ibd_ca, ibd_ca_port, NULL, &config))) {
			fprintf(stderr, "discover failed\n");
			rc = 1;
			goto close_port;
		}
	}

	if (!all && node_label.guid_str) {
		ibnd_port_t *p = ibnd_find_port_guid(fabric, node_label.guid);
		if (p && (!only_flag || p->node->type == only_type)) {
			ibnd_node_t *n = p->node;
			if (diff_fabric)
				diff_node(n, diff_fabric, fabric);
			else
				print_node(n, NULL);
		}
		else
			fprintf(stderr, "Failed to find port: %s\n", node_label.guid_str);
	} else if (!all && dr_path) {
		ibnd_port_t *p = NULL;
		mad_decode_field(ni, IB_NODE_PORT_GUID_F, &node_label.guid);

		p = ibnd_find_port_guid(fabric, node_label.guid);
		if (p && (!only_flag || p->node->type == only_type)) {
			ibnd_node_t *n = p->node;
			if (diff_fabric)
				diff_node(n, diff_fabric, fabric);
			else
				print_node(n, NULL);
		}
		else
			fprintf(stderr, "Failed to find port: %s\n", dr_path);
	} else {
		if (diff_fabric)
			diff_node(NULL, diff_fabric, fabric);
		else {
			if (only_flag)
				ibnd_iter_nodes_type(fabric, print_node,
						     only_type, NULL);
			else
				ibnd_iter_nodes(fabric, print_node, NULL);
		}
	}

	ibnd_destroy_fabric(fabric);
	if (diff_fabric)
		ibnd_destroy_fabric(diff_fabric);

close_port:
	close_node_name_map(node_name_map);
	exit(rc);
}
