/*
 * MAKE.C -- Main code for MAKE/VMS utility
 *
 *	MAKE/VMS:
 *
 *	VMS implementation of the UNIX(tm) MAKE utility
 *
 *	(C)1986,1987,1988,1989
 *		Todd Aven
 *		the Software Sweatshop
 *		564 Laurelton Boulevard
 *		Long Beach, NY  11561
 *
 *	This software may be freely copied and distributed with the
 *	following stipulations:
 *
 *	  o No fee except to recover costs of media and delivery may
 *	    be charged for the use or possession of this software.
 *	  o Sources to this utility must be made available in machine-
 *	    readable form along with the executable form.
 *	  o No portion of this program may be used in any program sold
 *	    for a fee.
 *	  o This copyright notice must not be removed.
 *
 */
#define VMSVERS \
"MAKE/VMS 3.4 (C)1986,1989 by Todd Aven, the Software Sweatshop, NY."

#include <stdio.h>
#include <ctype.h>
#include <stat.h>
#include <descrip.h>
#include "clidef.h"
#include <ssdef.h>
#include <string.h>

#include "simple.h"
#include "make.h"
#include "cmdline.h"
#include "strings.h"
#include "functions.h"

/************************************************************/
/*
 * MAKE parses the make file and constructs a somewhat tangled
 * directed graph describing dependencies.  There is a list of
 * TargNode structures, each of which points to a list of
 * preq_node structures.  These both point to _File structures,
 * which contain information about files such as date-time of write
 * and the filename.  To keep things simple, MAKE insures that only
 * one _File exists for any given file.
 */
