/*
 *
 * 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
 *	RBD/GDB
 *
 *	$Id: asc_parse.c,v 6.16 1999/09/04 21:59:38 jsquyres Exp $
 *
 *	Function:	- application schema parsing
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

#include "all_list.h"
#include "args.h"
#include "app_schema.h"
#include "lam.h"
#include "ndi.h"
#include "sfh.h"
#include "debug.h"

/*
 * local functions
 */
static int		parseline(LIST *, char *, int, char **);
static int		add_var(int *, char ***, char *);

/*
 *	asc_parse
 *
 *	Function:	- parse an application schema
 *			- returns line # in case of parsing error
 *	Accepts:	- app. schema file 
 *			- ptr line # (returned value)
 *	Returns:	- app. schema descriptor or NULL
 */
LIST *
asc_parse(const char *appfile, int *plineno, char **env)
{
	int		fd;			/* file desc. */
	int		nbytes;			/* # bytes read */
	int		bufsize;		/* buffer size */
	char		*filebuf;		/* file buffer */
	struct stat	fileinfo;		/* file information */
	LIST		*appd;			/* app. schema desc. */
/*
 * Get file info.
 */
	if (stat(appfile, &fileinfo)) return((LIST *) 0);

	bufsize = (int) fileinfo.st_size;
	if (bufsize <= 0) return((LIST *) 0);
/*
 * Allocate a file buffer.
 */
	filebuf = malloc((unsigned) bufsize);
	if (filebuf == 0) return((LIST *) 0);
/*
 * Open and read the file.
 */
	fd = open(appfile, O_RDONLY, 0);
	if (fd < 0) {
		free(filebuf);
		return((LIST *) 0);
	}

	nbytes = read(fd, filebuf, bufsize);
	close(fd);

	if (nbytes < bufsize) {
		if (nbytes >= 0) errno = EIO;
		free(filebuf);
		return((LIST *) 0);
	}
/*
 * Parse the app. schema buffer.
 */
	appd = asc_bufparse(filebuf, bufsize, plineno, env);

	free(filebuf);
	return(appd);
}

/*
 *	asc_bufparse
 *
 *	Function:	- parse an application schema from a buffer
 *			- returns line # in case of parsing error
 *	Accepts:	- buffer containing the app. schema
 *			- buffer size
 *			- ptr line # (returned value)
 *	Returns:	- app. schema descriptor or NULL
 */
LIST *
asc_bufparse(char *appbuf, int bufsize, int *pline, char **env)
{
	int		linesize;		/* size of parsed line */
	int		fl_comment;		/* comment mode flag */
	int		i;			/* favourite index */
	char		*p;			/* favourite pointer */
	LIST		*appd;			/* app. schema desc. */
/*
 * File must be pure ASCII.
 */
	*pline = 0;

	for (i = 0, p = appbuf; i < bufsize; ++i, ++p) {

		if ( ! isascii(*p)) {
			errno = ENOEXEC;
			return(0);
		}
	}

	appd = al_init(sizeof(struct aschema), (int (*)()) 0);
	if (appd == 0) return(0);
/*
 * Loop over the app. schema lines.
 */
	*pline = 1;
	fl_comment = 0;

	for ( ; bufsize > 0; ++appbuf, --bufsize) {
/*
 * Handle newline characters.
 */
		if (*appbuf == '\n') {
			++(*pline);
			fl_comment = 0;
		}
/*
 * Skip leading spaces and comments.
 */
		else if (isspace((int) *appbuf) || fl_comment) {
		}
/*
 * Skip comments.
 */
		else if (*appbuf == '#') {
			fl_comment = 1;
/*
 * Reject scripts.
 */
			if (*pline == 1 && bufsize > 2 && *(appbuf+1) == '!') {
				return(0);
			}
		}
/*
 * Parse the line.
 */
		else {
			linesize = parseline(appd, appbuf, bufsize, env);

			if (linesize < 0) {
				asc_free(appd);
				appd = 0;
				bufsize = 0;
			} else if (--linesize >= 0) {
				bufsize -= linesize;
				appbuf += linesize;
			}
		}
	}

	return(appd);
}

