/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
 */

#include <ctype.h>
#include <string.h>

#define import_spp
#define import_libc
#define import_finfo
#define import_stdio
#include <iraf.h>

#include "config.h"
#include "errs.h"
#include "operand.h"
#include "mem.h"
#include "param.h"
#include "task.h"
#include "grammar.h"
#include "proto.h"


/*
 * PFILES -- Parameter file access procedures.
 */

extern	int cldebug;
extern	char *undefval;
extern	char *nullstr;
extern	char *indefstr, *indeflc;
extern	FILE *yyin;
char	*uparmdir = UPARM;
long	filetime(char *fname, char *timecode);
static	void mapname(char *in, char *out, int maxlen);


/* NEWPFILE -- Allocate a new pfile on the dictionary and link in at parhead.
 * Set pfp->pf_ltp to ltp.  Null out all unused fields.  Call error() and don't
 * return if not enough core.
 */
struct pfile *
newpfile (
    struct ltask *ltp		/* ltask descriptor */
)
{
	register struct pfile *pfp, *head_pfp;

	pfp = (struct pfile *) memneed (PFILESIZ);
	head_pfp = reference (pfile, parhead);
	if (head_pfp >= pfp)
	    cl_error (E_IERR, "in newpfile: parhead exceeds topd");

	pfp->pf_npf = reference (pfile, parhead);
	parhead = dereference (pfp);

	pfp->pf_pp = NULL;
	pfp->pf_oldpfp = NULL;
	pfp->pf_npset = NULL;
	pfp->pf_psetp = NULL;
	pfp->pf_ltp = ltp;
	pfp->pf_flags = 0;
	pfp->pf_n = 0;

	return (pfp);
}


/* PFILEUNLINK -- Unlink a pfile from the pfile list.
 */
void
pfileunlink (
    register struct pfile *pfp	/* pfile to be unlinked	*/
)
{
	register struct pfile *npf;

	if ((npf = reference (pfile, parhead)) == pfp)
	    parhead = dereference (pfp->pf_npf);
	else {
	    while (npf && npf->pf_npf != pfp)
		npf = npf->pf_npf;
	    if (npf) {
		if (pfp->pf_npf == npf)
		    cl_error (E_IERR, "in pfileunlink: circular reference");
		else
		    npf->pf_npf = pfp->pf_npf;
	    }
	}
}


/* PFILEFIND -- Search the list of loaded pfiles for the pfile for a particular
 * ltask.  Return pfile pointer or NULL.   Note that all loaded pfiles are
 * linked on a single list regardless of which package or task they belong to.
 */
struct pfile *
pfilefind (
    register struct ltask *ltp		/* ltask descriptor */
)
{
	register struct pfile *pfp;

	for (pfp = reference (pfile, parhead);  pfp != NULL;  pfp = pfp->pf_npf)
	    if (pfp->pf_ltp == ltp)
		return (pfp);

	return (NULL);
}


/* PFILESRCH -- Given a pfile filename or the pathname of an ltask which
 * has a pfile, allocate a pfile descriptor and read the pfile into that
 * descriptor.
 */
struct pfile *
pfilesrch (
    char *pfilepath		/* filename or ltask pathname	*/
)
{
	struct	pfile *pfp;

	if (cldebug)
	    eprintf ("pfilesrch %s\n", pfilepath);

	if (is_pfilename (pfilepath)) {
	    if ((pfp = pfileread (NULL, pfilepath, 0)) == NULL)
		cl_error (E_UERR, e_badpfile, pfilepath);
	    strcpy (pfp->pf_pfilename, pfilepath);
	    return (pfp);

	} else {
	    char    *x1, *pk, *t, *x2;
	    struct  ltask *ltp;

	    breakout (pfilepath, &x1, &pk, &t, &x2);
	    ltp = ltasksrch (pk, t);
	    if (!(ltp->lt_flags & LT_PFILE))
		cl_error (E_UERR, e_nopfile, ltp->lt_lname);
	    if ((pfp = pfilefind (ltp)) != NULL)
		return (pfp);			/* already in core.	*/

	    return (pfileload (ltp));
	}
}


/* PFILELOAD -- Load the pfile for the ltask pointed to by ltp.  The input
 * pfile may be the source package pfile (read only), the users UPARM copy
 * of the package pfile, or a named user pfile in the case of a pset-task
 * reference.  Save the filename where the pfile is to be updated in the
 * pfile descriptor, for later use by pfileupdate().  Pfiles are always
 * updated in UPARM, except in the case of named pfiles, which are updated
 * in place.
 */
struct pfile *
pfileload (
    register struct ltask *ltp		/* ltask descriptor */
)
{
	static	long sys_ftime = 0;
	register struct	task *tp;
	register struct	param *pp;
	char	usr_pfile[SZ_FNAME+1];
	char	pkg_pfile[SZ_FNAME+1];
	char	pkgdir[SZ_FNAME+1];
	long	usr_ftime, pkg_ftime;
	char	*ltname, *pkname;
	struct	pfile *pfp;
	char	*sval;

	if (cldebug)
	    eprintf ("pfileload, task %s\n", ltp->lt_lname);

	/* If the ltask operand is a PSET task, the parameter file to be
	 * read is controlled by the value of a pset parameter of the same
	 * name as the ltask, in the main parameter set of the most recently
	 * executed task which includes that pset parameter.  If no running
	 * task references the PSET task then we use the pfile of the PSET
	 * task itself, i.e., we have a conventional task.param parameter
	 * reference.
	 *
	 * If we make it through this block of code without reading a named
	 * pfile and exiting, either nothing has happened (the pset was not
	 * redirected), or the pset was redirected to a different ltask and
	 * we are still faced with the equivalent problem of mapping an ltp
	 * into a pfp, but this time without the compilication of PSET
	 * indirection.
	 */
	if (ltp->lt_flags & LT_PSET) {
            /* Don't use newtask if it is pointing beyond end of stack. */
            tp = (newtask < (struct task *)&stack[topcs]) ? currentask:newtask;

            for (  ;  tp != firstask;  tp = next_task(tp)) {
		pfp = tp->t_pfp;
		if (!pfp || !(pfp->pf_flags & PF_PSETREF))
		    continue;

		/* Search pfile of currently executing task.
		 */
		for (pp = pfp->pf_pp;  pp != NULL;  pp = pp->p_np)
		    if (pp->p_type & PT_PSET)
			if (!strcmp (pp->p_name, ltp->lt_lname)) {
			    /* Found pset parameter with same name as ltask.
			     */
			    if (opundef (&pp->p_valo))
				sval = "";
			    else
				sval = pp->p_val.v_s;

			    if (*sval == EOS) {
				; /* Null string - no indirection */
			    } else if (is_pfilename (sval)) {
				/* Named pfile */
				if ((pfp = pfileread (ltp, sval, 0)) != NULL)
				    return (pfp);
				else
				    cl_error (E_UERR, e_badpfile, sval);
			    } else {
				/* Must be a reference to another task */
				char    *x1, *pk, *t, *x2;

				breakout (sval, &x1, &pk, &t, &x2);
				ltp = ltasksrch (pk, t);
				if (!(ltp->lt_flags & LT_PFILE))
				    cl_error (E_UERR, e_nopfile, ltp->lt_lname);
			    }

			    goto epset_;
			}
	    }
	}
epset_:
	ltname = ltp->lt_lname;
	pkname = ltp->lt_pkp->pk_name;

	/* Determine the UPARM filename of the pfile. */
	mkpfilename (usr_pfile, uparmdir, pkname, ltname, ".par");

	/* As an optimization, all the checking for filetimes, file sizes,
	 * and out of date pfiles is only performed once when a file is
	 * first accessed.  Once a valid up to date UPARM version of a pfile
	 * is obtained a bit is set in the ltask descriptor and thereafter
	 * we need only read the UPARM version of the pfile and exit.  If a
	 * problem occurs reading the pfile, or if the pfile is unlearned,
	 * the bit is cleared and all the checking and initialization is
	 * repeated.
	 */
	if (ltp->lt_flags & LT_UPFOK)
	    if ((pfp = pfileread (ltp, usr_pfile, 1)) != NULL)
		return (pfp);

	/* Get modification (creation) time of usr pfile and filename and
	 * modification time of pkg pfile.  Look for a .par version of the
	 * pkg pfile, and if not found, a .cl version (procedure script).
	 */
	usr_ftime = filetime (usr_pfile, "c");
	c_fnldir (ltp->lt_pname, pkgdir, SZ_FNAME);

	mkpfilename (pkg_pfile, pkgdir, pkname, ltname, ".par");
	if ((pkg_ftime = filetime (pkg_pfile, "m")) <= 0) {
	    mkpfilename (pkg_pfile, pkgdir, pkname, ltname, ".cl");
	    if ((pkg_ftime = filetime (pkg_pfile, "m")) <= 0)
		cl_error (E_UERR, e_nopfile, ltname);
	}

	/* Get the date when the iraf system was last installed or updated.
	 * This is indicated by the modify time of the special file hlib$utime,
	 * which is touched during the system installation process.  The file
	 * may actually be newer than the date of system update/install but
	 * that is harmless.
	 */
	if (sys_ftime <= 0)
	    sys_ftime = filetime ("hlib$utime", "m");

	/* If the system was installed more recently than the package pfile
	 * was modified, use the system modify time instead.
	 */
	if (sys_ftime > 0)
	    if (sys_ftime > pkg_ftime)
		pkg_ftime = sys_ftime;

	if (usr_ftime > 0) {
	    /* We have a user (UPARM) version of the pfile.  If it is newer
	     * than the pkg pfile, use it, else read the pkg pfile and merge
	     * the param values from the user pfile into the new pkg pfile.
	     */
	    if (usr_ftime>pkg_ftime && (pfp=pfileread(ltp,usr_pfile,1)) != NULL)
		ltp->lt_flags |= LT_UPFOK;
	    else {
		if ((pfp = pfileread (ltp, pkg_pfile, 0)) == NULL)
		    cl_error (E_UERR, e_badpfile, pkg_pfile);
		pfilemerge (pfp, usr_pfile);
		strcpy (pfp->pf_pfilename, usr_pfile);
	    }
	} else {
	    /* No user pfile; read pkg pfile.
	     */
	    if ((pfp = pfileread (ltp, pkg_pfile, 0)) == NULL) {
		FILE   *fp;
		if (!is_pfilename (pkg_pfile))
		    if ((fp = fopen (pkg_pfile, "r")) != NULL) {
			if (!procscript (fp))
			    cl_error (E_UERR, e_nopfile, ltname);
			fclose (fp);
		    }
		cl_error (E_UERR, e_badpfile, pkg_pfile);
	    } else
		strcpy (pfp->pf_pfilename, usr_pfile);
	}

	return (pfp);
}