/************************************************************/
usage()
{
  puts(VMSVERS);
  puts("This program may be copied freely for noncommercial purposes.");
  puts("See the head of MAKE.C for full copyright information.\n");
  puts("This program is an implementation for VAX/VMS of the MAKE utility");
  puts("supplied with Unix.\n");
  puts("Usage: MAKE [options ...] [targets ...]");
  puts("Options:");
  puts("   /INPUT=<filename>  Specify makefile, default is MAKEFILE");
  puts("   /OUTPUT=<filename> Specify name of output command file");
  puts("   /IGNORE            Ignore errors while processing");
  puts("   /EXECUTE           Negate to prevent execution of commands");
  puts("   /DEBUG             Print debug info");
  puts("   /KEEP              Keep command file");
  puts("   /VERIFY            Echo commands");
  puts("   /SPAWN             Don't wait for commands to complete");
  puts("   /DEFAULTS          Use defaults file");
  puts("   /FORCE             Always rebuild all targets");
  exit(SS$_NORMAL);
}
/************************************************************/
main(int argc,char **argv)
{
  int i, targcnt;
  char *targnames[MAXTARGETS];
  
  targcnt = process_cmdline(targnames);
  if(usage_flag) usage();

  init_queue(&Default_listhead);
  init_queue(&Target_listhead);
  init_queue(&File_listhead);
  init_queue(&Symbol_listhead);
  new_symbol("CMSGEN","");

  Dprintf("**************** Parsing rules file(s) ********************\n");
  learn_rules();

  Dprintf("****************** Making target(s) ***********************\n");
  if(targcnt) {			/* targets specified on command line */
    for(i=0; i<targcnt; i++)
      make(targnames[i]);
  }
  else {
    if(first_targ) {		/* make first target in makefile */
      make(first_targ->file->name);
    }
    else {
      error("No targets specified");
    }
  }

  if(cmd_file == NULL) {	/* No command file was needed, so... */
    printf("All targets were in synch.\n");
    exit(SS$_NORMAL);
  }

  if(execute)
    Dprintf("**************** Performing commands **********************\n"); 
  perform_commands();		/* Never return from this one */
}
/************************************************************/
parse(FILE *infile)
{
  int i,j;
  char *ptr;
  char *curline;
  char *targlist[MAXTARGETS],*preqlist[MAXPREREQS];
  int targnum,preqnum;		/* Num of targs and preqs in last targline */
  Target *targ[MAXTARGETS];	/* List of targ pointers in last targline */
  int first_nonwhite;

  targnum = 0;			/* Initially */

  while(curline = getline(infile)) {
    /* If this line is blank, skip it. */
    if((first_nonwhite = strspn(curline,WHITESPACE)) == strlen(curline)) {
      tfree(curline);
      continue;			/* Read next line */
    }

    /* Lines come in the following flavors (discovered in this order):
     * 1 shell lines	- Easy to spot: whitespace at the beginning
     * 2 include lines	- Easy to spot: 'INCLUDE' at the beginning
     * 3 symbol_defs	- Anything else and containing an '='
     * 4 target lines	- Anything else
     */
    if(first_nonwhite > 0) {	/* If leading space, this is a shell line */
      if(targnum == 0)		/* No targets yet */
	error("Target line must come before shell lines");
      for(i = 0; i < targnum; i++)
	add_shell_line(targ[i],curline+first_nonwhite);
      continue;			/* Read next line */
    }

    /* Anything but a shell line we substitute symbols immediately */
    ptr = substitute_symbols(curline);
    tfree(curline);
    curline = ptr;

    /* Is it a directive? */
    if(*curline == '%') {
      parse_directive(curline + 1);
      continue;			/* Read next line */
    }

    /* Is it a symbol definition? */
    if(strchr(curline,'=')) {
      parse_symbol_line(curline);
      continue;			/* Read next line */
    }

    /* Can only be a 'targets : prerequisites' line. */

    /* Collect the targets and prerequisites */
    parse_target_line(curline,targlist,&targnum,preqlist,&preqnum);

    /* Special handling for default rules */
    if(strchr(targlist[0],'*')) {
      if(targnum != 1 AND preqnum <= 1)
	error("Default rules have one target and one or no prerequisite.");
      targ[0] = new_rule(targlist[0]);
      if(preqnum)
	add_prereq(targ[0],preqlist[0]);
      continue;
    }

    for(i=0; i<targnum; i++) {
      /* If the target is not in the main list, add it. */
      targ[i] = LOOKUP_TARGET(targlist[i]);
      if(!targ[i]) targ[i] = new_target(targlist[i]);
      if(first_targ == NULL)	/* First REAL target */
	first_targ = targ[i];
      /* For each target, if prereqs are not in target's list, add 'em */
      for(j=0; j<preqnum; j++) {
	if(!search_queue(&targ[i]->Prereq_listhead,preqlist[j],match_prereq))
	  add_prereq(targ[i],preqlist[j]);
      }
    }				/* Target line has been interned. */
    tfree(curline);
  }
  fclose(infile);
}
/************************************************************/
Target *new_rule(char *name)
{
  Target *targ,*flink;

  Dprintf("new_rule(\"%s\")\n", name);

  /* Add new rule to top of list */
  targ = (Target *)talloc(sizeof(Target));
  targ->flink = Default_listhead.flink;
  if(!(targ->file = LOOKUP_FILE(name)))
    targ->file = new_file(name);
  init_queue(&targ->Prereq_listhead);
  init_queue(&targ->Command_listhead);
  /* Link it into the list of rules */
  Default_listhead.flink = targ;
  return(targ);
}
/************************************************************/
Target *new_target(char *name)
{
  Target *targ,*flink;

  Dprintf("new_target(\"%s\")\n", name);

  /* Add new target to top of list */
  targ = (Target *)talloc(sizeof(Target));
  targ->flink = Target_listhead.flink;
  if(!(targ->file = LOOKUP_FILE(name)))
    targ->file = new_file(name);
  init_queue(&targ->Prereq_listhead);
  init_queue(&targ->Command_listhead);
  init_queue(&targ->Symbol_listhead);
  /* Link it into the list of targets */
  Target_listhead.flink = targ;
  return(targ);
}
/************************************************************/
new_symbol(char *name, char *value)
{
  Symbol *sym;

  Dprintf("\tSymbol \"%s\" added.\n",name);
  sym = (Symbol *) talloc(sizeof(Symbol));
  sym->name = strperm(name);
  sym->value = strperm(value);
  sym->flink = Symbol_listhead.flink;
  Symbol_listhead.flink = sym;
}
/************************************************************/
set_target_symbol(Target *target,char *name, char *value)
{
  Symbol *sym;

  Dprintf("Target symbol %s=%s\n",name,value);

  if(!(sym = search_queue(&target->Symbol_listhead,name,match_symbol)))
    new_target_symbol(target,name,value);
  else {
    tfree(sym->value);
    sym->value = strperm(value);
    Dprintf("\tSymbol \"%s\" replaced.\n",name);
  }
}
/************************************************************/
new_target_symbol(Target *target,char *name,char *value)
{
  Symbol *sym;

  Dprintf("\tSymbol \"%s\" added.\n",name);
  sym = (Symbol *) talloc(sizeof(Symbol));
  sym->name = strperm(name);
  sym->value = strperm(value);
  sym->flink = target->Symbol_listhead.flink;
  target->Symbol_listhead.flink = sym;
}
/************************************************************/
add_shell_line(Target *targ, char *line)
{
  Command *cmdnode;
  Command *q;
  
  Dprintf("add_shell_line(\"%s\", \"%s\")\n", targ->file->name, line);

  /* Attach command to END of list so commands are performed in order */
  cmdnode = (Command *) talloc(sizeof(Command));
  cmdnode->command = strperm(line);
  /* Go to the end of the list */
  q = &targ->Command_listhead;
  while(q->flink)
    q = q->flink;
  cmdnode->flink = NULL;	/* Make this node the end of the list */
  q->flink = cmdnode;		/* Add it to the list */
}
/************************************************************/
add_prereq(Target *targ, char *name)
{
  Prereq *p,*q;

  /* Allocate a Prereq node. */
  p = (Prereq *)talloc(sizeof(Prereq));
  /* If the file name is new, link a new file. */
  if(!(p->file = LOOKUP_FILE(name))) {
    p->file = new_file(name);
  }

 /* Attach prereq to END of list so prereqs are made left-to-right */
  q = &targ->Prereq_listhead;
  while(q->flink)		/* go to end of list */
    q = q->flink;
  q->flink = p;			/* Q is now the last node in the list */
  p->flink = NULL;
}
/************************************************************/
File *new_file(char *name)
{
  File *f;

  Dprintf("new_file(\"%s\")\n", name);

  f = (File *) talloc(sizeof(File));
  f->name = strperm(name);
  f->status = 0;
  f->revdate = 0;
  f->flink = File_listhead.flink;
  File_listhead.flink = f;
  return(f);
}
/************************************************************/
getrevdate(File *file)
{
  File *lib;
  struct stat stat_info;
  long datetime, module_time = NOTIME;
  int status;
  char module[100], library[100];
  unsigned long libdate[2];

  if(!(file->status & PARSED))
    parse_file_name(file);

  switch(file->type) {
  case NORMAL_FILE:
    if(stat(file->name, &stat_info) == 0) /* Stat returns 0 if OK */
      module_time = stat_info.st_mtime;
    break;
  case LIBRARY_MODULE:
    /* First, if the library doesn't exist we might be able to build it. */
    if(!(lib = LOOKUP_FILE(file->library)))  
      lib = new_file(file->library);
    if(!exists(lib->name) AND !(lib->status & BUILT)) {
      Dprintf("Library %s doesn't exist. Trying to make it.\n",lib->name);
      make(lib->name);
      if(!(lib->status & BUILT))
	error(mstrcat("Couldn't find or build library ",lib->name));
    }
    /* Now get the module revdate */
    if(readlibdate(file->library, file->module, libdate)) {
      module_time = SHELL$FIX_TIME(libdate);
    }
    break;
  case CMS_MODULE:
    /* Get the module revdate */
    if(readcmsdate(file->library,cms_generation,file->module,libdate)) {
      module_time = SHELL$FIX_TIME(libdate);
    }
    break;
  default:
    error(mstrcat("Unrecognized file type for file ",file->name));
  }

  Dprintf("Module %s revision time: %s",file->name,ctime(&module_time));
  file->status |= DATED;
  file->revdate = module_time;
}
/************************************************************/
/*
 * make(name)
 *
 *      This routine actually does the work.  It scans the list of
 *      targets parsed from the makefile, and checks the target's
 *      prerequisites date/time values against the target's.  If
 *      the prerequisite is itself a target(present in target_list),
 *      we call make recursively to check it.  Then, if any of our
 *      prerequisites are newer than we are, we execute all our shell
 *      commands.  If there are no prerequisites specified at all, then
 *      also execute all our shell commands.
 */