/*
 *	parseline
 *
 *	Function:	- parse a single app. schema line
 *			- update the app. schema desc.
 *	Accepts:	- ptr app. schema desc.
 *			- line buffer
 *			- buffer size
 *			- top level environment (if any)
 *	Returns:	- parsed line size or LAMERROR
 */
static int
parseline(LIST *appd, char *buf, int bufsize, char **env)
{
	int		argtailc;		/* args after "--" */
	int		lsize;			/* line size */
	int		othersc;		/* line w/o options */
	int		cmdc;			/* command line count */
	char **		argtailv;		/* args after "--" */
	char *		cmdline;		/* command line buffer */
	char **		cmdv;			/* command line argv */
	char **		othersv;		/* line w/o options */
	char *		lenv = 0;		/* line environment */
	char *		wrkdir = 0;		/* working dir */
	char **		progv;			/* line w/o nodes */
	char **		pv1;			/* current arg vector */
	char *		p2;			/* after parsed integer */
	char *		p;
	struct apparg * procargv;		/* process argv */
	struct appenv * procenv;		/* process env */
	struct aschema	proc;			/* process list entry */

/*
 * Copy the line to a temporary buffer.
 */
	p = strchr(buf, '\n');
	lsize = (p == 0) ? bufsize : (int) (p - buf);
/*
 * Allocate one extra char for terminator and 2 extra so we can put a fake
 * argv[0] at the start and then use the usual parsing routines.
 */
	cmdline = malloc((unsigned) lsize + 1 + 2);
	if (cmdline == 0) return(LAMERROR);
/*
 * Insert the fake argv[0] and then copy the line.
 */
	strcpy(cmdline, "x ");
	memcpy(cmdline + 2, buf, lsize);
	cmdline[lsize + 2] = '\0';
/*
 * Split it into a command line argv.
 */
	cmdv = sfh_argv_break_quoted(cmdline, ' ', "\\'\"");
	free(cmdline);

	if (cmdv == 0) return(LAMERROR);

	cmdc = sfh_argv_count(cmdv);
	if (asc_compat(&cmdc, &cmdv)) {
	    sfh_argv_free(cmdv);
	    return(LAMERROR);
	}
/*
 * Parse the -c, -s, -x and -wd options and establish argtail.
 * We could use all_opt but we do not want to pull it in.
 */
	othersc = 0;
	othersv = 0;

	if (sfh_argv_add(&othersc, &othersv, cmdv[0])) {
		sfh_argv_free(cmdv);
		return(LAMERROR);
	}

	LAM_ZERO_ME(proc);
	proc.asc_errno = 0;
	proc.asc_proc_cnt = -1;
	proc.asc_srcnode = -1;
	pv1 = cmdv + 1;

	while (*pv1 && strcmp(*pv1, "--")) {

		if (!strcmp(*pv1, "-c") || !strcmp(*pv1, "-np")) {
			pv1++;
			proc.asc_proc_cnt = strtol(*pv1, &p2, 0);

			if (*pv1 == p2) {
				sfh_argv_free(cmdv);
				sfh_argv_free(othersv);
				return(LAMERROR);
			}
		} else if (!strcmp(*pv1, "-s")) {
			pv1++;
			proc.asc_srcnode = ndi_parse1(*pv1);

			if ((proc.asc_srcnode < 0)
					&& (proc.asc_srcnode != LOCAL)) {
				sfh_argv_free(cmdv);
				sfh_argv_free(othersv);
				return(LAMERROR);
			}
		} else if (!strcmp(*pv1, "-wd")) {
			pv1++;
			wrkdir = *pv1;

			if (!wrkdir || *wrkdir == '-') {
				sfh_argv_free(cmdv);
				sfh_argv_free(othersv);
				return(LAMERROR);
			}
		} else if (!strcmp(*pv1, "-x")) {
			pv1++;
			lenv = *pv1;

			if (!lenv || *lenv == '-') {
				sfh_argv_free(cmdv);
				sfh_argv_free(othersv);
				return(LAMERROR);
			}
		} else {
		  if (sfh_argv_add(&othersc, &othersv, *pv1)) {
		    sfh_argv_free(cmdv);
		    sfh_argv_free(othersv);
		    return(LAMERROR);
		  }
		}
		
		pv1++;
	}

	if (*pv1) {
		argtailv = pv1 + 1;
		argtailc = sfh_argv_count(argtailv);
	} else {
		argtailv = 0;
		argtailc = 0;
	}
/*
 * Parse the nodeids.
 */
	proc.asc_nodelist = ndi_parse(othersc, othersv, &progv);
	sfh_argv_free(othersv);

	if (proc.asc_nodelist == 0) {
		sfh_argv_free(cmdv);
		return(LAMERROR);
	}
/*
 * We should be left with the fake command name and the single program name.
 */
	if (sfh_argv_count(progv) != 2) {
		sfh_argv_free(cmdv);
		sfh_argv_free(progv);
		errno = EUSAGE;
		return(LAMERROR);
	}
/*
 * Build the process's argv structure.
 */
	procargv = (struct apparg *) malloc(sizeof(struct apparg));
	if (procargv == 0) {
		sfh_argv_free(cmdv);
		sfh_argv_free(progv);
		return(LAMERROR);
	}

	procargv->apa_refcount = 1;
	procargv->apa_argc = argtailc + 1;

	if (argtailc > 0) {
		p = argtailv[-1];
		argtailv[-1] = progv[1];
		procargv->apa_argv = sfh_argv_dup(argtailv - 1);
		argtailv[-1] = p;
	} else {
		procargv->apa_argv = sfh_argv_dup(progv + 1);
	}

	sfh_argv_free(progv);

	if (procargv->apa_argv == 0) {
		sfh_argv_free(cmdv);
		free((char *) procargv);
		return(LAMERROR);
	}

	proc.asc_args = procargv;
/*
 * Build the process's env structure.
 */
	procenv = (struct appenv *) malloc(sizeof(struct appenv));
	if (procenv == 0) {
	    sfh_argv_free(cmdv);
	    sfh_argv_free(progv);
	    sfh_argv_free(procargv->apa_argv);
	    free(procargv);
	    return(LAMERROR);
	}

	procenv->ape_refcount = 1;
	procenv->ape_envv = 0;
	procenv->ape_wrkdir = 0;

	if (env) {
	    if ((procenv->ape_envv = sfh_argv_dup(env)) == 0) {
		sfh_argv_free(cmdv);
		sfh_argv_free(procargv->apa_argv);
		free(procargv);
		free(procenv);
		return(LAMERROR);
	    }
	}

	if (lenv) {
/*
 * Add env vars specified by -x options on this line.
 */
	    if (asc_environment(0, lenv, &procenv->ape_envv)) {
		sfh_argv_free(cmdv);
		sfh_argv_free(procargv->apa_argv);
		free(procargv);
		sfh_argv_free(procenv->ape_envv);
		free(procenv);
	    }
	}

	if (wrkdir) {
	    procenv->ape_wrkdir = strdup(wrkdir);
	    if (procenv->ape_wrkdir == 0) {
		sfh_argv_free(cmdv);
		sfh_argv_free(procargv->apa_argv);
		free(procargv);
		sfh_argv_free(procenv->ape_envv);
		free(procenv);
	    }
	}

	proc.asc_env = procenv;

	sfh_argv_free(cmdv);

	if (al_append(appd, &proc) == 0) {
		sfh_argv_free(procargv->apa_argv);
		free(procargv);
		if (procenv->ape_wrkdir) {
		    free(procenv->ape_wrkdir);
		}
		sfh_argv_free(procenv->ape_envv);
		free(procenv);
		return(LAMERROR);
	}

	return(lsize);
}

