/*
 *
 * Copyright 1998-1999, University of Notre Dame.
 * Authors: Jeffrey M. Squyres, Kinis L. Meyer with M. D. McNally 
 *          and Andrew Lumsdaine
 *
 * This file is part of the Notre Dame LAM implementation of MPI.
 *
 * You should have received a copy of the License Agreement for the
 * Notre Dame LAM implementation of MPI along with the software; see
 * the file LICENSE.  If not, contact Office of Research, University
 * of Notre Dame, Notre Dame, IN 46556.
 *
 * Permission to modify the code and to distribute modified code is
 * granted, provided the text of this NOTICE is retained, a notice that
 * the code was modified is included with the above COPYRIGHT NOTICE and
 * with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
 * file is distributed with the modified code.
 *
 * LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
 * By way of example, but not limitation, Licensor MAKES NO
 * REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
 * PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
 * OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
 * OR OTHER RIGHTS.  
 *
 * Additional copyrights may follow.
 *
 *	Ohio Trollius
 *	Copyright 1997 The Ohio State University
 *	GDB
 *
 *	$Id: lamgrow.c,v 6.5 1999/06/12 17:11:18 kmeyer1 Exp $
 * 
 *	Function:	- adds a node to an existing LAM session
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#include <args.h>
#include <debug.h>
#include <dl_inet.h>
#include <events.h>
#include <lamnet.h>
#include <portable.h>
#include <preq.h>
#include <priority.h>
#include <rreq.h>
#include <t_types.h>
#include <terror.h>

/*
 * local variables
 */
static int		fl_verbose;	/* verbose mode */
static int              fl_debug;       /* debugging and help mode */
static char		*hname;		/* host name */
static char		*uname;		/* userid */

static char		*usage =
	"lamgrow [-dhvx] [-c <bhost>] [-u <userid>] [<node>] <hostname>\n";

/* 
 * local functions
 */
static void		cleanup();
static void		help();
static void		unique();

/*
 * external functions
 */
extern int		getinetaddr();
extern int		inetexec();
extern int		lambootagent();
extern void		nodespin_end();
extern void		nodespin_init();
extern void		nodespin_next();

/*
 * main
 */
int
main(argc, argv)  

int			argc;
char			*argv[];

