/* * Copyright (c) 2001-2004 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_parse.c,v 6.33 2004/01/09 18:03:34 jsquyres Exp $ * * Function: - application schema parsing */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 lsize; /* line size */ int i; char *cmdline; /* command line */ char *progname; /* program name for this line */ char *p; /* favorite pointer */ int orig_cmdc; /* Original cmdc / cmdv */ char ** orig_cmdv; /* from buf */ int cmdc; /* argv/argc faking */ char ** cmdv; int tmp_cmdc; /* tmp cmd line info */ char ** tmp_cmdv; struct apparg * procargv; /* process argv */ struct appenv * procenv; /* process env */ struct aschema proc; /* process list entry */ OPT *ad; /* Options for app schema */ /* prep the proc description */ LAM_ZERO_ME(proc); proc.asc_errno = 0; proc.asc_proc_cnt = -1; proc.asc_srcnode = -1; proc.asc_node = -1; /* * Set up the options. Do early for asc_compat() */ ad = ao_init(); ao_setopt(ad, "c", 0, 1, AOINT); ao_setopt(ad, "np", "c", 1, AOINT); ao_setopt(ad, "s", 0, 1, 0); ao_setopt(ad, "x", 0, 1, 0); ao_setopt(ad, "wd", 0, 1, 0); /* 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) { ao_free(ad); 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. Don't free to the blody end, * as all the code used believes that we are using the argv/argc * from main, which means they expect it to be there until end of * time. Many moons wasted on this. */ orig_cmdv = sfh_argv_break_quoted(cmdline, ' ', "\\'\""); free(cmdline); if (orig_cmdv == 0) { ao_free(ad); return(LAMERROR); } orig_cmdc = sfh_argv_count(orig_cmdv); cmdv = orig_cmdv; cmdc = orig_cmdc; /* * Put in -- form */ if (asc_compat(&cmdc, &cmdv, ad)) { ao_free(ad); sfh_argv_free(orig_cmdv); return(LAMERROR); } /* * Parse the options. We used to do this by hand, which seemed * crazy just to avoid pulling in all_opt. Maybe back when this * thing was actually running on a multicomputer... */ if (ao_parse(ad, &cmdc, cmdv)) { if (orig_cmdv != cmdv) { sfh_argv_free(orig_cmdv); } sfh_argv_free(cmdv); ao_free(ad); return LAMERROR; } /* * Parse the node identifiers. Would be easier if ndi_parse removed * from the array like everyone else... */ proc.asc_nodelist = ndi_parse(cmdc, cmdv, &tmp_cmdv); if (proc.asc_nodelist == 0) { if (orig_cmdv != cmdv) { sfh_argv_free(orig_cmdv); } sfh_argv_free(cmdv); return(LAMERROR); } /* Fix up cmdv and cmdc - from here on, cmdv and orig_cmdv are different*/ if (orig_cmdv != cmdv) { #if 0 /* BWB: Fix me later */ sfh_argv_free(cmdv); #endif } cmdv = tmp_cmdv; cmdc = sfh_argv_count(cmdv); tmp_cmdv = 0; /* * We should be left with the fake command name and the single * program name. Copy name into progname */ if (sfh_argv_count(cmdv) != 2) { sfh_argv_free(orig_cmdv); sfh_argv_free(cmdv); errno = EUSAGE; return(LAMERROR); } progname = strdup(cmdv[1]); #if 0 /* BWB: Fix me later */ sfh_argv_free(cmdv); #endif cmdc = 0; cmdv = 0; tmp_cmdc = 0; tmp_cmdv = 0; /* * Start filling in proc structure. */ if (ao_taken(ad, "s")) { proc.asc_srcnode = ndi_parse1(ao_param(ad, "s", 0, 0)); if ((proc.asc_srcnode < 0) && (proc.asc_srcnode != LOCAL)) { ao_free(ad); sfh_argv_free(orig_cmdv); return(LAMERROR); } } if (ao_taken(ad, "c")) { ao_intparam(ad, "c", 0, 0, &(proc.asc_proc_cnt)); } else if (ao_taken(ad, "np")) { ao_intparam(ad, "np", 0, 0, &(proc.asc_proc_cnt)); } /* * Build the process's argv structure. */ procargv = (struct apparg *) malloc(sizeof(struct apparg)); if (procargv == 0) { ao_free(ad); sfh_argv_free(orig_cmdv); return(LAMERROR); } procargv->apa_argc = 0; procargv->apa_argv = 0; procargv->apa_refcount = 1; /* build argument list */ if (ao_tail(ad, &tmp_cmdc, &tmp_cmdv)) { ao_free(ad); sfh_argv_free(orig_cmdv); } sfh_argv_add(&(procargv->apa_argc), &(procargv->apa_argv), progname); for (i = 0 ; i < tmp_cmdc ; ++i) { sfh_argv_add(&(procargv->apa_argc), &(procargv->apa_argv), tmp_cmdv[i]); } /* No need to free tmp_cmdv, as it is just a pointer into orig_cmdv */ tmp_cmdv = 0; tmp_cmdc = 0; free(progname); /* degenerate case - but it would be bad not to double check */ if (procargv->apa_argv == 0) { ao_free(ad); sfh_argv_free(orig_cmdv); free(procargv); return(LAMERROR); } proc.asc_args = procargv; /* * Build the process's env structure. */ procenv = (struct appenv *) malloc(sizeof(struct appenv)); if (procenv == 0) { ao_free(ad); sfh_argv_free(orig_cmdv); 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) { ao_free(ad); sfh_argv_free(orig_cmdv); sfh_argv_free(procargv->apa_argv); free(procargv); free(procenv); return(LAMERROR); } } if (ao_taken(ad, "x")) { /* * Add env vars specified by -x options on this line. */ if (asc_environment(0, ao_param(ad, "x", 0, 0), &procenv->ape_envv)) { ao_free(ad); sfh_argv_free(orig_cmdv); sfh_argv_free(procargv->apa_argv); free(procargv); sfh_argv_free(procenv->ape_envv); free(procenv); } } if (ao_taken(ad, "wd")) { procenv->ape_wrkdir = strdup(ao_param(ad, "wd", 0, 0)); if (procenv->ape_wrkdir == 0) { ao_free(ad); sfh_argv_free(orig_cmdv); sfh_argv_free(procargv->apa_argv); free(procargv); sfh_argv_free(procenv->ape_envv); free(procenv); } } proc.asc_env = procenv; /* * Add ourselves to the list */ 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); } /* * all done... Clean up */ ao_free(ad); #if 0 /* BWB: Fix me later */ sfh_argv_free(orig_cmdv); #endif 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) * - options descriptor (to parse argv) * Returns: - 0 (success) or -1 (error) * * Notes: - If we have to modify the argv array, we reallocate a * new one, since uargv may be a pointer to the argv * array for main sitting on the stack and modifying * that would be bad. This also means that we can't * free the old argv, so you should if appropriate. */ int asc_compat(int *uargc, char ***uargv, OPT *aod) { int argc = *uargc; char **argv = *uargv; int newargc = 0; /* new argument count */ char **newargv = 0; /* new argument vector */ int i,j; int nparams; /* * 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); } } /* * Add the command name to the new command line. */ if (sfh_argv_add(&newargc, &newargv, argv[0])) { return(-1); } /* * iterate over arguments until something not in aod or a node * specifier is found. Assume that is the binary or app schema * name. */ for (i = 1; i < argc; i++) { if (argv[i][0] == '-' && strlen(argv[i]) > 1 && ao_exam_isopt(aod, argv[i] + 1)) { /* * It's an option */ ao_exam_nparams(aod, argv[i] + 1, &nparams); /* add the option */ if (sfh_argv_add(&newargc, &newargv, argv[i])) return -1; /* Eat the params to the option. If this is a variable, eat * until the next thing that could be an option (starts with * -, which handles the -- case). This isn't perfect, but * then options parsing shouldn't require an AI to figure out * what the user wanted. */ if (nparams != AOVARNUM) { for (j = 0 ; j < nparams ; ++j) { /* bounds checking is good */ if (i + 1 >= argc) return -1; if (sfh_argv_add(&newargc, &newargv, argv[i + 1])) return -1; ++i; } } else { /* Note: This case is not well tested, as it is not * currently used anywhere in the LAM/MPI tree. But it * might be some day! */ while (i + 1 < argc && (argv[i + 1])[0] != '-') { if (sfh_argv_add(&newargc, &newargv, argv[i + 1])) return -1; ++i; } } } 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 exportme, char *varlist, char ***env) { extern char **environ; /* current environment */ char **p; char *v; int nenv; char bad_string[] = "LAM_MPI_SESSION_PREFIX="; nenv = sfh_argv_count(*env); if (exportme) /* * Export all environment variables of form LAM_MPI_* or LAM_IMPI_* or * IMPI_*. Special case: do *not* export LAM_MPI_SESSION_PREFIX. */ for (p = environ; p && *p; p++) { if (strncmp(*p, "LAM_MPI_", 8) == 0 && strncmp(*p, bad_string, sizeof(bad_string) - 1) != 0) { if (sfh_argv_add(&nenv, env, *p)) return LAMERROR; } #if LAM_WANT_IMPI else if (strncmp(*p, "LAM_IMPI_", 8) == 0) { if (sfh_argv_add(&nenv, env, *p)) return LAMERROR; } else if (strncmp(*p, "IMPI_", 5) == 0) { if (sfh_argv_add(&nenv, env, *p)) return LAMERROR; } #endif /* * Also export the PBS_ENVIRONMENT and PBS_JOBID vars, if they exist. * This allows us to use the right unix socket names on the other side. */ else if (strncmp(*p, "PBS_ENVIRONMENT=", 16) == 0) { if (sfh_argv_add(&nenv, env, *p)) return LAMERROR; } else if (strncmp(*p, "PBS_JOBID=", 10) == 0) { if (sfh_argv_add(&nenv, env, *p)) return LAMERROR; } } /* * 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; }