/* PFILEMERGE -- Merge the parameter values from the named (old user) pfile
 * into a loaded parameter set.
 */
int
pfilemerge (
    struct pfile *npf,			/* loaded parameter set	*/
    char *opfile			/* old parameter file	*/
)
{
	register struct param *o_pp, *n_pp, *l_pp;
	int	bastype;
	XINT	save_topd;
	struct	pfile *opf;
	struct	ltask *ltp;

	if (cldebug)
	    eprintf ("pfilemerge, task %s, pfile %s\n",
		(ltp = npf->pf_ltp) ? ltp->lt_lname : "", opfile);

	/* Open old pfile. */
	save_topd = topd;
	if ((opf = pfileread (npf->pf_ltp, opfile, 0)) == NULL)
	    return (ERR);

	/* For each parameter in the old pfile, locate the corresponding
	 * parameter in the new pfile and copy the value.  No other fields
	 * of the parameter structure are copied.
	 */
	for (n_pp = NULL, o_pp = opf->pf_pp;  o_pp;  o_pp = o_pp->p_np) {
	    /* Circular search, starting at position of last parameter.
	     */
	    n_pp = ((l_pp = n_pp) != NULL) ? n_pp->p_np : npf->pf_pp;
	    while (n_pp != l_pp) {
		if (n_pp == NULL)
		    n_pp = npf->pf_pp;
		else if (strcmp (n_pp->p_name, o_pp->p_name) == 0)
		    break;
		else
		    n_pp = n_pp->p_np;
	    }

	    /* If parameter not in new param set or the datatypes do not
	     * match, skip this parameter.
	     */
	    if (n_pp == l_pp)
		continue;
	    if (n_pp->p_type != o_pp->p_type)
		continue;

	    bastype = (n_pp->p_type & OT_BASIC);

	    /* Copy value */
	    n_pp->p_valo.o_type = o_pp->p_valo.o_type;

	    /* Handle arrays. */
	    /* The array descriptors should remain the same, only
	     * the stored values could change.
	     */
	    if (n_pp->p_type & PT_ARRAY) {
		int	dim, d, size_arr;
		short	*lenoff;

		/* Get size of array. */
		dim = n_pp->p_val.v_a->a_dim;
		lenoff = &(n_pp->p_val.v_a->a_len);
		size_arr = 1;
		if (bastype == OT_REAL)
		    size_arr = 2;
		for (d=0;  d < dim;  d++)
		    size_arr *= *(lenoff + 2*d);

		if (bastype != OT_STRING) {
		    int     *p, *q;
		    p = o_pp->p_aval.a_i;
		    q = n_pp->p_aval.a_i;
		    for (d=0;  d < size_arr;  d++)
			*q++ = *p++;
		} else {
		    char     **p, **q;
		    p = o_pp->p_aval.a_s;
		    q = n_pp->p_aval.a_s;
		    for (d=0;  d < size_arr;  d++)
			strcpy (*q++, *p++);
		}

	    } else if (!(o_pp->p_valo.o_type & (OT_INDEF|OT_UNDEF))) {
		if (((o_pp->p_valo.o_type & OT_BASIC) == OT_STRING) &&
		   (n_pp->p_val.v_s != NULL)) {
		   strncpy (n_pp->p_val.v_s, o_pp->p_val.v_s, n_pp->p_lenval-1);
		} else
		    n_pp->p_valo.o_val = o_pp->p_valo.o_val;
	    }
	}

	npf->pf_flags |= PF_UPDATE;

	/* Unlink scratch pfile descriptor and return dictionary space.
	 */
	pfileunlink (opf);
	topd = save_topd;

	return (OK);
}


/* PFILEUPDATE -- Update a parameter set in the pfile from which it was
 * originally read.  Nothing is done unless the parameter set has been
 * modified and needs updating, or if we have a fake (in-core) parameter set.
 */
void
pfileupdate (
    struct pfile *pfp		/* parameter file descriptor */
)
{
	if ((pfp->pf_flags & (PF_FAKE|PF_UPDATE)) != PF_UPDATE)
	    return;

	if (cldebug)
	    eprintf ("pfileupdate %s\n", pfp->pf_pfilename);
	
	/* Do not update the CL parameter file; we always read the system
	 * cl.par file upon startup.
	 */
	if (pfp->pf_ltp == firstask->t_ltp)
	    return;

	pfilewrite (pfp, pfp->pf_pfilename);
	pfp->pf_flags &= ~PF_UPDATE;

	if (pfp->pf_ltp)
	    pfp->pf_ltp->lt_flags |= LT_UPFOK;
}


/* PFILEREAD -- Allocate a pfile descriptor and read the named pfile into it.
 * The input file may be either a parameter file or a CL procedure script.
 */