/*
 *	asc_free
 *
 *	Function:	- free an application schema descriptor
 *	Accepts:	- app. schema desc.
 */
void
asc_free(LIST *appd)
{
	struct aschema	*pp;			/* ptr process entry */
	struct apparg	*pa;			/* ptr process args */
	struct appenv	*pe;			/* ptr process env */

	if (appd == 0) return;

	for (pp = (struct aschema *) al_top(appd); pp;
			pp = (struct aschema *) al_next(appd, pp)) {

		if (pp->asc_nodelist) {
			al_free(pp->asc_nodelist);
		}

		pa = pp->asc_args;
		if (--pa->apa_refcount == 0) {
			sfh_argv_free(pa->apa_argv);
			free((char *) pa);
		}

		pe = pp->asc_env;
		if (--pe->ape_refcount == 0) {
		  sfh_argv_free(pe->ape_envv);
		  if (pe->ape_wrkdir) {
		    free(pe->ape_wrkdir);
		  }
		  free((char *) pe);
		}
	}

	al_free(appd);
}

/*
 *	asc_compat
 *
 *	Function:	- convert argv into "--" style argv
 *	Accepts:	- argument count (inout)
 *			- argument vector (inout)
 *	Returns:	- 0 (success) or -1 (error)
 */
int
asc_compat(int *uargc, char ***uargv)
{
    int			argc = *uargc;
    char		**argv = *uargv;
    int 		newargc = 0;		/* new argument count */
    char		**newargv = 0;		/* new argument vector */
#if 0
    /* This appears to be outdated JMS.  See
       http://www.mpi.nd.edu/Internal/llamas/msg00599.php3 */
    int			isnp = 0;		/* there is a -np option */
#endif
    int			i;
/*
 * If there is already a "--" or a "-c" option then there is nothing to do.  
 */
    for (i = 1; i < argc; i++) {
	if (strcmp(argv[i], "--") == 0) {
	    return(0);
	}

#if 0
    /* This appears to be outdated JMS.  See
       http://www.mpi.nd.edu/Internal/llamas/msg00599.php3 */
	if (strcmp(argv[i], "-np") == 0) {
	    isnp = 1;
	} else if (strcmp(argv[i], "-c") == 0 && !isnp) {
	    return(0);
	}
#endif
    }
/*
 * Add the command name to the new command line.
 */
    if (sfh_argv_add(&newargc, &newargv, argv[0])) {
	return(-1);
    }
/*
 * Loop adding arguments until we find something that is not an option,
 * option argument or node specifier; in which case we assume that it is
 * the binary or application schema name.  
 */
    for (i = 1; i < argc; i++) {

	if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "-np") == 0
	    	|| strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "-x") == 0
	        || strcmp(argv[i], "-wd") == 0) {
/*
 * It's a followed option.
 */
	    if (sfh_argv_add(&newargc, &newargv, argv[i])) {
		return(-1);
	    }

	    if (i + 1 < argc) {
		if (sfh_argv_add(&newargc, &newargv, argv[i + 1])) {
		    return(-1);
		}

		i++;
	    }
	}
	/*
	 * JMS Added IMPI -client option, which takes 2 following parameters
	 * Do this even if !WANT_IMPI, because this way these args will
	 * be ignored -- they won't cause an error (mpirun is already 
	 * noisy that the args will be ignored)
	 */
	else if (strcmp(argv[i], "-client") == 0) {
/*
 * It's a followed option -- takes 2 params
 */
	    if (sfh_argv_add(&newargc, &newargv, argv[i])) {
		return(-1);
	    }

	    if (i + 1 < argc) {
		if (sfh_argv_add(&newargc, &newargv, argv[i + 1])) {
		    return(-1);
		}

		i++;
	    }
	    if (i + 1 < argc) {
		if (sfh_argv_add(&newargc, &newargv, argv[i + 1])) {
		    return(-1);
		}

		i++;
	    }
	}
	else if (argv[i][0] == '-') {
/*
 * It's a non-followed option.
 */
	    if (sfh_argv_add(&newargc, &newargv, argv[i])) {
		return(-1);
	    }
	}
	else if (ndi_parse1(argv[i]) != NOTNODEID) {
/*
 * It's a node identifier.
 */
	    if (sfh_argv_add(&newargc, &newargv, argv[i])) {
		return(-1);
	    }
	}
	else {
/*
 * Must now have the binary or application schema name.  Append it to
 * the new argument vector and then if there are trailing arguments add
 * a "--" option.  
 */
	    if (sfh_argv_add(&newargc, &newargv, argv[i])) {
		return(-1);
	    }	

	    if (i + 1 < argc) {
		if (sfh_argv_add(&newargc, &newargv, "--")) {
		    return(-1);
		}
	    }

	    i++;
	    break;
	}
    }