make(char *targname)
{
  Target *target;
  Prereq *prereq;
  long targtime;
  long date;
  long newest = NOTIME;

  Dprintf("Making %s\n", targname);

  target = LOOKUP_TARGET(targname);
  if(!target) {
    try_default(targname);
  }
  else {
    /* To add a dependency 'clause' to a default rule, provide no
     * command lines.
     */
    if(EMPTY_LIST(target->Command_listhead))
      try_default(targname);
  }
  /* Lookup target again. Default rules 'create' new targets. */
  target = LOOKUP_TARGET(targname);
  if(!target) {			/* It's not a target, so chain ends here */
    if(exists(targname))
      return;
    else
      error(mstrcat(targname," doesn't exist and couldn't be made."));
  }
  
  prereq = target->Prereq_listhead.flink;
  /* Now we proceed up the tree... make each prerequisite. */
  while(prereq) {
    /* If the branch has not been climbed, do it now */
    if(!(prereq->file->status & BUILT)) { /* See if it needs building */
       make(prereq->file->name); /* Try making the prereqs */
       /* Get the revision date of the module */
       if(!(prereq->file->status & DATED))
	 getrevdate(prereq->file);
       date = prereq->file->revdate;
     }
      
    if(prereq->file->status & BUILT)
      date = FORCE;		/* FORCE propagates down the tree */
    /* If it doesn't exist, force making target */
    if(date == NOTIME)
      date = FORCE;

    /* Keep the date of the 'newest' prerequisite. */
    if(date > newest) newest = date;
    prereq = prereq->flink;	/* Hit the next branch */
  }

  /* Get the date of the target */
  if(!(target->file->status & DATED))
    getrevdate(target->file);

  targtime = target->file->revdate;

  /* Force building if the target has no prerequisites and doesn't exist */
  if(EMPTY_LIST(target->Prereq_listhead) AND !exists(target->file->name))
    newest = FORCE;

  Dprintf("Target \"%s\" datetime is %08lx, newest prereq is %08lx\n",
	    target->file->name, targtime, newest);

  if((targtime < newest) OR alwaysrebuild) {
    if(!EMPTY_LIST(target->Command_listhead))
      build(target);
    target->file->status |= BUILT;
  }
}
/************************************************************/
/* try_default
 *
 *	Try a default rule for target for one of two reasons:
 *
 *	a) A prerequisite is not itself a target. There may be
 *	   a default rule describing how to make the prerequisite.
 *
 *	b) A target had at least one prerequisite, but there were
 *	   no shell lines to tell how to make it, so there might
 *	   be a default rule to tell how (otherwise, we've got
 *	   a boo-boo). For instance, we might have a default rule
 *  		*.obj : *.c
 *			cc $*
 *	   for C programs, but this C program depends on an
 *	   include file
 *		thisfile.obj : thisfile.h
 *
 *	There are two ways a rule can match:
 *		o EXACTLY -- target matches a rule, AND the prereq exists.
 *		o CLOSELY -- target matches a rule.
 */