struct pfile *
pfileread (
    struct ltask *ltp,		/* associated ltask		*/
    char *pfilename,		/* parameter file filename	*/
    int checkmode		/* check for "mode" parameter	*/
)
{
	register char	*ip;
	char	buf[SZ_LINE+1];
	struct	pfile *pfp;
	struct	param *pp;
	int	nerrs, gotmode, status, oldlines;
	FILE	*fp, *yysave;
	XINT	save_topd;

	if (cldebug)
	    eprintf ("pfileread, task %s, pfile %s\n",
		ltp ? ltp->lt_lname : "", pfilename);

	if ((fp = fopen (pfilename, "r")) == NULL)
	    return (NULL);

	save_topd = topd;
	pfp = newpfile (ltp);
	strcpy (pfp->pf_pfilename, pfilename);

	nerrs = 0;
	gotmode = 0;

	if (is_pfilename (pfilename)) {
	    /* Pfile has ".par" filename extension, format is a simple
	     * list of parameter structs, one parameter per line.
	     */
	    while (fgets (buf, PF_MAXLIN, fp) != NULL) {
		/* Skip comment lines and blank lines.
		 */
		for (ip=buf;  (*ip == ' ' || *ip == '\t');  ip++)
		    ;
		if (*ip == PF_COMMENT || *ip == '\n')
		    continue;

		if ((pp = addparam (pfp, ip, fp)) == NULL)
		    nerrs++;
		else if (!strcmp (pp->p_name, "mode")) {
		    if (gotmode) {
			eprintf ("more than one `mode' param\n");
			nerrs++;
		    } else
			gotmode++;
		}
	    }

	    /* When a pfile is udpated in uparm a "mode" parameter is
	     * always written out as the last parameter to mark the end of
	     * the parameter list.  If checkmode is enabled and the mode
	     * parameter is not seen, this indicates the the pfile has
	     * been truncated and should not be used.
	     */
	    if (nerrs > 0 || ferror(fp) || (checkmode && !gotmode))
		goto error_;

	} else if (procscript (fp)) {
	    extern int yyparse (void);

	    /* Parse the declarations section of a procedure script.
	     * The procscript() call leaves us positioned to the procedure
	     * statement.
	     */
	    parse_state = PARSE_PARAMS;
	    parse_pfile = pfp;
	    yysave = yyin;
	    yyin = fp;

	    /* Fool the parser into believing we are at the
	     * beginning of a script for any error messages
	     * which come out.
	     */
	    oldlines = newtask->t_scriptln;
	    newtask->t_scriptln = 0;

	    status = yyparse();

	    /* Reset the parse state in case we are in a free script.  */
	    parse_state = PARSE_FREE;
	    newtask->t_scriptln = oldlines;
	    yyin = yysave;

	    if (status) 
		goto error_;

	    if (paramfind (pfp, "mode", 0, YES) == NULL)
		gotmode = NO;
	    else
		gotmode = YES;
	} else
	    goto error_;

	/* Count the number of parameters.  If there are no parameters we
	 * probably have a zero length file, which is an error.
	 */
	for (status=0, pp=pfp->pf_pp;  pp;  pp=pp->p_np)
	    status++;
	if (status == 0)
	    goto error_;

	/* Add `mode' param.  Get the value from the current package
	 * or from the CL if there is no package pfile.
	 */
	if (gotmode == 0) {
	    struct param *qq;

	    /* Allocate the param with "ql" as the ultimate default.
	     */
	    pp = addparam (pfp, "mode,s,h,ql\n", fp);

	    if (curpack != NULL) {
		if (curpack->pk_pfp != NULL) {
		    qq = paramfind (curpack->pk_pfp, "mode", 0, YES);
		    if (qq != NULL && qq != (struct param *)ERR) {
			strcpy (pp->p_val.v_s, qq->p_val.v_s);
			gotmode++;
		    }
		}
	    }
	}

	if (gotmode == 0)	/* CL--This should rarely be needed */
	    if (firstask->t_modep != NULL)
		strcpy (pp->p_val.v_s, firstask->t_modep->p_val.v_s);

	fclose (fp);
	return (pfp);

error_:
	fclose (fp);
	pfileunlink (pfp);
	topd = save_topd;
	return (NULL);
}


/* PFILEWRITE -- Write out the parameters for given pfile into a file.
 * Any existing file is silently clobbered.  The filename extension is
 * always ".par".
 */
int
pfilewrite (
    struct pfile *pfp,		/* pfile descriptor	*/
    char *pfilename		/* file to be written	*/
)
{
	register char	*ip, *op, *dot;
	char	pfname[SZ_PATHNAME+1];
	struct	param *pp;
	int	nparams;
	FILE	*fp;

	if (cldebug)
	    eprintf ("pfilewrite %s\n", pfilename);

	/* Copy the filename, changing the extension to .par if necessary.
	 */
	for (dot=NULL, ip=pfilename, op=pfname;  (*op = *ip++);  op++)
	    if (*op == '.')
		dot = op;
	strcpy (dot ? dot : op, ".par");

	if (cldebug)
	    eprintf ("writing pfile `%s'\n", pfname);

	/* Delete any existing pfile before updating.
	 */
	c_delete (pfname);

	/* Disable interrupts while updating the pfile to eliminate the
	 * possibility of file truncation.  The "mode" parameter is always
	 * written last to mark the end of a valid pfile.
	 */
	intr_disable();
	nparams = 0;

	if ((fp = fopen (pfname, "w")) == NULL)
	    eprintf ("Unable to open parameter file `%s'.\n", pfname);
	else {
	    struct param *modepp = NULL;
	    for (pp = pfp->pf_pp;  pp != NULL;  pp = pp->p_np) {
		if (!(pp->p_mode & M_LOCAL)) {
		    if (!strcmp (pp->p_name, "mode")) {
			modepp = pp;
		    } else if (printparam (pp, fp) == ERR) {
			fclose (fp);
			cl_error (E_IERR|E_P,
			    "Error writing local pfile `%s'", pfname);
		    } else
			nparams++;
		}
	    }

	    if (modepp) {
		printparam (modepp, fp);
		nparams++;
	    }
	    fclose (fp);
	}

	intr_enable();
	return (nparams);
}


/* PFILEINIT -- Initialize or "unlearn" a pfile.  Look for user version of
 * pfile in uparm; if found, delete it.  If pfile is loaded, unlink from
 * pfile list.  Fix up flag bits in ltask descriptor.  We are called from
 * "unlearn" to restore the package default parameters for an ltask or package.
 */
int
pfileinit (struct ltask *ltp)
{
	struct	task *tp;
	struct	pfile *pfp;
	char	pfilename[SZ_FNAME];	/* user pfile			*/
	char	pkgdir[SZ_FNAME+1];
	char	*ltname;		/* name of the new pfile	*/
	char	*pkname;		/* name of its package		*/
	int	running;

	if (cldebug)
	    eprintf ("unlearn pfile for task %s\n", ltp->lt_lname);

	ltname = ltp->lt_lname;
	pkname = ltp->lt_pkp->pk_name;

	/* Determine if the pfile belongs to a loaded package or to a task
	 * which is currently executing.
	 */
	running = 0;
	if (ltp->lt_flags & LT_DEFPCK)
	    running++;
	else {
	    for (tp=currentask;  tp <= firstask;  tp = next_task(tp))
		if (tp->t_ltp == ltp) {
		    running++;
		    break;
		}
	}

	/* Delete any "learned" copy of the pfile in uparm. */
	mkpfilename (pfilename, uparmdir, pkname, ltname, ".par");
	c_delete (pfilename);

	/* Clear the flag that says we have a valid user param file. */
	ltp->lt_flags &= ~(LT_UPFOK);

	/* See if the pfile is in core; if so, unlink all copies.  If the
	 * pfile belongs to a currently executing task we can't unlink it,
	 * so reset the parameter values to the system defaults instead.
	 */
	while ((pfp = pfilefind (ltp)) != NULL)
	    if (running) {
		c_fnldir (ltp->lt_pname, pkgdir, SZ_FNAME);
		mkpfilename (pfilename, pkgdir, pkname, ltname, ".par");
		pfilemerge (pfp, pfilename);
		pfp->pf_flags &= ~PF_UPDATE;
		if (ltp->lt_flags & LT_DEFPCK)
		    break;
	    } else
		pfileunlink (pfp);

	return (OK);
}


/* IS_PFILENAME -- Test whether a string is a pfile filename, i.e., whether
 * or not the string has a ".par" extension.
 */
int
is_pfilename (char *opstr)
{
	register char	*ip;
	char	*dot;

	/* If the named object has a ".par" extension we assume it is a
	 * pfile filename, otherwise we assume it is an ltask pathname.
	 */
	for (ip=opstr, dot=NULL;  *ip;  ip++)
	    if (*ip == '.')
		dot = ip;

	return (dot && strcmp (dot, ".par") == 0);
}


