/*
 * Copyright (c) 2001-2003 The Trustees of Indiana University.  
 *                         All rights reserved.
 * Copyright (c) 1998-2001 University of Notre Dame. 
 *                         All rights reserved.
 * Copyright (c) 1994-1998 The Ohio State University.  
 *                         All rights reserved.
 * 
 * This file is part of the LAM/MPI software package.  For license
 * information, see the LICENSE file in the top level directory of the
 * LAM/MPI source distribution.
 * 
 * $HEADER$
 *
 * $Id: asc_run.c,v 6.17 2003/11/04 04:00:49 pkambadu Exp $
 *
 *	Function:	- application execution
 */

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

#include <all_list.h>
#include <args.h>
#include <app_mgmt.h>
#include <app_schema.h>
#include <bfreq.h>
#include <freq.h>
#include <kio.h>
#include <laminternal.h>
#include <net.h>
#include <portable.h>
#include <preq.h>
#include <terror.h>
#include <typical.h>
#include <etc_misc.h>

/*
 * external variables
 */
extern struct kio_t	_kio;			/* kernel I/O block */ 

/*
 * local functions
 */
static void fdincr(int4 incr);
static void starterror(char *file, int nodeid, int prerror);

/*
 *	asc_ger
 *
 *	Function:	- sets GER buffer limits for application
 *	Accepts:	- application schema
 *			- error printing flag
 */
int
asc_ger(LIST *appd, int flag_print)
{
	int4		anode;			/* LOCAL filtered out */
	int4		nnodes;			/* # nodes in session */
	int4 *		nodes;			/* the node IDs */
	int		i;
	int *		nprocs_node;		/* # procs on each node */
	int		nprocs;			/* # procs in application */
	struct aschema *p;
	struct bfparms	bfp;			/* bufferd parameters */
/*
 * Get an array of nodeids and allocate a mirror array of process counts.
 */
	nnodes = getnall();
	nodes = (int4 *) malloc((unsigned) (nnodes * sizeof(int4)));
	if (getall(nodes, nnodes)) return(LAMERROR);
	nprocs_node = (int *) malloc((unsigned) (nnodes * sizeof(int)));
	if (nprocs_node == 0) return(LAMERROR);

	for (i = 0; i < nnodes; i++) nprocs_node[i] = 0;

	nprocs = al_count(appd);
/*
 * Tally the number of processes on each node.
 */
	p = (struct aschema *) al_top(appd);

	while (p) {
/*
 * Nodeids are not necessarily 0 to N-1, so we have to search for it.  (BFI)
 */
		for (i = 0; i < nnodes; ++i) {
			anode = (p->asc_node == LOCAL) ? getnodeid() :
					p->asc_node;

			if (anode == nodes[i]) break;
		}

		if (i >= nnodes) {
			errno = EBADNODE;

			if (flag_print) {
				fprintf(stderr, "mpirun: %s: ",
						nid_fmt(p->asc_node));
				terror("");
			}

			return(LAMERROR);
		}

		nprocs_node[i]++;
		p = (struct aschema *) al_next(appd, p);
	}
/*
 * Adjust the buffer limit on each node.
 */
	for (i = 0; i < nnodes; i++) {

		if (nprocs_node[i] == 0) continue;

		bfp.bfp_maxspace =
/*
 * the basic GER requirement
 */
			(MPI_GER * (MAXNMSGLEN + sizeof(struct nmsg)) *
			nprocs * nprocs_node[i]) +
/*
 * GER flow control messages - This one is tricky.  For processes
 * on your own node, you will only see the envelopes or the flow control
 * messages, but not max of both.  Since we have already covered local
 * processes above, we need not count them again here.
 */
			((MPI_GER - 1) * nprocs * (nprocs - nprocs_node[i]) *
			sizeof(struct nmsg)) +
/*
 * one packet for consumables to move through the buffer daemon
 */
			sizeof(struct nmsg) + MAXNMSGLEN +
/*
 * slush to cover up my mistakes
 */
			0x100000;

		bfp.bfp_maxspace = LAM_max(bfp.bfp_maxspace, BFDEFSPACE);

		if (rbfparms(nodes[i], &bfp)) {

			if (flag_print) {
				fprintf(stderr,
			    "mpirun: cannot allocate GER (%d bytes) on %s: ",
						bfp.bfp_maxspace,
						nid_fmt(nodes[i]));
				terror("");
			}

			return(LAMERROR);
		}
	}

	free((char *) nodes);
	free((char *) nprocs_node);

	return(0);
}

/*
 *	asc_run
 *
 *	Function:	- run the described application atomically
 *			- if a process cannot start, doom the running ones
 *	Accepts:	- app. schema desc.
 *			- number of parents in parent world
 *			- run-time environment flag
 *			- verbose flag
 *			- print error flag
 *			- GPS array describing running processes
 *	Returns:	- 0 or LAMERROR
 */