try_default(char *targname)
{
  char pname[100],root[75];
  Target *deftarg;
  Prereq *prereq;
  Target *first_match;
  char *star;
  int i,offset;
  int matched;

  Dprintf("try_default(\"%s\")\n",targname);

  first_match = NULL;
  deftarg = &Default_listhead;
  while(deftarg = deftarg->flink) {
    Dprintf("...trying '%s'...",deftarg->file->name);

    /* Manually compare up to the wildcard. SHELL$MATCH_WILD won't. */
    star = strchr(deftarg->file->name,'*');
    offset = star - deftarg->file->name;
    matched = (offset > 0);	/* False if '*' is first character. */
    /* If targname is shorter, we'll stop on the NULL. */
    for(i=0; i<offset; i++) {	/* Pass over if '*' is first character. */
      if(*(targname+i) != *(deftarg->file->name+i)) {
	matched = 0;
	break;
      }
    }

    if(!matched &&
       !shell$match_wild(targname+offset,deftarg->file->name+offset)) {
	 Dprintf("failed.\n");
	 continue;			/* This rule didn't match the target */
       }

    Dprintf("matched.\n");

    if(!first_match)		/* Remember first 'CLOSELY' matched rule */
      first_match = deftarg;

    /* If there's no prerequisite, it can't match 'EXACTLY' */
    if(!(prereq = deftarg->Prereq_listhead.flink))
      continue;			/* No prereq for this target */
  
    /* Get the prerequisite name from the target name and the rule */
    incarnate(deftarg,targname,pname,root);

    if(!exists(pname))		/* If it doesn't exist, not an 'exact' match */
      continue;

    /* We've matched 'EXACTLY'. Let's put the incarnated rule in the list */
    instantiate_rule(deftarg,targname,pname,root);
    return(TRUE);		/* We matched EXACTLY */
  }
  if(!first_match)		/* No rule matched, even CLOSELY */
    return(FALSE);
  incarnate(first_match,targname,pname,root);
  instantiate_rule(first_match,targname,pname,root);
}
/************************************************************/
incarnate(Target *deftarg,char *targname,char *pname,char *root)
{
  char *ptr,*optr,*asterisk;
  Prereq *prereq;
  int left_length, middle_length, right_length;

  Dprintf("incarnate(\"%s\",\"%s\",,)\n",deftarg->file->name,targname);

  prereq = deftarg->Prereq_listhead.flink;
  *pname = '\0';		/* There might not be a prerequisite */

  /* Break the real target into three pieces:
   *    1. Left of '*'
   *    2. Right of '*'
   *    3. The part that matches '*'
   */

  asterisk = strchr(deftarg->file->name,'*');
  left_length = asterisk - deftarg->file->name;
  right_length = strlen(asterisk+1);
  middle_length = strlen(targname) - left_length - right_length;
  ptr = targname + left_length;

  /* Get the root name from the real target */
  strncpy(root,ptr,middle_length);
  root[middle_length] = '\0';

  if(!prereq)			/* If no prereq, don't bother with the rest */
    return;

  /* Replace the '*' in the prerequisite with the root name. */
  ptr = prereq->file->name;
  optr = pname;
  while(*ptr != '*') *(optr++) = *(ptr++);
  strcpy(optr,root);		/* Insert the root name */
  ptr++;
  Dprintf("Incarnate(): left + root = \"%s\"\n",pname);
  strcat(pname,ptr);		/* Add the rest */
  Dprintf("Incarnate(): rest = \"%s\"\n",ptr);
  return;
}
/************************************************************/
instantiate_rule(Target *deftarg,char *targname,char *pname, char *root)
{
  Target *newtarg;
  char symline[255];
  Command *defnode,*lastnode,*newnode;

  if(!(newtarg = LOOKUP_TARGET(targname))) /* Not necessarily a new target */
    newtarg = new_target(targname);
  if(strlen(pname))
    add_prereq(newtarg,pname);
  /* Even if target existed before this, there were no commands */
  /* COPY default rule's command list to newly-instantiated command list */ 
  defnode = &deftarg->Command_listhead;
  lastnode = &newtarg->Command_listhead;
  while(defnode = defnode->flink) { /* Traverse the deftarg's command list */
    newnode = (Command *)talloc(sizeof(Command));
    newnode->command = strperm(defnode->command);
    lastnode->flink = newnode;	/* Link it into the list */
    newnode->flink = NULL;
    lastnode = newnode;
  }
  /* Define symbols which refer to the current default rule match */
  set_target_symbol(newtarg,"*",root);
  set_target_symbol(newtarg,"@",newtarg->file->name);
  set_target_symbol(newtarg,"<",pname);
}
/************************************************************/
/*
 * build
 *
 *      Write DCL commands for a target that needs to be built
 */
