/*
 * Copyright (c) 2004-2009 Voltaire Inc.  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.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

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

#include "ibdiag_common.h"

#define MAX_CPUS 256

static struct ibmad_port *srcport;
static struct ibmad_ports_pair *srcports;

enum ib_sysstat_attr_t {
	IB_PING_ATTR = 0x10,
	IB_HOSTINFO_ATTR = 0x11,
	IB_CPUINFO_ATTR = 0x12,
};

typedef struct cpu_info {
	char *model;
	char *mhz;
} cpu_info;

static cpu_info cpus[MAX_CPUS];
static int host_ncpu;
static int server = 0, oui = IB_OPENIB_OUI;

static int server_respond(void *umad, int size)
{
	ib_rpc_t rpc = { 0 };
	ib_rmpp_hdr_t rmpp = { 0 };
	ib_portid_t rport;
	uint8_t *mad = umad_get_mad(umad);
	ib_mad_addr_t *mad_addr;

	if (!(mad_addr = umad_get_mad_addr(umad)))
		return -1;

	memset(&rport, 0, sizeof(rport));

	rport.lid = ntohs(mad_addr->lid);
	rport.qp = ntohl(mad_addr->qpn);
	rport.qkey = ntohl(mad_addr->qkey);
	rport.sl = mad_addr->sl;
	if (!rport.qkey && rport.qp == 1)
		rport.qkey = IB_DEFAULT_QP1_QKEY;

	rpc.mgtclass = mad_get_field(mad, 0, IB_MAD_MGMTCLASS_F);
	rpc.method = IB_MAD_METHOD_GET | IB_MAD_RESPONSE;
	rpc.attr.id = mad_get_field(mad, 0, IB_MAD_ATTRID_F);
	rpc.attr.mod = mad_get_field(mad, 0, IB_MAD_ATTRMOD_F);
	rpc.oui = mad_get_field(mad, 0, IB_VEND2_OUI_F);
	rpc.trid = mad_get_field64(mad, 0, IB_MAD_TRID_F);

	if (size > IB_MAD_SIZE)
		rmpp.flags = IB_RMPP_FLAG_ACTIVE;

	DEBUG("responding %d bytes to %s, attr 0x%x mod 0x%x qkey %x",
	      size, portid2str(&rport), rpc.attr.id, rpc.attr.mod, rport.qkey);

	if (mad_build_pkt(umad, &rpc, &rport, &rmpp, NULL) < 0)
		return -1;

	if (ibdebug > 1)
		xdump(stderr, "mad respond pkt\n", mad, IB_MAD_SIZE);

	if (umad_send(mad_rpc_portid(srcport),
		      mad_rpc_class_agent(srcport, rpc.mgtclass), umad, size,
		      rpc.timeout, 0) < 0) {
		DEBUG("send failed; %m");
		return -1;
	}

	return 0;
}

static int mk_reply(int attr, void *data, int sz)
{
	char *s = data;
	int n, i, ret = 0;

	switch (attr) {
	case IB_PING_ATTR:
		break;		/* nothing to do here, just reply */
	case IB_HOSTINFO_ATTR:
		if (gethostname(s, sz) < 0)
			snprintf(s, sz, "?hostname?");
		s[sz - 1] = 0;
		if ((n = strlen(s)) >= sz - 1) {
			ret = sz;
			break;
		}
		s[n] = '.';
		s += n + 1;
		sz -= n + 1;
		ret += n + 1;
		if (getdomainname(s, sz) < 0)
			snprintf(s, sz, "?domainname?");
		if ((n = strlen(s)) == 0)
			s[-1] = 0;	/* no domain */
		else
			ret += n;
		break;
	case IB_CPUINFO_ATTR:
		s[0] = '\0';
		for (i = 0; i < host_ncpu && sz > 0; i++) {
			n = snprintf(s, sz, "cpu %d: model %s MHZ %s\n",
				     i, cpus[i].model, cpus[i].mhz);
			if (n >= sz) {
				IBWARN("cpuinfo truncated");
				ret = sz;
				break;
			}
			sz -= n;
			s += n;
			ret += n;
		}
		ret++;
		break;
	default:
		DEBUG("unknown attr %d", attr);
	}
	return ret;
}

static uint8_t buf[2048 * 32];

static char *ibsystat_serv(void)
{
	void *umad;
	void *mad;
	int attr, mod, size;

	DEBUG("starting to serve...");

	while ((umad = mad_receive_via(buf, -1, srcport))) {
		if (umad_status(buf)) {
			DEBUG("drop mad with status %x: %s", umad_status(buf),
			      strerror(umad_status(buf)));
			continue;
		}

		mad = umad_get_mad(umad);

		attr = mad_get_field(mad, 0, IB_MAD_ATTRID_F);
		mod = mad_get_field(mad, 0, IB_MAD_ATTRMOD_F);

		DEBUG("got packet: attr 0x%x mod 0x%x", attr, mod);

		size =
		    mk_reply(attr, (uint8_t *) mad + IB_VENDOR_RANGE2_DATA_OFFS,
			     sizeof(buf) - umad_size() -
			     IB_VENDOR_RANGE2_DATA_OFFS);

		if (server_respond(umad, IB_VENDOR_RANGE2_DATA_OFFS + size) < 0)
			DEBUG("respond failed");
	}

	DEBUG("server out");
	return NULL;
}