{
	int		index;		/* cmd line node index */
	int		flags;		/* cmd line node flags */
	int4		i;
	int4		nboot;		/* # booted nodes */
	int4		ndoljones;	/* # links */
	int4		newnodeid;
	int4		nlamnet;	/* network description length */
	int4		nnodes;		/* # neighbour nodes */
	int4		nrun;		/* # running nodes */
	int4		*pnodes;	/* neighbour nodes */
	char		*p;
	FILE		*fp;		/* boot schema file */
	struct dolink	*doljones;	/* neighbour links */
	struct dilink	newdil;		/* new node link */
	struct lamnode	*lamnet;	/* network description */
	struct direq	*pdiq;		/* dli request */
	struct direply	*pdir;		/* dli reply */
	struct nmsg	nhq;		/* request message */

	/* Ensure that we are not root */

	if (getuid() == 0 || geteuid() == 0) {
	  show_help(NULL, "deny-root", NULL);
	  exit(EACCES);
	}

/*
 * Parse the command line.
 */
	validopts("dhvx");
	followed("cu");

	if (do_args(&argc, argv)) {
		fprintf(stderr, usage);
		exit(errno);
	}

	if (opt_taken('h')) {
		help();
		exit(0);
	}
/*
 * Set the flags.
 */
	fl_debug = opt_taken('d');
	fl_verbose = opt_taken('v');
/*
 * Initialize LAM.
 */
	if (kenter("lamgrow", PRCMD)) lamfail("lamgrow (kinit)");

	if (nid_parse(&argc, argv) || (errno = (argc == 2) ? 0 : EUSAGE)) {
		fprintf(stderr, usage);
		kexit(errno);
	}

	nnodes = getnall();
	if (nnodes < 0) lamfail("lamgrow (getnall)");

	if (nnodes == 0) {
		errno = EIMPOSSIBLE;
		lamfail("lamgrow (getnall)");
	}

	pnodes = (int4 *) malloc((unsigned) nnodes * sizeof(int4));
	if (pnodes == 0) lamfail("lamgrow (malloc)");
	getall(pnodes, nnodes);

	unique(pnodes, nnodes);
/*
 * Obtain neighbour link information from local dlo.
 */
	if (ldogetlinks(&doljones, &ndoljones))
			lamfail("lamgrow (ldogetlinks)");
/*
 * Determine the new node.  Allow only one node.
 */
	nid_get(&index, &newnodeid, &flags);

	if (index < 0) {
		newnodeid = ndoljones;
	} else {
		nid_get(&index, &newnodeid, &flags);

		if (index != 0) {
			fprintf(stderr, "usage: %s\n", usage);
			kexit(EUSAGE);
		}
	}

	if ((newnodeid < 0) || ((newnodeid < ndoljones) &&
			(doljones[newnodeid].dol_link != NOTLINKID))) {
		errno = EBADNODE;
		lamfail("lamgrow (usage)");
	}

	if (newnodeid > (ndoljones - 1)) {
		nlamnet = newnodeid + 1;
	} else {
		nlamnet = ndoljones;
	}
/*
 * Allocate node description array.
 */
	lamnet = (struct lamnode *) malloc((unsigned) nlamnet *
			sizeof(struct lamnode));
	if (lamnet == 0) lamfail("lamgrow (malloc)");

	for (i = 0; i < ndoljones; ++i) {

		if (doljones[i].dol_link == NOTLINKID) {
			lamnet[i].lnd_nodeid = NOTNODEID;
		} else {
			lamnet[i].lnd_nodeid = i;
			lamnet[i].lnd_type = 0;
			lamnet[i].lnd_addr = doljones[i].dol_addr;
		}
	}

	for (i = ndoljones; i < (nlamnet - 1); ++i) {
		lamnet[i].lnd_nodeid = NOTNODEID;
	}

	lamnet[getnodeid()].lnd_type |= NT_ME;

	if (getorigin() != NOTNODEID) {
		lamnet[getorigin()].lnd_type |= NT_ORIGIN;
	}

	hname = argv[1];
	uname = opt_taken('u') ? getparam('u') : 0;

	lamnet[newnodeid].lnd_nodeid = newnodeid;
	lamnet[newnodeid].lnd_type = NT_BOOT;
	lamnet[newnodeid].lnd_hname = hname;
	lamnet[newnodeid].lnd_uname = uname;
	lamnet[newnodeid].lnd_addr.sin_family = AF_INET;

	if (getinetaddr(lamnet[newnodeid].lnd_hname,
			&lamnet[newnodeid].lnd_addr.sin_addr))
			lamfail("lamgrow (getinetaddr)");

	if (lambootagent(lamnet, (int) nlamnet, &nboot, &nrun)) {
	  /* If the localhost was not in the boot schema, we must
             print the error message here, because lambootagent() does
             not know the name of the file (which is printed in the
             help message) */
	  if (errno == EINVAL)
	    show_help("boot", "no-localhost", "lamgrow", "<internal>", NULL);

	  /* lambootagent should print all other relevant error messages */
		if (nboot > 0) {
			cleanup("lamgrow (lambootagent)");
		} else {
			lamfail("lamgrow (lambootagent)");
		}
	}

	newdil.dil_link = ltot(newnodeid);
	newdil.dil_addr = lamnet[newnodeid].lnd_addr;
/*
 * Inform all pre-existing nodes of the new addition.
 */
	pdiq = (struct direq *) nhq.nh_data;
	pdir = (struct direply *) nhq.nh_data;
	nhq.nh_flags = NOBUF;
	nhq.nh_msg = (char *) &newdil;

	if (fl_verbose) {
		nodespin_init("update");
	}

	for (i = 0; i < nnodes; ++i) {

		if (fl_verbose) {
			nodespin_next(i);
		}

		pdiq->diq_req = DIQSETLINK;
		pdiq->diq_src_node = getnodeid();
		pdiq->diq_src_event = -getpid();
		nhq.nh_node = pnodes[i];
		nhq.nh_event = EVDLI;
		nhq.nh_type = 0;
		nhq.nh_length = sizeof(struct dilink);

		if (getroute(&nhq)) cleanup("lamgrow (getroute)");
/*
 * Re-route the local node.
 */
		if (nhq.nh_dl_event == nhq.nh_event) {
			nhq.nh_dl_event = EVDL0;
			nhq.nh_dl_link = getnodeid();
		}

		if (dsend(&nhq)) cleanup("lamgrow (nsend)");

		nhq.nh_event = -getpid();
		nhq.nh_length = 0;

		if (drecv(&nhq)) cleanup("lamgrow (nrecv)");

		if (pdir->dir_reply != 0) {
			errno = pdir->dir_reply;
			cleanup("lamgrow (DIQSETLINK)");
		}
	}

	if (fl_verbose) {
		nodespin_end();
	}

	if (opt_taken('c')) {
		if ((fp = fopen(getparam('c'), "a")) == NULL)
				cleanup("lamgrow (fopen)");
		p = "# next line added by lamgrow\n";

		fwrite(p, 1, strlen(p), fp);
		fwrite(hname, 1, strlen(hname), fp);

		if (uname != 0) {
			fwrite(" ", 1, 1, fp);
			fwrite(uname, 1, strlen(uname), fp);
		}

		fwrite("\n", 1, 1, fp);
		fclose(fp);
	}

	free((char *) doljones);
	kexit(0);
	return(0);
}