int
asc_run(LIST *appd, int nparent, int4 rtf, int verbose, int prerror,
	struct _gps *pgps)
{
    struct aschema 	*pp;			/* ptr process entry */
    int4		world_save;		/* saved ki_world */
    int4		parent_save;		/* saved ki_parent */
    int			i;			/* favourite index */
    int			temp_rtf;		/* temp rtf holder */
    int			pid;			/* process ID */
    int			idx;			/* process index */
    int			niod;			/* # processes using iod */
    int			remote;			/* remote process */
    int4		pfdio;			/* do IO via passed fds */
    int4		trycwd;			/* try change remote cwd */
    struct penv		env;			/* process environment */
    char		*cwd;

    if ((appd == 0) || (al_count(appd) == 0)) {
	errno = EINVAL;
	return(LAMERROR);
    }

    cwd = getworkdir();
/*
 * Set up the kernel I/O block.
 */
    world_save = _kio.ki_world;
    parent_save = _kio.ki_parent;

    _kio.ki_parent = nparent;
    _kio.ki_world = al_count(appd);

    memset((char *) pgps, 0, _kio.ki_world * sizeof(struct _gps));
/*
 * Count the number of processes using IO daemon.
 */
    niod = 0;
    if (rtf & RTF_IO) {
	for (pp = al_top(appd); pp; pp = al_next(appd, pp)) {
#if LAM_HAVE_FD_PASSING
	    if (pp->asc_node != LOCAL && pp->asc_node != getnodeid()) {
		niod++;
	    }
#else
            ++niod;
#endif
	}
    }
/*
 * Increment the stdio counts.
 */
    fdincr(niod);
/*
 * Loop starting each process.
 */
    for (pp = al_top(appd), i = 0; pp; ++i, pp = al_next(appd, pp)) {
/*
 * Standard IO is to be mapped to passed file descriptors only if the
 * process is to be run locally and passing of file descriptors
 * is suppported locally.
 */
	remote = (pp->asc_node != LOCAL && pp->asc_node != getnodeid());
#if LAM_HAVE_FD_PASSING
	pfdio = ((rtf & RTF_IO) && !remote) ? RTF_PFDIO : 0;
#else
	pfdio = 0;
#endif
/*
 * Start the process and store its pid.
 */
 	env.pe_rank = i;
 	env.pe_envv = pp->asc_env->ape_envv;
	trycwd = 0;
 	if (pp->asc_env->ape_wrkdir) {
 	    lam_strncpy(env.pe_wrkdir, pp->asc_env->ape_wrkdir, LAM_PATH_MAX);
 	} else {
	    if (remote && cwd && !(rtf & RTF_APPWD)) {
		trycwd = RTF_TRYCWD;
		lam_strncpy(env.pe_wrkdir, cwd, LAM_PATH_MAX);
	    } else {
		strcpy(env.pe_wrkdir, "");
	    }
 	}

	temp_rtf = rtf & ~(RTF_PFDIO | RTF_TRYCWD);
	if (rploadgov(pp->asc_srcnode, pp->asc_node,
		pp->asc_args->apa_argv[0], (temp_rtf | pfdio | trycwd),
	        pp->asc_args->apa_argv, &env, &pid, &idx)) {

	    pp->asc_errno = errno;
	    starterror(pp->asc_args->apa_argv[0], pp->asc_node, prerror);
	    break;
	}

	pgps[i].gps_node = (pp->asc_node == LOCAL) ? getnodeid() : pp->asc_node;
	pgps[i].gps_pid = pid;
	pgps[i].gps_idx = idx;
	pgps[i].gps_grank = i;

	if (rtf & RTF_IO) {
	  niod--;
	}

	if (verbose) {
	    printf("%d %s running on %s\n", pid, pp->asc_args->apa_argv[0],
		nid_fmt(pp->asc_node));
	    fflush(stdout);
	}
    }

    _kio.ki_world = world_save;
    _kio.ki_parent = parent_save;

    if (cwd)
	free(cwd);
/*
 * If all processes were not started, cleanup and kill the running ones.
 * This should not really be done here and we should simply return the
 * number of processes successfully started.  To help avoid a race with
 * processes entering the kernel we sleep before dooming them.
 */
    if (i < al_count(appd)) {
	fdincr(-niod);
	sleep(1);
	app_doom(i, pgps, -15);
	return(LAMERROR);
    }

    return(0);
}

/*
 *	fdincr
 *
 *	Function:	- increment stdio file desc.
 *	Accepts:	- increment value
 */
static void
fdincr(int4 incr)
{
    if (incr != 0) {
	if (_kio.ki_stdout > 2)
	    lam_rfincr(1, incr);
	if (_kio.ki_stderr > 2)
	    lam_rfincr(2, incr);
    }
}

/*
 *	starterror
 *
 *	Function:	- print can't start program error
 *	Accepts:	- file name
 *			- ID of node failed on
 *			- print error flag
 */
static void
starterror(char *file, int nodeid, int prerror)
{
    if (prerror) {
	fprintf(stderr, "mpirun: cannot start %s on %s: ",
		file, nid_fmt(nodeid));
	terror("");
    }
}


syntax highlighted by Code2HTML, v. 0.9.1