/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <locale.h>
#include <fcntl.h>
#include <libgen.h>

#include <sys/nsctl/cfg.h>
#include <sys/ncall/ncall.h>

static CFGFILE *cfg;
static int cfg_changed;
static char *progname;
static ncall_node_t *getnodelist(int, int *, int *);


static void
usage(int exitstat)
{
	(void) fprintf(stderr, gettext("usage:\n"));
	(void) fprintf(stderr, gettext("       %s -d\n"), progname);
	(void) fprintf(stderr, gettext("       %s -e\n"), progname);
	(void) fprintf(stderr, gettext("       %s -h\n"), progname);
#ifdef DEBUG
	(void) fprintf(stderr, gettext("       %s -c [nodeid <nodeid>]\n"),
	    progname);
	(void) fprintf(stderr, gettext("       %s -i\n"), progname);
	(void) fprintf(stderr, gettext("       %s -p <host>\n"), progname);
#endif

	(void) fprintf(stderr, gettext("where:\n"));
	(void) fprintf(stderr, gettext("       -d    disable ncall\n"));
	(void) fprintf(stderr, gettext("       -e    enable ncall core\n"));
	(void) fprintf(stderr, gettext("       -h    this help message\n"));
#ifdef DEBUG
	(void) fprintf(stderr,
	    gettext("       -c    set or print ncall configuration\n"));
	(void) fprintf(stderr, gettext("       -i    ncall information\n"));
	(void) fprintf(stderr, gettext("       -p    ncall ping <host>\n"));
#endif

	exit(exitstat);
}


static void
ncall_cfg_open(CFGLOCK lk)
{
	char hostid[32];

	if (cfg != NULL) {
		return;
	}

	if (snprintf(hostid, sizeof (hostid), "%lx", gethostid()) >=
	    sizeof (hostid)) {
		(void) fprintf(stderr, gettext("%s: hostid %lx too large\n"),
		    progname, gethostid());
		exit(1);
	}

	if ((cfg = cfg_open(NULL)) == NULL) {
		(void) fprintf(stderr,
		    gettext("%s: unable to access the configuration: %s\n"),
		    progname, cfg_error(NULL));
		exit(1);
	}

	if (!cfg_lock(cfg, lk)) {
		(void) fprintf(stderr,
		    gettext("%s: unable to lock the configuration: %s\n"),
		    progname, cfg_error(NULL));
		exit(1);
	}

	cfg_resource(cfg, hostid);
}


static void
ncall_cfg_close(void)
{
	if (cfg_changed && cfg_commit(cfg) < 0) {
		(void) fprintf(stderr,
		    gettext("%s: unable to update the configuration: %s\n"),
		    progname, cfg_error(NULL));
		exit(1);
	}

	cfg_close(cfg);
	cfg = NULL;
}


/*
 * Get config from dscfg.
 */
static int
get_nodeid_from_cfg(int *nodeid)
{
	char buf[CFG_MAX_BUF];
	int ret = -1;
	int rc;

	ncall_cfg_open(CFG_RDLOCK);

	if (cfg_get_cstring(cfg, "ncallcore.set1", buf, sizeof (buf)) >= 0) {
		rc = sscanf(buf, "%d", nodeid);
		if (rc == 1) {
			ret = 0;
		}
	}

	ncall_cfg_close();

	return (ret);
}


static void
ncall_print(void)
{
	int cfnodeid, clnodeid, rc;

	clnodeid = cfg_issuncluster();

	rc = get_nodeid_from_cfg(&cfnodeid);

	if (rc < 0 && clnodeid > 0) {
		(void) printf(gettext("%s: ncall is using the SunCluster "
		    "nodeid: %d\n"), progname, clnodeid);
	} else if (rc < 0) {
		(void) printf(gettext("%s: ncall is using the default "
		    "nodeid: %d\n"), progname, 0);
	} else {
		(void) printf(gettext("%s: current configuration:\n"),
		    progname);
		/* deliberately not i18n'd - "nodeid" is a keyword */
		(void) printf("nodeid %d\n", cfnodeid);
	}
}


static void
ncall_config(const int nodeid)
{
	char buf[CFG_MAX_BUF];

	ncall_cfg_open(CFG_WRLOCK);

	if (cfg_get_cstring(cfg, "ncallcore.set1", buf, sizeof (buf)) >= 0) {
		/* remove old config */
		if (cfg_put_cstring(cfg, "ncallcore.set1", NULL, 0) < 0) {
			(void) fprintf(stderr,
			    gettext("%s: unable to update the configuration: "
			    "%s\n"), cfg_error(NULL));
			exit(1);
		}
	}

	if (snprintf(buf, sizeof (buf), "%d", nodeid) >= sizeof (buf)) {
		(void) fprintf(stderr,
		    gettext("%s: unable to update configuration: "
		    "data too long\n"), progname);
		exit(1);
	}

	if (cfg_put_cstring(cfg, "ncallcore", buf, sizeof (buf)) < 0) {
		(void) fprintf(stderr,
		    gettext("%s: unable to update the configuration: %s\n"),
		    cfg_error(NULL));
		exit(1);
	}

	cfg_changed = 1;
	ncall_cfg_close();

	(void) printf(gettext("%s: configuration set to:\n"), progname);
	/* deliberately not i18n'd - "nodeid" is a keyword */
	(void) printf("nodeid %d\n", nodeid);
}