/* MKPFILENAME -- Generate a parameter file name, given a directory prefix
 * the names of the package and ltask, and the filename extension.  The form
 * of the filename depends upon whether the pfile is to be stored in UPARM.
 * UPARM pfile names have the form "uparm$ // pakltask.par", where `pak' is
 * the package prefix, consisting of the first LEN_PKPREFIX-1 characters of
 * the package name plus the final character, and `ltask' is the ltask name
 * squeezed to LEN_PFILENAME characters.  If not writing to UPARM, we just
 * use the full filename.
 */
void
mkpfilename (
    char *buf,			/* receives output filename	*/
    char *dir,			/* dir name or prefix		*/
    char *pkname,		/* package name			*/
    char *ltname,		/* ltask name			*/
    char *extn			/* filename extension		*/
)
{
	char	temp[SZ_FNAME+1];

	strcpy (buf, dir);		/* start with directory name	*/

	if (strcmp (dir, uparmdir) == 0) {
	    strcat (buf, "$");
	    mapname (pkname, temp, LEN_PKPREFIX);
	    strcat (buf, temp);
	    mapname (ltname, temp, LEN_PFILENAME);
	    strcat (buf, temp);
	} else
	    strcat (buf, ltname);

	strcat (buf, extn);		/* add extension for pfile	*/
}


/* MAPNAME -- Apply the N+1 mapping convention (first N-1 plus last chars)
 * to generate a name no longer than N characters.  Returns the number of
 * characters generated.
 */
static void
mapname (char *in, char *out, int maxlen)
{
	register int ip, op;

	ip = 0;
	op = 0;
	while (op < maxlen-1 && (out[op++] = in[ip++]) != '\0')
	    ;
	if (out[op-1] != '\0') {	/* append last char	*/
	    if (in[ip] != '\0') {
		while (in[ip] != '\0')
		    ip++;
		out[op++] = in[ip-1];
	    }
	    out[op++] = '\0';
	}
}


/* FILETIME -- Get the time of creation or of last modify of a file.  If the
 * file does not exist or cannot be accessed zero is returned.
 */
long
filetime (
    char *fname,			/* file name		*/
    char *timecode		/* "c" or "m"		*/
)
{
	struct	_finfo fi;

	if (c_finfo (fname, &fi) == ERR)
	    return (0L);
	else {
	    switch (*timecode) {
	    case 'c':
		return (fi.fi_ctime);
	    case 'm':
		return (fi.fi_mtime);
	    default:
		return (0L);
	    }
	}
}


/* PFILECOPY -- Make a new copy of paramfile at pfp for a new task.  Command
 * line changes, queries and assignments are done to this copy.  Link in the
 * usual fashion off parhead.  Copy all the parameters as well, taking care to
 * make new copies of strings and setting pointers in new params to their own
 * copies.  Return pointer to new entry; no error return.
 * Reset P_CLSET, P_SET and P_QUERY flags so pfcopyback() can tell whether
 *   these events happened for this particular run of the task.
 */
struct pfile *
pfilecopy (register struct pfile *pfp)
{
	register struct param *pp, *newpp;
	struct	pfile *newpfp;
	int	bastype;

	if (cldebug) {
	    if (pfp->pf_ltp)
		eprintf ("copying pfile for `%s'\n", pfp->pf_ltp->lt_lname);
	    else
		eprintf ("copying pfile `%s'\n", pfp->pf_pfilename);
	}

	newpfp = newpfile (pfp->pf_ltp);
	for (pp = pfp->pf_pp;  pp;  pp = pp->p_np) {

	    /* Allocate new parameter */
	    newpp = newparam (newpfp);
	    bastype = pp->p_type & OT_BASIC;

	    /* COPY VALUE */

	    newpp->p_valo = pp->p_valo;

	    /* Handle arrays. */
	    if (pp->p_type & PT_ARRAY) {
		struct arr_desc *parrd, *qarrd;
		int	size_arr;
		short	*lenoff, *qlenoff;
		int	dim, d, *pval, *qval;

		parrd = pp->p_val.v_a;
		dim = parrd->a_dim;
		size_arr = 1;

		lenoff = &(parrd->a_len) ;
		for (d=0;  d < dim;  d++)
		    size_arr *= *(lenoff + 2*d);
		if (bastype == OT_REAL)
		    size_arr *= 2;

		/* Ready to allocate new descriptor and data block */
		qarrd = (struct arr_desc *)memneed (2 + dim);
		newpp->p_val.v_a = qarrd;

		qarrd->a_ptr.a_i = (int *) memneed(size_arr);

		qarrd->a_dim = dim;
		qlenoff = &(qarrd->a_len);
		for (d=0; d<2*dim; d++)
		    *qlenoff++ = *lenoff++;

		if (bastype != OT_STRING) {
		    /* If not string then copy values across. */

		    pval = parrd->a_ptr.a_i;
		    qval = qarrd->a_ptr.a_i;
		    for (d=0;  d < size_arr;  d++)
			*qval++ = *pval++;

		} else {
		    /* Copy strings one by one. */

		    int     len;
		    char    **p, **q;

		    if (pp->p_maxo.o_type == OT_INT)
			len = pp->p_maxo.o_val.v_i;
		    else
			len = SZ_FNAME;

		    p = parrd->a_ptr.a_s;
		    q = qarrd->a_ptr.a_s;
		    for (d=0;  d < size_arr; d++, q++, p++) {
			*q = memneed (btoi(len));
			strncpy (*q, *p, len-1);
			(*q)[len-1] = '\0';
		    }
		}

	    } else if ((pp->p_valo.o_type & OT_BASIC) == OT_STRING) {
		/* Regular (i.e. scalar) strings.
		 */
		newpp->p_val.v_s = memneed (btoi(pp->p_lenval));
		strncpy (newpp->p_val.v_s, pp->p_val.v_s, pp->p_lenval-1);
	    }

	    /* COPY MIN */
	    newpp->p_mino = pp->p_mino;
	    if ((pp->p_mino.o_type & OT_BASIC) == OT_STRING &&
		!(pp->p_flags & P_UMIN)) {
		newpp->p_min.v_s = memneed (btoi (PF_SZMINSTR));
		strncpy (newpp->p_min.v_s, pp->p_min.v_s, PF_SZMINSTR-1);
	    }

	    /* COPY MAX */
	    newpp->p_maxo = pp->p_maxo;
	    if ((pp->p_maxo.o_type & OT_BASIC) == OT_STRING &&
		!(pp->p_flags & P_UMAX)) {
		newpp->p_max.v_s = memneed (btoi (PF_SZMAXSTR));
		strncpy (newpp->p_max.v_s, pp->p_max.v_s, PF_SZMAXSTR-1);
	    }

	    /* COPY PROMPT */
	    newpp->p_prompt = comdstr (pp->p_prompt);

	    /* Copy all the easy entries last; we made it! */
	    newpp->p_name = pp->p_name;
	    newpp->p_type = pp->p_type;
	    newpp->p_mode = pp->p_mode;
	    newpp->p_flags = pp->p_flags & ~(P_CLSET|P_QUERY|P_SET);
	    newpp->p_listfp = pp->p_listfp;
	    newpp->p_listval = pp->p_listval;
	    newpp->p_lenval = pp->p_lenval;
	}

	newpfp->pf_oldpfp = pfp;
	strcpy (newpfp->pf_pfilename, pfp->pf_pfilename);
	newpfp->pf_flags = (pfp->pf_flags & PF_PSETREF);
	newpfp->pf_flags |= PF_COPY;

	return (newpfp);
}


/* PFCOPYBACK -- Copy the contents of each param that is to be changed
 *   permanently in the given pfile to the corresponding param in original
 *   pfile.  Once thus copied, they are considered permanently changed since
 *   restor() will write out to their pfile.  Call the target pfile pft.
 * Copy only those params for which P_SET is set or for which P_QUERY or
 *   P_CLSET is set provided learn mode is on and the param is not M_HIDDEN.
 *   Since P_SET was cleared by pfilecopy(), it can only be set in the copy
 *   if it was set since the task started.
 * Set PF_UPDATE in pft if, in fact, any copying took place.
 * Don't copy at all if the working file is not a copy; this is primarily
 *   to stop the final copy on eof from the first cl and as a nice safety chk.
 * N.B. we assume pff was made from pft with pfilecopy() and so the params are
 *   in the same order; we also assume none were added.
 *
 * N.B. After copying, unlink the copy pfile from the pfile list, to insure
 *   that hidden params modified on the command line are not preserved after
 *   termination of a task which called KEEP.  Restor() will not lop off the
 *   dead pfile if it is below the new topd set by keep.
 */