build(Target *target)
{
  char symline[8000];
  Symbol *sym;
  Command *cmdnode;
  char *cmd,*ptr;
  char tmpbuf[8000];
  char cmdbuf[8000];
  int cont_line,input_line;
  
  if(cmd_file == NULL) {	/* Command file hasn't yet been opened. */
    if(cmd_fname == NULL) cmd_fname = strperm(mktemp("makeXXXXXXXX.com"));
    cmd_file = fopen(cmd_fname,"w");
    tfree(cmd_fname);
    cmd_fname = strperm(fgetname(cmd_file,tmpbuf));
    Dprintf("Opening command file \"%s\"\n", cmd_fname);
    if(verify)
      fputs("$ Verify_Flag = F$Verify(1)\n", cmd_file);
    fputs("$ On Control_Y Then Goto The_Exit\n",cmd_file);
    if(ignore_errors)
      fputs("$ set noon\n", cmd_file);
    else
      fputs("$ On Error Then Goto The_Exit\n",cmd_file);
  }

  /* Define any of this target's local symbols in the global list now */
  sym = &target->Symbol_listhead;
  while(sym = sym->flink) {
    sprintf(symline,"%s=%s",sym->name,sym->value);
    parse_symbol_line(symline);
  }

  cont_line = FALSE;		/* True when previous line ended with '-' */
  
  cmdnode = target->Command_listhead.flink;
  while(cmdnode) {
    cmd = substitute_symbols(cmdnode->command);
    tfree(cmdnode->command);
    cmdnode->command = strperm(cmd);
    breakup_command_line(cmdnode); /* Break lines at '\' */
    cmd = cmdnode->command;
    input_line = (*lastnonspace(cmd) == '$');
    if(input_line)		/* Get rid of the '$' now */
      *strrchr(cmd,'$') = '\0';
    if(cont_line || input_line)
      cmdbuf[0] = '\0';		/* No '$ ' at beginning of these lines */
    else
      strcpy(cmdbuf,"$ ");	/* Make it a normal DCL line */
    strcat(cmdbuf,cmd);
    strcat(cmdbuf,"\n");
    cont_line = (*lastnonspace(cmd) == '-');
    Dprintf("Putting \"%s\" into command file.\n", cmdbuf);
    fputs(cmdbuf, cmd_file);
    tfree(cmd);
    cmdnode = cmdnode->flink;
  }
}
/************************************************************/
char *substitute_symbols(char *input)
{
  char output[8000];
  char *iptr,*dollar;
  char *lgroup,*rgroup,*next;
  char symbol[8000];
  Symbol *sym;

  Dprintf("substitute_symbols(\"%s\")\n",input);
  iptr = input;
  output[0] = '\0';
  while(dollar = strchr(iptr,'$')) {
    next = dollar + 1;
    if(!isspace(*next) AND *next != '\0') { /* Maybe a symbol */
      if(*next == '(' OR *next == '{') { /* It's definitely a symbol */
	lgroup = next + 1;	/* Beginning of symbol */
	if(!(rgroup = strpbrk(lgroup,")}")))
	  error("Missing right paren on macro");
	*rgroup = '\0';
	strcpy(symbol,lgroup);
	*rgroup = '}';
	if(sym = LOOKUP_SYMBOL(symbol)) {
	  *dollar = '\0';
	  strcat(output,input);
	  *dollar = '$';
	  strcat(output,sym->value);
	  input = rgroup + 1;
	  iptr = input;
	}
	else
	  error(mstrcat("Undefined symbol ",symbol));
      }
      else {			/* Maybe a single-char symbol */
	symbol[0] = *next;
	symbol[1] = '\0';
	if(sym = LOOKUP_SYMBOL(symbol)) {
	  *dollar = '\0';
	  strcat(output,input);	/* Take everything up to the symbol */
	  *dollar = '$';
	  strcat(output,sym->value); /* Take the symbol's value */
	  input = next + 1;	/* Drop what we've already moved to output */
	  iptr = input;		/* Look at the beginning of new segment */
	}
	else {
	  iptr = next;		/* This '$' was not a symbol signifier */
	}
      }
    }
    else {
      iptr = next;		/* This '$' was not a symbol signifier */
    }
  }
  strcat(output,input);
  Dprintf("\treturn(\"%s\")\n",output);
  return(strperm(output));
}
/************************************************************/
/* learn_rules
 *
 *	Parse the appropriate rules files. Try make.ini in the current
 *	directory first, then in the home directory. Also try 
 *	makefile in the current directory if no file was specified on
 *	the command line, else use the specified file.
 */