#ifdef lint
int
ncalladm_lintmain(int argc, char *argv[])
#else
int
main(int argc, char *argv[])
#endif
{
	const char *dev = "/dev/ncall";
	extern int optind, opterr;
	ncall_node_t nodeinfo, *nodes;
	int nsize;
	int i;
	int cflag, dflag, eflag, iflag, pflag;
	int rc, fd, opt;
	int clnodeid, cfnodeid;
	int up;
	char *cp, *ping;
	int mnode;	/* mirror nodeid */

	(void) setlocale(LC_ALL, "");
	(void) textdomain("ncalladm");

	opterr = 0;
	cflag = dflag = eflag = iflag = pflag = 0;
	ping = NULL;

	progname = basename(argv[0]);

	while ((opt = getopt(argc, argv,
#ifdef DEBUG
	    "cip:"
#endif
	    "deh")) != -1) {
		switch (opt) {
		case 'c':
			cflag = 1;
			break;

		case 'd':
			dflag = 1;
			break;

		case 'e':
			eflag = 1;
			break;

		case 'h':
			usage(0);
			break;

		case 'i':
			iflag = 1;
			break;

		case 'p':
			ping = optarg;
			pflag = 1;
			break;

		default:
			(void) fprintf(stderr, gettext("%s: unknown option\n"),
			    progname);
			usage(1);
			break;
		}
	}

	if (!(cflag || dflag || eflag || iflag || pflag)) {
		usage(1);
	}

	if (argc != optind) {
		if (!cflag ||
		    (argc - optind) != 2 ||
		    strcmp(argv[optind], "nodeid") != 0) {
			usage(1);
		}
	}

	if ((cflag + dflag + eflag + iflag + pflag) > 1) {
		(void) fprintf(stderr,
		    gettext("%s: multiple options are not supported\n"),
		    progname);
		usage(1);
	}

	if (!cflag) {
		fd = open(dev, O_RDONLY);
		if (fd < 0) {
			(void) fprintf(stderr,
			    gettext("%s: unable to open %s: %s\n"),
			    progname, dev, strerror(errno));
			exit(1);
		}
	}

	if (dflag) {
		/* ioctl stop into kernel */
		if (ioctl(fd, NC_IOC_STOP, 0) < 0) {
			(void) fprintf(stderr,
			    gettext("%s: unable to disable ncall: %s\n"),
			    progname, strerror(errno));
			exit(1);
		}
	} else if (eflag) {
		bzero(&nodeinfo, sizeof (nodeinfo));

		clnodeid = cfg_issuncluster();
		cfnodeid = 0;

		/* get node info */
		rc = gethostname(nodeinfo.nc_nodename,
		    sizeof (nodeinfo.nc_nodename));
		if (rc < 0) {
			(void) fprintf(stderr,
			    gettext("%s: unable to determine hostname: %s\n"),
			    progname, strerror(errno));
			exit(1);
		}

		rc = get_nodeid_from_cfg(&cfnodeid);

		if (clnodeid > 0 && rc == 0) {
			/*
			 * check that the nodeids from the cf file and
			 * cluster match.
			 */
			if (clnodeid != cfnodeid) {
				(void) fprintf(stderr,
				    gettext("%s: nodeid from configuration "
				    "(%d) != cluster nodeid (%d)\n"),
				    progname, cfnodeid, clnodeid);
				exit(1);
			}
		}

		if (rc == 0) {
			nodeinfo.nc_nodeid = cfnodeid;
		} else if (clnodeid > 0) {
			nodeinfo.nc_nodeid = clnodeid;
		} else {
			nodeinfo.nc_nodeid = 0;
		}

		/* ioctl node info into kernel and start ncall */
		rc = ioctl(fd, NC_IOC_START, &nodeinfo);
		if (rc < 0) {
			(void) fprintf(stderr,
			    gettext("%s: unable to enable ncall: %s\n"),
			    progname, strerror(errno));
			exit(1);
		}
	}

	if (iflag || pflag) {
		nodes = getnodelist(fd, &nsize, &mnode);

		if (nodes == NULL) {
			(void) fprintf(stderr,
			    gettext("%s: unable to get node info\n"),
			    progname);
			exit(1);
		}
	}

	if (iflag) {
		char *mname;
		char *pnodestr;

		(void) printf(gettext("Self Node Name: %s\n"),
		    nodes[0].nc_nodename);
		(void) printf(gettext("Self Node ID: %d\n"),
		    nodes[0].nc_nodeid);
		/*
		 * determine which slot is the mirror node.
		 */
		if (mnode != -1) {
			for (i = 1; i < nsize; i++) {
				if (nodes[i].nc_nodeid == mnode) {
					mname = nodes[i].nc_nodename;
					break;
				}
			}
		}
		if ((mnode == -1) || (i >= nsize)) {
			mname = gettext("unknown");
			mnode = -1;
		}

		(void) printf(gettext("Mirror Node Name: %s\n"), mname);
		(void) printf(gettext("Mirror Node ID: %d\n"), mnode);
		/*
		 * See if we need to translate the node strings.
		 */
		if (nsize > 1) {
			pnodestr = gettext("Node Name: %s\nNode ID: %d\n");
			for (i = 1; i < nsize; i++) {
				/*
				 * Don't print the mirror twice.
				 */
				if (nodes[i].nc_nodeid != mnode) {
					(void) printf(pnodestr,
					    nodes[i].nc_nodename,
					    nodes[i].nc_nodeid);
				}
			}
		}
	}

	if (pflag) {
		if (strlen(ping) >= sizeof (nodeinfo.nc_nodename)) {
			(void) fprintf(stderr,
			    gettext("%s: hostname '%s' is too long\n"),
			    progname, ping);
			exit(1);
		}
		up = 0;
		if (strcmp(nodes[0].nc_nodename, ping) == 0) {
			up = 1;		/* self */
		} else {
			/* not self, so ask kernel */
			bzero(&nodeinfo, sizeof (nodeinfo));
			/* strlen(ping) checked above */
			(void) strcpy(nodeinfo.nc_nodename, ping);
			up = ioctl(fd, NC_IOC_PING, nodeinfo);
		}

		/* model the ping messages on ping(1m) */

		if (up < 0) {
			(void) fprintf(stderr,
			    gettext("%s: unable to ping host '%s': %s\n"),
			    progname, ping, strerror(errno));
			exit(1);
		} else if (up > 0) {
			(void) printf(gettext("%s is alive\n"), ping);
		} else {
			(void) printf(gettext("no answer from %s\n"), ping);
			exit(1);
		}
	}

	if (iflag || pflag) {
		free(nodes);
	}

	if (cflag) {
		if (argc == optind) {
			ncall_print();
			return (0);
		}

		cp = NULL;
		cfnodeid = (int)strtol(argv[optind+1], &cp, 0);
		if (cp != NULL && *cp != '\0') {
			(void) fprintf(stderr,
			    gettext("%s: nodeid \"%s\" is not an "
			    "integer number\n"), progname, argv[optind+1]);
			exit(1);
		}

		clnodeid = cfg_issuncluster();
		if (clnodeid > 0 && cfnodeid != clnodeid) {
			(void) fprintf(stderr,
			    gettext("%s: nodeid from command line "
			    "(%d) != cluster nodeid (%d)\n"),
			    progname, cfnodeid, clnodeid);
			exit(1);
		}

		ncall_config(cfnodeid);
	}

	if (!cflag) {
		(void) close(fd);
	}

	return (0);
}