void
pfcopyback (struct pfile *pff)
{
	register struct param *pt, *pf;
	struct	pfile *pft;
	int	bastype;
	int	pfflags;
	int	copy;			/* set if a real copy occurred	*/
	int	learn;			/* set if learn is on 		*/

	if (cldebug)
	    eprintf ("pfcopyback %s\n", pff->pf_pfilename);

	if (!(pff->pf_flags & PF_COPY))
	    return;
	pft = pff->pf_oldpfp;

	learn = effmode ((struct param *) NULL) & M_LEARN;
	copy = 0;

	for (pt=pft->pf_pp, pf=pff->pf_pp;  pf&&pt;  pt=pt->p_np, pf=pf->p_np) {
	    pfflags = pf->p_flags;

	    /* Always copy back the list file pointer else the list file, if
	     * opened during task execution, will not be closed.
	     */
	    pt->p_listfp = pf->p_listfp;

	    /* Copy param back if it was set in an explicit assignment,
	     * or if it was set in a query or on the command line, and we are
	     * in learn mode, and the parameter is not hidden.
	     */
	    if (!((pfflags & P_SET) || ((pfflags&(P_QUERY|P_CLSET)) && learn &&
		!(pf->p_mode & M_HIDDEN))))
		continue;

	    bastype = pt->p_type & OT_BASIC;
	    copy++;

	    /* Don't bother copying name since it couldn't have changed.
	     * Other fields copy directly.
	     */
	    pt->p_type = pf->p_type;
	    pt->p_mode = pf->p_mode;

	    /* Use all new flags bits but discard CLSET and QUERY and merge
	     * SET with its original state so either it or the copy can
	     * cause a permanent change to the parameter.
	     */
	    pt->p_flags &= P_SET;
	    pt->p_flags |= pfflags & ~(P_CLSET|P_QUERY);

	    /* Copy value */
	    pt->p_valo.o_type = pf->p_valo.o_type;

	    /* Handle arrays. */
	    /* The array descriptors should remain the same, only
	     * the stored values could change.
	     */
	    if (pt->p_type&PT_ARRAY) {
		int	dim, d, size_arr;
		short	*lenoff;

		/* Get size of array. */
		dim = pt->p_val.v_a->a_dim;
		lenoff = &(pt->p_val.v_a->a_len);
		size_arr = 1;
		if (bastype  ==  OT_REAL)
		    size_arr = 2;
		for (d=0; d<dim; d++)
		    size_arr *= *(lenoff + 2*d);

		if (bastype != OT_STRING) {
		    int	*p, *q;
		    p = pf->p_aval.a_i;
		    q = pt->p_aval.a_i;
		    for (d=0; d<size_arr; d++)
			*q++ = *p++;
		} else {
		    char **p, **q;
		    p = pf->p_aval.a_s;
		    q = pt->p_aval.a_s;
		    for (d=0; d<size_arr; d++)
			strcpy(*q++, *p++) ;
		}

	    } else if (!(pf->p_valo.o_type & (OT_INDEF|OT_UNDEF))) {
		if (((pf->p_valo.o_type & OT_BASIC) == OT_STRING) &&
		   (pt->p_val.v_s != NULL)) {
		    strncpy (pt->p_val.v_s, pf->p_val.v_s, pf->p_lenval-1);
		} else
		    pt->p_valo.o_val = pf->p_valo.o_val;
	    }
		
	    /* Copy min */
	    if (!(pf->p_flags & P_UMIN)) {
		pt->p_mino.o_type = pf->p_mino.o_type;
		if ((pf->p_mino.o_type & OT_BASIC) == OT_STRING &&
		    pt->p_min.v_s != NULL)
		    strncpy (pt->p_min.v_s, pf->p_min.v_s, PF_SZMINSTR-1);
		else
		    pt->p_mino.o_val = pf->p_mino.o_val;
	    }

	    /* Copy max */
	    if (!(pf->p_flags & P_UMAX)) {
		pt->p_maxo.o_type = pf->p_maxo.o_type;
		if ((pf->p_maxo.o_type & OT_BASIC) == OT_STRING &&
		    pt->p_max.v_s != NULL)
		    strncpy (pt->p_max.v_s, pf->p_max.v_s, PF_SZMAXSTR-1);
		else
		    pt->p_maxo.o_val = pf->p_maxo.o_val;
	    }
	}

	if (copy) {
	    if (cldebug) {
		if (pff->pf_ltp) {
		    eprintf ("copied back pfile for `%s'\n",
			pff->pf_ltp->lt_lname);
		} else
		    eprintf ("copied back pfile `%s'\n", pff->pf_pfilename);
	    }
	    pft->pf_flags |= PF_UPDATE;
	}

	/* Unlink pfile to ensure that it never gets reused.
	 */
	pfileunlink (pff);
}


/* ADDPARAM -- Allocate a new param off *pfp and fill with fields derived
 *   from line buf.
 * Buf should have trailing '\n' '\0' as per fgets.
 * Set UNDEF for those fields that are left blank, INDEF for those fields
 *   so indicating.
 * FP is used to read a structure, cursor, long quoted string, or arrays.
 * Return pointer to new param if ok, else NULL.  In order to handle multiple
 *   errors while reading a param file, we print informative info directly
 *   here with eprintf.  This avoids calling error() and gives us a chance
 *   to handle a file with multiple errors and find many of them in one pass.
 * Besides pfileread(), we are also called from various other places, such as
 *   execnewtask(), to add such parameters as $nargs and mode.
 */