/*
 *	cleanup
 *
 *	Function:	- cleans up the mess after something goes wrong
 *	Accepts:	- context error message
 */
static void
cleanup(context)

char			*context;

{
	int		cmdn;
	int		err_save;
	char		**cmdv;

	terror(context);
	err_save = errno;
	cmdn = 0;
	cmdv = 0;
	argvadd(&cmdn, &cmdv, DEFTTKILL);

	VERBOSE("%s ...\n", DEFTTKILL);

	if (inetexec(hname, uname, cmdv, (fl_debug ? "lamgrow" : NULL))) 
	  lamfail("lamgrow (wipe)");

	kexit(err_save);
}

/*
 *	unique
 *
 *	Function:	- ensures that no other lamgrow is running
 *	Accepts:	- node array
 *			- node array size
 */
static void
unique(pnodes, nnodes)

int4			*pnodes;
int4			nnodes;

{
	struct pstate	*pps;		/* process stat array */
	int		i, j;
	int		nps;		/* ret'n # processes */

	pps = (struct pstate *) malloc((unsigned) PMAX *
			sizeof(struct pstate));
	if (pps == 0) lamfail("lamgrow (malloc)");

	for (i = 0; i < nnodes; ++i) {
		nps = rpstate(pnodes[i], SELECT_ALL, 0, pps, PMAX);
		if (nps < 0) lamfail("lamgrow (rpstate)");

		for (j = 0; j < nps; ++j) {

			if ((! strcmp("lamgrow", pps[j].ps_name)) &&
					((pps[j].ps_pid != getpid()) ||
					(pnodes[i] != getnodeid()))) {
				fprintf(stderr,
					"lamgrow: already running on n%d\n",
						pnodes[i]);
				free((char *) pps);
				kexit(EUSAGE);
			}
		}
	}

	free((char *) pps);
}

/*
 *	help
 *
 *	Function:	- prints usage information
 */
static void 
help()

{
	printf("Synopsis:\tlamgrow [options] [<node>] <hostname>\n");
	printf("\nDescription:\tAdd a node to a LAM multicomputer.\n");
	printf("\nOptions:\t-h\t\tPrint this message.\n");
	printf("\t\t-d\t\tTurn on debugging / help mode.\n");
	printf("\t\t-v\t\tBe verbose.\n");
	printf("\t\t-c <bhost>\tUpdate this boot schema.\n");
	printf("\t\t-u <userid>\tUse another userid.\n");
	printf("\t\t-x\t\tRun node in fault tolerant mode.\n");
	printf("\nNode:\t\tn<id>, eg., n5\n");
	printf("\nExample:\tlamgrow n3 host1\n");
	printf("\t\t\t\"Add host1 as node 3.\"\n\n");
}