/*
 * return a pointer to a list of currently configured
 * nodes.
 * Return the number of nodes via the nodesizep pointer.
 * Return the mirror nodeid via the mirrorp pointer.
 * Return NULL on errors.
 */
static ncall_node_t *
getnodelist(int ifd, int *nodesizep, int *mirrorp)
{
	int maxsize;
	int cnt;
	ncall_node_t *noderet = NULL;
	ncall_node_t *nodelist;
	ncall_node_t thisnode;
	int mirror;
	int nonet;

	/*
	 * Get this host info and mirror nodeid.
	 */
	mirror = ioctl(ifd, NC_IOC_GETNODE, &thisnode);

	if (mirror < 0) {
		return (NULL);
	}

	/*
	 * See if we need to allocate the buffer.
	 */
	nonet = 0;
	maxsize = ioctl(ifd, NC_IOC_GETNETNODES, 0);
	if (maxsize < 1) {
		maxsize = 1;
		nonet = 1;
	}
	nodelist = malloc(sizeof (*nodelist) * maxsize);
	if (nodelist) {
		if (nonet == 0) {
			/*
			 * fetch the node data.
			 */
			cnt = ioctl(ifd, NC_IOC_GETNETNODES, nodelist);
			if (cnt > 0) {
				*nodesizep = cnt;
				noderet = nodelist;
				*mirrorp = mirror;
			} else {
				*nodesizep = 0;
				free(nodelist);
			}
		} else {
			(void) memcpy(nodelist, &thisnode, sizeof (*nodelist));
			*nodesizep = 1;
			noderet = nodelist;
			/*
			 * Although we know the mirror nodeid, there
			 * is no point in returning it as we have
			 * no information about any other hosts.
			 */
			*mirrorp = -1;
		}
	}
	return (noderet);
}