struct param *
addparam (struct pfile *pfp, char *buf, FILE *fp)
{
	static char *minfields =
		"must specify at least name,type,mode for `%s'\n";
	static char *nominmax =
		"ranges not allowed for struct/cursor/string/bool param `%s'\n";
	static char *umquotes =
		"unmatched quotes in %s field for `%s'\n";

	register struct param *pp; /* new param being filled up		*/
	register char *s;	/* pointer to compiled string.		*/
	char	*pnamehold;	/* param's name as soon as we know it	*/
	int	len;		/* used to measure string lengths	*/
	int	bastype;	/* OT_BASIC part of type as soon as know*/
	int	arrflag;	/* Is param an array?			*/
	struct arr_desc *parrd;	/* Pointer to array descriptor.		*/
	int	size_arr=0;	/* Size of array.			*/
	char	**tbuf;

	pp = newparam (pfp);

	/* P_NAME */

	pnamehold = "<no name>";
	tbuf = &buf;
	if ((s = nextfield (tbuf, fp)) == NULL) {
	    eprintf (minfields, pnamehold);
	    return (NULL);
	} else if (s == (char *)ERR) {
	    eprintf (umquotes, "name", pnamehold);
	    return (NULL);
	} else
	    pnamehold = pp->p_name = s;


	/* P_TYPE */

	if ((s = nextfield (tbuf, fp)) == NULL) {
	    eprintf (minfields, pnamehold);
	    return (NULL);
	} else if (s == (char *)ERR) {
	    eprintf (umquotes, "type", pnamehold);
	    return (NULL);
	} else {
	    if (strcmp (s, "pset") == 0)
		pfp->pf_flags |= PF_PSETREF;
	    if ((pp->p_type = scantype (s)) == ERR) {
		eprintf (" in `%s'\n", pnamehold);
		return (NULL);
	    }
	}
	bastype = pp->p_type & OT_BASIC;
	arrflag = pp->p_type & PT_ARRAY;

	/* P_MODE */

	if ((s = nextfield (tbuf, fp)) == NULL) {
	    eprintf (minfields, pnamehold);
	    return (NULL);
	} else if (s == (char *)ERR) {
	    eprintf (umquotes, "mode", pnamehold);
	    return (NULL);
	} else if ((pp->p_mode = scanmode (s)) == ERR) {
	    eprintf (" in `%s'\n", pnamehold);
	    return (NULL);
	}


	/* P_VAL */

	pp->p_valo.o_type = bastype;

	if ((s = nextfield (tbuf, fp)) == (char *)ERR) {
	    eprintf (umquotes, "value", pnamehold);
	    return (NULL);
	}

	if (pp->p_type & (PT_LIST|PT_FILNAM|PT_PSET)) {
	    pp->p_val.v_s = memneed (btoi(SZ_FNAME));
	    pp->p_val.v_s[SZ_FNAME-1] = '\0';
	    pp->p_lenval = SZ_FNAME;

	    if (pvaldefined (pp, s)) {
	        char  *p;

		/* Change a whitespace-only filename into a null string; this
		 * makes it easier for users to check null filenames in 
		 * scripts.  It makes sense anyway since these are invalid
		 * filenames.
		 */
		p = s;
	        while (*p == ' ' || *p == '\t')
		    p++;
		if (*p == '\0' || *p == '\n')
		    pp->p_val.v_s[0] = '\0';
		else
		    strncpy (pp->p_val.v_s, s, SZ_FNAME-1);
	    } else
		*pp->p_val.v_s = '\0';

	    if (pp->p_type & PT_LIST)
		pp->p_listval = memneed (btoi(SZ_LINE));
	    pp->p_valo.o_type = OT_STRING;

	} else if (pp->p_type & (PT_STRUCT|PT_GCUR|PT_IMCUR|PT_UKEY)) {

	    /* Non-list structs read next line and store at p_val.v_s
	     *   unless the length field begins with a PF_NOSTRUCT.
	     * The storage allocated in the dictionary, and pointed to by
	     *   p_val.v_s, is the max of the number in the value field and
	     *   the length of the structure init string, on the next line.
	     *   it is an error for the init string to be longer than the
	     *   length given, if any, or for either to be greater than
	     *   SZ_LINE-2.
	     * SZ_LINE-2 is the default length if neither a len not init is
	     *   given.
	     * OT_INDEF/UNDEF refer to p_val; p_lenval always set to length
	     *   (max length) of value string if value is a string.
	     * Nextfield() compiles the length spec into the dictionary;
	     *   it's short and not worth trying to dig out...
	     */

	    int readinit = 0;  		 	/* 1 if init is in next ln */

	    if (s == NULL) {
		readinit++;
		len = SZ_LINE-1;			/* supply default */
	    } else {
		if (*s == PF_NOSTRUCT)
		    s++;
		else
		    readinit++;

		len = atoi (s);
		if (len <= 0)
		    len = SZ_LINE-1;			/* supply default */
		else if (len > SZ_LINE-1) {
		    eprintf ("`%s' struct lengths limited to %d\n",
			pnamehold, SZ_LINE-1);
		    return (NULL);
		}
	    }
	    len++;				/* allow for \0 */

	    if (readinit) {
		/* Initialize with next line.  Lots of pathology here...
		 */
		char initbuf[SZ_LINE];
		int initlen;

		if (fgets (initbuf, SZ_LINE, fp) == NULL) {
		    eprintf ("`%s' has no initialized\n",
			pnamehold);
		    return (NULL);
		}

		initlen = strlen (initbuf); /* includes \n, if present */

		if (initbuf[initlen-1] == '\n')
		    initbuf[initlen-1] = '\0';
		else {
		    int c;
		    eprintf ("`%s' initialization too long\n",
			pnamehold);
		    while ((c = fgetc(fp)) != '\n' && c != EOF)
			;
		    return (NULL);
		}

		if (initlen > len) {
		    eprintf ("initialization for `%s' > %d\n",
			pnamehold, len-1);
		    return (NULL);
		}

		pp->p_val.v_s = memneed (btoi (len));
		if (pvaldefined (pp, initbuf))
		    strcpy (pp->p_val.v_s, initbuf);

	    } else {
		/* Allocate space but don't init from next line.
		 */
		pp->p_val.v_s = memneed (btoi (len));
	    }

	    pp->p_val.v_s[len-1] = '\0';	 /* the permanent eos */
	    pp->p_lenval = len;
	    pp->p_valo.o_type = OT_STRING;

	} else if ((bastype == OT_STRING ||
	    (s != NULL && *s == PF_INDIRECT)) && !arrflag) {

	    /* Strings are stored like structs, but are inited from s.
	     * OT_INDEF/UNDEF refer to p_val.
	     */
	    if (pvaldefined (pp, s)) {
		/* String was something conventional.  If shorter than SZ_LINE
		 * call memneed() again to increase the dictionary space. This
		 * ASSUMES that nothing called memneed() since nextfield() did.
		 */
		pp->p_valo.o_type = OT_STRING;
		len = strlen (s) + 1;		/* allow for eos */
		if (len < SZ_LINE) {
		    memneed (btoi(SZ_LINE) - btoi(len));
		    len = SZ_LINE;
		}
	    } else {
		/* Either no string was given or it was INDEF/UNDEF.
		 */
		len = SZ_LINE;
		s = memneed (btoi (len));
	    }

	    pp->p_val.v_s = s;
	    pp->p_val.v_s[len-1] = '\0'; /* add the permanent eos */
	    pp->p_maxo.o_type = OT_INT;
	    pp->p_lenval = len;

	} else if (arrflag) {
	    /* For arrays get the array definition block */

	    int		dim, it;	/* Dimensionality of array.	*/
	    short	itemp;		/* Length and offsets of array.	*/
	    short	*lenoff;	/* Pointer to length or offset. */
	    int		d;

	    /* Dimensionality. */
	    if (s == NULL) {
		eprintf ("Dimensionality not specified for %s.\n", pnamehold);
		return (NULL);
	    }
	    if (ck_atoi (s, &dim) == ERR) {	/* Convert to integer. */
		eprintf ("Non-integer dimensionality for %s.\n", pnamehold);
	        return (NULL);
	    }
	    if (dim <= 0) {			/* Dimensionality > 0 ? */
		eprintf ("Dimensionality not positive for %d.\n", pnamehold);
		return (NULL);
	    }

	    /* Get space for array descriptor. */
	    parrd = (struct arr_desc *) memneed (2 + dim);
	    size_arr = 1;
	    if (bastype == OT_REAL)		/* Doubles take 2 INT's. */
		size_arr = 2;
	    
	    parrd->a_dim = dim;
	    lenoff = &(parrd->a_len);
	    

	    /* Lengths and offsets.
	     */
	    for (d=0;  d < 2*dim;  d++) {
		if ((s = nextfield (tbuf, fp)) == NULL) {
		    eprintf ("Dimensions not specified for %s.\n", pnamehold);
		    return (NULL);
		}

		if (ck_atoi (s, &it) == ERR) {	/* Convert to integer. */
		    eprintf ("Integer length/offset required for %s.\n",
			pnamehold);
		    return (NULL);
	        }

		itemp = it;
		if ((d%2 == 0)  &&  itemp<=0) {/* Length < 0 ? */
		    eprintf ("Illegal negative dimension for %s.\n", pnamehold);
		    return (NULL);
		}

		*lenoff++ = itemp;
		if (d%2 == 0)
		    size_arr = itemp * size_arr;

	    }
	    /* Get the space for the array. */
	    parrd->a_ptr.a_i = (int *) memneed(size_arr);

	    /* The "value" of the parameter is a pointer to the
	     * array descriptor.
	     */
	    pp->p_valo.o_val.v_a = parrd;
	    pp->p_valo.o_type = PT_ARRAY|bastype;

	} else {
	    /* Simple non-string type.
	     */
	    if (pvaldefined (pp, s))
		pp->p_valo = makeop (s, pp->p_type & OT_BASIC);
	}


	/* P_MIN */

	pp->p_mino.o_type = bastype;

	if ((s = nextfield (tbuf, fp)) == (char *)ERR) {
	    eprintf (umquotes, "minimum", pnamehold);
	    return (NULL);
	}

	if (s != NULL && *s != '\0') {
	    if (bastype == OT_BOOL ||
		pp->p_type & (PT_STRUCT|PT_GCUR|PT_IMCUR|PT_UKEY|PT_PSET)) {
		eprintf (nominmax, pnamehold);
		return (NULL);
	    } else if (!strcmp(s,indefstr) || !strcmp(s,indeflc)) {
		pp->p_flags |= P_IMIN; 
	    } else if (bastype == OT_STRING || *s == PF_INDIRECT) {
		/* Filename, enumerated string, or indirect reference.
		 */
		pp->p_mino.o_type = OT_STRING;
		pp->p_min.v_s = memneed (btoi(PF_SZMINSTR));
		pp->p_min.v_s[PF_SZMINSTR-1] = '\0';
		strncpy (pp->p_min.v_s, s, PF_SZMINSTR-1);
	    } else {
		/* Type is equivalent to a simple non-string wrt mins.
		 */
		pp->p_mino = makeop (s, pp->p_type & OT_BASIC);
	    }
	} else
	    pp->p_flags |= P_UMIN;


	/* P_MAX */

	pp->p_maxo.o_type = bastype;

	if ((s = nextfield (tbuf, fp)) == (char *)ERR) {
	    eprintf (umquotes, "maximum", pnamehold);
	    return (NULL);
	}

	if (s != NULL && *s != '\0') {
	    if (bastype == OT_BOOL ||
		pp->p_type & (PT_STRUCT|PT_GCUR|PT_IMCUR|PT_UKEY|PT_PSET)) {
		eprintf (nominmax, pnamehold);
		return (NULL);
	    } else if (!strcmp(s,indefstr) || !strcmp(s,indeflc)) {
		pp->p_flags |= P_IMAX;
	    } else if (bastype == OT_STRING || *s == PF_INDIRECT) {
		/* Filename, enumerated string, or indirect reference.
		 */
		pp->p_maxo.o_type = OT_STRING;
		pp->p_max.v_s = memneed (btoi(PF_SZMAXSTR));
		pp->p_max.v_s[PF_SZMAXSTR-1] = '\0';
		strncpy (pp->p_max.v_s, s, PF_SZMAXSTR-1);
	    } else {
		/* Type is equivalent to a simple non-string wrt mins.
		 */
		pp->p_maxo = makeop (s, pp->p_type & OT_BASIC);
	    }
	} else
	    pp->p_flags |= P_UMAX;


	/* P_PROMPT */

	if ((s = nextfield (tbuf, fp)) == (char *)ERR) {
	    eprintf (umquotes, "prompt", pnamehold);
	    return (NULL);
	}

	pp->p_prompt = (s == NULL) ? nullstr : s;


	/* ARRAY INITIALIZATION */

	if (arrflag) {
	    int     i, len=0;

	    /* First initialize all fields, since we do not
	     * require initialization of the entire array.
	     */
	    if (bastype == OT_BOOL || bastype == OT_INT) {
		int	*p;
		p = pp->p_aval.a_i;
		for (i=0;  i < size_arr;  i++)
		    *p++ = INDEFL;

	    } else if (bastype == OT_REAL) {
		double	*p;
		size_arr = size_arr / 2;
		p = pp->p_aval.a_r;
		for (i=0;  i < size_arr;  i++)
		    *p++ = INDEFR;

	    } else {				/* Strings. */
		char	**p;

		/* Check if max_length specified in p_max.
		 */
		if (pp->p_maxo.o_type == OT_INT)
		    len = pp->p_max.v_i;
		else
		    len = SZ_FNAME;
		pp->p_lenval = len;

		/* Set up indef strings.
		 */
		p = pp->p_aval.a_s;
		for (i=0;  i < size_arr;  i++) {
		    *p = (char *) memneed (btoi (len) );
		    strcpy(*p, INDEFSTR);
		    *(*p + len - 1) = '\0';
		    p++;
		}
	    }

	    /* Now get any initialization which may be present.
	     * If we reach the end of the parameter before the
	     * array is filled it is not an error and the
	     * values are left with defaults. Values can be
	     * skipped with successive commas.
	     */
	    for (i=0; i<size_arr; i++) {
		if ((s = nextfield (tbuf, fp)) == NULL)
		    break;
		if (s == (char *) ERR) {
		    eprintf (umquotes, pnamehold);
		    return (NULL);
		}

		/* If the field was empty a pointer to the external
		 * string undefval was returned.
		 */
		if (s == undefval)
		    continue;

		if (bastype == OT_BOOL) {
		    makelower(s);
		    if (strcmp(s, "no") )
			*(pp->p_aval.a_i + i) = 1;
		    else
			*(pp->p_aval.a_i + i) = 0;
		} else if (bastype == OT_INT) {
		    *(pp->p_aval.a_i + i) = atoi(s);
		} else if (bastype == OT_REAL) {
		    *(pp->p_aval.a_r + i) = atof(s);
		} else {
		    char *dest;
		    dest = *(pp->p_aval.a_s + i) ;
		    strncpy (dest, s, len-1);
		}
	    }
	}

	/* Is there still more.
	 */
	if (nextfield (tbuf, fp) != NULL) {
	    eprintf ("too many fields for `%s'\n", pnamehold);
	    return (NULL);
	}

	/* Got through whole line without errors.
	 */
	return (pp);
}