/*
 * Append user program arguments to the new argument vector.
 */
    for (; i < argc; i++) {
	if (sfh_argv_add(&newargc, &newargv, argv[i])) {
	    return(-1);
	}
    }

    *uargc = newargc;
    *uargv = newargv;

    return(0);
}

/*
 *	asc_environment
 *
 *	Function:	- build job environment
 *	Accepts:	- export all LAM_MPI_* vars?
 *			- explicit list of variables to export
 *			- environment (inout)
 *	Returns:	- 0 or LAMERROR
 */
int
asc_environment(int export, char *varlist, char ***env)
{
	extern char	**environ;		/* current environment */
	char		**p;
	char		*v;
	int		nenv;

	nenv = sfh_argv_count(*env);

	if (export)
/*
 * Export all environment variables of form LAM_MPI_* or LAM_IMPI_*.
 */
		for (p = environ; p && *p; p++) {
			if (strncmp(*p, "LAM_MPI_", 8) == 0)
				if (sfh_argv_add(&nenv, env, *p))
					return LAMERROR;
#if LAM_WANT_IMPI
			if (strncmp(*p, "LAM_IMPI_", 8) == 0)
				if (sfh_argv_add(&nenv, env, *p))
					return LAMERROR;
#endif
		}
/*
 * Export explicitly specified variables.
 */
	if (varlist) {
		v = varlist + strlen(varlist);

		while (v != varlist) {
			/* search backwards for , */
			for ( ; *v != ',' && v != varlist; v--) ;

			if (v != varlist) {
				if (*(v-1) != '\\') {
					/* it's not escaped so we have a var */
					if (add_var(&nenv, env, v+1))
						return(LAMERROR);
					*v = 0;
				}
			
				v--;
			}
		}

		/* add first var */
		if (add_var(&nenv, env, varlist))
			return(LAMERROR);
	}

	return 0;
}