static int match_attr(char *str)
{
	if (!strcmp(str, "ping"))
		return IB_PING_ATTR;
	if (!strcmp(str, "host"))
		return IB_HOSTINFO_ATTR;
	if (!strcmp(str, "cpu"))
		return IB_CPUINFO_ATTR;
	return -1;
}

static char *ibsystat(ib_portid_t * portid, int attr)
{
	ib_rpc_t rpc = { 0 };
	int fd, agent, timeout, len;
	void *data = (uint8_t *) umad_get_mad(buf) + IB_VENDOR_RANGE2_DATA_OFFS;

	DEBUG("Sysstat ping..");

	rpc.mgtclass = IB_VENDOR_OPENIB_SYSSTAT_CLASS;
	rpc.method = IB_MAD_METHOD_GET;
	rpc.attr.id = attr;
	rpc.attr.mod = 0;
	rpc.oui = oui;
	rpc.timeout = 0;
	rpc.datasz = IB_VENDOR_RANGE2_DATA_SIZE;
	rpc.dataoffs = IB_VENDOR_RANGE2_DATA_OFFS;

	portid->qp = 1;
	if (!portid->qkey)
		portid->qkey = IB_DEFAULT_QP1_QKEY;

	if ((len = mad_build_pkt(buf, &rpc, portid, NULL, NULL)) < 0)
		IBPANIC("cannot build packet.");

	fd = mad_rpc_portid(srcport);
	agent = mad_rpc_class_agent(srcport, rpc.mgtclass);
	timeout = ibd_timeout ? ibd_timeout : MAD_DEF_TIMEOUT_MS;

	if (umad_send(fd, agent, buf, len, timeout, 0) < 0)
		IBPANIC("umad_send failed.");

	len = sizeof(buf) - umad_size();
	if (umad_recv(fd, buf, &len, timeout) < 0)
		IBPANIC("umad_recv failed.");

	if (umad_status(buf))
		return strerror(umad_status(buf));

	DEBUG("Got sysstat pong..");
	if (attr != IB_PING_ATTR)
		puts(data);
	else
		printf("sysstat ping succeeded\n");
	return NULL;
}

static int build_cpuinfo(void)
{
	char line[1024] = { 0 }, *s, *e;
	FILE *f;
	int ncpu = 0;

	if (!(f = fopen("/proc/cpuinfo", "r"))) {
		IBWARN("couldn't open /proc/cpuinfo");
		return 0;
	}

	while (fgets(line, sizeof(line) - 1, f)) {
		if (!strncmp(line, "processor\t", 10)) {
			ncpu++;
			if (ncpu > MAX_CPUS) {
				fclose(f);
				return MAX_CPUS;
			}
			continue;
		}

		if (!ncpu || !(s = strchr(line, ':')))
			continue;

		if ((e = strchr(s, '\n')))
			*e = 0;
		if (!strncmp(line, "model name\t", 11))
			cpus[ncpu - 1].model = strdup(s + 1);
		else if (!strncmp(line, "cpu MHz\t", 8))
			cpus[ncpu - 1].mhz = strdup(s + 1);
	}

	fclose(f);

	DEBUG("ncpu %d", ncpu);

	return ncpu;
}

static int process_opt(void *context, int ch)
{
	switch (ch) {
	case 'o':
		oui = strtoul(optarg, NULL, 0);
		break;
	case 'S':
		server++;
		break;
	default:
		return -1;
	}
	return 0;
}

int main(int argc, char **argv)
{
	int mgmt_classes[3] =
	    { IB_SMI_CLASS, IB_SMI_DIRECT_CLASS, IB_SA_CLASS };
	int sysstat_class = IB_VENDOR_OPENIB_SYSSTAT_CLASS;
	ib_portid_t portid = { 0 };
	int attr = IB_PING_ATTR;
	char *err;

	const struct ibdiag_opt opts[] = {
		{"oui", 'o', 1, NULL, "use specified OUI number"},
		{"Server", 'S', 0, NULL, "start in server mode"},
		{}
	};
	char usage_args[] = "<dest lid|guid> [<op>]";

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

	argc -= optind;
	argv += optind;

	if (!argc && !server)
		ibdiag_show_usage();

	if (argc > 1 && (attr = match_attr(argv[1])) < 0)
		ibdiag_show_usage();

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

	srcport = srcports->gsi.port;
	if (!srcport)
		IBEXIT("Failed to open '%s' port '%d'", ibd_ca, ibd_ca_port);

	if (server) {
		if (mad_register_server_via(sysstat_class, 1, NULL, oui, srcport) <
		    0)
			IBEXIT("can't serve class %d", sysstat_class);

		host_ncpu = build_cpuinfo();

		if ((err = ibsystat_serv()))
			IBEXIT("ibssystat to %s: %s", portid2str(&portid),
				err);
		exit(0);
	}

	if (mad_register_client_via(sysstat_class, 1, srcport) < 0)
		IBEXIT("can't register to sysstat class %d", sysstat_class);

	if (resolve_portid_str(srcports->gsi.ca_name, ibd_ca_port, &portid, argv[0],
			       ibd_dest_type, ibd_sm_id, srcport) < 0)
		IBEXIT("can't resolve destination port %s", argv[0]);

	if ((err = ibsystat(&portid, attr)))
		IBEXIT("ibsystat to %s: %s", portid2str(&portid), err);

	mad_rpc_close_port2(srcports);
	exit(0);
}