/* CK_ATOI -- Check a string for non-numerics before conversion.
 */
int
ck_atoi (char *str, int *val)
{
	char	*s;

	s = str;
	while (*s == ' ' || *s == '\t')
	    s++;

	if (*s == '-')
	    s++;

	while (*s)
	   if (!isdigit(*s++))
		return (ERR);

	*val = atoi(str);
	return (0);
}


/* NEXTFIELD -- Compile the next field of a paramfile line into the dictionary
 *   and return a pointer to the new entry.
 * PP is the address of a pointer to the start of a param field. skip leading
 *   blanks and handle quoted strings. strings ending in \ are continued after
 *   absorbing both the \ and the newline. strings ending with just newlines
 *   will contain the newline. the string may be delimited by ' or ".
 * The callers pointer, *pp, will be set to the beginning of the next field.
 * FP is a file pointer, needed if the field is quoted and extends to another
 *   lines.
 * The field must be part of a line read with fgets (buf, SZ_LINE, fp); we
 *   rely on the max length as well as the trailing \n\0 sequence.
 * Return NULL if no further fields, ERR if don't find closing quote,
 *   else pointer to field as compiled in dictionary.  If the field was
 *   empty return a pointer to the string "undefval".
 */
char *
nextfield (char **pp, FILE *fp)
{
	static char readbuf[SZ_LINE];
	register char c, *p;	/* fast references to field	 	*/
	char	buf[SZ_LINE];	/* working scratch buffer		*/
	char	*bp = buf;	/* pointer into scratch buffer		*/
	char	*start = NULL;	/* start of compiled string in dictnry	*/
	char	quote;		/* set to opening quote; go until match	*/

	p = *pp;
	if (p == NULL)
	    return (NULL);

	/* Skip white space at beginning.  This may include one or
	 * more newlines if they are prefixed by a '\\'.
	 */
	forever {
	    while (*p == ' ' || *p == '\t') 
		p++;
	    if (*p == '\\' && *(p+1) == '\n') {
		if (fgets (readbuf, SZ_LINE, fp) == NULL)
		    return ((char *) ERR);
		p = readbuf;
		continue;
	    } else
		break;
	}

	c = *p;

	if (c == '\0' || c == '\n') {
	    *pp = NULL;
	    return (NULL);
	}

	if (c == '\'' || c == '"') {
	    quote = c;
	    p++;

	    forever {
		c = *p++;
		if (c == '\n') {
		    *bp++ = c;
		    continue;
		} else if (c == '\\') {
		    switch (c = *p++) {
		    case '\n':
			continue;
		    case 'n':
			*bp++ = '\n';
			break;
		    case 't':
			*bp++ = '\t';
			break;
		    case 'r':
			*bp++ = '\r';
			break;
		    case 'f':
			*bp++ = '\f';
			break;
		    case '\'':
		    case '"':
			*bp++ = c;
			break;
		    default:
			*bp++ = '\\';		/* preserve esc seq.	*/
			*bp++ = c;
			break;
		    }
		} else if (c == '\0' || c == quote) {
		    *bp = '\0';
		    if (start == NULL)
			start = comdstr (buf);
		    else
			catdstr (start, buf);

		    if (c == quote)
			break;
		    else {
			if (fgets (readbuf, SZ_LINE, fp) == NULL)
			    return ((char *)ERR);
			p = readbuf;
			bp = buf;
		    }
		} else
		    *bp++ = c;
	    }
	    *bp++ = '\0';

	    /* Skip any white space.  We assume that we needn't skip
	     * lines here.
	     */
	    while (*p == ' ' || *p == '\t')
		p++;

	    c = *p;

	} else {
	    /* Unquoted string.
	     * Changed 2/15/85 by TAM.
	     * This code is no longer seen by quoted strings
	     */
	    while (*p != '\0' &&  *p != '\n' &&  *p != ',' && *p != '#') {
		c = *p;

		/* Allow multi-line definitions by ignoring newlines
		 * prefixed by backslash.
		 */
	 	if (c == '\\' && *(p+1) == '\n') {
		    if (fgets (readbuf, SZ_LINE, fp) == NULL)
			return ((char *)ERR);
		    p = readbuf;
		    continue;
		} else
		    *bp++ = c;

		p++;
	    }
	}

	/* Get rid of comments after the field. */
	if (*p == '#')
	    while (*p != '\0')
		p++;

	c = *p;

	/* At this point we must be at a field terminator, i.e.
	 * comma, newline or null.
	 */
	if (c != ',' && c != '\n' && c != '\0')
	    return ((char *)ERR);


	/* if stopped due to \n or , skip over it.
	 * set caller's pointer to start of next field.
	 * if we've not already compiled a string, compile this field.
	 */
	if (c == '\n' || c == ',')
	    p++;

	if (start == NULL) {
	    if (bp == buf) {
		/* The field was empty (i.e., ",,").  Return point to the
		 * null string "undefval" to flag value as undefined.
		 */
		start = undefval;
	    } else {
		*bp = '\0';
		start = comdstr (buf);
	    }
	    *pp = p;
	} else if (*pp != NULL)
	    *pp = p;

	return (start);
}