void learn_rules()
{
  FILE *makefile;

  if(usedefaults) {
    makefile = fopen(MAKE_DEFAULTS, "r"); /* Try MAKE_DEFAULTS. */
    if(!makefile)
      makefile = fopen("make.ini", "r"); /* Try MAKE.INI */
    if(makefile)
      parse(makefile);		/* If one exists, parse it. */
  }
  first_targ = NULL;	/* No default targets allowed from MAKE_DEFAULTS */
  if(makefile = fopen(makefilename, "r"))
    parse(makefile);
}
/************************************************************/
void perform_commands()
{
#define CLI$M_NOWAIT 0x00000001
#define CLI$M_NOTIFY 0x00000010
  register unsigned long spawn_flags = CLI$M_NOWAIT | CLI$M_NOTIFY;
  char *kill_string = "$ Delete/NoLog 'F$Environment(\"PROCEDURE\")'\n";
  strdesc cmd_descrip;
  char *cmd_string;
  unsigned long status;

  cmd_string = mstrcat("@", cmd_fname); /* Now that we know the name. */
  fputs("$The_Exit:\n", cmd_file);
  fputs("$ Save_Status = $STATUS\n", cmd_file);
  if(!keep) fputs(kill_string, cmd_file);
  if(verify) fputs("$ Verify_Flag = F$Verify(Verify_Flag)\n", cmd_file);
  fputs("$ exit Save_Status\n", cmd_file);
  fclose(cmd_file);
  if(execute) {
    DESCRIP(cmd_descrip,cmd_string);
    if(spawn) {
      if(verify) printf("$ Spawn/NoWait/Notify %s\n", cmd_string);
      status = lib$spawn(&cmd_descrip, NULL, NULL, &spawn_flags);
      if(!(status & 1))
	lib$signal(status);
    }
    else {
      if(verify) printf("$ %s\n", cmd_string);
      /* lib$do_command never returns unless there's an error */
      lib$signal(lib$do_command(&cmd_descrip));
    }
  }
}
/************************************************************/
include(char *name)
{ 
  FILE *incfile;

  Dprintf("Reading include file \"%s\"\n",name);
  if(incfile = fopen(name,"r"))
    parse(incfile);
  else
    error(mstrcat("Couldn't open include file ",name));
}
/************************************************************/
init_queue(Qentry *queue_header)
{
  queue_header->flink = NULL;
}
/************************************************************/
Qentry *search_queue(Qentry *queue_header,char *value,int (*compare)())
{
  Qentry *p = queue_header->flink;
  while(p) {
    if((*compare)(p,value))
      return(p);
    else
      p = p->flink;
  }
  /* No entry matched */
  return(NULL);
}
/************************************************************/
match_target(Target *target,char *name)
{
  if(0 == strcmp(target->file->name,name))
    return(TRUE);
  else
    return(FALSE);
}
/************************************************************/
match_prereq(Prereq *prereq,char *name)
{
  return(0 == strcmp(prereq->file->name,name));
}
/************************************************************/
match_file(File *file,char *name)
{
  return(0 == strcmp(file->name,name));
}
/************************************************************/
match_symbol(Symbol *symbol,char *name)
{
  return(0 == strcmp(symbol->name,name));
}
/************************************************************/
parse_target_line(char *line,char *targs[],int *tnum,char *preqs[],int *pnum)
{
  char *ptr,*part;
  char *left,*right;
  char *slash;

  /* First change ',' and '\' into ' '. */
  for(ptr = line; *ptr; ptr++) {
    if(*ptr == ',') *ptr = ' ';	/* Allow commas as separators */
    if(*ptr == '\\') *ptr = ' '; /* Backslash is an implicit separator */
  }

  part = line;
  while(ptr = strchr(part,':')) {
    if(isspace(*(ptr+1)) OR (*(ptr+1) == '\0')) {
      *ptr = '\0';
      left = strperm(line);
      ptr++;
      right = strperm(ptr);
      *(ptr-1) = ':';
      break;
    }
    part = ptr + 1;
  }
  if(!ptr)
    error("No separating ':'");

  *tnum = 0;
  part = left;
  while(ptr = findnonwhite(part)) {
    if(part = findwhite(ptr))
      *part = '\0';
    if(slash = strchr(ptr,'/'))
      *slash = '\0';		/* For file names such as foo.opt/opt */
    targs[(*tnum)++] = strperm(ptr);
    if(part)
      *part = ' ';
    else
      break;
  }
  *pnum = 0;
  part = right;
  while(ptr = findnonwhite(part)) {
    if(part = findwhite(ptr))
      *part = '\0';
    if(slash = strchr(ptr,'/'))
      *slash = '\0';		/* For file names such as foo.opt/opt */
    preqs[(*pnum)++] = strperm(ptr);
    if(part)
      *part = ' ';
    else
      break;
  }
  tfree(left);
  tfree(right);
}
/************************************************************/
parse_symbol_line(char *line)
{
  char *eq,*sp;
  char tmpbuf[8000];
  char *left,*right;
  char symbol[8000],value[8000];
  Symbol *sym;

  left = line;
  right = strchr(line,'=');
  *(right++) = '\0';
  sscanf(left," %s ",symbol);
  right = findnonwhite(right);	/* Remove leading spaces */
  if(right)
    strcpy(value,right);	/* It's EVERYTHING to the right of the '=' */
  else
    strcpy(value,"");		/* It's a null symbol */

  Dprintf("Symbol: %s=%s\n",symbol,value);

  if(sym = LOOKUP_SYMBOL(symbol)) { /* If it exists, replace the value */
    Dprintf("\tSymbol \"%s\" redefined.\n",symbol);
    tfree(sym->value);
    sym->value = strperm(value);
  }
  else {			/* Just add it to the list */
    new_symbol(symbol,value);
  }
}
/************************************************************/
breakup_command_line(Command *cmdnode)
{
  char *ptr,*oldcmd;
  Command *newnode;
  int broke_one = FALSE;

  oldcmd = cmdnode->command;	/* Don't lose the original command */

  /* Prune new commands off the end of the original command at '\' */
  while(ptr = strrchr(oldcmd,'\\')) { /* As long as we have a slash */
    broke_one = TRUE;
    newnode = (Command *)talloc(sizeof(Command));
    newnode->command = strperm(ptr + 1);
    newnode->flink = cmdnode->flink; /* Insert this line before next node */
    cmdnode->flink = newnode;
    *ptr = '-';			/* Change to a DCL continuation char */
    *(ptr+1) = '\0';		/* Split after the continuation */
  }
  if(broke_one) {		/* Clean up the first line */
    cmdnode->command = strperm(oldcmd);	/* It's shorter now */
    tfree(oldcmd);
  }
}
/************************************************************/
parse_file_name(File *file)
{
  char *rparen,*special;

  special = strpbrk(file->name,"~("); /* Special 'file' signifiers */

  file->status |= PARSED;
  file->type = NORMAL_FILE;	/* By default */

  if(!special)
    return;

  switch(*special) {
  case '(':
    file->type = LIBRARY_MODULE; /* Syntax <library>(<module>) */
    if((rparen = strchr(file->name,')')) < special)
      error(mstrcat("Syntax error in filename ",file->name));
    *special = '\0';
    file->library = strperm(file->name);
    *special = '(';
    *rparen = '\0';
    file->module = strperm(special+1);
    *rparen = ')';
    break;
  case '~':
    file->type = CMS_MODULE;	/* Syntax: <module>~<library> */
    *special = '\0';
    file->module = strperm(file->name);
    if(strlen(special+1)) {
      file->library = strperm(special+1);
    }
    else {
      if(cms_default)
	file->library = cms_default;
      else
	error(mstrcat("No default CMS library for ",file->module));
    }
    *special = '~';
    break;
  }
}
/************************************************************/
exists(char *name)
{
  File *file = LOOKUP_FILE(name);

  if(!file) file = new_file(name);

  if(!(file->status & DATED))
    getrevdate(file);
  if(file->revdate == NOTIME) {
    Dprintf("Object doesn't exist: \"%s\"\n",file->name);
    return(FALSE);
  }
  else {
    Dprintf("Object exists: \"%s\"\n",file->name);
    return(TRUE);
  }
}
/************************************************************/
parse_directive(char *line)
{
  char tmpbuf[8000];
  char *directive = line;
  char *object = NULL;
  char *end;

  /* Lines in the file like %DIRECTIVE [<object>] */
  if(end = findwhite(directive)) {
    *end = '\0';
    object = findnonwhite(end+1);
  }

  Dprintf("Parsing directive %s\n",directive);

  /* Is it an 'INCLUDE <file>' line? */
  if(strcmp(directive,"INCLUDE") == 0) {
    if(!object)
      error("No INCLUDE file specified");
    include(object);
  }
  else if(strcmp(directive,"CMSLIB") == 0) {
    if(!object)
      error("No CMS library specified");
    end = findwhite(object);
    if(end)
      *end = '\0';
    tfree(cms_default);
    cms_default = strperm(object);
    sprintf(tmpbuf,"CMSLIB = %s",cms_default);
    parse_symbol_line(tmpbuf);
  }
  else if(strcmp(directive,"CMSGEN") == 0) {
    if(!object)
      error("No CMS generation specified");
    end = findwhite(object);
    if(end)
      *end = '\0';
    tfree(cms_generation);
    cms_generation = strperm(object);
    sprintf(tmpbuf,"CMSGEN = /GEN=%s",cms_generation);
    parse_symbol_line(tmpbuf);
  }
  else if(strcmp(directive,"DEBUG") == 0)
    debug = TRUE;
  else if(strcmp(directive,"NODEBUG") == 0)
    debug = FALSE;
  else if(strcmp(directive,"SPAWN") == 0)
    spawn = TRUE;
  else if(strcmp(directive,"NOSPAWN") == 0)
    spawn = FALSE;
  else
    error("Unrecognized DIRECTIVE.");
}