/*
 *	add_var
 *
 *	Function:	- add variable to environment vector
 *	Accepts:	- length of environment vector (inout)
 *			- environment vector (inout)
 *			- variable
 *	Returns:	- 0 or LAMERROR
 */
static int
add_var(int *nenv, char ***env, char *var)
{
	char		*val;
	char		*v;
	int		len;
	int		i, j;

	len = strlen(var);
	if (len == 0)
		return 0;
/*
 * Copy removing any escapes.
 */
	v = malloc(len+1);

	for (i = 0, j = 0; i < len; i++) {
		if (var[i] != '\\' || var[i+1] != ',') {
			v[j++] = var[i];
		} else {
			v[j++] = ',';
			i++;
		}
	}
	v[j] = 0;
/*
 * If there is an equals the variable's value has been given.
 */
	if (strchr(v, '=')) {
		if (sfh_argv_add(nenv, env, v))
			return LAMERROR;
	} else {
/*
 * Get value from the current environment.
 */
		if ((val = getenv(v))) {
			if ((var = malloc(strlen(v) + strlen(val) + 2)) == 0)
				return LAMERROR;
			strcpy(var, v);
			strcat(var, "=");
			strcat(var, val);

			if (sfh_argv_add(nenv, env, var))
				return LAMERROR;
			free(var);
		}
	}

	free(v);
	return 0;
}