/* MAKELOWER -- Convert, in-place, any upper case characters in the string
 * cp to lower.  Using isupper and tolower is fast and portable, but making
 * simple range test and subtraction will save the table space if you know
 * you have ASCII.
 */
char *
makelower (register char *cp)
{
	char *start = cp;
	register char c;

	while ((c = *cp) != '\0') {
	    if ('A' <= c && c <= 'Z')
		*cp = c + ('a' - 'A');
	    cp++;
	}

	return (start);
}


/* SCANMODE -- Read through string s and build up an int full of M_XXX type
 *   mode bits.  Return it if ok, else ERR.
 * We write a diagnostic with eprint() if ERR but not a '\n' so
 *   caller can include more info if necessary.
 * N.B. we assume ERR doesn't map into a reasonable set of flags.
 */
int
scanmode (char *s)
{
	register int mode = 0;
	register char *str, *ip, *op;
	static	char *badstr = "bad mode string `%s'";
	char	strings[4][25];
	int	i, n;

	str = s;
	if (strchr (str, ',') != NULL || strchr (str, '+') != NULL) {
	    if (*str == '"' || *str == '\'')
		str++;

	    /* Break str into alpha strings separated by '+', ' ', or ','.
	     * We will not see any more than 4 such strings.
	     */
	    for (n=0, ip=str;  n < 4;  n++) {
		while (*ip == ' ' || *ip == '\t')
		    ip++;
		for (op=strings[n];  (*op = *ip++) != '\0';  op++)
		    if (!isalpha (*op)) {
			*op = '\0';
			break;
		    }
	    }
	    if (n == 0 || n == 5) {
		eprintf (badstr, str);
		return (ERR);
	    }

	    for (i=0;  i < n;  i++) {
		str = strings[i];
		makelower (str);
		if (!strcmp (str, "auto") || !strcmp (str, "a"))
		    mode |= M_AUTO;
		else if (!strcmp (str, "hidden") || !strcmp (str, "h"))
		    mode |= M_HIDDEN;
		else if (!strcmp (str, "learn") || !strcmp (str, "l"))
		    mode |= M_LEARN;
		else if (!strcmp (str, "query") || !strcmp (str, "q"))
		    mode |= M_QUERY;
		else if (!strcmp (str, "menu") || !strcmp (str, "m"))
		    mode |= M_MENU;
		else {
		    eprintf (badstr, str);	
		    return (ERR);
		}
	    }

	} else {
	    for (ip=str;  *ip != '\0';  ip++) {
		/* Handle the case of a set of qlha run together, as in
		 * a parameter file spec.
		 */
		switch (*ip) {
		case PF_AUTO: case PF_AUTO - ('a' - 'A'):
		    mode |= M_AUTO;
		    break;
		case PF_HIDDEN: case PF_HIDDEN - ('a' - 'A'):
		    mode |= M_HIDDEN;
		    break;
		case PF_LEARN: case PF_LEARN - ('a' - 'A'):
		    mode |= M_LEARN;
		    break;
		case PF_QUERY: case PF_QUERY - ('a' - 'A'):
		    mode |= M_QUERY;
		    break;
		case PF_MENU: case PF_MENU - ('a' - 'A'):
		    mode |= M_MENU;
		    break;
		default:
		    eprintf ("Bad mode spec `%c' in `%s'\n", *ip, str);
		    return (ERR);
		}
	    }
	}

	return (mode);
}


/* SCANTYPE -- Read through string s and build up an int full of OT_XXX and
 * PT_XXX type bits.  Return it if ok, else ERR.
 * OT_ bits are not unique so be a bit carefile.
 * we write a diagnostic with eprint() if ERR but not a '\n' so
 *   caller can include more info if necessary.
 * N.B. hope ERR doesn't map into a reasonable set of flags.
 */
int
scantype (register char *s)
{
	static char *badtype =  "bad type spec `%c'";
	static char *cnfltype = "conflicting type spec `%c'";
	register int type;

	type = 0;

	if (*s == '*') {
	    type |= PT_LIST;
	    s++;
	}

	if (*s == 'a' || *s == 'A') {
	    if (type & PT_LIST) {	/* No list structured arrays. */
		eprintf (cnfltype, *s);
		return (ERR);
	    }
	    s++;
	    type |= PT_ARRAY;
	}

	if (s[1] == '\0') {
	    switch (*s) {
	    case 'b': case 'B': type |= OT_BOOL; break;
	    case 'i': case 'I': type |= OT_INT; break;
	    case 'r': case 'R': type |= OT_REAL; break;
	    case 's': case 'S': type |= OT_STRING; break;
	    case 'f': case 'F': type |= (PT_FILNAM|OT_STRING); break;
	    default: eprintf (badtype, *s);
		return (ERR);
	    }

	} else if (*s == 'f') {
	    type |= (PT_FILNAM + OT_STRING);
	    while (*++s != '\0')
		switch (*s) {
		case 'b': case 'B': type |= PT_FBIN; break;
		case 'n': case 'N': type |= PT_FNOE; break;
		case 'r': case 'R': type |= PT_FER; break;
		case 't': case 'T': type |= PT_FTXT; break;
		case 'w': case 'W': type |= PT_FEW; break;
		default: eprintf (badtype, *s);
		    return (ERR);
		}
	} else if (!strcmp (makelower (s), "struct")) {
	    type |= (PT_STRUCT|OT_STRING);
	} else if (!strcmp (makelower (s), "gcur")) {
	    type |= (PT_GCUR|OT_STRING);
	} else if (!strcmp (makelower (s), "imcur")) {
	    type |= (PT_IMCUR|OT_STRING);
	} else if (!strcmp (makelower (s), "ukey")) {
	    type |= (PT_UKEY|OT_STRING);
	} else if (!strcmp (makelower (s), "pset")) {
	    type |= (PT_PSET|OT_STRING);
	} else {
	    eprintf (badtype, *s);
	    return (ERR);
	}

	return (type);
}
