Index: trunk/src/kmk/Makefile
===================================================================
--- trunk/src/kmk/Makefile	(revision 9)
+++ trunk/src/kmk/Makefile	(revision 9)
@@ -0,0 +1,44 @@
+#	@(#)Makefile	5.2 (Berkeley) 12/28/90
+#	$Id$
+# $FreeBSD: src/usr.bin/make/Makefile,v 1.29 2002/10/10 19:27:48 jmallett Exp $
+
+PROG=	make
+CFLAGS+=-I${.CURDIR}
+SRCS=	arch.c buf.c compat.c cond.c dir.c for.c hash.c job.c main.c \
+	make.c parse.c str.c suff.c targ.c util.c var.c
+SRCS+=	lstAppend.c lstAtEnd.c lstAtFront.c lstClose.c lstConcat.c \
+	lstDatum.c lstDeQueue.c lstDestroy.c lstDupl.c lstEnQueue.c \
+	lstFind.c lstFindFrom.c lstFirst.c lstForEach.c lstForEachFrom.c \
+	lstInit.c lstInsert.c lstIsAtEnd.c lstIsEmpty.c lstLast.c \
+	lstMember.c lstNext.c lstOpen.c lstRemove.c lstReplace.c lstSucc.c
+.PATH:	${.CURDIR}/lst.lib
+
+NOSHARED?=	YES
+
+CFLAGS+=-DMAKE_VERSION=\"5200209170\"
+.if defined(_UPGRADING)
+CFLAGS+=-D__FBSDID=__RCSID
+.endif
+
+# XXX: kernel currently broken
+# CFLAGS+=-DUSE_KQUEUE
+
+main.o: ${MAKEFILE}
+
+# Set the shell which make(1) uses.  Bourne is the default, but a decent
+# Korn shell works fine, and much faster.  Using the C shell for this
+# will almost certainly break everything, but it's Unix tradition to
+# allow you to shoot yourself in the foot if you want to :-)
+
+MAKE_SHELL?=	sh
+.if ${MAKE_SHELL} == "csh"
+CFLAGS+=	-DDEFSHELL=0
+.elif ${MAKE_SHELL} == "sh"
+CFLAGS+=	-DDEFSHELL=1
+.elif ${MAKE_SHELL} == "ksh"
+CFLAGS+=	-DDEFSHELL=2
+.else
+.error "MAKE_SHELL must be set to one of \"csh\", \"sh\" or \"ksh\"."
+.endif
+
+.include <bsd.prog.mk>
Index: trunk/src/kmk/Makefile.dist
===================================================================
--- trunk/src/kmk/Makefile.dist	(revision 9)
+++ trunk/src/kmk/Makefile.dist	(revision 9)
@@ -0,0 +1,8 @@
+# $FreeBSD: src/usr.bin/make/Makefile.dist,v 1.2 2001/03/01 06:26:32 imp Exp $
+# a very simple makefile...
+pmake:
+	@echo 'make started.'
+	cc -DDEFSHELL=1 -I. -c *.c
+	cd lst.lib; cc -I.. -c *.c
+	cc *.o lst.lib/*.o -o pmake
+	@echo 'make completed.'
Index: trunk/src/kmk/PSD.doc/Makefile
===================================================================
--- trunk/src/kmk/PSD.doc/Makefile	(revision 9)
+++ trunk/src/kmk/PSD.doc/Makefile	(revision 9)
@@ -0,0 +1,8 @@
+#	@(#)Makefile	8.1 (Berkeley) 8/14/93
+# $FreeBSD: src/usr.bin/make/PSD.doc/Makefile,v 1.5 1999/08/28 01:03:43 peter Exp $
+
+DIR=	psd/12.make
+SRCS=	tutorial.ms
+MACROS=	-ms
+
+.include <bsd.doc.mk>
Index: trunk/src/kmk/PSD.doc/stubs
===================================================================
--- trunk/src/kmk/PSD.doc/stubs	(revision 9)
+++ trunk/src/kmk/PSD.doc/stubs	(revision 9)
@@ -0,0 +1,9 @@
+.\" $FreeBSD: src/usr.bin/make/PSD.doc/stubs,v 1.1 2000/11/24 10:05:30 ru Exp $
+.\"
+.de Ix
+..
+.de Rd
+..
+.de Rm
+..
+.if n .ftr CR R
Index: trunk/src/kmk/PSD.doc/tutorial.ms
===================================================================
--- trunk/src/kmk/PSD.doc/tutorial.ms	(revision 9)
+++ trunk/src/kmk/PSD.doc/tutorial.ms	(revision 9)
@@ -0,0 +1,3748 @@
+.\" Copyright (c) 1988, 1989 by Adam de Boor
+.\" Copyright (c) 1989 by Berkeley Softworks
+.\" Copyright (c) 1988, 1989, 1993
+.\"	The Regents of the University of California.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Adam de Boor.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\"    must display the following acknowledgement:
+.\"	This product includes software developed by the University of
+.\"	California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"	@(#)tutorial.ms	8.1 (Berkeley) 8/18/93
+.\" $FreeBSD: src/usr.bin/make/PSD.doc/tutorial.ms,v 1.12 2002/05/16 02:19:09 trhodes Exp $
+.\"
+.so stubs
+.EH 'PSD:12-%''PMake \*- A Tutorial'
+.OH 'PMake \*- A Tutorial''PSD:12-%'
+.\" xH is a macro to provide numbered headers that are automatically stuffed
+.\" into a table-of-contents, properly indented, etc. If the first argument
+.\" is numeric, it is taken as the depth for numbering (as for .NH), else
+.\" the default (1) is assumed.
+.\"
+.\" @P The initial paragraph distance.
+.\" @Q The piece of section number to increment (or 0 if none given)
+.\" @R Section header.
+.\" @S Indent for toc entry
+.\" @T Argument to NH (can't use @Q b/c giving 0 to NH resets the counter)
+.de xH
+.NH \\$1
+\\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.nr PD .1v
+.XS \\n%
+.ta 0.6i
+\\*(SN	\\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
+.XE
+.nr PD .3v
+..
+.ig
+.\" CW is used to place a string in fixed-width or switch to a
+.\" fixed-width font.
+.\" C is a typewriter font for a laserwriter. Use something else if
+.\" you don't have one...
+.de CW
+.ie !\\n(.$ .ft S
+.el \&\\$3\fS\\$1\fP\\$2
+..
+.\" Anything I put in a display I want to be in fixed-width
+.am DS
+.CW
+..
+.\" The stuff in .No produces a little stop sign in the left margin
+.\" that says NOTE in it. Unfortunately, it does cause a break, but
+.\" hey. Can't have everything. In case you're wondering how I came
+.\" up with such weird commands, they came from running grn on a
+.\" gremlin file...
+.de No
+.br
+.ne 0.5i
+.po -0.5i
+.br
+.mk 
+.nr g3 \\n(.f
+.nr g4 \\n(.s
+.ig ft
+.sp -1
+.\" .st cf
+\D's -1u'\D't 5u'
+.sp -1
+\h'50u'\D'l 71u 0u'\D'l 50u 50u'\D'l 0u 71u'\D'l -50u 50u'\D'l -71u 0u'\D'l -50u -50u'\D'l 0u -71u'\D'l 50u -50u'
+.sp -1
+\D't 3u'
+.sp -1
+.sp 7u
+\h'53u'\D'p 14 68u 0u 46u 46u 0u 68u -46u 46u -68u 0u -47u -46u 0u -68u 47u -46u'
+.sp -1
+.ft R
+.ps 6
+.nr g8 \\n(.d
+.ds g9 "NOTE
+.sp 74u
+\h'85u'\v'0.85n'\h-\w\\*(g9u/2u\&\\*(g9
+.sp |\\n(g8u
+.sp 166u
+.ig br
+\D't 3u'\D's -1u'
+.br
+.po
+.rt 
+.ft \\n(g3
+.ps \\n(g4
+..
+.de Bp
+.ie !\\n(.$ .IP \(bu 2
+.el .IP "\&" 2
+..
+.po +.3i
+.TL
+PMake \*- A Tutorial
+.AU
+Adam de Boor
+.AI
+Berkeley Softworks
+2150 Shattuck Ave, Penthouse
+Berkeley, CA 94704
+adam@bsw.uu.net
+\&...!uunet!bsw!adam
+.FS
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appears in all copies.
+The University of California, Berkeley Softworks, and Adam de Boor make no
+representations about the suitability of this software for any
+purpose.  It is provided "as is" without express or implied warranty.
+.FE
+.PP
+.xH 1 Introduction
+.LP
+PMake is a program for creating other programs, or anything else you
+can think of for it to do.  The basic idea behind PMake is that, for
+any given system, be it a program or a document or whatever, there
+will be some files that depend on the state of other files (on when
+they were last modified). PMake takes these dependencies, which you
+must specify, and uses them to build whatever it is you want it to
+build.
+.LP
+PMake is almost fully-compatible with Make, with which you may already
+be familiar. PMake's most important feature is its ability to run
+several different jobs at once, making the creation of systems
+considerably faster. It also has a great deal more functionality than
+Make. Throughout the text, whenever something is mentioned that is an
+important difference between PMake and Make (i.e.  something that will
+cause a makefile to fail if you don't do something about it), or is
+simply important, it will be flagged with a little sign in the left
+margin, like this:
+.No
+.LP
+This tutorial is divided into three main sections corresponding to basic,
+intermediate and advanced PMake usage. If you already know Make well,
+you will only need to skim chapter 2 (there are some aspects of
+PMake that I consider basic to its use that didn't exist in Make).
+Things in chapter 3 make life much easier, while those in chapter 4
+are strictly for those who know what they are doing. Chapter 5 has
+definitions for the jargon I use and chapter 6 contains possible
+solutions to the problems presented throughout the tutorial.
+.xH 1 The Basics of PMake
+.LP
+PMake takes as input a file that tells a) which files depend on which
+other files to be complete and b) what to do about files that are
+``out-of-date.'' This file is known as a ``makefile'' and is usually
+.Ix 0 def makefile
+kept in the top-most directory of the system to be built. While you
+can call the makefile anything you want, PMake will look for
+.CW Makefile
+and
+.CW makefile
+(in that order) in the current directory if you don't tell it
+otherwise.
+.Ix 0 def makefile default
+To specify a different makefile, use the
+.B \-f
+flag (e.g.
+.CW "pmake -f program.mk" ''). ``
+.Ix 0 ref flags -f
+.Ix 0 ref makefile other
+.LP
+A makefile has four different types of lines in it:
+.RS
+.IP \(bu 2
+File dependency specifications
+.IP \(bu 2
+Creation commands
+.IP \(bu 2
+Variable assignments
+.IP \(bu 2
+Comments, include statements and conditional directives
+.RE
+.LP
+Any line may be continued over multiple lines by ending it with a
+backslash.
+.Ix 0 def "continuation line"
+The backslash, following newline and any initial whitespace
+on the following line are compressed into a single space before the
+input line is examined by PMake.
+.xH 2 Dependency Lines
+.LP
+As mentioned in the introduction, in any system, there are
+dependencies between the files that make up the system.  For instance,
+in a program made up of several C source files and one header file,
+the C files will need to be re-compiled should the header file be
+changed. For a document of several chapters and one macro file, the
+chapters will need to be reprocessed if any of the macros changes.
+.Ix 0 def "dependency"
+These are dependencies and are specified by means of dependency lines in
+the makefile.
+.LP
+.Ix 0 def "dependency line"
+On a dependency line, there are targets and sources, separated by a
+one- or two-character operator.
+The targets ``depend'' on the sources and are usually created from
+them.
+.Ix 0 def target
+.Ix 0 def source
+.Ix 0 ref operator
+Any number of targets and sources may be specified on a dependency line.
+All the targets in the line are made to depend on all the sources.
+Targets and sources need not be actual files, but every source must be
+either an actual file or another target in the makefile.
+If you run out of room, use a backslash at the end of the line to continue onto
+the next one.
+.LP
+Any file may be a target and any file may be a source, but the
+relationship between the two (or however many) is determined by the
+``operator'' that separates them.
+.Ix 0 def operator
+Three types of operators exist: one specifies that the datedness of a
+target is determined by the state of its sources, while another
+specifies other files (the sources) that need to be dealt with before
+the target can be re-created. The third operator is very similar to
+the first, with the additional condition that the target is
+out-of-date if it has no sources. These operations are represented by
+the colon, the exclamation point and the double-colon, respectively, and are
+mutually exclusive. Their exact semantics are as follows:
+.IP ":"
+.Ix 0 def operator colon
+.Ix 0 def :
+If a colon is used, a target on the line is considered to be
+``out-of-date'' (and in need of creation) if 
+.RS
+.IP \(bu 2
+any of the sources has been modified more recently than the target, or
+.IP \(bu 2
+the target doesn't exist.
+.RE
+.Ix 0 def out-of-date
+.IP "\&"
+Under this operation, steps will be taken to re-create the target only
+if it is found to be out-of-date by using these two rules.
+.IP "!"
+.Ix 0 def operator force
+.Ix 0 def !
+If an exclamation point is used, the target will always be re-created,
+but this will not happen until all of its sources have been examined
+and re-created, if necessary.
+.IP "::"
+.Ix 0 def operator double-colon
+.Ix 0 def ::
+If a double-colon is used, a target is out-of-date if:
+.RS
+.IP \(bu 2
+any of the sources has been modified more recently than the target, or
+.IP \(bu 2
+the target doesn't exist, or
+.IP \(bu 2
+the target has no sources.
+.RE
+.IP "\&"
+If the target is out-of-date according to these rules, it will be re-created.
+This operator also does something else to the targets, but I'll go
+into that in the next section (``Shell Commands'').
+.LP
+Enough words, now for an example. Take that C program I mentioned
+earlier. Say there are three C files
+.CW a.c , (
+.CW b.c
+and
+.CW  c.c )
+each of which
+includes the file
+.CW defs.h .
+The dependencies between the files could then be expressed as follows:
+.DS
+program         : a.o b.o c.o
+a.o b.o c.o     : defs.h
+a.o             : a.c
+b.o             : b.c
+c.o             : c.c
+.DE
+.LP
+You may be wondering at this point, where
+.CW a.o ,
+.CW b.o
+and
+.CW c.o
+came in and why
+.I they
+depend on
+.CW defs.h
+and the C files don't. The reason is quite simple:
+.CW program
+cannot be made by linking together .c files \*- it must be
+made from .o files. Likewise, if you change
+.CW defs.h ,
+it isn't the .c files that need to be re-created, it's the .o files.
+If you think of dependencies in these terms \*- which files (targets)
+need to be created from which files (sources) \*- you should have no problems.
+.LP
+An important thing to notice about the above example, is that all the
+\&.o files appear as targets on more than one line. This is perfectly
+all right: the target is made to depend on all the sources mentioned
+on all the dependency lines. E.g.
+.CW a.o
+depends on both
+.CW defs.h
+and
+.CW a.c .
+.Ix 0 ref dependency
+.No
+.LP
+The order of the dependency lines in the makefile is
+important: the first target on the first dependency line in the
+makefile will be the one that gets made if you don't say otherwise.
+That's why
+.CW program
+comes first in the example makefile, above.
+.LP
+Both targets and sources may contain the standard C-Shell wildcard
+characters
+.CW { , (
+.CW } ,
+.CW * ,
+.CW ? ,
+.CW [ ,
+and
+.CW ] ),
+but the non-curly-brace ones may only appear in the final component
+(the file portion) of the target or source. The characters mean the
+following things:
+.IP \fB{}\fP
+These enclose a comma-separated list of options and cause the pattern
+to be expanded once for each element of the list. Each expansion
+contains a different element. For example, 
+.CW src/{whiffle,beep,fish}.c
+expands to the three words
+.CW src/whiffle.c ,
+.CW src/beep.c ,
+and 
+.CW src/fish.c .
+These braces may be nested and, unlike the other wildcard characters,
+the resulting words need not be actual files. All other wildcard
+characters are expanded using the files that exist when PMake is
+started.
+.IP \fB*\fP
+This matches zero or more characters of any sort. 
+.CW src/*.c
+will expand to the same three words as above as long as 
+.CW src
+contains those three files (and no other files that end in 
+.CW .c ).
+.IP \fB?\fP
+Matches any single character.
+.IP \fB[]\fP
+This is known as a character class and contains either a list of
+single characters, or a series of character ranges 
+.CW a-z , (
+for example means all characters between a and z), or both. It matches
+any single character contained in the list. E.g.
+.CW [A-Za-z]
+will match all letters, while
+.CW [0123456789]
+will match all numbers.
+.xH 2 Shell Commands
+.LP
+``Isn't that nice,'' you say to yourself, ``but how are files
+actually `re-created,' as he likes to spell it?''
+The re-creation is accomplished by commands you place in the makefile.
+These commands are passed to the Bourne shell (better known as
+``/bin/sh'') to be executed and are
+.Ix 0 ref shell
+.Ix 0 ref re-creation
+.Ix 0 ref update
+expected to do what's necessary to update the target file (PMake
+doesn't actually check to see if the target was created. It just
+assumes it's there).
+.Ix 0 ref target
+.LP
+Shell commands in a makefile look a lot like shell commands you would
+type at a terminal, with one important exception: each command in a
+makefile
+.I must
+be preceded by at least one tab.
+.LP
+Each target has associated with it a shell script made up of
+one or more of these shell commands. The creation script for a target
+should immediately follow the dependency line for that target. While
+any given target may appear on more than one dependency line, only one
+of these dependency lines may be followed by a creation script, unless
+the `::' operator was used on the dependency line.
+.Ix 0 ref operator double-colon
+.Ix 0 ref ::
+.No
+.LP
+If the double-colon was used, each dependency line for the target
+may be followed by a shell script. That script will only be executed
+if the target on the associated dependency line is out-of-date with
+respect to the sources on that line, according to the rules I gave
+earlier.
+I'll give you a good example of this later on.
+.LP
+To expand on the earlier makefile, you might add commands as follows:
+.DS
+program         : a.o b.o c.o
+        cc a.o b.o c.o \-o program
+a.o b.o c.o     : defs.h
+a.o             : a.c
+        cc \-c a.c
+b.o             : b.c
+        cc \-c b.c
+c.o             : c.c
+        cc \-c c.c
+.DE
+.LP
+Something you should remember when writing a makefile is, the
+commands will be executed if the
+.I target
+on the dependency line is out-of-date, not the sources.
+.Ix 0 ref target
+.Ix 0 ref source
+.Ix 0 ref out-of-date
+In this example, the command
+.CW "cc \-c a.c" '' ``
+will be executed if
+.CW a.o
+is out-of-date. Because of the `:' operator,
+.Ix 0 ref :
+.Ix 0 ref operator colon
+this means that should
+.CW a.c
+.I or
+.CW defs.h
+have been modified more recently than
+.CW a.o ,
+the command will be executed
+.CW a.o "\&" (
+will be considered out-of-date).
+.Ix 0 ref out-of-date
+.LP
+Remember how I said the only difference between a makefile shell
+command and a regular shell command was the leading tab? I lied. There
+is another way in which makefile commands differ from regular ones.
+The first two characters after the initial whitespace are treated
+specially.
+If they are any combination of `@' and `\-', they cause PMake to do
+different things.
+.LP
+In most cases, shell commands are printed before they're
+actually executed. This is to keep you informed of what's going on. If
+an `@' appears, however, this echoing is suppressed. In the case of an
+.CW echo
+command, say
+.CW "echo Linking index" ,'' ``
+it would be
+rather silly to see
+.DS
+echo Linking index
+Linking index
+.DE
+.LP
+so PMake allows you to place an `@' before the command
+.CW "@echo Linking index" '') (``
+to prevent the command from being printed.
+.LP
+The other special character is the `\-'. In case you didn't know,
+shell commands finish with a certain ``exit status.'' This status is
+made available by the operating system to whatever program invoked the
+command. Normally this status will be 0 if everything went ok and
+non-zero if something went wrong. For this reason, PMake will consider
+an error to have occurred if one of the shells it invokes returns a non-zero
+status. When it detects an error, PMake's usual action is to abort
+whatever it's doing and exit with a non-zero status itself (any other
+targets that were being created will continue being made, but nothing
+new will be started. PMake will exit after the last job finishes).
+This behavior can be altered, however, by placing a `\-' at the front
+of a command
+.CW "\-mv index index.old" ''), (``
+certain command-line arguments,
+or doing other things, to be detailed later. In such
+a case, the non-zero status is simply ignored and PMake keeps chugging
+along.
+.No
+.LP
+Because all the commands are given to a single shell to execute, such
+things as setting shell variables, changing directories, etc., last
+beyond the command in which they are found. This also allows shell
+compound commands (like
+.CW for
+loops) to be entered in a natural manner.
+Since this could cause problems for some makefiles that depend on
+each command being executed by a single shell, PMake has a
+.B \-B
+.Ix 0 ref compatibility
+.Ix 0 ref flags -B
+flag (it stands for backwards-compatible) that forces each command to
+be given to a separate shell. It also does several other things, all
+of which I discourage since they are now old-fashioned.\|.\|.\|.
+.No
+.LP
+A target's shell script is fed to the shell on its (the shell's) input stream.
+This means that any commands, such as
+.CW ci
+that need to get input from the terminal won't work right \*- they'll
+get the shell's input, something they probably won't find to their
+liking. A simple way around this is to give a command like this:
+.DS
+ci $(SRCS) < /dev/tty
+.DE
+This would force the program's input to come from the terminal. If you
+can't do this for some reason, your only other alternative is to use
+PMake in its fullest compatibility mode. See 
+.B Compatibility
+in chapter 4.
+.Ix 0 ref compatibility
+.LP
+.xH 2 Variables
+.LP
+PMake, like Make before it, has the ability to save text in variables
+to be recalled later at your convenience. Variables in PMake are used
+much like variables in the shell and, by tradition, consist of
+all upper-case letters (you don't
+.I have
+to use all upper-case letters.
+In fact there's nothing to stop you from calling a variable
+.CW @^&$%$ .
+Just tradition). Variables are assigned-to using lines of the form
+.Ix 0 def variable assignment
+.DS
+VARIABLE = value
+.DE
+.Ix 0 def variable assignment
+appended-to by
+.DS
+VARIABLE += value
+.DE
+.Ix 0 def variable appending
+.Ix 0 def variable assignment appended
+.Ix 0 def +=
+conditionally assigned-to (if the variable isn't already defined) by
+.DS
+VARIABLE ?= value
+.DE
+.Ix 0 def variable assignment conditional
+.Ix 0 def ?=
+and assigned-to with expansion (i.e. the value is expanded (see below)
+before being assigned to the variable\*-useful for placing a value at
+the beginning of a variable, or other things) by
+.DS
+VARIABLE := value
+.DE
+.Ix 0 def variable assignment expanded
+.Ix 0 def :=
+.LP
+Any whitespace before
+.I value
+is stripped off. When appending, a space is placed between the old
+value and the stuff being appended.
+.LP
+The final way a variable may be assigned to is using
+.DS
+VARIABLE != shell-command
+.DE
+.Ix 0 def variable assignment shell-output
+.Ix 0 def !=
+In this case, 
+.I shell-command
+has all its variables expanded (see below) and is passed off to a
+shell to execute. The output of the shell is then placed in the
+variable. Any newlines (other than the final one) are replaced by
+spaces before the assignment is made. This is typically used to find
+the current directory via a line like:
+.DS
+CWD             != pwd
+.DE
+.LP
+.B Note:
+this is intended to be used to execute commands that produce small amounts
+of output (e.g. ``pwd''). The implementation is less than intelligent and will
+likely freeze if you execute something that produces thousands of
+bytes of output (8 Kb is the limit on many UNIX systems).
+.LP
+The value of a variable may be retrieved by enclosing the variable
+name in parentheses or curly braces and preceding the whole thing
+with a dollar sign.
+.LP
+For example, to set the variable CFLAGS to the string
+.CW "\-I/sprite/src/lib/libc \-O" ,'' ``
+you would place a line
+.DS
+CFLAGS = \-I/sprite/src/lib/libc \-O
+.DE
+in the makefile and use the word
+.CW "$(CFLAGS)"
+wherever you would like the string
+.CW "\-I/sprite/src/lib/libc \-O"
+to appear. This is called variable expansion.
+.Ix 0 def variable expansion
+.No
+.LP
+Unlike Make, PMake will not expand a variable unless it knows
+the variable exists. E.g. if you have a
+.CW "${i}"
+in a shell command and you have not assigned a value to the variable
+.CW i 
+(the empty string is considered a value, by the way), where Make would have
+substituted the empty string, PMake will leave the
+.CW "${i}"
+alone.
+To keep PMake from substituting for a variable it knows, precede the
+dollar sign with another dollar sign.
+(e.g. to pass
+.CW "${HOME}"
+to the shell, use
+.CW "$${HOME}" ).
+This causes PMake, in effect, to expand the
+.CW $
+macro, which expands to a single
+.CW $ .
+For compatibility, Make's style of variable expansion will be used
+if you invoke PMake with any of the compatibility flags (\c
+.B \-V ,
+.B \-B
+or
+.B \-M .
+The
+.B \-V
+flag alters just the variable expansion).
+.Ix 0 ref flags -V
+.Ix 0 ref flags -B
+.Ix 0 ref flags -M
+.Ix 0 ref compatibility
+.LP
+.Ix 0 ref variable expansion
+There are two different times at which variable expansion occurs:
+When parsing a dependency line, the expansion occurs immediately
+upon reading the line. If any variable used on a dependency line is
+undefined, PMake will print a message and exit.
+Variables in shell commands are expanded when the command is
+executed.
+Variables used inside another variable are expanded whenever the outer
+variable is expanded (the expansion of an inner variable has no effect
+on the outer variable. I.e. if the outer variable is used on a dependency
+line and in a shell command, and the inner variable changes value
+between when the dependency line is read and the shell command is
+executed, two different values will be substituted for the outer
+variable).
+.Ix 0 def variable types
+.LP
+Variables come in four flavors, though they are all expanded the same
+and all look about the same. They are (in order of expanding scope):
+.RS
+.IP \(bu 2
+Local variables.
+.Ix 0 ref variable local
+.IP \(bu 2
+Command-line variables.
+.Ix 0 ref variable command-line
+.IP \(bu 2
+Global variables.
+.Ix 0 ref variable global
+.IP \(bu 2
+Environment variables.
+.Ix 0 ref variable environment
+.RE
+.LP
+The classification of variables doesn't matter much, except that the
+classes are searched from the top (local) to the bottom (environment)
+when looking up a variable. The first one found wins.
+.xH 3 Local Variables
+.LP
+.Ix 0 def variable local
+Each target can have as many as seven local variables. These are
+variables that are only ``visible'' within that target's shell script
+and contain such things as the target's name, all of its sources (from
+all its dependency lines), those sources that were out-of-date, etc.
+Four local variables are defined for all targets. They are:
+.RS
+.IP ".TARGET"
+.Ix 0 def variable local .TARGET
+.Ix 0 def .TARGET
+The name of the target.
+.IP ".OODATE"
+.Ix 0 def variable local .OODATE
+.Ix 0 def .OODATE
+The list of the sources for the target that were considered out-of-date.
+The order in the list is not guaranteed to be the same as the order in
+which the dependencies were given.
+.IP ".ALLSRC"
+.Ix 0 def variable local .ALLSRC
+.Ix 0 def .ALLSRC
+The list of all sources for this target in the order in which they
+were given.
+.IP ".PREFIX"
+.Ix 0 def variable local .PREFIX
+.Ix 0 def .PREFIX
+The target without its suffix and without any leading path. E.g. for
+the target
+.CW ../../lib/compat/fsRead.c ,
+this variable would contain
+.CW fsRead .
+.RE
+.LP
+Three other local variables are set only for certain targets under
+special circumstances. These are the ``.IMPSRC,''
+.Ix 0 ref variable local .IMPSRC
+.Ix 0 ref .IMPSRC
+``.ARCHIVE,''
+.Ix 0 ref variable local .ARCHIVE
+.Ix 0 ref .ARCHIVE
+and ``.MEMBER''
+.Ix 0 ref variable local .MEMBER
+.Ix 0 ref .MEMBER
+variables. When they are set and how they are used is described later.
+.LP
+Four of these variables may be used in sources as well as in shell
+scripts.
+.Ix 0 def "dynamic source"
+.Ix 0 def source dynamic
+These are ``.TARGET'', ``.PREFIX'', ``.ARCHIVE'' and ``.MEMBER''. The
+variables in the sources are expanded once for each target on the
+dependency line, providing what is known as a ``dynamic source,''
+.Rd 0
+allowing you to specify several dependency lines at once. For example,
+.DS
+$(OBJS)         : $(.PREFIX).c
+.DE
+will create a dependency between each object file and its
+corresponding C source file.
+.xH 3 Command-line Variables
+.LP
+.Ix 0 def variable command-line
+Command-line variables are set when PMake is first invoked by giving a
+variable assignment as one of the arguments. For example,
+.DS
+pmake "CFLAGS = -I/sprite/src/lib/libc -O"
+.DE
+would make 
+.CW CFLAGS
+be a command-line variable with the given value. Any assignments to
+.CW CFLAGS
+in the makefile will have no effect, because once it
+is set, there is (almost) nothing you can do to change a command-line
+variable (the search order, you see). Command-line variables may be
+set using any of the four assignment operators, though only
+.CW =
+and
+.CW ?=
+behave as you would expect them to, mostly because assignments to
+command-line variables are performed before the makefile is read, thus
+the values set in the makefile are unavailable at the time.
+.CW +=
+.Ix 0 ref +=
+.Ix 0 ref variable assignment appended
+is the same as
+.CW = ,
+because the old value of the variable is sought only in the scope in
+which the assignment is taking place (for reasons of efficiency that I
+won't get into here).
+.CW :=
+and
+.CW ?=
+.Ix 0 ref :=
+.Ix 0 ref ?=
+.Ix 0 ref variable assignment expanded
+.Ix 0 ref variable assignment conditional
+will work if the only variables used are in the environment.
+.CW !=
+is sort of pointless to use from the command line, since the same
+effect can no doubt be accomplished using the shell's own command
+substitution mechanisms (backquotes and all that).
+.xH 3 Global Variables
+.LP
+.Ix 0 def variable global
+Global variables are those set or appended-to in the makefile.
+There are two classes of global variables: those you set and those PMake sets.
+As I said before, the ones you set can have any name you want them to have,
+except they may not contain a colon or an exclamation point.
+The variables PMake sets (almost) always begin with a
+period and always contain upper-case letters, only. The variables are
+as follows:
+.RS
+.IP .PMAKE
+.Ix 0 def variable global .PMAKE
+.Ix 0 def .PMAKE
+.Ix 0 def variable global MAKE
+.Ix 0 def MAKE
+The name by which PMake was invoked is stored in this variable. For
+compatibility, the name is also stored in the MAKE variable.
+.IP .MAKEFLAGS
+.Ix 0 def variable global .MAKEFLAGS
+.Ix 0 def .MAKEFLAGS variable
+.Ix 0 def variable global MFLAGS
+.Ix 0 def MFLAGS
+All the relevant flags with which PMake was invoked. This does not
+include such things as
+.B \-f
+or variable assignments. Again for compatibility, this value is stored
+in the MFLAGS variable as well.
+.RE
+.LP
+Two other variables, ``.INCLUDES'' and ``.LIBS,'' are covered in the
+section on special targets in chapter 3.
+.Ix 0 ref variable global .INCLUDES
+.Ix 0 ref variable global .LIBS
+.LP
+Global variables may be deleted using lines of the form:
+.Ix 0 def #undef
+.Ix 0 def variable deletion
+.DS
+#undef \fIvariable\fP
+.DE
+The
+.CW # ' `
+must be the first character on the line. Note that this may only be
+done on global variables.
+.xH 3 Environment Variables
+.LP
+.Ix 0 def variable environment
+Environment variables are passed by the shell that invoked PMake and
+are given by PMake to each shell it invokes. They are expanded like
+any other variable, but they cannot be altered in any way.
+.LP
+One special environment variable,
+.CW PMAKE ,
+.Ix 0 def variable environment PMAKE
+is examined by PMake for command-line flags, variable assignments,
+etc., it should always use. This variable is examined before the
+actual arguments to PMake are. In addition, all flags given to PMake,
+either through the
+.CW PMAKE
+variable or on the command line, are placed in this environment
+variable and exported to each shell PMake executes. Thus recursive
+invocations of PMake automatically receive the same flags as the
+top-most one.
+.LP
+Using all these variables, you can compress the sample makefile even more:
+.DS
+OBJS            = a.o b.o c.o
+program         : $(OBJS)
+        cc $(.ALLSRC) \-o $(.TARGET)
+$(OBJS)         : defs.h
+a.o             : a.c
+        cc \-c a.c
+b.o             : b.c
+        cc \-c b.c
+c.o             : c.c
+        cc \-c c.c
+.DE
+.Ix 0 ref variable local .ALLSRC
+.Ix 0 ref .ALLSRC
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref .TARGET
+.Rd 3
+.xH 2 Comments
+.LP
+.Ix 0 def comments
+Comments in a makefile start with a `#' character and extend to the
+end of the line. They may appear
+anywhere you want them, except in a shell command (though the shell
+will treat it as a comment, too). If, for some reason, you need to use the `#'
+in a variable or on a dependency line, put a backslash in front of it.
+PMake will compress the two into a single `#' (Note: this isn't true
+if PMake is operating in full-compatibility mode).
+.Ix 0 ref flags -M
+.Ix 0 ref compatibility
+.xH 2 Parallelism
+.No
+.LP
+PMake was specifically designed to re-create several targets at once,
+when possible. You do not have to do anything special to cause this to
+happen (unless PMake was configured to not act in parallel, in which
+case you will have to make use of the
+.B \-L
+and
+.B \-J
+flags (see below)),
+.Ix 0 ref flags -L
+.Ix 0 ref flags -J
+but you do have to be careful at times.
+.LP
+There are several problems you are likely to encounter. One is
+that some makefiles (and programs) are written in such a way that it is
+impossible for two targets to be made at once. The program
+.CW xstr ,
+for example,
+always modifies the files
+.CW strings
+and
+.CW x.c .
+There is no way to change it. Thus you cannot run two of them at once
+without something being trashed. Similarly, if you have commands
+in the makefile that always send output to the same file, you will not
+be able to make more than one target at once unless you change the
+file you use. You can, for instance, add a
+.CW $$$$
+to the end of the file name to tack on the process ID of the shell
+executing the command (each
+.CW $$
+expands to a single
+.CW $ ,
+thus giving you the shell variable
+.CW $$ ).
+Since only one shell is used for all the
+commands, you'll get the same file name for each command in the
+script.
+.LP
+The other problem comes from improperly-specified dependencies that
+worked in Make because of its sequential, depth-first way of examining
+them. While I don't want to go into depth on how PMake
+works (look in chapter 4 if you're interested), I will warn you that
+files in two different ``levels'' of the dependency tree may be
+examined in a different order in PMake than they were in Make. For
+example, given the makefile
+.DS
+a               : b c
+b               : d
+.DE
+PMake will examine the targets in the order
+.CW c ,
+.CW d ,
+.CW b ,
+.CW a .
+If the makefile's author expected PMake to abort before making
+.CW c
+if an error occurred while making
+.CW b ,
+or if
+.CW b
+needed to exist before
+.CW c
+was made,
+s/he will be sorely disappointed. The dependencies are
+incomplete, since in both these cases,
+.CW c
+would depend on
+.CW b .
+So watch out.
+.LP
+Another problem you may face is that, while PMake is set up to handle the
+output from multiple jobs in a graceful fashion, the same is not so for input.
+It has no way to regulate input to different jobs,
+so if you use the redirection from
+.CW /dev/tty
+I mentioned earlier, you must be careful not to run two of the jobs at once.
+.xH 2 Writing and Debugging a Makefile
+.LP
+Now you know most of what's in a makefile, what do you do next? There
+are two choices: (1) use one of the uncommonly-available makefile
+generators or (2) write your own makefile (I leave out the third choice of
+ignoring PMake and doing everything by hand as being beyond the bounds
+of common sense).
+.LP
+When faced with the writing of a makefile, it is usually best to start
+from first principles: just what
+.I are
+you trying to do? What do you want the makefile finally to produce?
+.LP
+To begin with a somewhat traditional example, let's say you need to
+write a makefile to create a program,
+.CW expr ,
+that takes standard infix expressions and converts them to prefix form (for
+no readily apparent reason). You've got three source files, in C, that
+make up the program:
+.CW main.c ,
+.CW parse.c ,
+and
+.CW output.c .
+Harking back to my pithy advice about dependency lines, you write the
+first line of the file:
+.DS
+expr            : main.o parse.o output.o
+.DE
+because you remember
+.CW expr
+is made from
+.CW .o
+files, not
+.CW .c
+files. Similarly for the
+.CW .o
+files you produce the lines:
+.DS
+main.o          : main.c
+parse.o         : parse.c
+output.o        : output.c
+main.o parse.o output.o : defs.h
+.DE
+.LP
+Great. You've now got the dependencies specified. What you need now is
+commands. These commands, remember, must produce the target on the
+dependency line, usually by using the sources you've listed.
+You remember about local variables? Good, so it should come
+to you as no surprise when you write
+.DS
+expr            : main.o parse.o output.o
+        cc -o $(.TARGET) $(.ALLSRC)
+.DE
+Why use the variables? If your program grows to produce postfix
+expressions too (which, of course, requires a name change or two), it
+is one fewer place you have to change the file. You cannot do this for
+the object files, however, because they depend on their corresponding
+source files
+.I and
+.CW defs.h ,
+thus if you said
+.DS
+	cc -c $(.ALLSRC)
+.DE
+you'd get (for
+.CW main.o ):
+.DS
+	cc -c main.c defs.h
+.DE
+which is wrong. So you round out the makefile with these lines:
+.DS
+main.o          : main.c
+        cc -c main.c
+parse.o         : parse.c
+        cc -c parse.c
+output.o        : output.c
+        cc -c output.c
+.DE
+.LP
+The makefile is now complete and will, in fact, create the program you
+want it to without unnecessary compilations or excessive typing on
+your part. There are two things wrong with it, however (aside from it
+being altogether too long, something I'll address in chapter 3):
+.IP 1)
+The string
+.CW "main.o parse.o output.o" '' ``
+is repeated twice, necessitating two changes when you add postfix
+(you were planning on that, weren't you?). This is in direct violation
+of de Boor's First Rule of writing makefiles:
+.QP
+.I
+Anything that needs to be written more than once
+should be placed in a variable.
+.IP "\&"
+I cannot emphasize this enough as being very important to the
+maintenance of a makefile and its program.
+.IP 2)
+There is no way to alter the way compilations are performed short of
+editing the makefile and making the change in all places. This is evil
+and violates de Boor's Second Rule, which follows directly from the
+first:
+.QP
+.I
+Any flags or programs used inside a makefile should be placed in a variable so
+they may be changed, temporarily or permanently, with the greatest ease.
+.LP
+The makefile should more properly read:
+.DS
+OBJS            = main.o parse.o output.o
+expr            : $(OBJS)
+        $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
+main.o          : main.c
+        $(CC) $(CFLAGS) -c main.c
+parse.o         : parse.c
+        $(CC) $(CFLAGS) -c parse.c
+output.o        : output.c
+        $(CC) $(CFLAGS) -c output.c
+$(OBJS)         : defs.h
+.DE
+Alternatively, if you like the idea of dynamic sources mentioned in
+section 2.3.1,
+.Rm 0 2.3.1
+.Rd 4
+.Ix 0 ref "dynamic source"
+.Ix 0 ref source dynamic
+you could write it like this:
+.DS
+OBJS            = main.o parse.o output.o
+expr            : $(OBJS)
+        $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC)
+$(OBJS)         : $(.PREFIX).c defs.h
+        $(CC) $(CFLAGS) -c $(.PREFIX).c
+.DE
+These two rules and examples lead to de Boor's First Corollary:
+.QP
+.I
+Variables are your friends.
+.LP
+Once you've written the makefile comes the sometimes-difficult task of
+.Ix 0 ref debugging
+making sure the darn thing works. Your most helpful tool to make sure
+the makefile is at least syntactically correct is the
+.B \-n
+.Ix 0 ref flags -n
+flag, which allows you to see if PMake will choke on the makefile. The
+second thing the
+.B \-n
+flag lets you do is see what PMake would do without it actually doing
+it, thus you can make sure the right commands would be executed were
+you to give PMake its head.
+.LP
+When you find your makefile isn't behaving as you hoped, the first
+question that comes to mind (after ``What time is it, anyway?'') is
+``Why not?'' In answering this, two flags will serve you well:
+.CW "-d m" '' ``
+.Ix 0 ref flags -d
+and
+.CW "-p 2" .'' ``
+.Ix 0 ref flags -p
+The first causes PMake to tell you as it examines each target in the
+makefile and indicate why it is deciding whatever it is deciding. You
+can then use the information printed for other targets to see where
+you went wrong. The
+.CW "-p 2" '' ``
+flag makes PMake print out its internal state when it is done,
+allowing you to see that you forgot to make that one chapter depend on
+that file of macros you just got a new version of. The output from
+.CW "-p 2" '' ``
+is intended to resemble closely a real makefile, but with additional
+information provided and with variables expanded in those commands
+PMake actually printed or executed.
+.LP
+Something to be especially careful about is circular dependencies.
+.Ix 0 def dependency circular
+E.g.
+.DS
+a		: b
+b		: c d
+d		: a
+.DE
+In this case, because of how PMake works,
+.CW c
+is the only thing PMake will examine, because
+.CW d
+and
+.CW a
+will effectively fall off the edge of the universe, making it
+impossible to examine
+.CW b
+(or them, for that matter).
+PMake will tell you (if run in its normal mode) all the targets
+involved in any cycle it looked at (i.e. if you have two cycles in the
+graph (naughty, naughty), but only try to make a target in one of
+them, PMake will only tell you about that one. You'll have to try to
+make the other to find the second cycle). When run as Make, it will
+only print the first target in the cycle.
+.xH 2 Invoking PMake
+.LP
+.Ix 0 ref flags
+.Ix 0 ref arguments
+.Ix 0 ref usage
+PMake comes with a wide variety of flags to choose from.
+They may appear in any order, interspersed with command-line variable
+assignments and targets to create.
+The flags are as follows:
+.IP "\fB\-d\fP \fIwhat\fP"
+.Ix 0 def flags -d
+.Ix 0 ref debugging
+This causes PMake to spew out debugging information that
+may prove useful to you. If you can't
+figure out why PMake is doing what it's doing, you might try using
+this flag. The
+.I what
+parameter is a string of single characters that tell PMake what
+aspects you are interested in. Most of what I describe will make
+little sense to you, unless you've dealt with Make before. Just
+remember where this table is and come back to it as you read on.
+The characters and the information they produce are as follows:
+.RS
+.IP a
+Archive searching and caching.
+.IP c
+Conditional evaluation.
+.IP d
+The searching and caching of directories.
+.IP j
+Various snippets of information related to the running of the multiple
+shells. Not particularly interesting.
+.IP m
+The making of each target: what target is being examined; when it was
+last modified; whether it is out-of-date; etc.
+.IP p
+Makefile parsing.
+.IP r
+Remote execution.
+.IP s
+The application of suffix-transformation rules. (See chapter 3)
+.IP t
+The maintenance of the list of targets.
+.IP v
+Variable assignment.
+.RE
+.IP "\&"
+Of these all, the
+.CW m
+and
+.CW s
+letters will be most useful to you.
+If the
+.B \-d
+is the final argument or the argument from which it would get these
+key letters (see below for a note about which argument would be used)
+begins with a
+.B \- ,
+all of these debugging flags will be set, resulting in massive amounts
+of output.
+.IP "\fB\-f\fP \fImakefile\fP"
+.Ix 0 def flags -f
+Specify a makefile to read different from the standard makefiles
+.CW Makefile "\&" (
+or
+.CW makefile ).
+.Ix 0 ref makefile default
+.Ix 0 ref makefile other
+If
+.I makefile
+is ``\-'', PMake uses the standard input. This is useful for making
+quick and dirty makefiles.\|.\|.
+.Ix 0 ref makefile "quick and dirty"
+.IP \fB\-h\fP
+.Ix 0 def flags -h
+Prints out a summary of the various flags PMake accepts. It can also
+be used to find out what level of concurrency was compiled into the
+version of PMake you are using (look at
+.B \-J
+and
+.B \-L )
+and various other information on how PMake was configured.
+.Ix 0 ref configuration
+.Ix 0 ref makefilesystem
+.IP \fB\-i\fP
+.Ix 0 def flags -i
+If you give this flag, PMake will ignore non-zero status returned
+by any of its shells. It's like placing a `\-' before all the commands
+in the makefile.
+.IP \fB\-k\fP
+.Ix 0 def flags -k
+This is similar to
+.B \-i
+in that it allows PMake to continue when it sees an error, but unlike
+.B \-i ,
+where PMake continues blithely as if nothing went wrong,
+.B \-k
+causes it to recognize the error and only continue work on those
+things that don't depend on the target, either directly or indirectly (through
+depending on something that depends on it), whose creation returned the error.
+The `k' is for ``keep going''.\|.\|.
+.Ix 0 ref target
+.IP \fB\-l\fP
+.Ix 0 def flags -l
+PMake has the ability to lock a directory against other
+people executing it in the same directory (by means of a file called
+``LOCK.make'' that it creates and checks for in the directory). This
+is a Good Thing because two people doing the same thing in the same place
+can be disastrous for the final product (too many cooks and all that).
+Whether this locking is the default is up to your system
+administrator. If locking is on,
+.B \-l
+will turn it off, and vice versa. Note that this locking will not
+prevent \fIyou\fP from invoking PMake twice in the same place \*- if
+you own the lock file, PMake will warn you about it but continue to execute.
+.IP "\fB\-m\fP \fIdirectory\fP"
+.Ix 0 def flags -m
+Tells PMake another place to search for included makefiles via the <...>
+style.  Several
+.B \-m
+options can be given to form a search path.  If this construct is used the
+default system makefile search path is completely overridden.
+To be explained in chapter 3, section 3.2.
+.Rm 2 3.2
+.IP \fB\-n\fP
+.Ix 0 def flags -n
+This flag tells PMake not to execute the commands needed to update the
+out-of-date targets in the makefile. Rather, PMake will simply print
+the commands it would have executed and exit. This is particularly
+useful for checking the correctness of a makefile. If PMake doesn't do
+what you expect it to, it's a good chance the makefile is wrong.
+.IP "\fB\-p\fP \fInumber\fP"
+.Ix 0 def flags -p
+.Ix 0 ref debugging
+This causes PMake to print its input in a reasonable form, though
+not necessarily one that would make immediate sense to anyone but me. The
+.I number
+is a bitwise-or of 1 and 2 where 1 means it should print the input
+before doing any processing and 2 says it should print it after
+everything has been re-created. Thus
+.CW "\-p 3"
+would print it twice\*-once before processing and once after (you
+might find the difference between the two interesting). This is mostly
+useful to me, but you may find it informative in some bizarre circumstances.
+.IP \fB\-q\fP
+.Ix 0 def flags -q
+If you give PMake this flag, it will not try to re-create anything. It
+will just see if anything is out-of-date and exit non-zero if so.
+.IP \fB\-r\fP
+.Ix 0 def flags -r
+When PMake starts up, it reads a default makefile that tells it what
+sort of system it's on and gives it some idea of what to do if you
+don't tell it anything. I'll tell you about it in chapter 3. If you
+give this flag, PMake won't read the default makefile.
+.IP \fB\-s\fP
+.Ix 0 def flags -s
+This causes PMake to not print commands before they're executed. It
+is the equivalent of putting an `@' before every command in the
+makefile.
+.IP \fB\-t\fP
+.Ix 0 def flags -t
+Rather than try to re-create a target, PMake will simply ``touch'' it
+so as to make it appear up-to-date. If the target didn't exist before,
+it will when PMake finishes, but if the target did exist, it will
+appear to have been updated.
+.IP \fB\-v\fP
+.Ix 0 def flags -v
+This is a mixed-compatibility flag intended to mimic the System V
+version of Make. It is the same as giving
+.B \-B ,
+and
+.B \-V
+as well as turning off directory locking. Targets can still be created
+in parallel, however. This is the mode PMake will enter if it is
+invoked either as
+.CW smake '' ``
+or
+.CW vmake ''. ``
+.IP \fB\-x\fP
+.Ix 0 def flags -x
+This tells PMake it's ok to export jobs to other machines, if they're
+available. It is used when running in Make mode, as exporting in this
+mode tends to make things run slower than if the commands were just
+executed locally.
+.IP \fB\-B\fP
+.Ix 0 ref compatibility
+.Ix 0 def flags -B
+Forces PMake to be as backwards-compatible with Make as possible while
+still being itself.
+This includes:
+.RS
+.IP \(bu 2
+Executing one shell per shell command
+.IP \(bu 2
+Expanding anything that looks even vaguely like a variable, with the
+empty string replacing any variable PMake doesn't know.
+.IP \(bu 2
+Refusing to allow you to escape a `#' with a backslash.
+.IP \(bu 2
+Permitting undefined variables on dependency lines and conditionals
+(see below). Normally this causes PMake to abort.
+.RE
+.IP \fB\-C\fP
+.Ix 0 def flags -C
+This nullifies any and all compatibility mode flags you may have given
+or implied up to the time the
+.B \-C
+is encountered. It is useful mostly in a makefile that you wrote for PMake
+to avoid bad things happening when someone runs PMake as
+.CW make '' ``
+or has things set in the environment that tell it to be compatible.
+.B \-C
+is
+.I not
+placed in the
+.CW PMAKE
+environment variable or the
+.CW .MAKEFLAGS
+or
+.CW MFLAGS
+global variables.
+.Ix 0 ref variable environment PMAKE
+.Ix 0 ref variable global .MAKEFLAGS
+.Ix 0 ref variable global MFLAGS
+.Ix 0 ref .MAKEFLAGS variable
+.Ix 0 ref MFLAGS
+.IP "\fB\-D\fP \fIvariable\fP"
+.Ix 0 def flags -D
+Allows you to define a variable to have 
+.CW 1 '' ``
+as its value.  The variable is a global variable, not a command-line
+variable. This is useful mostly for people who are used to the C
+compiler arguments and those using conditionals, which I'll get into
+in section 4.3
+.Rm 1 4.3
+.IP "\fB\-I\fP \fIdirectory\fP"
+.Ix 0 def flags -I
+Tells PMake another place to search for included makefiles. Yet
+another thing to be explained in chapter 3 (section 3.2, to be
+precise).
+.Rm 2 3.2
+.IP "\fB\-J\fP \fInumber\fP"
+.Ix 0 def flags -J
+Gives the absolute maximum number of targets to create at once on both
+local and remote machines.
+.IP "\fB\-L\fP \fInumber\fP"
+.Ix 0 def flags -L
+This specifies the maximum number of targets to create on the local
+machine at once. This may be 0, though you should be wary of doing
+this, as PMake may hang until a remote machine becomes available, if
+one is not available when it is started.
+.IP \fB\-M\fP
+.Ix 0 ref compatibility
+.Ix 0 def flags -M
+This is the flag that provides absolute, complete, full compatibility
+with Make. It still allows you to use all but a few of the features of
+PMake, but it is non-parallel. This is the mode PMake enters if you
+call it
+.CW make .'' ``
+.IP \fB\-P\fP
+.Ix 0 def flags -P
+.Ix 0 ref "output control"
+When creating targets in parallel, several shells are executing at
+once, each wanting to write its own two cent's-worth to the screen.
+This output must be captured by PMake in some way in order to prevent
+the screen from being filled with garbage even more indecipherable
+than you usually see. PMake has two ways of doing this, one of which
+provides for much cleaner output and a clear separation between the
+output of different jobs, the other of which provides a more immediate
+response so one can tell what is really happening. The former is done
+by notifying you when the creation of a target starts, capturing the
+output and transferring it to the screen all at once when the job
+finishes. The latter is done by catching the output of the shell (and
+its children) and buffering it until an entire line is received, then
+printing that line preceded by an indication of which job produced
+the output. Since I prefer this second method, it is the one used by
+default. The first method will be used if you give the
+.B \-P
+flag to PMake.
+.IP \fB\-V\fP
+.Ix 0 def flags -V
+As mentioned before, the
+.B \-V
+flag tells PMake to use Make's style of expanding variables,
+substituting the empty string for any variable it doesn't know.
+.IP \fB\-W\fP
+.Ix 0 def flags -W
+There are several times when PMake will print a message at you that is
+only a warning, i.e. it can continue to work in spite of your having
+done something silly (such as forgotten a leading tab for a shell
+command). Sometimes you are well aware of silly things you have done
+and would like PMake to stop bothering you. This flag tells it to shut
+up about anything non-fatal.
+.IP \fB\-X\fP
+.Ix 0 def flags -X
+This flag causes PMake to not attempt to export any jobs to another
+machine.
+.LP
+Several flags may follow a single `\-'. Those flags that require
+arguments take them from successive parameters. E.g.
+.DS
+pmake -fDnI server.mk DEBUG /chip2/X/server/include
+.DE
+will cause PMake to read
+.CW server.mk
+as the input makefile, define the variable
+.CW DEBUG
+as a global variable and look for included makefiles in the directory
+.CW /chip2/X/server/include .
+.xH 2 Summary
+.LP
+A makefile is made of four types of lines:
+.RS
+.IP \(bu 2
+Dependency lines
+.IP \(bu 2
+Creation commands
+.IP \(bu 2
+Variable assignments
+.IP \(bu 2
+Comments, include statements and conditional directives
+.RE
+.LP
+A dependency line is a list of one or more targets, an operator
+.CW : ', (`
+.CW :: ', `
+or
+.CW ! '), `
+and a list of zero or more sources. Sources may contain wildcards and
+certain local variables.
+.LP
+A creation command is a regular shell command preceded by a tab. In
+addition, if the first two characters after the tab (and other
+whitespace) are a combination of
+.CW @ ' `
+or
+.CW - ', `
+PMake will cause the command to not be printed (if the character is
+.CW @ ') `
+or errors from it to be ignored (if
+.CW - '). `
+A blank line, dependency line or variable assignment terminates a
+creation script. There may be only one creation script for each target
+with a
+.CW : ' `
+or
+.CW ! ' `
+operator.
+.LP
+Variables are places to store text. They may be unconditionally
+assigned-to using the
+.CW = ' `
+.Ix 0 ref =
+.Ix 0 ref variable assignment
+operator, appended-to using the
+.CW += ' `
+.Ix 0 ref +=
+.Ix 0 ref variable assignment appended
+operator, conditionally (if the variable is undefined) assigned-to
+with the
+.CW ?= ' `
+.Ix 0 ref ?=
+.Ix 0 ref variable assignment conditional
+operator, and assigned-to with variable expansion with the
+.CW := ' `
+.Ix 0 ref :=
+.Ix 0 ref variable assignment expanded
+operator. The output of a shell command may be assigned to a variable
+using the
+.CW != ' `
+.Ix 0 ref !=
+.Ix 0 ref variable assignment shell-output
+operator.  Variables may be expanded (their value inserted) by enclosing
+their name in parentheses or curly braces, preceded by a dollar sign.
+A dollar sign may be escaped with another dollar sign. Variables are
+not expanded if PMake doesn't know about them. There are seven local
+variables:
+.CW .TARGET ,
+.CW .ALLSRC ,
+.CW .OODATE ,
+.CW .PREFIX ,
+.CW .IMPSRC ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER .
+Four of them
+.CW .TARGET , (
+.CW .PREFIX ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER )
+may be used to specify ``dynamic sources.''
+.Ix 0 ref "dynamic source"
+.Ix 0 ref source dynamic
+Variables are good. Know them. Love them. Live them.
+.LP
+Debugging of makefiles is best accomplished using the
+.B \-n ,
+.B "\-d m" ,
+and
+.B "\-p 2"
+flags.
+.xH 2 Exercises
+.ce
+\s+4\fBTBA\fP\s0
+.xH 1 Short-cuts and Other Nice Things
+.LP
+Based on what I've told you so far, you may have gotten the impression
+that PMake is just a way of storing away commands and making sure you
+don't forget to compile something. Good. That's just what it is.
+However, the ways I've described have been inelegant, at best, and
+painful, at worst.
+This chapter contains things that make the
+writing of makefiles easier and the makefiles themselves shorter and
+easier to modify (and, occasionally, simpler). In this chapter, I
+assume you are somewhat more
+familiar with Sprite (or UNIX, if that's what you're using) than I did
+in chapter 2, just so you're on your toes.
+So without further ado...
+.xH 2 Transformation Rules
+.LP
+As you know, a file's name consists of two parts: a base name, which
+gives some hint as to the contents of the file, and a suffix, which
+usually indicates the format of the file.
+Over the years, as
+.UX
+has developed,
+naming conventions, with regard to suffixes, have also developed that have
+become almost as incontrovertible as Law. E.g. a file ending in
+.CW .c
+is assumed to contain C source code; one with a
+.CW .o
+suffix is assumed to be a compiled, relocatable object file that may
+be linked into any program; a file with a
+.CW .ms
+suffix is usually a text file to be processed by Troff with the \-ms
+macro package, and so on.
+One of the best aspects of both Make and PMake comes from their
+understanding of how the suffix of a file pertains to its contents and
+their ability to do things with a file based solely on its suffix. This
+ability comes from something known as a transformation rule. A
+transformation rule specifies how to change a file with one suffix
+into a file with another suffix.
+.LP
+A transformation rule looks much like a dependency line, except the
+target is made of two known suffixes stuck together. Suffixes are made
+known to PMake by placing them as sources on a dependency line whose
+target is the special target
+.CW .SUFFIXES .
+E.g.
+.DS
+\&.SUFFIXES       : .o .c
+\&.c.o            :
+        $(CC) $(CFLAGS) -c $(.IMPSRC)
+.DE
+The creation script attached to the target is used to transform a file with
+the first suffix (in this case,
+.CW .c )
+into a file with the second suffix (here,
+.CW .o ).
+In addition, the target inherits whatever attributes have been applied
+to the transformation rule.
+The simple rule given above says that to transform a C source file
+into an object file, you compile it using
+.CW cc
+with the
+.CW \-c
+flag.
+This rule is taken straight from the system makefile. Many
+transformation rules (and suffixes) are defined there, and I refer you
+to it for more examples (type
+.CW "pmake -h" '' ``
+to find out where it is).
+.LP
+There are several things to note about the transformation rule given
+above:
+.RS
+.IP 1)
+The
+.CW .IMPSRC 
+variable.
+.Ix 0 def variable local .IMPSRC
+.Ix 0 def .IMPSRC
+This variable is set to the ``implied source'' (the file from which
+the target is being created; the one with the first suffix), which, in this
+case, is the .c file.
+.IP 2)
+The
+.CW CFLAGS
+variable. Almost all of the transformation rules in the system
+makefile are set up using variables that you can alter in your
+makefile to tailor the rule to your needs. In this case, if you want
+all your C files to be compiled with the
+.B \-g
+flag, to provide information for
+.CW dbx ,
+you would set the
+.CW CFLAGS
+variable to contain
+.CW -g
+.CW "CFLAGS = -g" '') (``
+and PMake would take care of the rest.
+.RE
+.LP
+To give you a quick example, the makefile in 2.3.4 
+.Rm 3 2.3.4
+could be changed to this:
+.DS
+OBJS            = a.o b.o c.o
+program         : $(OBJS)
+        $(CC) -o $(.TARGET) $(.ALLSRC)
+$(OBJS)         : defs.h
+.DE
+The transformation rule I gave above takes the place of the 6 lines\**
+.FS
+This is also somewhat cleaner, I think, than the dynamic source
+solution presented in 2.6
+.FE
+.Rm 4 2.6
+.DS
+a.o             : a.c
+        cc -c a.c
+b.o             : b.c
+        cc -c b.c
+c.o             : c.c
+        cc -c c.c
+.DE
+.LP
+Now you may be wondering about the dependency between the
+.CW .o
+and
+.CW .c
+files \*- it's not mentioned anywhere in the new makefile. This is
+because it isn't needed: one of the effects of applying a
+transformation rule is the target comes to depend on the implied
+source. That's why it's called the implied
+.I source .
+.LP
+For a more detailed example. Say you have a makefile like this:
+.DS
+a.out           : a.o b.o
+        $(CC) $(.ALLSRC)
+.DE
+and a directory set up like this:
+.DS
+total 4
+-rw-rw-r--  1 deboor         34 Sep  7 00:43 Makefile
+-rw-rw-r--  1 deboor        119 Oct  3 19:39 a.c
+-rw-rw-r--  1 deboor        201 Sep  7 00:43 a.o
+-rw-rw-r--  1 deboor         69 Sep  7 00:43 b.c
+.DE
+While just typing
+.CW pmake '' ``
+will do the right thing, it's much more informative to type
+.CW "pmake -d s" ''. ``
+This will show you what PMake is up to as it processes the files. In
+this case, PMake prints the following:
+.DS
+Suff_FindDeps (a.out)
+	using existing source a.o
+	applying .o -> .out to "a.o"
+Suff_FindDeps (a.o)
+	trying a.c...got it
+	applying .c -> .o to "a.c"
+Suff_FindDeps (b.o)
+	trying b.c...got it
+	applying .c -> .o to "b.c"
+Suff_FindDeps (a.c)
+	trying a.y...not there
+	trying a.l...not there
+	trying a.c,v...not there
+	trying a.y,v...not there
+	trying a.l,v...not there
+Suff_FindDeps (b.c)
+	trying b.y...not there
+	trying b.l...not there
+	trying b.c,v...not there
+	trying b.y,v...not there
+	trying b.l,v...not there
+--- a.o ---
+cc  -c a.c
+--- b.o ---
+cc  -c b.c
+--- a.out ---
+cc a.o b.o
+.DE
+.LP
+.CW Suff_FindDeps
+is the name of a function in PMake that is called to check for implied
+sources for a target using transformation rules.
+The transformations it tries are, naturally
+enough, limited to the ones that have been defined (a transformation
+may be defined multiple times, by the way, but only the most recent
+one will be used). You will notice, however, that there is a definite
+order to the suffixes that are tried. This order is set by the
+relative positions of the suffixes on the
+.CW .SUFFIXES
+line \*- the earlier a suffix appears, the earlier it is checked as
+the source of a transformation. Once a suffix has been defined, the
+only way to change its position in the pecking order is to remove all
+the suffixes (by having a
+.CW .SUFFIXES
+dependency line with no sources) and redefine them in the order you
+want. (Previously-defined transformation rules will be automatically
+redefined as the suffixes they involve are re-entered.)
+.LP
+Another way to affect the search order is to make the dependency
+explicit. In the above example,
+.CW a.out
+depends on
+.CW a.o
+and
+.CW b.o .
+Since a transformation exists from
+.CW .o
+to
+.CW .out ,
+PMake uses that, as indicated by the
+.CW "using existing source a.o" '' ``
+message.
+.LP
+The search for a transformation starts from the suffix of the target
+and continues through all the defined transformations, in the order
+dictated by the suffix ranking, until an existing file with the same
+base (the target name minus the suffix and any leading directories) is
+found. At that point, one or more transformation rules will have been
+found to change the one existing file into the target.
+.LP
+For example, ignoring what's in the system makefile for now, say you
+have a makefile like this:
+.DS
+\&.SUFFIXES       : .out .o .c .y .l
+\&.l.c            :
+        lex $(.IMPSRC)
+        mv lex.yy.c $(.TARGET)
+\&.y.c            :
+        yacc $(.IMPSRC)
+        mv y.tab.c $(.TARGET)
+\&.c.o            :
+        cc -c $(.IMPSRC)
+\&.o.out          :
+        cc -o $(.TARGET) $(.IMPSRC)
+.DE
+and the single file
+.CW jive.l .
+If you were to type
+.CW "pmake -rd ms jive.out" ,'' ``
+you would get the following output for
+.CW jive.out :
+.DS
+Suff_FindDeps (jive.out)
+	trying jive.o...not there
+	trying jive.c...not there
+	trying jive.y...not there
+	trying jive.l...got it
+	applying .l -> .c to "jive.l"
+	applying .c -> .o to "jive.c"
+	applying .o -> .out to "jive.o"
+.DE
+and this is why: PMake starts with the target
+.CW jive.out ,
+figures out its suffix
+.CW .out ) (
+and looks for things it can transform to a
+.CW .out
+file. In this case, it only finds
+.CW .o ,
+so it looks for the file
+.CW jive.o .
+It fails to find it, so it looks for transformations into a
+.CW .o
+file. Again it has only one choice:
+.CW .c .
+So it looks for
+.CW jive.c
+and, as you know, fails to find it. At this point it has two choices:
+it can create the
+.CW .c
+file from either a
+.CW .y
+file or a
+.CW .l
+file. Since
+.CW .y
+came first on the
+.CW .SUFFIXES
+line, it checks for
+.CW jive.y
+first, but can't find it, so it looks for
+.CW jive.l
+and, lo and behold, there it is.
+At this point, it has defined a transformation path as follows:
+.CW .l
+\(->
+.CW .c
+\(->
+.CW .o
+\(->
+.CW .out
+and applies the transformation rules accordingly. For completeness,
+and to give you a better idea of what PMake actually did with this
+three-step transformation, this is what PMake printed for the rest of
+the process:
+.DS
+Suff_FindDeps (jive.o)
+	using existing source jive.c
+	applying .c -> .o to "jive.c"
+Suff_FindDeps (jive.c)
+	using existing source jive.l
+	applying .l -> .c to "jive.l"
+Suff_FindDeps (jive.l)
+Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date
+Examining jive.c...non-existent...out-of-date
+--- jive.c ---
+lex jive.l
+\&.\|.\|. meaningless lex output deleted .\|.\|.
+mv lex.yy.c jive.c
+Examining jive.o...non-existent...out-of-date
+--- jive.o ---
+cc -c jive.c
+Examining jive.out...non-existent...out-of-date
+--- jive.out ---
+cc -o jive.out jive.o
+.DE
+.LP
+One final question remains: what does PMake do with targets that have
+no known suffix? PMake simply pretends it actually has a known suffix
+and searches for transformations accordingly.
+The suffix it chooses is the source for the
+.CW .NULL
+.Ix 0 ref .NULL
+target mentioned later. In the system makefile, 
+.CW .out
+is chosen as the ``null suffix''
+.Ix 0 def suffix null
+.Ix 0 def "null suffix"
+because most people use PMake to create programs. You are, however,
+free and welcome to change it to a suffix of your own choosing.
+The null suffix is ignored, however, when PMake is in compatibility
+mode (see chapter 4).
+.xH 2 Including Other Makefiles
+.Ix 0 def makefile inclusion
+.Rd 2
+.LP
+Just as for programs, it is often useful to extract certain parts of a
+makefile into another file and just include it in other makefiles
+somehow. Many compilers allow you say something like
+.DS
+#include "defs.h"
+.DE
+to include the contents of
+.CW defs.h
+in the source file. PMake allows you to do the same thing for
+makefiles, with the added ability to use variables in the filenames.
+An include directive in a makefile looks either like this:
+.DS
+#include <file>
+.DE
+or this
+.DS
+#include "file"
+.DE
+The difference between the two is where PMake searches for the file:
+the first way, PMake will look for
+the file only in the system makefile directory (or directories)
+(to find out what that directory is, give PMake the
+.B \-h
+flag).
+.Ix 0 ref flags -h
+The system makefile directory search path can be overridden via the
+.B \-m
+option.
+.Ix 0 ref flags -m
+For files in double-quotes, the search is more complex:
+.RS
+.IP 1)
+The directory of the makefile that's including the file.
+.IP 2)
+The current directory (the one in which you invoked PMake).
+.IP 3)
+The directories given by you using
+.B \-I
+flags, in the order in which you gave them.
+.IP 4)
+Directories given by
+.CW .PATH
+dependency lines (see chapter 4).
+.IP 5)
+The system makefile directory.
+.RE
+.LP
+in that order.
+.LP
+You are free to use PMake variables in the filename\*-PMake will
+expand them before searching for the file. You must specify the
+searching method with either angle brackets or double-quotes
+.I outside
+of a variable expansion. I.e. the following
+.DS
+SYSTEM	= <command.mk>
+
+#include $(SYSTEM)
+.DE
+won't work.
+.xH 2 Saving Commands
+.LP
+.Ix 0 def ...
+There may come a time when you will want to save certain commands to
+be executed when everything else is done. For instance: you're
+making several different libraries at one time and you want to create the
+members in parallel. Problem is,
+.CW ranlib
+is another one of those programs that can't be run more than once in
+the same directory at the same time (each one creates a file called
+.CW __.SYMDEF
+into which it stuffs information for the linker to use. Two of them
+running at once will overwrite each other's file and the result will
+be garbage for both parties). You might want a way to save the ranlib
+commands til the end so they can be run one after the other, thus
+keeping them from trashing each other's file. PMake allows you to do
+this by inserting an ellipsis (``.\|.\|.'') as a command between
+commands to be run at once and those to be run later.
+.LP
+So for the
+.CW ranlib
+case above, you might do this:
+.Rd 5
+.DS
+lib1.a          : $(LIB1OBJS)
+        rm -f $(.TARGET)
+        ar cr $(.TARGET) $(.ALLSRC)
+        ...
+        ranlib $(.TARGET)
+
+lib2.a          : $(LIB2OBJS)
+        rm -f $(.TARGET)
+        ar cr $(.TARGET) $(.ALLSRC)
+        ...
+        ranlib $(.TARGET)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+This would save both
+.DS
+ranlib $(.TARGET)
+.DE
+commands until the end, when they would run one after the other
+(using the correct value for the
+.CW .TARGET
+variable, of course).
+.LP
+Commands saved in this manner are only executed if PMake manages to
+re-create everything without an error.
+.xH 2 Target Attributes
+.LP
+PMake allows you to give attributes to targets by means of special
+sources. Like everything else PMake uses, these sources begin with a
+period and are made up of all upper-case letters. There are various
+reasons for using them, and I will try to give examples for most of
+them. Others you'll have to find uses for yourself. Think of it as ``an
+exercise for the reader.'' By placing one (or more) of these as a source on a
+dependency line, you are ``marking the target(s) with that
+attribute.'' That's just the way I phrase it, so you know.
+.LP
+Any attributes given as sources for a transformation rule are applied
+to the target of the transformation rule when the rule is applied.
+.Ix 0 def attributes
+.Ix 0 ref source
+.Ix 0 ref target
+.nr pw 12
+.IP .DONTCARE \n(pw
+.Ix 0 def attributes .DONTCARE
+.Ix 0 def .DONTCARE
+If a target is marked with this attribute and PMake can't figure out
+how to create it, it will ignore this fact and assume the file isn't
+really needed or actually exists and PMake just can't find it. This may prove
+wrong, but the error will be noted later on, not when PMake tries to create
+the target so marked. This attribute also prevents PMake from
+attempting to touch the target if it is given the
+.B \-t
+flag.
+.Ix 0 ref flags -t
+.IP .EXEC \n(pw
+.Ix 0 def attributes .EXEC
+.Ix 0 def .EXEC
+This attribute causes its shell script to be executed while having no
+effect on targets that depend on it. This makes the target into a sort
+of subroutine.  An example. Say you have some LISP files that need to
+be compiled and loaded into a LISP process. To do this, you echo LISP
+commands into a file and execute a LISP with this file as its input
+when everything's done. Say also that you have to load other files
+from another system before you can compile your files and further,
+that you don't want to go through the loading and dumping unless one
+of
+.I your
+files has changed. Your makefile might look a little bit
+like this (remember, this is an educational example, and don't worry
+about the
+.CW COMPILE
+rule, all will soon become clear, grasshopper):
+.DS
+system          : init a.fasl b.fasl c.fasl
+        for i in $(.ALLSRC);
+        do
+                echo -n '(load "' >> input
+                echo -n ${i} >> input
+                echo '")' >> input
+        done
+        echo '(dump "$(.TARGET)")' >> input
+        lisp < input
+
+a.fasl          : a.l init COMPILE
+b.fasl          : b.l init COMPILE
+c.fasl          : c.l init COMPILE
+COMPILE         : .USE
+        echo '(compile "$(.ALLSRC)")' >> input
+init            : .EXEC
+        echo '(load-system)' > input
+.DE
+.Ix 0 ref .USE
+.Ix 0 ref attributes .USE
+.Ix 0 ref variable local .ALLSRC
+.IP "\&"
+.CW .EXEC
+sources, don't appear in the local variables of targets that depend on
+them (nor are they touched if PMake is given the
+.B \-t
+flag).
+.Ix 0 ref flags -t
+Note that all the rules, not just that for
+.CW system ,
+include
+.CW init
+as a source. This is because none of the other targets can be made
+until
+.CW init
+has been made, thus they depend on it.
+.IP .EXPORT \n(pw
+.Ix 0 def attributes .EXPORT
+.Ix 0 def .EXPORT
+This is used to mark those targets whose creation should be sent to
+another machine if at all possible. This may be used by some
+exportation schemes if the exportation is expensive. You should ask
+your system administrator if it is necessary.
+.IP .EXPORTSAME \n(pw
+.Ix 0 def attributes .EXPORTSAME
+.Ix 0 def .EXPORTSAME
+Tells the export system that the job should be exported to a machine
+of the same architecture as the current one. Certain operations (e.g.
+running text through
+.CW nroff )
+can be performed the same on any architecture (CPU and
+operating system type), while others (e.g. compiling a program with
+.CW cc )
+must be performed on a machine with the same architecture. Not all
+export systems will support this attribute.
+.IP .IGNORE \n(pw
+.Ix 0 def attributes .IGNORE
+.Ix 0 def .IGNORE attribute
+Giving a target the
+.CW .IGNORE
+attribute causes PMake to ignore errors from any of the target's commands, as
+if they all had `\-' before them.
+.IP .INVISIBLE \n(pw
+.Ix 0 def attributes .INVISIBLE
+.Ix 0 def .INVISIBLE
+This allows you to specify one target as a source for another without
+the one affecting the other's local variables. Useful if, say, you
+have a makefile that creates two programs, one of which is used to
+create the other, so it must exist before the other is created. You
+could say
+.DS
+prog1           : $(PROG1OBJS) prog2 MAKEINSTALL
+prog2           : $(PROG2OBJS) .INVISIBLE MAKEINSTALL
+.DE
+where
+.CW MAKEINSTALL
+is some complex .USE rule (see below) that depends on the
+.Ix 0 ref .USE
+.CW .ALLSRC
+variable containing the right things. Without the
+.CW .INVISIBLE
+attribute for
+.CW prog2 ,
+the
+.CW MAKEINSTALL
+rule couldn't be applied. This is not as useful as it should be, and
+the semantics may change (or the whole thing go away) in the
+not-too-distant future.
+.IP .JOIN \n(pw
+.Ix 0 def attributes .JOIN
+.Ix 0 def .JOIN
+This is another way to avoid performing some operations in parallel
+while permitting everything else to be done so. Specifically it
+forces the target's shell script to be executed only if one or more of the
+sources was out-of-date. In addition, the target's name,
+in both its
+.CW .TARGET
+variable and all the local variables of any target that depends on it,
+is replaced by the value of its
+.CW .ALLSRC
+variable.
+As an example, suppose you have a program that has four libraries that
+compile in the same directory along with, and at the same time as, the
+program. You again have the problem with
+.CW ranlib
+that I mentioned earlier, only this time it's more severe: you
+can't just put the ranlib off to the end since the program
+will need those libraries before it can be re-created. You can do
+something like this:
+.DS
+program         : $(OBJS) libraries
+        cc -o $(.TARGET) $(.ALLSRC)
+
+libraries       : lib1.a lib2.a lib3.a lib4.a .JOIN
+        ranlib $(.OODATE)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+.Ix 0 ref variable local .OODATE
+.Ix 0 ref .TARGET
+.Ix 0 ref .ALLSRC
+.Ix 0 ref .OODATE
+In this case, PMake will re-create the
+.CW $(OBJS)
+as necessary, along with
+.CW lib1.a ,
+.CW lib2.a ,
+.CW lib3.a
+and
+.CW lib4.a .
+It will then execute
+.CW ranlib
+on any library that was changed and set
+.CW program 's
+.CW .ALLSRC
+variable to contain what's in
+.CW $(OBJS)
+followed by
+.CW "lib1.a lib2.a lib3.a lib4.a" .'' ``
+In case you're wondering, it's called
+.CW .JOIN
+because it joins together different threads of the ``input graph'' at
+the target marked with the attribute.
+Another aspect of the .JOIN attribute is it keeps the target from
+being created if the
+.B \-t
+flag was given.
+.Ix 0 ref flags -t
+.IP .MAKE \n(pw
+.Ix 0 def attributes .MAKE
+.Ix 0 def .MAKE
+The
+.CW .MAKE
+attribute marks its target as being a recursive invocation of PMake.
+This forces PMake to execute the script associated with the target (if
+it's out-of-date) even if you gave the
+.B \-n
+or
+.B \-t
+flag. By doing this, you can start at the top of a system and type
+.DS
+pmake -n
+.DE
+and have it descend the directory tree (if your makefiles are set up
+correctly), printing what it would have executed if you hadn't
+included the
+.B \-n
+flag.
+.IP .NOEXPORT \n(pw
+.Ix 0 def attributes .NOEXPORT
+.Ix 0 def .NOEXPORT attribute
+If possible, PMake will attempt to export the creation of all targets to
+another machine (this depends on how PMake was configured). Sometimes,
+the creation is so simple, it is pointless to send it to another
+machine. If you give the target the
+.CW .NOEXPORT
+attribute, it will be run locally, even if you've given PMake the
+.B "\-L 0"
+flag.
+.IP .NOTMAIN \n(pw
+.Ix 0 def attributes .NOTMAIN
+.Ix 0 def .NOTMAIN
+Normally, if you do not specify a target to make in any other way,
+PMake will take the first target on the first dependency line of a
+makefile as the target to create. That target is known as the ``Main
+Target'' and is labeled as such if you print the dependencies out
+using the
+.B \-p
+flag.
+.Ix 0 ref flags -p
+Giving a target this attribute tells PMake that the target is
+definitely
+.I not
+the Main Target.
+This allows you to place targets in an included makefile and
+have PMake create something else by default.
+.IP .PRECIOUS \n(pw
+.Ix 0 def attributes .PRECIOUS
+.Ix 0 def .PRECIOUS attribute
+When PMake is interrupted (you type control-C at the keyboard), it
+will attempt to clean up after itself by removing any half-made
+targets. If a target has the
+.CW .PRECIOUS
+attribute, however, PMake will leave it alone. An additional side
+effect of the `::' operator is to mark the targets as
+.CW .PRECIOUS .
+.Ix 0 ref operator double-colon
+.Ix 0 ref ::
+.IP .SILENT \n(pw
+.Ix 0 def attributes .SILENT
+.Ix 0 def .SILENT attribute
+Marking a target with this attribute keeps its commands from being
+printed when they're executed, just as if they had an `@' in front of them.
+.IP .USE \n(pw
+.Ix 0 def attributes .USE
+.Ix 0 def .USE
+By giving a target this attribute, you turn it into PMake's equivalent
+of a macro. When the target is used as a source for another target,
+the other target acquires the commands, sources and attributes (except
+.CW .USE )
+of the source.
+If the target already has commands, the
+.CW .USE
+target's commands are added to the end. If more than one .USE-marked
+source is given to a target, the rules are applied sequentially.
+.IP "\&" \n(pw
+The typical .USE rule (as I call them) will use the sources of the
+target to which it is applied (as stored in the
+.CW .ALLSRC
+variable for the target) as its ``arguments,'' if you will.
+For example, you probably noticed that the commands for creating
+.CW lib1.a
+and
+.CW lib2.a
+in the example in section 3.3
+.Rm 5 3.3
+were exactly the same. You can use the
+.CW .USE
+attribute to eliminate the repetition, like so:
+.DS
+lib1.a          : $(LIB1OBJS) MAKELIB
+lib2.a          : $(LIB2OBJS) MAKELIB
+
+MAKELIB         : .USE
+        rm -f $(.TARGET)
+        ar cr $(.TARGET) $(.ALLSRC)
+        ...
+        ranlib $(.TARGET)
+.DE
+.Ix 0 ref variable local .TARGET
+.Ix 0 ref variable local .ALLSRC
+.IP "\&" \n(pw
+Several system makefiles (not to be confused with The System Makefile)
+make use of these  .USE rules to make your
+life easier (they're in the default, system makefile directory...take a look).
+Note that the .USE rule source itself
+.CW MAKELIB ) (
+does not appear in any of the targets's local variables.
+There is no limit to the number of times I could use the
+.CW MAKELIB
+rule. If there were more libraries, I could continue with
+.CW "lib3.a : $(LIB3OBJS) MAKELIB" '' ``
+and so on and so forth.
+.xH 2 Special Targets
+.LP
+As there were in Make, so there are certain targets that have special
+meaning to PMake. When you use one on a dependency line, it is the
+only target that may appear on the left-hand-side of the operator.
+.Ix 0 ref target
+.Ix 0 ref operator
+As for the attributes and variables, all the special targets
+begin with a period and consist of upper-case letters only.
+I won't describe them all in detail because some of them are rather
+complex and I'll describe them in more detail than you'll want in
+chapter 4.
+The targets are as follows:
+.nr pw 10
+.IP .BEGIN \n(pw
+.Ix 0 def .BEGIN
+Any commands attached to this target are executed before anything else
+is done. You can use it for any initialization that needs doing.
+.IP .DEFAULT \n(pw
+.Ix 0 def .DEFAULT
+This is sort of a .USE rule for any target (that was used only as a
+source) that PMake can't figure out any other way to create. It's only
+``sort of'' a .USE rule because only the shell script attached to the
+.CW .DEFAULT
+target is used. The
+.CW .IMPSRC
+variable of a target that inherits
+.CW .DEFAULT 's
+commands is set to the target's own name.
+.Ix 0 ref .IMPSRC
+.Ix 0 ref variable local .IMPSRC
+.IP .END \n(pw
+.Ix 0 def .END
+This serves a function similar to
+.CW .BEGIN ,
+in that commands attached to it are executed once everything has been
+re-created (so long as no errors occurred). It also serves the extra
+function of being a place on which PMake can hang commands you put off
+to the end. Thus the script for this target will be executed before
+any of the commands you save with the ``.\|.\|.''.
+.Ix 0 ref ...
+.IP .EXPORT \n(pw
+The sources for this target are passed to the exportation system compiled
+into PMake. Some systems will use these sources to configure
+themselves. You should ask your system administrator about this.
+.IP .IGNORE \n(pw
+.Ix 0 def .IGNORE target
+.Ix 0 ref .IGNORE attribute
+.Ix 0 ref attributes .IGNORE
+This target marks each of its sources with the
+.CW .IGNORE
+attribute. If you don't give it any sources, then it is like
+giving the
+.B \-i
+flag when you invoke PMake \*- errors are ignored for all commands.
+.Ix 0 ref flags -i
+.IP .INCLUDES \n(pw
+.Ix 0 def .INCLUDES target
+.Ix 0 def variable global .INCLUDES
+.Ix 0 def .INCLUDES variable
+The sources for this target are taken to be suffixes that indicate a
+file that can be included in a program source file.
+The suffix must have already been declared with
+.CW .SUFFIXES
+(see below).
+Any suffix so marked will have the directories on its search path
+(see
+.CW .PATH ,
+below) placed in the
+.CW .INCLUDES
+variable, each preceded by a
+.B \-I
+flag. This variable can then be used as an argument for the compiler
+in the normal fashion. The
+.CW .h
+suffix is already marked in this way in the system makefile.
+.Ix 0 ref makefilesystem
+E.g. if you have
+.DS
+\&.SUFFIXES       : .bitmap
+\&.PATH.bitmap    : /usr/local/X/lib/bitmaps
+\&.INCLUDES       : .bitmap
+.DE
+PMake will place
+.CW "-I/usr/local/X/lib/bitmaps" '' ``
+in the
+.CW .INCLUDES
+variable and you can then say
+.DS
+cc $(.INCLUDES) -c xprogram.c
+.DE
+(Note: the
+.CW .INCLUDES
+variable is not actually filled in until the entire makefile has been read.)
+.IP .INTERRUPT \n(pw
+.Ix 0 def .INTERRUPT
+When PMake is interrupted,
+it will execute the commands in the script for this target, if it
+exists.
+.IP .LIBS \n(pw
+.Ix 0 def .LIBS target
+.Ix 0 def .LIBS variable
+.Ix 0 def variable global .LIBS
+This does for libraries what
+.CW .INCLUDES
+does for include files, except the flag used is
+.B \-L ,
+as required by those linkers that allow you to tell them where to find
+libraries. The variable used is
+.CW .LIBS .
+Be forewarned that PMake may not have been compiled to do this if the
+linker on your system doesn't accept the
+.B \-L
+flag, though the
+.CW .LIBS
+variable will always be defined once the makefile has been read.
+.IP .MAIN \n(pw
+.Ix 0 def .MAIN
+If you didn't give a target (or targets) to create when you invoked
+PMake, it will take the sources of this target as the targets to
+create.
+.IP .MAKEFLAGS \n(pw
+.Ix 0 def .MAKEFLAGS target
+This target provides a way for you to always specify flags for PMake
+when the makefile is used. The flags are just as they would be typed
+to the shell (except you can't use shell variables unless they're in
+the environment),
+though the
+.B \-f
+and
+.B \-r
+flags have no effect.
+.IP .NULL \n(pw
+.Ix 0 def .NULL
+.Ix 0 ref suffix null
+.Ix 0 ref "null suffix"
+This allows you to specify what suffix PMake should pretend a file has
+if, in fact, it has no known suffix. Only one suffix may be so
+designated. The last source on the dependency line is the suffix that
+is used (you should, however, only give one suffix.\|.\|.).
+.IP .PATH \n(pw
+.Ix 0 def .PATH
+If you give sources for this target, PMake will take them as
+directories in which to search for files it cannot find in the current
+directory. If you give no sources, it will clear out any directories
+added to the search path before. Since the effects of this all get
+very complex, I'll leave it til chapter four to give you a complete
+explanation.
+.IP .PATH\fIsuffix\fP \n(pw
+.Ix 0 ref .PATH
+This does a similar thing to
+.CW .PATH ,
+but it does it only for files with the given suffix. The suffix must
+have been defined already. Look at
+.B "Search Paths"
+(section 4.1)
+.Rm 6 4.1
+for more information.
+.IP .PRECIOUS \n(pw
+.Ix 0 def .PRECIOUS target
+.Ix 0 ref .PRECIOUS attribute
+.Ix 0 ref attributes .PRECIOUS
+Similar to
+.CW .IGNORE ,
+this gives the
+.CW .PRECIOUS
+attribute to each source on the dependency line, unless there are no
+sources, in which case the
+.CW .PRECIOUS
+attribute is given to every target in the file.
+.IP .RECURSIVE \n(pw
+.Ix 0 def .RECURSIVE
+.Ix 0 ref attributes .MAKE
+.Ix 0 ref .MAKE
+This target applies the
+.CW .MAKE
+attribute to all its sources. It does nothing if you don't give it any sources.
+.IP .SHELL \n(pw
+.Ix 0 def .SHELL
+PMake is not constrained to only using the Bourne shell to execute
+the commands you put in the makefile. You can tell it some other shell
+to use with this target. Check out
+.B "A Shell is a Shell is a Shell"
+(section 4.4)
+.Rm 7 4.4
+for more information.
+.IP .SILENT \n(pw
+.Ix 0 def .SILENT target
+.Ix 0 ref .SILENT attribute
+.Ix 0 ref attributes .SILENT
+When you use
+.CW .SILENT
+as a target, it applies the
+.CW .SILENT
+attribute to each of its sources. If there are no sources on the
+dependency line, then it is as if you gave PMake the
+.B \-s
+flag and no commands will be echoed.
+.IP .SUFFIXES \n(pw
+.Ix 0 def .SUFFIXES
+This is used to give new file suffixes for PMake to handle. Each
+source is a suffix PMake should recognize. If you give a
+.CW .SUFFIXES
+dependency line with no sources, PMake will forget about all the
+suffixes it knew (this also nukes the null suffix).
+For those targets that need to have suffixes defined, this is how you do it.
+.LP
+In addition to these targets, a line of the form
+.DS
+\fIattribute\fP : \fIsources\fP
+.DE
+applies the
+.I attribute
+to all the targets listed as
+.I sources .
+.xH 2 Modifying Variable Expansion
+.LP
+.Ix 0 def variable expansion modified
+.Ix 0 ref variable expansion
+.Ix 0 def variable modifiers
+Variables need not always be expanded verbatim. PMake defines several
+modifiers that may be applied to a variable's value before it is
+expanded. You apply a modifier by placing it after the variable name
+with a colon between the two, like so:
+.DS
+${\fIVARIABLE\fP:\fImodifier\fP}
+.DE
+Each modifier is a single character followed by something specific to
+the modifier itself.
+You may apply as many modifiers as you want \*- each one is applied to
+the result of the previous and is separated from the previous by
+another colon.
+.LP
+There are seven ways to modify a variable's expansion, most of which
+come from the C shell variable modification characters:
+.RS
+.IP "M\fIpattern\fP"
+.Ix 0 def :M
+.Ix 0 def modifier match
+This is used to select only those words (a word is a series of
+characters that are neither spaces nor tabs) that match the given
+.I pattern .
+The pattern is a wildcard pattern like that used by the shell, where
+.CW *
+means 0 or more characters of any sort;
+.CW ?
+is any single character;
+.CW [abcd]
+matches any single character that is either `a', `b', `c' or `d'
+(there may be any number of characters between the brackets);
+.CW [0-9]
+matches any single character that is between `0' and `9' (i.e. any
+digit. This form may be freely mixed with the other bracket form), and
+`\\' is used to escape any of the characters `*', `?', `[' or `:',
+leaving them as regular characters to match themselves in a word.
+For example, the system makefile
+.CW <makedepend.mk>
+uses
+.CW "$(CFLAGS:M-[ID]*)" '' ``
+to extract all the
+.CW \-I
+and
+.CW \-D
+flags that would be passed to the C compiler. This allows it to
+properly locate include files and generate the correct dependencies.
+.IP "N\fIpattern\fP"
+.Ix 0 def :N
+.Ix 0 def modifier nomatch
+This is identical to
+.CW :M
+except it substitutes all words that don't match the given pattern.
+.IP "S/\fIsearch-string\fP/\fIreplacement-string\fP/[g]"
+.Ix 0 def :S
+.Ix 0 def modifier substitute
+Causes the first occurrence of
+.I search-string
+in the variable to be replaced by
+.I replacement-string ,
+unless the
+.CW g
+flag is given at the end, in which case all occurrences of the string
+are replaced. The substitution is performed on each word in the
+variable in turn. If 
+.I search-string
+begins with a
+.CW ^ ,
+the string must match starting at the beginning of the word. If
+.I search-string
+ends with a
+.CW $ ,
+the string must match to the end of the word (these two may be
+combined to force an exact match). If a backslash precedes these two
+characters, however, they lose their special meaning. Variable
+expansion also occurs in the normal fashion inside both the
+.I search-string
+and the
+.I replacement-string ,
+.B except
+that a backslash is used to prevent the expansion of a
+.CW $ ,
+not another dollar sign, as is usual.
+Note that
+.I search-string
+is just a string, not a pattern, so none of the usual
+regular-expression/wildcard characters have any special meaning save
+.CW ^
+and
+.CW $ .
+In the replacement string,
+the
+.CW &
+character is replaced by the
+.I search-string
+unless it is preceded by a backslash.
+You are allowed to use any character except
+colon or exclamation point to separate the two strings. This so-called
+delimiter character may be placed in either string by preceding it
+with a backslash.
+.IP T
+.Ix 0 def :T
+.Ix 0 def modifier tail
+Replaces each word in the variable expansion by its last
+component (its ``tail''). For example, given
+.DS
+OBJS = ../lib/a.o b /usr/lib/libm.a
+TAILS = $(OBJS:T)
+.DE
+the variable
+.CW TAILS
+would expand to
+.CW "a.o b libm.a" .'' ``
+.IP H
+.Ix 0 def :H
+.Ix 0 def modifier head
+This is similar to
+.CW :T ,
+except that every word is replaced by everything but the tail (the
+``head''). Using the same definition of
+.CW OBJS ,
+the string
+.CW "$(OBJS:H)" '' ``
+would expand to
+.CW "../lib /usr/lib" .'' ``
+Note that the final slash on the heads is removed and
+anything without a head is replaced by the empty string.
+.IP E
+.Ix 0 def :E
+.Ix 0 def modifier extension
+.Ix 0 def modifier suffix
+.Ix 0 ref suffix "variable modifier"
+.CW :E
+replaces each word by its suffix (``extension''). So
+.CW "$(OBJS:E)" '' ``
+would give you
+.CW ".o .a" .'' ``
+.IP R
+.Ix 0 def :R
+.Ix 0 def modifier root
+.Ix 0 def modifier base
+This replaces each word by everything but the suffix (the ``root'' of
+the word).
+.CW "$(OBJS:R)" '' ``
+expands to ``
+.CW "../lib/a b /usr/lib/libm" .''
+.RE
+.LP
+In addition, the System V style of substitution is also supported.
+This looks like:
+.DS
+$(\fIVARIABLE\fP:\fIsearch-string\fP=\fIreplacement\fP)
+.DE
+It must be the last modifier in the chain. The search is anchored at
+the end of each word, so only suffixes or whole words may be replaced.
+.xH 2 More on Debugging
+.xH 2 More Exercises
+.IP (3.1)
+You've got a set programs, each of which is created from its own
+assembly-language source file (suffix
+.CW .asm ).
+Each program can be assembled into two versions, one with error-checking
+code assembled in and one without. You could assemble them into files
+with different suffixes
+.CW .eobj \& (
+and
+.CW .obj ,
+for instance), but your linker only understands files that end in
+.CW .obj .
+To top it all off, the final executables
+.I must
+have the suffix
+.CW .exe .
+How can you still use transformation rules to make your life easier
+(Hint: assume the error-checking versions have
+.CW ec
+tacked onto their prefix)?
+.IP (3.2)
+Assume, for a moment or two, you want to perform a sort of
+``indirection'' by placing the name of a variable into another one,
+then you want to get the value of the first by expanding the second
+somehow. Unfortunately, PMake doesn't allow constructs like
+.DS I
+$($(FOO))
+.DE
+What do you do? Hint: no further variable expansion is performed after
+modifiers are applied, thus if you cause a $ to occur in the
+expansion, that's what will be in the result.
+.xH 1 PMake for Gods
+.LP
+This chapter is devoted to those facilities in PMake that allow you to
+do a great deal in a makefile with very little work, as well as do
+some things you couldn't do in Make without a great deal of work (and
+perhaps the use of other programs). The problem with these features,
+is they must be handled with care, or you will end up with a mess.
+.LP
+Once more, I assume a greater familiarity with
+.UX
+or Sprite than I did in the previous two chapters.
+.xH 2 Search Paths
+.Rd 6
+.LP
+PMake supports the dispersal of files into multiple directories by
+allowing you to specify places to look for sources with
+.CW .PATH
+targets in the makefile. The directories you give as sources for these
+targets make up a ``search path.'' Only those files used exclusively
+as sources are actually sought on a search path, the assumption being
+that anything listed as a target in the makefile can be created by the
+makefile and thus should be in the current directory.
+.LP
+There are two types of search paths
+in PMake: one is used for all types of files (including included
+makefiles) and is specified with a plain
+.CW .PATH
+target (e.g.
+.CW ".PATH : RCS" ''), ``
+while the other is specific to a certain type of file, as indicated by
+the file's suffix. A specific search path is indicated by immediately following
+the
+.CW .PATH
+with the suffix of the file. For instance
+.DS
+\&.PATH.h         : /sprite/lib/include /sprite/att/lib/include
+.DE
+would tell PMake to look in the directories
+.CW /sprite/lib/include
+and
+.CW /sprite/att/lib/include
+for any files whose suffix is
+.CW .h .
+.LP
+The current directory is always consulted first to see if a file
+exists. Only if it cannot be found there are the directories in the
+specific search path, followed by those in the general search path,
+consulted.
+.LP
+A search path is also used when expanding wildcard characters. If the
+pattern has a recognizable suffix on it, the path for that suffix will
+be used for the expansion. Otherwise the default search path is employed.
+.LP
+When a file is found in some directory other than the current one, all
+local variables that would have contained the target's name
+.CW .ALLSRC , (
+and
+.CW .IMPSRC )
+will instead contain the path to the file, as found by PMake.
+Thus if you have a file
+.CW ../lib/mumble.c
+and a makefile
+.DS
+\&.PATH.c         : ../lib
+mumble          : mumble.c
+        $(CC) -o $(.TARGET) $(.ALLSRC)
+.DE
+the command executed to create
+.CW mumble
+would be
+.CW "cc -o mumble ../lib/mumble.c" .'' ``
+(As an aside, the command in this case isn't strictly necessary, since
+it will be found using transformation rules if it isn't given. This is because
+.CW .out
+is the null suffix by default and a transformation exists from
+.CW .c
+to
+.CW .out .
+Just thought I'd throw that in.)
+.LP
+If a file exists in two directories on the same search path, the file
+in the first directory on the path will be the one PMake uses. So if
+you have a large system spread over many directories, it would behoove
+you to follow a naming convention that avoids such conflicts.
+.LP
+Something you should know about the way search paths are implemented
+is that each directory is read, and its contents cached, exactly once
+\&\*- when it is first encountered \*- so any changes to the
+directories while PMake is running will not be noted when searching
+for implicit sources, nor will they be found when PMake attempts to
+discover when the file was last modified, unless the file was created in the
+current directory. While people have suggested that PMake should read
+the directories each time, my experience suggests that the caching seldom
+causes problems. In addition, not caching the directories slows things
+down enormously because of PMake's attempts to apply transformation
+rules through non-existent files \*- the number of extra file-system
+searches is truly staggering, especially if many files without
+suffixes are used and the null suffix isn't changed from
+.CW .out .
+.xH 2 Archives and Libraries
+.LP
+.UX
+and Sprite allow you to merge files into an archive using the
+.CW ar
+command. Further, if the files are relocatable object files, you can
+run
+.CW ranlib
+on the archive and get yourself a library that you can link into any
+program you want. The main problem with archives is they double the
+space you need to store the archived files, since there's one copy in
+the archive and one copy out by itself. The problem with libraries is
+you usually think of them as
+.CW -lm
+rather than
+.CW /usr/lib/libm.a
+and the linker thinks they're out-of-date if you so much as look at
+them.
+.LP
+PMake solves the problem with archives by allowing you to tell it to
+examine the files in the archives (so you can remove the individual
+files without having to regenerate them later). To handle the problem
+with libraries, PMake adds an additional way of deciding if a library
+is out-of-date:
+.IP \(bu 2
+If the table of contents is older than the library, or is missing, the
+library is out-of-date.
+.LP
+A library is any target that looks like
+.CW \-l name'' ``
+or that ends in a suffix that was marked as a library using the
+.CW .LIBS
+target.
+.CW .a
+is so marked in the system makefile.
+.LP
+Members of an archive are specified as
+``\fIarchive\fP(\fImember\fP[ \fImember\fP...])''.
+Thus
+.CW libdix.a(window.o) '' ``'
+specifies the file
+.CW window.o
+in the archive
+.CW libdix.a .
+You may also use wildcards to specify the members of the archive. Just
+remember that most the wildcard characters will only find 
+.I existing
+files.
+.LP
+A file that is a member of an archive is treated specially. If the
+file doesn't exist, but it is in the archive, the modification time
+recorded in the archive is used for the file when determining if the
+file is out-of-date. When figuring out how to make an archived member target
+(not the file itself, but the file in the archive \*- the
+\fIarchive\fP(\fImember\fP) target), special care is
+taken with the transformation rules, as follows:
+.IP \(bu 2
+\&\fIarchive\fP(\fImember\fP) is made to depend on \fImember\fP.
+.IP \(bu 2
+The transformation from the \fImember\fP's suffix to the
+\fIarchive\fP's suffix is applied to the \fIarchive\fP(\fImember\fP) target.
+.IP \(bu 2
+The \fIarchive\fP(\fImember\fP)'s
+.CW .TARGET
+variable is set to the name of the \fImember\fP if \fImember\fP is
+actually a target, or the path to the member file if \fImember\fP is
+only a source.
+.IP \(bu 2
+The
+.CW .ARCHIVE
+variable for the \fIarchive\fP(\fImember\fP) target is set to the name
+of the \fIarchive\fP.
+.Ix 0 def variable local .ARCHIVE
+.Ix 0 def .ARCHIVE
+.IP \(bu 2
+The
+.CW .MEMBER
+variable is set to the actual string inside the parentheses. In most
+cases, this will be the same as the
+.CW .TARGET
+variable.
+.Ix 0 def variable local .MEMBER
+.Ix 0 def .MEMBER
+.IP \(bu 2
+The \fIarchive\fP(\fImember\fP)'s place in the local variables of the
+targets that depend on it is taken by the value of its
+.CW .TARGET
+variable.
+.LP
+Thus, a program library could be created with the following makefile:
+.DS
+\&.o.a            :
+        ...
+        rm -f $(.TARGET:T)
+OBJS            = obj1.o obj2.o obj3.o
+libprog.a       : libprog.a($(OBJS))
+        ar cru $(.TARGET) $(.OODATE)
+        ranlib $(.TARGET)
+.DE
+This will cause the three object files to be compiled (if the
+corresponding source files were modified after the object file or, if
+that doesn't exist, the archived object file), the out-of-date ones
+archived in
+.CW libprog.a ,
+a table of contents placed in the archive and the newly-archived
+object files to be removed.
+.LP
+All this is used in the 
+.CW makelib.mk
+system makefile to create a single library with ease. This makefile
+looks like this:
+.DS
+.SM
+#
+# Rules for making libraries. The object files that make up the library
+# are removed once they are archived.
+#
+# To make several libraries in parallel, you should define the variable
+# "many_libraries". This will serialize the invocations of ranlib.
+#
+# To use, do something like this:
+#
+# OBJECTS = <files in the library>
+#
+# fish.a: fish.a($(OBJECTS)) MAKELIB
+#
+#
+
+#ifndef _MAKELIB_MK
+_MAKELIB_MK	=
+
+#include	<po.mk>
+
+\&.po.a .o.a	:
+	...
+	rm -f $(.MEMBER)
+
+ARFLAGS		?= crl
+
+#
+# Re-archive the out-of-date members and recreate the library's table of
+# contents using ranlib. If many_libraries is defined, put the ranlib
+# off til the end so many libraries can be made at once.
+#
+MAKELIB		: .USE .PRECIOUS
+	ar $(ARFLAGS) $(.TARGET) $(.OODATE)
+#ifndef no_ranlib
+# ifdef many_libraries
+	...
+# endif many_libraries
+	ranlib $(.TARGET)
+#endif no_ranlib
+
+#endif _MAKELIB_MK
+.DE
+.xH 2 On the Condition...
+.Rd 1
+.LP
+Like the C compiler before it, PMake allows you to configure the makefile,
+based on the current environment, using conditional statements. A
+conditional looks like this:
+.DS
+#if \fIboolean expression\fP
+\fIlines\fP
+#elif \fIanother boolean expression\fP
+\fImore lines\fP
+#else
+\fIstill more lines\fP
+#endif
+.DE
+They may be nested to a maximum depth of 30 and may occur anywhere
+(except in a comment, of course). The
+.CW # '' ``
+must the very first character on the line.
+.LP
+Each
+.I "boolean expression"
+is made up of terms that look like function calls, the standard C
+boolean operators
+.CW && ,
+.CW || ,
+and
+.CW ! ,
+and the standard relational operators
+.CW == ,
+.CW != ,
+.CW > ,
+.CW >= ,
+.CW < ,
+and
+.CW <= ,
+with
+.CW ==
+and
+.CW !=
+being overloaded to allow string comparisons as well.
+.CW &&
+represents logical AND;
+.CW ||
+is logical OR and
+.CW !
+is logical NOT.  The arithmetic and string operators take precedence
+over all three of these operators, while NOT takes precedence over
+AND, which takes precedence over OR.  This precedence may be
+overridden with parentheses, and an expression may be parenthesized to
+your heart's content.  Each term looks like a call on one of four
+functions:
+.nr pw 9
+.Ix 0 def make
+.Ix 0 def conditional make
+.Ix 0 def if make
+.IP make \n(pw
+The syntax is
+.CW make( \fItarget\fP\c
+.CW )
+where
+.I target
+is a target in the makefile. This is true if the given target was
+specified on the command line, or as the source for a
+.CW .MAIN
+target (note that the sources for
+.CW .MAIN
+are only used if no targets were given on the command line).
+.IP defined \n(pw
+.Ix 0 def defined
+.Ix 0 def conditional defined
+.Ix 0 def if defined
+The syntax is
+.CW defined( \fIvariable\fP\c
+.CW )
+and is true if
+.I variable
+is defined. Certain variables are defined in the system makefile that
+identify the system on which PMake is being run.
+.IP exists \n(pw
+.Ix 0 def exists
+.Ix 0 def conditional exists
+.Ix 0 def if exists
+The syntax is
+.CW exists( \fIfile\fP\c
+.CW )
+and is true if the file can be found on the global search path (i.e.
+that defined by
+.CW .PATH
+targets, not by
+.CW .PATH \fIsuffix\fP
+targets).
+.IP empty \n(pw
+.Ix 0 def empty
+.Ix 0 def conditional empty
+.Ix 0 def if empty
+This syntax is much like the others, except the string inside the
+parentheses is of the same form as you would put between parentheses
+when expanding a variable, complete with modifiers and everything. The
+function returns true if the resulting string is empty (NOTE: an undefined
+variable in this context will cause at the very least a warning
+message about a malformed conditional, and at the worst will cause the
+process to stop once it has read the makefile. If you want to check
+for a variable being defined or empty, use the expression
+.CW !defined( \fIvar\fP\c ``
+.CW ") || empty(" \fIvar\fP\c
+.CW ) ''
+as the definition of
+.CW ||
+will prevent the
+.CW empty()
+from being evaluated and causing an error, if the variable is
+undefined). This can be used to see if a variable contains a given
+word, for example:
+.DS
+#if !empty(\fIvar\fP:M\fIword\fP)
+.DE
+.LP
+The arithmetic and string operators may only be used to test the value
+of a variable. The lefthand side must contain the variable expansion,
+while the righthand side contains either a string, enclosed in
+double-quotes, or a number. The standard C numeric conventions (except
+for specifying an octal number) apply to both sides. E.g.
+.DS
+#if $(OS) == 4.3
+
+#if $(MACHINE) == "sun3"
+
+#if $(LOAD_ADDR) < 0xc000
+.DE
+are all valid conditionals. In addition, the numeric value of a
+variable can be tested as a boolean as follows:
+.DS
+#if $(LOAD)
+.DE
+would see if
+.CW LOAD
+contains a non-zero value and
+.DS
+#if !$(LOAD)
+.DE
+would test if
+.CW LOAD
+contains a zero value.
+.LP
+In addition to the bare
+.CW #if ,'' ``
+there are other forms that apply one of the first two functions to each
+term. They are as follows:
+.DS
+	ifdef	\fRdefined\fP
+	ifndef	\fR!defined\fP
+	ifmake	\fRmake\fP
+	ifnmake	\fR!make\fP
+.DE
+There are also the ``else if'' forms:
+.CW elif ,
+.CW elifdef ,
+.CW elifndef ,
+.CW elifmake ,
+and
+.CW elifnmake .
+.LP
+For instance, if you wish to create two versions of a program, one of which
+is optimized (the production version) and the other of which is for debugging
+(has symbols for dbx), you have two choices: you can create two
+makefiles, one of which uses the
+.CW \-g
+flag for the compilation, while the other uses the
+.CW \-O
+flag, or you can use another target (call it
+.CW debug )
+to create the debug version. The construct below will take care of
+this for you. I have also made it so defining the variable
+.CW DEBUG
+(say with
+.CW "pmake -D DEBUG" )
+will also cause the debug version to be made.
+.DS
+#if defined(DEBUG) || make(debug)
+CFLAGS		+= -g
+#else
+CFLAGS		+= -O
+#endif
+.DE
+There are, of course, problems with this approach. The most glaring
+annoyance is that if you want to go from making a debug version to
+making a production version, you have to remove all the object files,
+or you will get some optimized and some debug versions in the same
+program. Another annoyance is you have to be careful not to make two
+targets that ``conflict'' because of some conditionals in the
+makefile. For instance
+.DS
+#if make(print)
+FORMATTER	= ditroff -Plaser_printer
+#endif
+#if make(draft)
+FORMATTER	= nroff -Pdot_matrix_printer
+#endif
+.DE
+would wreak havoc if you tried
+.CW "pmake draft print" '' ``
+since you would use the same formatter for each target. As I said,
+this all gets somewhat complicated.
+.xH 2 A Shell is a Shell is a Shell
+.Rd 7
+.LP
+In normal operation, the Bourne Shell (better known as
+.CW sh '') ``
+is used to execute the commands to re-create targets. PMake also allows you
+to specify a different shell for it to use when executing these
+commands. There are several things PMake must know about the shell you
+wish to use. These things are specified as the sources for the
+.CW .SHELL
+.Ix 0 ref .SHELL
+.Ix 0 ref target .SHELL
+target by keyword, as follows:
+.IP "\fBpath=\fP\fIpath\fP"
+PMake needs to know where the shell actually resides, so it can
+execute it. If you specify this and nothing else, PMake will use the
+last component of the path and look in its table of the shells it
+knows and use the specification it finds, if any. Use this if you just
+want to use a different version of the Bourne or C Shell (yes, PMake knows
+how to use the C Shell too).
+.IP "\fBname=\fP\fIname\fP"
+This is the name by which the shell is to be known. It is a single
+word and, if no other keywords are specified (other than
+.B path ),
+it is the name by which PMake attempts to find a specification for
+it (as mentioned above). You can use this if you would just rather use
+the C Shell than the Bourne Shell
+.CW ".SHELL: name=csh" '' (``
+will do it).
+.IP "\fBquiet=\fP\fIecho-off command\fP"
+As mentioned before, PMake actually controls whether commands are
+printed by introducing commands into the shell's input stream. This
+keyword, and the next two, control what those commands are. The
+.B quiet
+keyword is the command used to turn echoing off. Once it is turned
+off, echoing is expected to remain off until the echo-on command is given.
+.IP "\fBecho=\fP\fIecho-on command\fP"
+The command PMake should give to turn echoing back on again.
+.IP "\fBfilter=\fP\fIprinted echo-off command\fP"
+Many shells will echo the echo-off command when it is given. This
+keyword tells PMake in what format the shell actually prints the
+echo-off command. Wherever PMake sees this string in the shell's
+output, it will delete it and any following whitespace, up to and
+including the next newline. See the example at the end of this section
+for more details.
+.IP "\fBechoFlag=\fP\fIflag to turn echoing on\fP"
+Unless a target has been marked
+.CW .SILENT ,
+PMake wants to start the shell running with echoing on. To do this, it
+passes this flag to the shell as one of its arguments. If either this
+or the next flag begins with a `\-', the flags will be passed to the
+shell as separate arguments. Otherwise, the two will be concatenated
+(if they are used at the same time, of course).
+.IP "\fBerrFlag=\fP\fIflag to turn error checking on\fP"
+Likewise, unless a target is marked
+.CW .IGNORE ,
+PMake wishes error-checking to be on from the very start. To this end,
+it will pass this flag to the shell as an argument. The same rules for
+an initial `\-' apply as for the
+.B echoFlag .
+.IP "\fBcheck=\fP\fIcommand to turn error checking on\fP"
+Just as for echo-control, error-control is achieved by inserting
+commands into the shell's input stream. This is the command to make
+the shell check for errors. It also serves another purpose if the
+shell doesn't have error-control as commands, but I'll get into that
+in a minute. Again, once error checking has been turned on, it is
+expected to remain on until it is turned off again.
+.IP "\fBignore=\fP\fIcommand to turn error checking off\fP"
+This is the command PMake uses to turn error checking off. It has
+another use if the shell doesn't do error-control, but I'll tell you
+about that.\|.\|.\|now.
+.IP "\fBhasErrCtl=\fP\fIyes or no\fP"
+This takes a value that is either
+.B yes
+or
+.B no .
+Now you might think that the existence of the
+.B check
+and
+.B ignore
+keywords would be enough to tell PMake if the shell can do
+error-control, but you'd be wrong. If
+.B hasErrCtl
+is
+.B yes ,
+PMake uses the check and ignore commands in a straight-forward manner.
+If this is
+.B no ,
+however, their use is rather different. In this case, the check
+command is used as a template, in which the string
+.B %s
+is replaced by the command that's about to be executed, to produce a
+command for the shell that will echo the command to be executed. The
+ignore command is also used as a template, again with
+.B %s
+replaced by the command to be executed, to produce a command that will
+execute the command to be executed and ignore any error it returns.
+When these strings are used as templates, you must provide newline(s)
+.CW \en '') (``
+in the appropriate place(s).
+.LP
+The strings that follow these keywords may be enclosed in single or
+double quotes (the quotes will be stripped off) and may contain the
+usual C backslash-characters (\en is newline, \er is return, \eb is
+backspace, \e' escapes a single-quote inside single-quotes, \e"
+escapes a double-quote inside double-quotes). Now for an example.
+.LP
+This is actually the contents of the
+.CW <shx.mk>
+system makefile, and causes PMake to use the Bourne Shell in such a
+way that each command is printed as it is executed. That is, if more
+than one command is given on a line, each will be printed separately.
+Similarly, each time the body of a loop is executed, the commands
+within that loop will be printed, etc. The specification runs like
+this:
+.DS
+#
+# This is a shell specification to have the Bourne shell echo
+# the commands just before executing them, rather than when it reads
+# them. Useful if you want to see how variables are being expanded, etc.
+#
+\&.SHELL 	: path=/bin/sh \e
+	quiet="set -" \e
+	echo="set -x" \e
+	filter="+ set - " \e
+	echoFlag=x \e
+	errFlag=e \e
+	hasErrCtl=yes \e
+	check="set -e" \e
+	ignore="set +e"
+.DE
+.LP
+It tells PMake the following:
+.Bp
+The shell is located in the file
+.CW /bin/sh .
+It need not tell PMake that the name of the shell is
+.CW sh 
+as PMake can figure that out for itself (it's the last component of
+the path).
+.Bp
+The command to stop echoing is
+.CW "set -" .
+.Bp
+The command to start echoing is
+.CW "set -x" .
+.Bp
+When the echo off command is executed, the shell will print
+.CW "+ set - " 
+(The `+' comes from using the
+.CW \-x
+flag (rather than the
+.CW \-v
+flag PMake usually uses)). PMake will remove all occurrences of this
+string from the output, so you don't notice extra commands you didn't
+put there.
+.Bp
+The flag the Bourne Shell will take to start echoing in this way is
+the
+.CW \-x
+flag. The Bourne Shell will only take its flag arguments concatenated
+as its first argument, so neither this nor the
+.B errFlag
+specification begins with a \-.
+.Bp
+The flag to use to turn error-checking on from the start is
+.CW \-e .
+.Bp
+The shell can turn error-checking on and off, and the commands to do
+so are
+.CW "set +e"
+and
+.CW "set -e" ,
+respectively.
+.LP
+I should note that this specification is for Bourne Shells that are
+not part of Berkeley
+.UX ,
+as shells from Berkeley don't do error control. You can get a similar
+effect, however, by changing the last three lines to be:
+.DS
+	hasErrCtl=no \e
+	check="echo \e"+ %s\e"\en" \e
+	ignore="sh -c '%s || exit 0\en"
+.DE
+.LP
+This will cause PMake to execute the two commands
+.DS
+echo "+ \fIcmd\fP"
+sh -c '\fIcmd\fP || true'
+.DE
+for each command for which errors are to be ignored. (In case you are
+wondering, the thing for
+.CW ignore
+tells the shell to execute another shell without error checking on and
+always exit 0, since the
+.B ||
+causes the
+.CW "exit 0"
+to be executed only if the first command exited non-zero, and if the
+first command exited zero, the shell will also exit zero, since that's
+the last command it executed).
+.xH 2 Compatibility
+.Ix 0 ref compatibility
+.LP
+There are three (well, 3 \(12) levels of backwards-compatibility built
+into PMake.  Most makefiles will need none at all. Some may need a
+little bit of work to operate correctly when run in parallel. Each
+level encompasses the previous levels (e.g.
+.B \-B
+(one shell per command) implies
+.B \-V )
+The three levels are described in the following three sections.
+.xH 3 DEFCON 3 \*- Variable Expansion
+.Ix 0 ref compatibility
+.LP
+As noted before, PMake will not expand a variable unless it knows of a
+value for it. This can cause problems for makefiles that expect to
+leave variables undefined except in special circumstances (e.g. if
+more flags need to be passed to the C compiler or the output from a
+text processor should be sent to a different printer). If the
+variables are enclosed in curly braces
+.CW ${PRINTER} ''), (``
+the shell will let them pass. If they are enclosed in parentheses,
+however, the shell will declare a syntax error and the make will come
+to a grinding halt.
+.LP
+You have two choices: change the makefile to define the variables
+(their values can be overridden on the command line, since that's
+where they would have been set if you used Make, anyway) or always give the
+.B \-V
+flag (this can be done with the
+.CW .MAKEFLAGS
+target, if you want).
+.xH 3 DEFCON 2 \*- The Number of the Beast
+.Ix 0 ref compatibility
+.LP
+Then there are the makefiles that expect certain commands, such as
+changing to a different directory, to not affect other commands in a
+target's creation script. You can solve this is either by going
+back to executing one shell per command (which is what the
+.B \-B
+flag forces PMake to do), which slows the process down a good bit and
+requires you to use semicolons and escaped newlines for shell constructs, or
+by changing the makefile to execute the offending command(s) in a subshell
+(by placing the line inside parentheses), like so:
+.DS
+install :: .MAKE
+	(cd src; $(.PMAKE) install)
+	(cd lib; $(.PMAKE) install)
+	(cd man; $(.PMAKE) install)
+.DE
+.Ix 0 ref operator double-colon
+.Ix 0 ref variable global .PMAKE
+.Ix 0 ref .PMAKE
+.Ix 0 ref .MAKE
+.Ix 0 ref attribute .MAKE
+This will always execute the three makes (even if the
+.B \-n
+flag was given) because of the combination of the ``::'' operator and
+the
+.CW .MAKE
+attribute. Each command will change to the proper directory to perform
+the install, leaving the main shell in the directory in which it started.
+.xH 3 "DEFCON 1 \*- Imitation is the Not the Highest Form of Flattery"
+.Ix 0 ref compatibility
+.LP
+The final category of makefile is the one where every command requires
+input, the dependencies are incompletely specified, or you simply
+cannot create more than one target at a time, as mentioned earlier. In
+addition, you may not have the time or desire to upgrade the makefile
+to run smoothly with PMake. If you are the conservative sort, this is
+the compatibility mode for you. It is entered either by giving PMake
+the
+.B \-M
+flag (for Make), or by executing PMake as
+.CW make .'' ``
+In either case, PMake performs things exactly like Make (while still
+supporting most of the nice new features PMake provides). This
+includes:
+.IP \(bu 2
+No parallel execution.
+.IP \(bu 2
+Targets are made in the exact order specified by the makefile. The
+sources for each target are made in strict left-to-right order, etc.
+.IP \(bu 2
+A single Bourne shell is used to execute each command, thus the
+shell's
+.CW $$
+variable is useless, changing directories doesn't work across command
+lines, etc.
+.IP \(bu 2
+If no special characters exist in a command line, PMake will break the
+command into words itself and execute the command directly, without
+executing a shell first. The characters that cause PMake to execute a
+shell are:
+.CW # ,
+.CW = ,
+.CW | ,
+.CW ^ ,
+.CW ( ,
+.CW ) ,
+.CW { ,
+.CW } ,
+.CW ; ,
+.CW & ,
+.CW < ,
+.CW > ,
+.CW * ,
+.CW ? ,
+.CW [ ,
+.CW ] ,
+.CW : ,
+.CW $ ,
+.CW ` ,
+and
+.CW \e .
+You should notice that these are all the characters that are given
+special meaning by the shell (except
+.CW '
+and
+.CW " ,
+which PMake deals with all by its lonesome).
+.IP \(bu 2
+The use of the null suffix is turned off.
+.Ix 0 ref "null suffix"
+.Ix 0 ref suffix null
+.xH 2 The Way Things Work
+.LP
+When PMake reads the makefile, it parses sources and targets into
+nodes in a graph. The graph is directed only in the sense that PMake
+knows which way is up. Each node contains not only links to all its
+parents and children (the nodes that depend on it and those on which
+it depends, respectively), but also a count of the number of its
+children that have already been processed.
+.LP
+The most important thing to know about how PMake uses this graph is
+that the traversal is breadth-first and occurs in two passes.
+.LP
+After PMake has parsed the makefile, it begins with the nodes the user
+has told it to make (either on the command line, or via a 
+.CW .MAIN
+target, or by the target being the first in the file not labeled with
+the
+.CW .NOTMAIN
+attribute) placed in a queue. It continues to take the node off the
+front of the queue, mark it as something that needs to be made, pass
+the node to 
+.CW Suff_FindDeps
+(mentioned earlier) to find any implicit sources for the node, and
+place all the node's children that have yet to be marked at the end of
+the queue. If any of the children is a
+.CW .USE
+rule, its attributes are applied to the parent, then its commands are
+appended to the parent's list of commands and its children are linked
+to its parent. The parent's unmade children counter is then decremented
+(since the
+.CW .USE
+node has been processed). You will note that this allows a
+.CW .USE
+node to have children that are
+.CW .USE
+nodes and the rules will be applied in sequence.
+If the node has no children, it is placed at the end of
+another queue to be examined in the second pass. This process
+continues until the first queue is empty.
+.LP
+At this point, all the leaves of the graph are in the examination
+queue. PMake removes the node at the head of the queue and sees if it
+is out-of-date. If it is, it is passed to a function that will execute
+the commands for the node asynchronously. When the commands have
+completed, all the node's parents have their unmade children counter
+decremented and, if the counter is then 0, they are placed on the
+examination queue. Likewise, if the node is up-to-date. Only those
+parents that were marked on the downward pass are processed in this
+way. Thus PMake traverses the graph back up to the nodes the user
+instructed it to create. When the examination queue is empty and no
+shells are running to create a target, PMake is finished.
+.LP
+Once all targets have been processed, PMake executes the commands
+attached to the
+.CW .END
+target, either explicitly or through the use of an ellipsis in a shell
+script. If there were no errors during the entire process but there
+are still some targets unmade (PMake keeps a running count of how many
+targets are left to be made), there is a cycle in the graph. PMake does
+a depth-first traversal of the graph to find all the targets that
+weren't made and prints them out one by one.
+.xH 1 Answers to Exercises
+.IP (3.1)
+This is something of a trick question, for which I apologize. The
+trick comes from the UNIX definition of a suffix, which PMake doesn't
+necessarily share. You will have noticed that all the suffixes used in
+this tutorial (and in UNIX in general) begin with a period
+.CW .ms , (
+.CW .c ,
+etc.). Now, PMake's idea of a suffix is more like English's: it's the
+characters at the end of a word. With this in mind, one possible
+.Ix 0 def suffix
+solution to this problem goes as follows:
+.DS I
+\&.SUFFIXES       : ec.exe .exe ec.obj .obj .asm
+ec.objec.exe .obj.exe :
+        link -o $(.TARGET) $(.IMPSRC)
+\&.asmec.obj      :
+        asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC)
+\&.asm.obj        :
+        asm -o $(.TARGET) $(.IMPSRC)
+.DE
+.IP (3.2)
+The trick to this one lies in the ``:='' variable-assignment operator
+and the ``:S'' variable-expansion modifier. 
+.Ix 0 ref variable assignment expanded
+.Ix 0 ref variable expansion modified
+.Ix 0 ref modifier substitute
+.Ix 0 ref :S
+.Ix 0 ref :=
+Basically what you want is to take the pointer variable, so to speak,
+and transform it into an invocation of the variable at which it
+points. You might try something like
+.DS I
+$(PTR:S/^/\e$(/:S/$/))
+.DE
+which places
+.CW $( '' ``
+at the front of the variable name and
+.CW ) '' ``
+at the end, thus transforming
+.CW VAR ,'' ``
+for example, into
+.CW $(VAR) ,'' ``
+which is just what we want. Unfortunately (as you know if you've tried
+it), since, as it says in the hint, PMake does no further substitution
+on the result of a modified expansion, that's \fIall\fP you get. The
+solution is to make use of ``:='' to place that string into yet
+another variable, then invoke the other variable directly:
+.DS I
+*PTR            := $(PTR:S/^/\e$(/:S/$/)/)
+.DE
+You can then use
+.CW $(*PTR) '' ``
+to your heart's content.
+.de Gp
+.XP
+\&\fB\\$1:\fP
+..
+.xH 1 Glossary of Jargon
+.Gp "attribute"
+A property given to a target that causes PMake to treat it differently.
+.Gp "command script"
+The lines immediately following a dependency line that specify
+commands to execute to create each of the targets on the dependency
+line. Each line in the command script must begin with a tab.
+.Gp "command-line variable"
+A variable defined in an argument when PMake is first executed.
+Overrides all assignments to the same variable name in the makefile.
+.Gp "conditional"
+A construct much like that used in C that allows a makefile to be
+configured on the fly based on the local environment, or on what is being
+made by that invocation of PMake.
+.Gp "creation script"
+Commands used to create a target. See ``command script.''
+.Gp "dependency"
+The relationship between a source and a target. This comes in three
+flavors, as indicated by the operator between the target and the
+source. `:' gives a straight time-wise dependency (if the target is
+older than the source, the target is out-of-date), while `!' provides
+simply an ordering and always considers the target out-of-date. `::'
+is much like `:', save it creates multiple instances of a target each
+of which depends on its own list of sources.
+.Gp "dynamic source"
+This refers to a source that has a local variable invocation in it. It
+allows a single dependency line to specify a different source for each
+target on the line.
+.Gp "global variable"
+Any variable defined in a makefile. Takes precedence over variables
+defined in the environment, but not over command-line or local variables.
+.Gp "input graph"
+What PMake constructs from a makefile. Consists of nodes made of the
+targets in the makefile, and the links between them (the
+dependencies). The links are directed (from source to target) and
+there may not be any cycles (loops) in the graph.
+.Gp "local variable"
+A variable defined by PMake visible only in a target's shell script.
+There are seven local variables, not all of which are defined for
+every target:
+.CW .TARGET ,
+.CW .ALLSRC ,
+.CW .OODATE ,
+.CW .PREFIX ,
+.CW .IMPSRC ,
+.CW .ARCHIVE ,
+and
+.CW .MEMBER .
+.CW .TARGET ,
+.CW .PREFIX ,
+.CW .ARCHIVE ,
+and 
+.CW .MEMBER
+may be used on dependency lines to create ``dynamic sources.''
+.Gp "makefile"
+A file that describes how a system is built. If you don't know what it
+is after reading this tutorial.\|.\|.\|.
+.Gp "modifier"
+A letter, following a colon, used to alter how a variable is expanded.
+It has no effect on the variable itself.
+.Gp "operator"
+What separates a source from a target (on a dependency line) and specifies
+the relationship between the two. There are three:
+.CW : ', `
+.CW :: ', `
+and
+.CW ! '. `
+.Gp "search path"
+A list of directories in which a file should be sought. PMake's view
+of the contents of directories in a search path does not change once
+the makefile has been read. A file is sought on a search path only if
+it is exclusively a source.
+.Gp "shell"
+A program to which commands are passed in order to create targets.
+.Gp "source"
+Anything to the right of an operator on a dependency line. Targets on
+the dependency line are usually created from the sources.
+.Gp "special target"
+A target that causes PMake to do special things when it's encountered.
+.Gp "suffix"
+The tail end of a file name. Usually begins with a period,
+.CW .c
+or
+.CW .ms ,
+e.g.
+.Gp "target"
+A word to the left of the operator on a dependency line. More
+generally, any file that PMake might create. A file may be (and often
+is) both a target and a source (what it is depends on how PMake is
+looking at it at the time \*- sort of like the wave/particle duality
+of light, you know).
+.Gp "transformation rule"
+A special construct in a makefile that specifies how to create a file
+of one type from a file of another, as indicated by their suffixes.
+.Gp "variable expansion"
+The process of substituting the value of a variable for a reference to
+it. Expansion may be altered by means of modifiers.
+.Gp "variable"
+A place in which to store text that may be retrieved later. Also used
+to define the local environment. Conditionals exist that test whether
+a variable is defined or not.
+.bp
+.\" Output table of contents last, with an entry for the index, making
+.\" sure to save and restore the last real page number for the index...
+.nr @n \n(PN+1
+.\" We are not generating an index
+.\" .XS \n(@n
+.\" Index
+.\" .XE
+.nr %% \n%
+.PX
+.nr % \n(%%
Index: trunk/src/kmk/arch.c
===================================================================
--- trunk/src/kmk/arch.c	(revision 9)
+++ trunk/src/kmk/arch.c	(revision 9)
@@ -0,0 +1,1217 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)arch.c	8.2 (Berkeley) 1/2/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/arch.c,v 1.32 2002/10/09 03:42:09 jmallett Exp $");
+
+/*-
+ * arch.c --
+ *	Functions to manipulate libraries, archives and their members.
+ *
+ *	Once again, cacheing/hashing comes into play in the manipulation
+ * of archives. The first time an archive is referenced, all of its members'
+ * headers are read and hashed and the archive closed again. All hashed
+ * archives are kept on a list which is searched each time an archive member
+ * is referenced.
+ *
+ * The interface to this module is:
+ *	Arch_ParseArchive   	Given an archive specification, return a list
+ *	    	  	    	of GNode's, one for each member in the spec.
+ *	    	  	    	FAILURE is returned if the specification is
+ *	    	  	    	invalid for some reason.
+ *
+ *	Arch_Touch	    	Alter the modification time of the archive
+ *	    	  	    	member described by the given node to be
+ *	    	  	    	the current time.
+ *
+ *	Arch_TouchLib	    	Update the modification time of the library
+ *	    	  	    	described by the given node. This is special
+ *	    	  	    	because it also updates the modification time
+ *	    	  	    	of the library's table of contents.
+ *
+ *	Arch_MTime	    	Find the modification time of a member of
+ *	    	  	    	an archive *in the archive*. The time is also
+ *	    	  	    	placed in the member's GNode. Returns the
+ *	    	  	    	modification time.
+ *
+ *	Arch_MemTime	    	Find the modification time of a member of
+ *	    	  	    	an archive. Called when the member doesn't
+ *	    	  	    	already exist. Looks in the archive for the
+ *	    	  	    	modification time. Returns the modification
+ *	    	  	    	time.
+ *
+ *	Arch_FindLib	    	Search for a library along a path. The
+ *	    	  	    	library name in the GNode should be in
+ *	    	  	    	-l<name> format.
+ *
+ *	Arch_LibOODate	    	Special function to decide if a library node
+ *	    	  	    	is out-of-date.
+ *
+ *	Arch_Init 	    	Initialize this module.
+ *
+ *	Arch_End 	    	Cleanup this module.
+ */
+
+#include    <sys/types.h>
+#include    <sys/stat.h>
+#include    <sys/time.h>
+#include    <sys/param.h>
+#include    <ctype.h>
+#include    <ar.h>
+#include    <utime.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "config.h"
+
+static Lst	  archives;   /* Lst of archives we've already examined */
+
+typedef struct Arch {
+    char	  *name;      /* Name of archive */
+    Hash_Table	  members;    /* All the members of the archive described
+			       * by <name, struct ar_hdr *> key/value pairs */
+    char	  *fnametab;  /* Extended name table strings */
+    size_t	  fnamesize;  /* Size of the string table */
+} Arch;
+
+static int ArchFindArchive(void *, void *);
+static void ArchFree(void *);
+static struct ar_hdr *ArchStatMember(char *, char *, Boolean);
+static FILE *ArchFindMember(char *, char *, struct ar_hdr *, char *);
+#if defined(__svr4__) || defined(__SVR4) || defined(__ELF__)
+#define	SVR4ARCHIVES
+static int ArchSVR4Entry(Arch *, char *, size_t, FILE *);
+#endif
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFree --
+ *	Free memory used by an archive
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ArchFree(void *ap)
+{
+    Arch *a = (Arch *) ap;
+    Hash_Search	  search;
+    Hash_Entry	  *entry;
+
+    /* Free memory from hash entries */
+    for (entry = Hash_EnumFirst(&a->members, &search);
+	 entry != NULL;
+	 entry = Hash_EnumNext(&search))
+	free(Hash_GetValue(entry));
+
+    free(a->name);
+    efree(a->fnametab);
+    Hash_DeleteTable(&a->members);
+    free(a);
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_ParseArchive --
+ *	Parse the archive specification in the given line and find/create
+ *	the nodes for the specified archive members, placing their nodes
+ *	on the given list, given the pointer to the start of the
+ *	specification, a Lst on which to place the nodes, and a context
+ *	in which to expand variables.
+ *
+ * Results:
+ *	SUCCESS if it was a valid specification. The linePtr is updated
+ *	to point to the first non-space after the archive spec. The
+ *	nodes for the members are placed on the given list.
+ *
+ * Side Effects:
+ *	Some nodes may be created. The given list is extended.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Arch_ParseArchive (char **linePtr, Lst nodeLst, GNode *ctxt)
+{
+    char            *cp;	    /* Pointer into line */
+    GNode	    *gn;     	    /* New node */
+    char	    *libName;  	    /* Library-part of specification */
+    char	    *memName;  	    /* Member-part of specification */
+    char	    *nameBuf;	    /* temporary place for node name */
+    char	    saveChar;  	    /* Ending delimiter of member-name */
+    Boolean 	    subLibName;	    /* TRUE if libName should have/had
+				     * variable substitution performed on it */
+
+    libName = *linePtr;
+
+    subLibName = FALSE;
+
+    for (cp = libName; *cp != '(' && *cp != '\0'; cp++) {
+	if (*cp == '$') {
+	    /*
+	     * Variable spec, so call the Var module to parse the puppy
+	     * so we can safely advance beyond it...
+	     */
+	    int 	length;
+	    Boolean	freeIt;
+	    char	*result;
+
+	    result=Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
+	    if (result == var_Error) {
+		return(FAILURE);
+	    } else {
+		subLibName = TRUE;
+	    }
+
+	    if (freeIt) {
+		free(result);
+	    }
+	    cp += length-1;
+	}
+    }
+
+    *cp++ = '\0';
+    if (subLibName) {
+	libName = Var_Subst(NULL, libName, ctxt, TRUE);
+    }
+
+
+    for (;;) {
+	/*
+	 * First skip to the start of the member's name, mark that
+	 * place and skip to the end of it (either white-space or
+	 * a close paren).
+	 */
+	Boolean	doSubst = FALSE; /* TRUE if need to substitute in memName */
+
+	while (*cp != '\0' && *cp != ')' && isspace ((unsigned char) *cp)) {
+	    cp++;
+	}
+	memName = cp;
+	while (*cp != '\0' && *cp != ')' && !isspace ((unsigned char) *cp)) {
+	    if (*cp == '$') {
+		/*
+		 * Variable spec, so call the Var module to parse the puppy
+		 * so we can safely advance beyond it...
+		 */
+		int 	length;
+		Boolean	freeIt;
+		char	*result;
+
+		result=Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
+		if (result == var_Error) {
+		    return(FAILURE);
+		} else {
+		    doSubst = TRUE;
+		}
+
+		if (freeIt) {
+		    free(result);
+		}
+		cp += length;
+	    } else {
+		cp++;
+	    }
+	}
+
+	/*
+	 * If the specification ends without a closing parenthesis,
+	 * chances are there's something wrong (like a missing backslash),
+	 * so it's better to return failure than allow such things to happen
+	 */
+	if (*cp == '\0') {
+	    printf("No closing parenthesis in archive specification\n");
+	    return (FAILURE);
+	}
+
+	/*
+	 * If we didn't move anywhere, we must be done
+	 */
+	if (cp == memName) {
+	    break;
+	}
+
+	saveChar = *cp;
+	*cp = '\0';
+
+	/*
+	 * XXX: This should be taken care of intelligently by
+	 * SuffExpandChildren, both for the archive and the member portions.
+	 */
+	/*
+	 * If member contains variables, try and substitute for them.
+	 * This will slow down archive specs with dynamic sources, of course,
+	 * since we'll be (non-)substituting them three times, but them's
+	 * the breaks -- we need to do this since SuffExpandChildren calls
+	 * us, otherwise we could assume the thing would be taken care of
+	 * later.
+	 */
+	if (doSubst) {
+	    char    *buf;
+	    char    *sacrifice;
+	    char    *oldMemName = memName;
+	    size_t   sz;
+
+	    memName = Var_Subst(NULL, memName, ctxt, TRUE);
+
+	    /*
+	     * Now form an archive spec and recurse to deal with nested
+	     * variables and multi-word variable values.... The results
+	     * are just placed at the end of the nodeLst we're returning.
+	     */
+
+	    sz = strlen(memName) + strlen(libName) + 3;
+	    buf = sacrifice = emalloc(sz);
+
+	    snprintf(buf, sz, "%s(%s)", libName, memName);
+
+	    if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) {
+		/*
+		 * Must contain dynamic sources, so we can't deal with it now.
+		 * Just create an ARCHV node for the thing and let
+		 * SuffExpandChildren handle it...
+		 */
+		gn = Targ_FindNode(buf, TARG_CREATE);
+
+		if (gn == NULL) {
+		    free(buf);
+		    return(FAILURE);
+		} else {
+		    gn->type |= OP_ARCHV;
+		    (void)Lst_AtEnd(nodeLst, (void *)gn);
+		}
+	    } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) {
+		/*
+		 * Error in nested call -- free buffer and return FAILURE
+		 * ourselves.
+		 */
+		free(buf);
+		return(FAILURE);
+	    }
+	    /*
+	     * Free buffer and continue with our work.
+	     */
+	    free(buf);
+	} else if (Dir_HasWildcards(memName)) {
+	    Lst	  members = Lst_Init(FALSE);
+	    char  *member;
+	    size_t sz = MAXPATHLEN;
+	    size_t nsz;
+	    nameBuf = emalloc(sz);
+
+	    Dir_Expand(memName, dirSearchPath, members);
+	    while (!Lst_IsEmpty(members)) {
+		member = (char *)Lst_DeQueue(members);
+		nsz = strlen(libName) + strlen(member) + 3;
+		if (sz > nsz)
+			nameBuf = erealloc(nameBuf, sz = nsz * 2);
+
+		snprintf(nameBuf, sz, "%s(%s)", libName, member);
+		free(member);
+		gn = Targ_FindNode (nameBuf, TARG_CREATE);
+		if (gn == NULL) {
+		    free(nameBuf);
+		    return (FAILURE);
+		} else {
+		    /*
+		     * We've found the node, but have to make sure the rest of
+		     * the world knows it's an archive member, without having
+		     * to constantly check for parentheses, so we type the
+		     * thing with the OP_ARCHV bit before we place it on the
+		     * end of the provided list.
+		     */
+		    gn->type |= OP_ARCHV;
+		    (void) Lst_AtEnd (nodeLst, (void *)gn);
+		}
+	    }
+	    Lst_Destroy(members, NOFREE);
+	    free(nameBuf);
+	} else {
+	    size_t sz = strlen(libName) + strlen(memName) + 3;
+	    nameBuf = emalloc(sz);
+	    snprintf(nameBuf, sz, "%s(%s)", libName, memName);
+	    gn = Targ_FindNode (nameBuf, TARG_CREATE);
+	    free(nameBuf);
+	    if (gn == NULL) {
+		return (FAILURE);
+	    } else {
+		/*
+		 * We've found the node, but have to make sure the rest of the
+		 * world knows it's an archive member, without having to
+		 * constantly check for parentheses, so we type the thing with
+		 * the OP_ARCHV bit before we place it on the end of the
+		 * provided list.
+		 */
+		gn->type |= OP_ARCHV;
+		(void) Lst_AtEnd (nodeLst, (void *)gn);
+	    }
+	}
+	if (doSubst) {
+	    free(memName);
+	}
+
+	*cp = saveChar;
+    }
+
+    /*
+     * If substituted libName, free it now, since we need it no longer.
+     */
+    if (subLibName) {
+	free(libName);
+    }
+
+    /*
+     * We promised the pointer would be set up at the next non-space, so
+     * we must advance cp there before setting *linePtr... (note that on
+     * entrance to the loop, cp is guaranteed to point at a ')')
+     */
+    do {
+	cp++;
+    } while (*cp != '\0' && isspace ((unsigned char) *cp));
+
+    *linePtr = cp;
+    return (SUCCESS);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFindArchive --
+ *	See if the given archive is the one we are looking for. Called
+ *	From ArchStatMember and ArchFindMember via Lst_Find with the
+ *	current list element and the name we want.
+ *
+ * Results:
+ *	0 if it is, non-zero if it isn't.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ArchFindArchive (void *ar, void *archName)
+{
+    return (strcmp ((char *) archName, ((Arch *) ar)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchStatMember --
+ *	Locate a member of an archive, given the path of the archive and
+ *	the path of the desired member, and a boolean representing whether
+ *	or not the archive should be hashed (if not already hashed).
+ *
+ * Results:
+ *	A pointer to the current struct ar_hdr structure for the member. Note
+ *	That no position is returned, so this is not useful for touching
+ *	archive members. This is mostly because we have no assurances that
+ *	The archive will remain constant after we read all the headers, so
+ *	there's not much point in remembering the position...
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+static struct ar_hdr *
+ArchStatMember (char *archive, char *member, Boolean hash)
+{
+#define	AR_MAX_NAME_LEN	    (sizeof(arh.ar_name)-1)
+    FILE *	  arch;	      /* Stream to archive */
+    int		  size;       /* Size of archive member */
+    char	  *cp;	      /* Useful character pointer */
+    char	  magic[SARMAG];
+    LstNode	  ln;	      /* Lst member containing archive descriptor */
+    Arch	  *ar;	      /* Archive descriptor */
+    Hash_Entry	  *he;	      /* Entry containing member's description */
+    struct ar_hdr arh;        /* archive-member header for reading archive */
+    char	  memName[MAXPATHLEN+1];
+    	    	    	    /* Current member name while hashing. */
+
+    /*
+     * Because of space constraints and similar things, files are archived
+     * using their final path components, not the entire thing, so we need
+     * to point 'member' to the final component, if there is one, to make
+     * the comparisons easier...
+     */
+    cp = strrchr (member, '/');
+    if ((cp != NULL) && (strcmp(member, RANLIBMAG) != 0))
+	member = cp + 1;
+
+    ln = Lst_Find (archives, (void *) archive, ArchFindArchive);
+    if (ln != NULL) {
+	ar = (Arch *) Lst_Datum (ln);
+
+	he = Hash_FindEntry (&ar->members, member);
+
+	if (he != NULL) {
+	    return ((struct ar_hdr *) Hash_GetValue (he));
+	} else {
+	    /* Try truncated name */
+	    char copy[AR_MAX_NAME_LEN+1];
+	    size_t len = strlen (member);
+
+	    if (len > AR_MAX_NAME_LEN) {
+		len = AR_MAX_NAME_LEN;
+		strncpy(copy, member, AR_MAX_NAME_LEN);
+		copy[AR_MAX_NAME_LEN] = '\0';
+	    }
+	    if ((he = Hash_FindEntry (&ar->members, copy)) != NULL)
+		return ((struct ar_hdr *) Hash_GetValue (he));
+	    return (NULL);
+	}
+    }
+
+    if (!hash) {
+	/*
+	 * Caller doesn't want the thing hashed, just use ArchFindMember
+	 * to read the header for the member out and close down the stream
+	 * again. Since the archive is not to be hashed, we assume there's
+	 * no need to allocate extra room for the header we're returning,
+	 * so just declare it static.
+	 */
+	 static struct ar_hdr	sarh;
+
+	 arch = ArchFindMember(archive, member, &sarh, "r");
+
+	if (arch == NULL) {
+	    return (NULL);
+	} else {
+	    fclose(arch);
+	    return (&sarh);
+	}
+    }
+
+    /*
+     * We don't have this archive on the list yet, so we want to find out
+     * everything that's in it and cache it so we can get at it quickly.
+     */
+    arch = fopen (archive, "r");
+    if (arch == NULL) {
+	return (NULL);
+    }
+
+    /*
+     * We use the ARMAG string to make sure this is an archive we
+     * can handle...
+     */
+    if ((fread (magic, SARMAG, 1, arch) != 1) ||
+    	(strncmp (magic, ARMAG, SARMAG) != 0)) {
+	    fclose (arch);
+	    return (NULL);
+    }
+
+    ar = (Arch *)emalloc (sizeof (Arch));
+    ar->name = estrdup (archive);
+    ar->fnametab = NULL;
+    ar->fnamesize = 0;
+    Hash_InitTable (&ar->members, -1);
+    memName[AR_MAX_NAME_LEN] = '\0';
+
+    while (fread ((char *)&arh, sizeof (struct ar_hdr), 1, arch) == 1) {
+	if (strncmp ( arh.ar_fmag, ARFMAG, sizeof (arh.ar_fmag)) != 0) {
+	    /*
+	     * The header is bogus, so the archive is bad
+	     * and there's no way we can recover...
+	     */
+	    goto badarch;
+	} else {
+	    /*
+	     * We need to advance the stream's pointer to the start of the
+	     * next header. Files are padded with newlines to an even-byte
+	     * boundary, so we need to extract the size of the file from the
+	     * 'size' field of the header and round it up during the seek.
+	     */
+	    arh.ar_size[sizeof(arh.ar_size)-1] = '\0';
+	    size = (int) strtol(arh.ar_size, NULL, 10);
+
+	    (void) strncpy (memName, arh.ar_name, sizeof(arh.ar_name));
+	    for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) {
+		continue;
+	    }
+	    cp[1] = '\0';
+
+#ifdef SVR4ARCHIVES
+	    /*
+	     * svr4 names are slash terminated. Also svr4 extended AR format.
+	     */
+	    if (memName[0] == '/') {
+		/*
+		 * svr4 magic mode; handle it
+		 */
+		switch (ArchSVR4Entry(ar, memName, size, arch)) {
+		case -1:  /* Invalid data */
+		    goto badarch;
+		case 0:	  /* List of files entry */
+		    continue;
+		default:  /* Got the entry */
+		    break;
+		}
+	    }
+	    else {
+		if (cp[0] == '/')
+		    cp[0] = '\0';
+	    }
+#endif
+
+#ifdef AR_EFMT1
+	    /*
+	     * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+	     * first <namelen> bytes of the file
+	     */
+	    if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 &&
+		isdigit(memName[sizeof(AR_EFMT1) - 1])) {
+
+		unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]);
+
+		if (elen > MAXPATHLEN)
+			goto badarch;
+		if (fread (memName, elen, 1, arch) != 1)
+			goto badarch;
+		memName[elen] = '\0';
+		fseek (arch, -elen, SEEK_CUR);
+		/* XXX Multiple levels may be asked for, make this conditional
+		 * on one, and use DEBUGF.
+		 */
+		if (DEBUG(ARCH) || DEBUG(MAKE)) {
+		    fprintf(stderr, "ArchStat: Extended format entry for %s\n", memName);
+		}
+	    }
+#endif
+
+	    he = Hash_CreateEntry (&ar->members, memName, NULL);
+	    Hash_SetValue (he, (void *)emalloc (sizeof (struct ar_hdr)));
+	    memcpy (Hash_GetValue (he), &arh,
+		sizeof (struct ar_hdr));
+	}
+	fseek (arch, (size + 1) & ~1, SEEK_CUR);
+    }
+
+    fclose (arch);
+
+    (void) Lst_AtEnd (archives, (void *) ar);
+
+    /*
+     * Now that the archive has been read and cached, we can look into
+     * the hash table to find the desired member's header.
+     */
+    he = Hash_FindEntry (&ar->members, member);
+
+    if (he != NULL) {
+	return ((struct ar_hdr *) Hash_GetValue (he));
+    } else {
+	return (NULL);
+    }
+
+badarch:
+    fclose (arch);
+    Hash_DeleteTable (&ar->members);
+    efree(ar->fnametab);
+    free (ar);
+    return (NULL);
+}
+
+#ifdef SVR4ARCHIVES
+/*-
+ *-----------------------------------------------------------------------
+ * ArchSVR4Entry --
+ *	Parse an SVR4 style entry that begins with a slash.
+ *	If it is "//", then load the table of filenames
+ *	If it is "/<offset>", then try to substitute the long file name
+ *	from offset of a table previously read.
+ *
+ * Results:
+ *	-1: Bad data in archive
+ *	 0: A table was loaded from the file
+ *	 1: Name was successfully substituted from table
+ *	 2: Name was not successfully substituted from table
+ *
+ * Side Effects:
+ *	If a table is read, the file pointer is moved to the next archive
+ *	member
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch)
+{
+#define	ARLONGNAMES1 "//"
+#define	ARLONGNAMES2 "/ARFILENAMES"
+    size_t entry;
+    char *ptr, *eptr;
+
+    if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 ||
+	strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) {
+
+	if (ar->fnametab != NULL) {
+	    DEBUGF(ARCH, ("Attempted to redefine an SVR4 name table\n"));
+	    return -1;
+	}
+
+	/*
+	 * This is a table of archive names, so we build one for
+	 * ourselves
+	 */
+	ar->fnametab = emalloc(size);
+	ar->fnamesize = size;
+
+	if (fread(ar->fnametab, size, 1, arch) != 1) {
+	    DEBUGF(ARCH, ("Reading an SVR4 name table failed\n"));
+	    return -1;
+	}
+	eptr = ar->fnametab + size;
+	for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++)
+	    switch (*ptr) {
+	    case '/':
+		entry++;
+		*ptr = '\0';
+		break;
+
+	    case '\n':
+		break;
+
+	    default:
+		break;
+	    }
+	DEBUGF(ARCH, ("Found svr4 archive name table with %zu entries\n", entry));
+	return 0;
+    }
+
+    if (name[1] == ' ' || name[1] == '\0')
+	return 2;
+
+    entry = (size_t) strtol(&name[1], &eptr, 0);
+    if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) {
+	DEBUGF(ARCH, ("Could not parse SVR4 name %s\n", name));
+	return 2;
+    }
+    if (entry >= ar->fnamesize) {
+	DEBUGF(ARCH, ("SVR4 entry offset %s is greater than %zu\n",
+	       name, ar->fnamesize));
+	return 2;
+    }
+
+    DEBUGF(ARCH, ("Replaced %s with %s\n", name, &ar->fnametab[entry]));
+
+    (void) strncpy(name, &ar->fnametab[entry], MAXPATHLEN);
+    name[MAXPATHLEN] = '\0';
+    return 1;
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchFindMember --
+ *	Locate a member of an archive, given the path of the archive and
+ *	the path of the desired member. If the archive is to be modified,
+ *	the mode should be "r+", if not, it should be "r".  arhPtr is a
+ *	poitner to the header structure to fill in.
+ *
+ * Results:
+ *	An FILE *, opened for reading and writing, positioned at the
+ *	start of the member's struct ar_hdr, or NULL if the member was
+ *	nonexistent. The current struct ar_hdr for member.
+ *
+ * Side Effects:
+ *	The passed struct ar_hdr structure is filled in.
+ *
+ *-----------------------------------------------------------------------
+ */
+static FILE *
+ArchFindMember (char *archive, char *member, struct ar_hdr *arhPtr, char *mode)
+{
+    FILE *	  arch;	      /* Stream to archive */
+    int		  size;       /* Size of archive member */
+    char	  *cp;	      /* Useful character pointer */
+    char	  magic[SARMAG];
+    size_t	  len, tlen;
+
+    arch = fopen (archive, mode);
+    if (arch == NULL) {
+	return (NULL);
+    }
+
+    /*
+     * We use the ARMAG string to make sure this is an archive we
+     * can handle...
+     */
+    if ((fread (magic, SARMAG, 1, arch) != 1) ||
+    	(strncmp (magic, ARMAG, SARMAG) != 0)) {
+	    fclose (arch);
+	    return (NULL);
+    }
+
+    /*
+     * Because of space constraints and similar things, files are archived
+     * using their final path components, not the entire thing, so we need
+     * to point 'member' to the final component, if there is one, to make
+     * the comparisons easier...
+     */
+    cp = strrchr (member, '/');
+    if ((cp != NULL) && (strcmp(member, RANLIBMAG) != 0)) {
+	member = cp + 1;
+    }
+    len = tlen = strlen (member);
+    if (len > sizeof (arhPtr->ar_name)) {
+	tlen = sizeof (arhPtr->ar_name);
+    }
+
+    while (fread ((char *)arhPtr, sizeof (struct ar_hdr), 1, arch) == 1) {
+	if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof (arhPtr->ar_fmag) ) != 0) {
+	     /*
+	      * The header is bogus, so the archive is bad
+	      * and there's no way we can recover...
+	      */
+	     fclose (arch);
+	     return (NULL);
+	} else if (strncmp (member, arhPtr->ar_name, tlen) == 0) {
+	    /*
+	     * If the member's name doesn't take up the entire 'name' field,
+	     * we have to be careful of matching prefixes. Names are space-
+	     * padded to the right, so if the character in 'name' at the end
+	     * of the matched string is anything but a space, this isn't the
+	     * member we sought.
+	     */
+	    if (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){
+		goto skip;
+	    } else {
+		/*
+		 * To make life easier, we reposition the file at the start
+		 * of the header we just read before we return the stream.
+		 * In a more general situation, it might be better to leave
+		 * the file at the actual member, rather than its header, but
+		 * not here...
+		 */
+		fseek (arch, -sizeof(struct ar_hdr), SEEK_CUR);
+		return (arch);
+	    }
+	} else
+#ifdef AR_EFMT1
+		/*
+		 * BSD 4.4 extended AR format: #1/<namelen>, with name as the
+		 * first <namelen> bytes of the file
+		 */
+	    if (strncmp(arhPtr->ar_name, AR_EFMT1,
+					sizeof(AR_EFMT1) - 1) == 0 &&
+		isdigit(arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) {
+
+		unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]);
+		char ename[MAXPATHLEN];
+
+		if (elen > MAXPATHLEN) {
+			fclose (arch);
+			return NULL;
+		}
+		if (fread (ename, elen, 1, arch) != 1) {
+			fclose (arch);
+			return NULL;
+		}
+		ename[elen] = '\0';
+		/*
+		 * XXX choose one.
+		 */
+		if (DEBUG(ARCH) || DEBUG(MAKE)) {
+		    printf("ArchFind: Extended format entry for %s\n", ename);
+		}
+		if (strncmp(ename, member, len) == 0) {
+			/* Found as extended name */
+			fseek (arch, -sizeof(struct ar_hdr) - elen, SEEK_CUR);
+			return (arch);
+		}
+		fseek (arch, -elen, SEEK_CUR);
+		goto skip;
+	} else
+#endif
+	{
+skip:
+	    /*
+	     * This isn't the member we're after, so we need to advance the
+	     * stream's pointer to the start of the next header. Files are
+	     * padded with newlines to an even-byte boundary, so we need to
+	     * extract the size of the file from the 'size' field of the
+	     * header and round it up during the seek.
+	     */
+	    arhPtr->ar_size[sizeof(arhPtr->ar_size)-1] = '\0';
+	    size = (int) strtol(arhPtr->ar_size, NULL, 10);
+	    fseek (arch, (size + 1) & ~1, SEEK_CUR);
+	}
+    }
+
+    /*
+     * We've looked everywhere, but the member is not to be found. Close the
+     * archive and return NULL -- an error.
+     */
+    fclose (arch);
+    return (NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_Touch --
+ *	Touch a member of an archive.
+ *
+ * Results:
+ *	The 'time' field of the member's header is updated.
+ *
+ * Side Effects:
+ *	The modification time of the entire archive is also changed.
+ *	For a library, this could necessitate the re-ranlib'ing of the
+ *	whole thing.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_Touch (GNode *gn)
+{
+    FILE *	  arch;	  /* Stream open to archive, positioned properly */
+    struct ar_hdr arh;	  /* Current header describing member */
+    char *p1, *p2;
+
+    arch = ArchFindMember(Var_Value (ARCHIVE, gn, &p1),
+			  Var_Value (TARGET, gn, &p2),
+			  &arh, "r+");
+    efree(p1);
+    efree(p2);
+    snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now);
+
+    if (arch != NULL) {
+	(void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch);
+	fclose (arch);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_TouchLib --
+ *	Given a node which represents a library, touch the thing, making
+ *	sure that the table of contents also is touched.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Both the modification time of the library and of the RANLIBMAG
+ *	member are set to 'now'.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_TouchLib (GNode *gn)
+{
+#ifdef RANLIBMAG
+    FILE *	    arch;	/* Stream open to archive */
+    struct ar_hdr   arh;      	/* Header describing table of contents */
+    struct utimbuf  times;	/* Times for utime() call */
+
+    arch = ArchFindMember (gn->path, RANLIBMAG, &arh, "r+");
+    snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now);
+
+    if (arch != NULL) {
+	(void)fwrite ((char *)&arh, sizeof (struct ar_hdr), 1, arch);
+	fclose (arch);
+
+	times.actime = times.modtime = now;
+	utime(gn->path, &times);
+    }
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_MTime --
+ *	Return the modification time of a member of an archive, given its
+ *	name.
+ *
+ * Results:
+ *	The modification time (seconds).
+ *
+ * Side Effects:
+ *	The mtime field of the given node is filled in with the value
+ *	returned by the function.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Arch_MTime(GNode *gn)
+{
+    struct ar_hdr *arhPtr;    /* Header of desired member */
+    int		  modTime;    /* Modification time as an integer */
+    char *p1, *p2;
+
+    arhPtr = ArchStatMember (Var_Value (ARCHIVE, gn, &p1),
+			     Var_Value (TARGET, gn, &p2),
+			     TRUE);
+    efree(p1);
+    efree(p2);
+
+    if (arhPtr != NULL) {
+	modTime = (int) strtol(arhPtr->ar_date, NULL, 10);
+    } else {
+	modTime = 0;
+    }
+
+    gn->mtime = modTime;
+    return (modTime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_MemMTime --
+ *	Given a non-existent archive member's node, get its modification
+ *	time from its archived form, if it exists.
+ *
+ * Results:
+ *	The modification time.
+ *
+ * Side Effects:
+ *	The mtime field is filled in.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Arch_MemMTime (GNode *gn)
+{
+    LstNode 	  ln;
+    GNode   	  *pgn;
+    char    	  *nameStart,
+		  *nameEnd;
+
+    if (Lst_Open (gn->parents) != SUCCESS) {
+	gn->mtime = 0;
+	return (0);
+    }
+    while ((ln = Lst_Next (gn->parents)) != NULL) {
+	pgn = (GNode *) Lst_Datum (ln);
+
+	if (pgn->type & OP_ARCHV) {
+	    /*
+	     * If the parent is an archive specification and is being made
+	     * and its member's name matches the name of the node we were
+	     * given, record the modification time of the parent in the
+	     * child. We keep searching its parents in case some other
+	     * parent requires this child to exist...
+	     */
+	    nameStart = strchr (pgn->name, '(') + 1;
+	    nameEnd = strchr (nameStart, ')');
+
+	    if (pgn->make &&
+		strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) {
+				     gn->mtime = Arch_MTime(pgn);
+	    }
+	} else if (pgn->make) {
+	    /*
+	     * Something which isn't a library depends on the existence of
+	     * this target, so it needs to exist.
+	     */
+	    gn->mtime = 0;
+	    break;
+	}
+    }
+
+    Lst_Close (gn->parents);
+
+    return (gn->mtime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_FindLib --
+ *	Search for a named library along the given search path.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The node's 'path' field is set to the found path (including the
+ *	actual file name, not -l...). If the system can handle the -L
+ *	flag when linking (or we cannot find the library), we assume that
+ *	the user has placed the .LIBRARIES variable in the final linking
+ *	command (or the linker will know where to find it) and set the
+ *	TARGET variable for this node to be the node's name. Otherwise,
+ *	we set the TARGET variable to be the full path of the library,
+ *	as returned by Dir_FindFile.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_FindLib (GNode *gn, Lst path)
+{
+    char	    *libName;   /* file name for archive */
+    size_t	    sz;
+
+    sz = strlen(gn->name) + 4;
+    libName = (char *)emalloc(sz);
+    snprintf(libName, sz, "lib%s.a", &gn->name[2]);
+
+    gn->path = Dir_FindFile (libName, path);
+
+    free (libName);
+
+#ifdef LIBRARIES
+    Var_Set (TARGET, gn->name, gn);
+#else
+    Var_Set (TARGET, gn->path == NULL ? gn->name : gn->path, gn);
+#endif /* LIBRARIES */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_LibOODate --
+ *	Decide if a node with the OP_LIB attribute is out-of-date. Called
+ *	from Make_OODate to make its life easier, with the library's
+ *	graph node.
+ *
+ *	There are several ways for a library to be out-of-date that are
+ *	not available to ordinary files. In addition, there are ways
+ *	that are open to regular files that are not available to
+ *	libraries. A library that is only used as a source is never
+ *	considered out-of-date by itself. This does not preclude the
+ *	library's modification time from making its parent be out-of-date.
+ *	A library will be considered out-of-date for any of these reasons,
+ *	given that it is a target on a dependency line somewhere:
+ *	    Its modification time is less than that of one of its
+ *	    	  sources (gn->mtime < gn->cmtime).
+ *	    Its modification time is greater than the time at which the
+ *	    	  make began (i.e. it's been modified in the course
+ *	    	  of the make, probably by archiving).
+ *	    The modification time of one of its sources is greater than
+ *		  the one of its RANLIBMAG member (i.e. its table of contents
+ *	    	  is out-of-date). We don't compare of the archive time
+ *		  vs. TOC time because they can be too close. In my
+ *		  opinion we should not bother with the TOC at all since
+ *		  this is used by 'ar' rules that affect the data contents
+ *		  of the archive, not by ranlib rules, which affect the
+ *		  TOC.
+ *
+ * Results:
+ *	TRUE if the library is out-of-date. FALSE otherwise.
+ *
+ * Side Effects:
+ *	The library will be hashed if it hasn't been already.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Arch_LibOODate (GNode *gn)
+{
+    Boolean 	  oodate;
+
+    if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) {
+	oodate = FALSE;
+    } else if ((gn->mtime > now) || (gn->mtime < gn->cmtime)) {
+	oodate = TRUE;
+    } else {
+#ifdef RANLIBMAG
+	struct ar_hdr  	*arhPtr;    /* Header for __.SYMDEF */
+	int 	  	modTimeTOC; /* The table-of-contents's mod time */
+
+	arhPtr = ArchStatMember (gn->path, RANLIBMAG, FALSE);
+
+	if (arhPtr != NULL) {
+	    modTimeTOC = (int) strtol(arhPtr->ar_date, NULL, 10);
+
+	    /* XXX choose one. */
+	    if (DEBUG(ARCH) || DEBUG(MAKE)) {
+		printf("%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC));
+	    }
+	    oodate = (gn->cmtime > modTimeTOC);
+	} else {
+	    /*
+	     * A library w/o a table of contents is out-of-date
+	     */
+	    if (DEBUG(ARCH) || DEBUG(MAKE)) {
+		printf("No t.o.c....");
+	    }
+	    oodate = TRUE;
+	}
+#else
+	oodate = (gn->mtime == 0); /* out-of-date if not present */
+#endif
+    }
+    return (oodate);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_Init --
+ *	Initialize things for this module.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The 'archives' list is initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_Init (void)
+{
+    archives = Lst_Init (FALSE);
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_End --
+ *	Cleanup things for this module.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The 'archives' list is freed
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Arch_End (void)
+{
+    Lst_Destroy(archives, ArchFree);
+}
Index: trunk/src/kmk/buf.c
===================================================================
--- trunk/src/kmk/buf.c	(revision 9)
+++ trunk/src/kmk/buf.c	(revision 9)
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)buf.c	8.1 (Berkeley) 6/6/93
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/buf.c,v 1.18 2002/10/09 03:42:09 jmallett Exp $");
+
+/*-
+ * buf.c --
+ *	Functions for automatically-expanded buffers.
+ */
+
+#include    "sprite.h"
+#include    "make.h"
+#include    "buf.h"
+
+#ifndef max
+#define	max(a,b)  ((a) > (b) ? (a) : (b))
+#endif
+
+/*
+ * BufExpand --
+ * 	Expand the given buffer to hold the given number of additional
+ *	bytes.
+ *	Makes sure there's room for an extra NULL byte at the end of the
+ *	buffer in case it holds a string.
+ */
+#define	BufExpand(bp,nb) \
+ 	if (bp->left < (nb)+1) {\
+	    int newSize = (bp)->size + max((nb)+1,BUF_ADD_INC); \
+	    Byte  *newBuf = (Byte *) erealloc((bp)->buffer, newSize); \
+	    \
+	    (bp)->inPtr = newBuf + ((bp)->inPtr - (bp)->buffer); \
+	    (bp)->outPtr = newBuf + ((bp)->outPtr - (bp)->buffer);\
+	    (bp)->buffer = newBuf;\
+	    (bp)->size = newSize;\
+	    (bp)->left = newSize - ((bp)->inPtr - (bp)->buffer);\
+	}
+
+#define	BUF_DEF_SIZE	256 	/* Default buffer size */
+#define	BUF_ADD_INC	256 	/* Expansion increment when Adding */
+#define	BUF_UNGET_INC	16  	/* Expansion increment when Ungetting */
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_OvAddByte --
+ *	Add a single byte to the buffer.  left is zero or negative.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The buffer may be expanded.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_OvAddByte (Buffer bp, int byte)
+{
+    bp->left = 0;
+    BufExpand (bp, 1);
+
+    *bp->inPtr++ = byte;
+    bp->left--;
+
+    /*
+     * Null-terminate
+     */
+    *bp->inPtr = 0;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_AddBytes --
+ *	Add a number of bytes to the buffer.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_AddBytes (Buffer bp, int numBytes, const Byte *bytesPtr)
+{
+
+    BufExpand (bp, numBytes);
+
+    memcpy (bp->inPtr, bytesPtr, numBytes);
+    bp->inPtr += numBytes;
+    bp->left -= numBytes;
+
+    /*
+     * Null-terminate
+     */
+    *bp->inPtr = 0;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_UngetByte --
+ *	Place the byte back at the beginning of the buffer.
+ *
+ * Results:
+ *	SUCCESS if the byte was added ok. FAILURE if not.
+ *
+ * Side Effects:
+ *	The byte is stuffed in the buffer and outPtr is decremented.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_UngetByte (Buffer bp, int byte)
+{
+
+    if (bp->outPtr != bp->buffer) {
+	bp->outPtr--;
+	*bp->outPtr = byte;
+    } else if (bp->outPtr == bp->inPtr) {
+	*bp->inPtr = byte;
+	bp->inPtr++;
+	bp->left--;
+	*bp->inPtr = 0;
+    } else {
+	/*
+	 * Yech. have to expand the buffer to stuff this thing in.
+	 * We use a different expansion constant because people don't
+	 * usually push back many bytes when they're doing it a byte at
+	 * a time...
+	 */
+	int 	  numBytes = bp->inPtr - bp->outPtr;
+	Byte	  *newBuf;
+
+	newBuf = (Byte *)emalloc(bp->size + BUF_UNGET_INC);
+	memcpy ((char *)(newBuf+BUF_UNGET_INC), (char *)bp->outPtr, numBytes+1);
+	bp->outPtr = newBuf + BUF_UNGET_INC;
+	bp->inPtr = bp->outPtr + numBytes;
+	free ((char *)bp->buffer);
+	bp->buffer = newBuf;
+	bp->size += BUF_UNGET_INC;
+	bp->left = bp->size - (bp->inPtr - bp->buffer);
+	bp->outPtr -= 1;
+	*bp->outPtr = byte;
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_UngetBytes --
+ *	Push back a series of bytes at the beginning of the buffer.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	outPtr is decremented and the bytes copied into the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_UngetBytes (Buffer bp, int numBytes, Byte *bytesPtr)
+{
+
+    if (bp->outPtr - bp->buffer >= numBytes) {
+	bp->outPtr -= numBytes;
+	memcpy (bp->outPtr, bytesPtr, numBytes);
+    } else if (bp->outPtr == bp->inPtr) {
+	Buf_AddBytes (bp, numBytes, bytesPtr);
+    } else {
+	int 	  curNumBytes = bp->inPtr - bp->outPtr;
+	Byte	  *newBuf;
+	int 	  newBytes = max(numBytes,BUF_UNGET_INC);
+
+	newBuf = (Byte *)emalloc (bp->size + newBytes);
+	memcpy((char *)(newBuf+newBytes), (char *)bp->outPtr, curNumBytes+1);
+	bp->outPtr = newBuf + newBytes;
+	bp->inPtr = bp->outPtr + curNumBytes;
+	free ((char *)bp->buffer);
+	bp->buffer = newBuf;
+	bp->size += newBytes;
+	bp->left = bp->size - (bp->inPtr - bp->buffer);
+	bp->outPtr -= numBytes;
+	memcpy ((char *)bp->outPtr, (char *)bytesPtr, numBytes);
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_GetByte --
+ *	Return the next byte from the buffer. Actually returns an integer.
+ *
+ * Results:
+ *	Returns BUF_ERROR if there's no byte in the buffer, or the byte
+ *	itself if there is one.
+ *
+ * Side Effects:
+ *	outPtr is incremented and both outPtr and inPtr will be reset if
+ *	the buffer is emptied.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Buf_GetByte (Buffer bp)
+{
+    int	    res;
+
+    if (bp->inPtr == bp->outPtr) {
+	return (BUF_ERROR);
+    } else {
+	res = (int) *bp->outPtr;
+	bp->outPtr += 1;
+	if (bp->outPtr == bp->inPtr) {
+	    bp->outPtr = bp->inPtr = bp->buffer;
+	    bp->left = bp->size;
+	    *bp->inPtr = 0;
+	}
+	return (res);
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_GetBytes --
+ *	Extract a number of bytes from the buffer.
+ *
+ * Results:
+ *	The number of bytes gotten.
+ *
+ * Side Effects:
+ *	The passed array is overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Buf_GetBytes (Buffer bp, int numBytes, Byte *bytesPtr)
+{
+
+    if (bp->inPtr - bp->outPtr < numBytes) {
+	numBytes = bp->inPtr - bp->outPtr;
+    }
+    memcpy (bytesPtr, bp->outPtr, numBytes);
+    bp->outPtr += numBytes;
+
+    if (bp->outPtr == bp->inPtr) {
+	bp->outPtr = bp->inPtr = bp->buffer;
+	bp->left = bp->size;
+	*bp->inPtr = 0;
+    }
+    return (numBytes);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_GetAll --
+ *	Get all the available data at once.
+ *
+ * Results:
+ *	A pointer to the data and the number of bytes available.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Byte *
+Buf_GetAll (Buffer bp, int *numBytesPtr)
+{
+
+    if (numBytesPtr != (int *)NULL) {
+	*numBytesPtr = bp->inPtr - bp->outPtr;
+    }
+
+    return (bp->outPtr);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Discard --
+ *	Throw away bytes in a buffer.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The bytes are discarded.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Discard (Buffer bp, int numBytes)
+{
+
+    if (bp->inPtr - bp->outPtr <= numBytes) {
+	bp->inPtr = bp->outPtr = bp->buffer;
+	bp->left = bp->size;
+	*bp->inPtr = 0;
+    } else {
+	bp->outPtr += numBytes;
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Size --
+ *	Returns the number of bytes in the given buffer. Doesn't include
+ *	the null-terminating byte.
+ *
+ * Results:
+ *	The number of bytes.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Buf_Size (Buffer buf)
+{
+    return (buf->inPtr - buf->outPtr);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Init --
+ *	Initialize a buffer. If no initial size is given, a reasonable
+ *	default is used.
+ *
+ * Results:
+ *	A buffer to be given to other functions in this library.
+ *
+ * Side Effects:
+ *	The buffer is created, the space allocated and pointers
+ *	initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+Buffer
+Buf_Init (int size)
+{
+    Buffer bp;	  	/* New Buffer */
+
+    bp = (Buffer)emalloc(sizeof(*bp));
+
+    if (size <= 0) {
+	size = BUF_DEF_SIZE;
+    }
+    bp->left = bp->size = size;
+    bp->buffer = (Byte *)emalloc(size);
+    bp->inPtr = bp->outPtr = bp->buffer;
+    *bp->inPtr = 0;
+
+    return (bp);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Destroy --
+ *	Destroy a buffer, and optionally free its data, too.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The buffer is freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Destroy (Buffer buf, Boolean freeData)
+{
+
+    if (freeData) {
+	free ((char *)buf->buffer);
+    }
+    free ((char *)buf);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_ReplaceLastByte --
+ *     Replace the last byte in a buffer.
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     If the buffer was empty intially, then a new byte will be added.
+ *     Otherwise, the last byte is overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_ReplaceLastByte (Buffer buf, int byte)
+{
+    if (buf->inPtr == buf->outPtr)
+        Buf_AddByte(buf, byte);
+    else
+        *(buf->inPtr - 1) = byte;
+}
Index: trunk/src/kmk/buf.h
===================================================================
--- trunk/src/kmk/buf.h	(revision 9)
+++ trunk/src/kmk/buf.h	(revision 9)
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)buf.h	8.2 (Berkeley) 4/28/95
+ * $FreeBSD: src/usr.bin/make/buf.h,v 1.12 2002/09/17 21:29:06 jmallett Exp $
+ */
+
+/*-
+ * buf.h --
+ *	Header for users of the buf library.
+ */
+
+#ifndef _BUF_H
+#define	_BUF_H
+
+#include    "sprite.h"
+
+typedef char Byte;
+
+typedef struct Buffer {
+    int	    size; 	/* Current size of the buffer */
+    int     left;	/* Space left (== size - (inPtr - buffer)) */
+    Byte    *buffer;	/* The buffer itself */
+    Byte    *inPtr;	/* Place to write to */
+    Byte    *outPtr;	/* Place to read from */
+} *Buffer;
+
+/* Buf_AddByte adds a single byte to a buffer. */
+#define	Buf_AddByte(bp, byte) \
+	(void) (--(bp)->left <= 0 ? Buf_OvAddByte(bp, byte), 1 : \
+		(*(bp)->inPtr++ = (byte), *(bp)->inPtr = 0), 1)
+
+#define	BUF_ERROR 256
+
+void Buf_OvAddByte(Buffer, int);
+void Buf_AddBytes(Buffer, int, const Byte *);
+void Buf_UngetByte(Buffer, int);
+void Buf_UngetBytes(Buffer, int, Byte *);
+int Buf_GetByte(Buffer);
+int Buf_GetBytes(Buffer, int, Byte *);
+Byte *Buf_GetAll(Buffer, int *);
+void Buf_Discard(Buffer, int);
+int Buf_Size(Buffer);
+Buffer Buf_Init(int);
+void Buf_Destroy(Buffer, Boolean);
+void Buf_ReplaceLastByte(Buffer, int);
+
+#endif /* _BUF_H */
Index: trunk/src/kmk/compat.c
===================================================================
--- trunk/src/kmk/compat.c	(revision 9)
+++ trunk/src/kmk/compat.c	(revision 9)
@@ -0,0 +1,702 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)compat.c	8.2 (Berkeley) 3/19/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/compat.c,v 1.34 2002/10/09 03:42:09 jmallett Exp $");
+
+/*-
+ * compat.c --
+ *	The routines in this file implement the full-compatibility
+ *	mode of PMake. Most of the special functionality of PMake
+ *	is available in this mode. Things not supported:
+ *	    - different shells.
+ *	    - friendly variable substitution.
+ *
+ * Interface:
+ *	Compat_Run	    Initialize things for this module and recreate
+ *	    	  	    thems as need creatin'
+ */
+
+#include    <stdio.h>
+#include    <sys/types.h>
+#include    <sys/stat.h>
+#include    <sys/wait.h>
+#include    <ctype.h>
+#include    <errno.h>
+#include    <signal.h>
+#include    <unistd.h>
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "job.h"
+
+/*
+ * The following array is used to make a fast determination of which
+ * characters are interpreted specially by the shell.  If a command
+ * contains any of these characters, it is executed by the shell, not
+ * directly by us.
+ */
+
+static char 	    meta[256];
+
+static GNode	    *curTarg = NULL;
+static GNode	    *ENDNode;
+static void CompatInterrupt(int);
+static int CompatRunCommand(void *, void *);
+static int CompatMake(void *, void *);
+static int shellneed(char *);
+
+static char *sh_builtin[] = { 
+	"alias", "cd", "eval", "exec", "exit", "read", "set", "ulimit", 
+	"unalias", "umask", "unset", "wait", ":", 0};
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatInterrupt --
+ *	Interrupt the creation of the current target and remove it if
+ *	it ain't precious.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The target is removed and the process exits. If .INTERRUPT exists,
+ *	its commands are run first WITH INTERRUPTS IGNORED..
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CompatInterrupt (int signo)
+{
+    GNode   *gn;
+
+    if ((curTarg != NULL) && !Targ_Precious (curTarg)) {
+	char	  *p1;
+	char 	  *file = Var_Value (TARGET, curTarg, &p1);
+
+	if (!noExecute && eunlink(file) != -1) {
+	    printf ("*** %s removed\n", file);
+	}
+	efree(p1);
+
+	/*
+	 * Run .INTERRUPT only if hit with interrupt signal
+	 */
+	if (signo == SIGINT) {
+	    gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+	    if (gn != NULL) {
+		Lst_ForEach(gn->commands, CompatRunCommand, (void *)gn);
+	    }
+	}
+
+    }
+    if (signo == SIGQUIT)
+	exit(signo);
+    (void) signal(signo, SIG_DFL);
+    (void) kill(getpid(), signo);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * shellneed --
+ *	
+ * Results:
+ *	Returns 1 if a specified line must be executed by the shell,
+ *	0 if it can be run via execve, and -1 if the command is a no-op.
+ *
+ * Side Effects:
+ *	None.
+ *	
+ *-----------------------------------------------------------------------
+ */
+static int
+shellneed (char *cmd)
+{
+	char **av, **p;
+	int ac;
+
+	av = brk_string(cmd, &ac, TRUE);
+	for(p = sh_builtin; *p != 0; p++)
+		if (strcmp(av[1], *p) == 0)
+			return (1);
+	return (0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatRunCommand --
+ *	Execute the next command for a target. If the command returns an
+ *	error, the node's made field is set to ERROR and creation stops.
+ *	The node from which the command came is also given.
+ *
+ * Results:
+ *	0 if the command succeeded, 1 if an error occurred.
+ *
+ * Side Effects:
+ *	The node's 'made' field may be set to ERROR.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CompatRunCommand (void *cmdp, void *gnp)
+{
+    char    	  *cmdStart;	/* Start of expanded command */
+    char	  *cp;
+    Boolean 	  silent,   	/* Don't print command */
+		  errCheck; 	/* Check errors */
+    int 	  reason;   	/* Reason for child's death */
+    int	    	  status;   	/* Description of child's death */
+    int	    	  cpid;	    	/* Child actually found */
+    ReturnStatus  rstat;	/* Status of fork */
+    LstNode 	  cmdNode;  	/* Node where current command is located */
+    char    	  **av;	    	/* Argument vector for thing to exec */
+    int	    	  argc;	    	/* Number of arguments in av or 0 if not
+				 * dynamically allocated */
+    int		  internal;	/* Various values.. */
+    char	  *cmd = (char *) cmdp;
+    GNode	  *gn = (GNode *) gnp;
+
+    /*
+     * Avoid clobbered variable warnings by forcing the compiler
+     * to ``unregister'' variables
+     */
+#if __GNUC__
+    (void) &av;
+    (void) &errCheck;
+#endif
+    silent = gn->type & OP_SILENT;
+    errCheck = !(gn->type & OP_IGNORE);
+
+    cmdNode = Lst_Member (gn->commands, (void *)cmd);
+    cmdStart = Var_Subst (NULL, cmd, gn, FALSE);
+
+    /*
+     * brk_string will return an argv with a NULL in av[0], thus causing
+     * execvp to choke and die horribly. Besides, how can we execute a null
+     * command? In any case, we warn the user that the command expanded to
+     * nothing (is this the right thing to do?).
+     */
+
+    if (*cmdStart == '\0') {
+	free(cmdStart);
+	Error("%s expands to empty string", cmd);
+	return(0);
+    } else {
+	cmd = cmdStart;
+    }
+    Lst_Replace (cmdNode, (void *)cmdStart);
+
+    if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
+	(void)Lst_AtEnd(ENDNode->commands, (void *)cmdStart);
+	return(0);
+    } else if (strcmp(cmdStart, "...") == 0) {
+	gn->type |= OP_SAVE_CMDS;
+	return(0);
+    }
+
+    while ((*cmd == '@') || (*cmd == '-')) {
+	if (*cmd == '@') {
+	    silent = DEBUG(LOUD) ? FALSE : TRUE;
+	} else {
+	    errCheck = FALSE;
+	}
+	cmd++;
+    }
+
+    while (isspace((unsigned char)*cmd))
+	cmd++;
+
+    /*
+     * Search for meta characters in the command. If there are no meta
+     * characters, there's no need to execute a shell to execute the
+     * command.
+     */
+    for (cp = cmd; !meta[(unsigned char)*cp]; cp++) {
+	continue;
+    }
+
+    /*
+     * Print the command before echoing if we're not supposed to be quiet for
+     * this one. We also print the command if -n given.
+     */
+    if (!silent || noExecute) {
+	printf ("%s\n", cmd);
+	fflush(stdout);
+    }
+
+    /*
+     * If we're not supposed to execute any commands, this is as far as
+     * we go...
+     */
+    if (noExecute) {
+	return (0);
+    }
+
+    if (*cp != '\0') {
+	/*
+	 * If *cp isn't the null character, we hit a "meta" character and
+	 * need to pass the command off to the shell. We give the shell the
+	 * -e flag as well as -c if it's supposed to exit when it hits an
+	 * error.
+	 */
+	static char	*shargv[4] = { "/bin/sh" };
+
+	shargv[1] = (errCheck ? "-ec" : "-c");
+	shargv[2] = cmd;
+	shargv[3] = (char *)NULL;
+	av = shargv;
+	argc = 0;
+    } else if ((internal = shellneed(cmd))) {
+	/*
+	 * This command must be passed by the shell for other reasons..
+	 * or.. possibly not at all.
+	 */
+	static char	*shargv[4] = { "/bin/sh" };
+
+	if (internal == -1) {
+		/* Command does not need to be executed */
+		return (0);
+	}
+
+	shargv[1] = (errCheck ? "-ec" : "-c");
+	shargv[2] = cmd;
+	shargv[3] = (char *)NULL;
+	av = shargv;
+	argc = 0;
+    } else {
+	/*
+	 * No meta-characters, so no need to exec a shell. Break the command
+	 * into words to form an argument vector we can execute.
+	 * brk_string sticks our name in av[0], so we have to
+	 * skip over it...
+	 */
+	av = brk_string(cmd, &argc, TRUE);
+	av += 1;
+    }
+
+    /*
+     * Fork and execute the single command. If the fork fails, we abort.
+     */
+    cpid = vfork();
+    if (cpid < 0) {
+	Fatal("Could not fork");
+    }
+    if (cpid == 0) {
+	execvp(av[0], av);
+	(void) write (STDERR_FILENO, av[0], strlen (av[0]));
+	(void) write (STDERR_FILENO, ":", 1);
+	(void) write (STDERR_FILENO, strerror(errno), strlen(strerror(errno)));
+	(void) write (STDERR_FILENO, "\n", 1);
+	exit(1);
+    }
+
+    /* 
+     * we need to print out the command associated with this Gnode in
+     * Targ_PrintCmd from Targ_PrintGraph when debugging at level g2,
+     * in main(), Fatal() and DieHorribly(), therefore do not free it
+     * when debugging. 
+     */
+    if (!DEBUG(GRAPH2)) {
+	free(cmdStart);
+	Lst_Replace (cmdNode, cmdp);
+    }
+
+    /*
+     * The child is off and running. Now all we can do is wait...
+     */
+    while (1) {
+
+	while ((rstat = wait(&reason)) != cpid) {
+	    if (rstat == -1 && errno != EINTR) {
+		break;
+	    }
+	}
+
+	if (rstat > -1) {
+	    if (WIFSTOPPED(reason)) {
+		status = WSTOPSIG(reason);		/* stopped */
+	    } else if (WIFEXITED(reason)) {
+		status = WEXITSTATUS(reason);		/* exited */
+		if (status != 0) {
+		    printf ("*** Error code %d", status);
+		}
+	    } else {
+		status = WTERMSIG(reason);		/* signaled */
+		printf ("*** Signal %d", status);
+	    }
+
+
+	    if (!WIFEXITED(reason) || (status != 0)) {
+		if (errCheck) {
+		    gn->made = ERROR;
+		    if (keepgoing) {
+			/*
+			 * Abort the current target, but let others
+			 * continue.
+			 */
+			printf (" (continuing)\n");
+		    }
+		} else {
+		    /*
+		     * Continue executing commands for this target.
+		     * If we return 0, this will happen...
+		     */
+		    printf (" (ignored)\n");
+		    status = 0;
+		}
+	    }
+	    break;
+	} else {
+	    Fatal ("error in wait: %d", rstat);
+	    /*NOTREACHED*/
+	}
+    }
+
+    return (status);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CompatMake --
+ *	Make a target, given the parent, to abort if necessary.
+ *
+ * Results:
+ *	0
+ *
+ * Side Effects:
+ *	If an error is detected and not being ignored, the process exits.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CompatMake (void *gnp, void *pgnp)
+{
+    GNode *gn = (GNode *) gnp;
+    GNode *pgn = (GNode *) pgnp;
+    if (gn->type & OP_USE) {
+	Make_HandleUse(gn, pgn);
+    } else if (gn->made == UNMADE) {
+	/*
+	 * First mark ourselves to be made, then apply whatever transformations
+	 * the suffix module thinks are necessary. Once that's done, we can
+	 * descend and make all our children. If any of them has an error
+	 * but the -k flag was given, our 'make' field will be set FALSE again.
+	 * This is our signal to not attempt to do anything but abort our
+	 * parent as well.
+	 */
+	gn->make = TRUE;
+	gn->made = BEINGMADE;
+	Suff_FindDeps (gn);
+	Lst_ForEach (gn->children, CompatMake, (void *)gn);
+	if (!gn->make) {
+	    gn->made = ABORTED;
+	    pgn->make = FALSE;
+	    return (0);
+	}
+
+	if (Lst_Member (gn->iParents, pgn) != NULL) {
+	    char *p1;
+	    Var_Set (IMPSRC, Var_Value(TARGET, gn, &p1), pgn);
+	    efree(p1);
+	}
+
+	/*
+	 * All the children were made ok. Now cmtime contains the modification
+	 * time of the newest child, we need to find out if we exist and when
+	 * we were modified last. The criteria for datedness are defined by the
+	 * Make_OODate function.
+	 */
+	DEBUGF(MAKE, ("Examining %s...", gn->name));
+	if (! Make_OODate(gn)) {
+	    gn->made = UPTODATE;
+	    DEBUGF(MAKE, ("up-to-date.\n"));
+	    return (0);
+	} else {
+	    DEBUGF(MAKE, ("out-of-date.\n"));
+	}
+
+	/*
+	 * If the user is just seeing if something is out-of-date, exit now
+	 * to tell him/her "yes".
+	 */
+	if (queryFlag) {
+	    exit (-1);
+	}
+
+	/*
+	 * We need to be re-made. We also have to make sure we've got a $?
+	 * variable. To be nice, we also define the $> variable using
+	 * Make_DoAllVar().
+	 */
+	Make_DoAllVar(gn);
+
+	/*
+	 * Alter our type to tell if errors should be ignored or things
+	 * should not be printed so CompatRunCommand knows what to do.
+	 */
+	if (Targ_Ignore (gn)) {
+	    gn->type |= OP_IGNORE;
+	}
+	if (Targ_Silent (gn)) {
+	    gn->type |= OP_SILENT;
+	}
+
+	if (Job_CheckCommands (gn, Fatal)) {
+	    /*
+	     * Our commands are ok, but we still have to worry about the -t
+	     * flag...
+	     */
+	    if (!touchFlag) {
+		curTarg = gn;
+		Lst_ForEach (gn->commands, CompatRunCommand, (void *)gn);
+		curTarg = NULL;
+	    } else {
+		Job_Touch (gn, gn->type & OP_SILENT);
+	    }
+	} else {
+	    gn->made = ERROR;
+	}
+
+	if (gn->made != ERROR) {
+	    /*
+	     * If the node was made successfully, mark it so, update
+	     * its modification time and timestamp all its parents. Note
+	     * that for .ZEROTIME targets, the timestamping isn't done.
+	     * This is to keep its state from affecting that of its parent.
+	     */
+	    gn->made = MADE;
+#ifndef RECHECK
+	    /*
+	     * We can't re-stat the thing, but we can at least take care of
+	     * rules where a target depends on a source that actually creates
+	     * the target, but only if it has changed, e.g.
+	     *
+	     * parse.h : parse.o
+	     *
+	     * parse.o : parse.y
+	     *  	yacc -d parse.y
+	     *  	cc -c y.tab.c
+	     *  	mv y.tab.o parse.o
+	     *  	cmp -s y.tab.h parse.h || mv y.tab.h parse.h
+	     *
+	     * In this case, if the definitions produced by yacc haven't
+	     * changed from before, parse.h won't have been updated and
+	     * gn->mtime will reflect the current modification time for
+	     * parse.h. This is something of a kludge, I admit, but it's a
+	     * useful one..
+	     *
+	     * XXX: People like to use a rule like
+	     *
+	     * FRC:
+	     *
+	     * To force things that depend on FRC to be made, so we have to
+	     * check for gn->children being empty as well...
+	     */
+	    if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) {
+		gn->mtime = now;
+	    }
+#else
+	    /*
+	     * This is what Make does and it's actually a good thing, as it
+	     * allows rules like
+	     *
+	     *	cmp -s y.tab.h parse.h || cp y.tab.h parse.h
+	     *
+	     * to function as intended. Unfortunately, thanks to the stateless
+	     * nature of NFS (and the speed of this program), there are times
+	     * when the modification time of a file created on a remote
+	     * machine will not be modified before the stat() implied by
+	     * the Dir_MTime occurs, thus leading us to believe that the file
+	     * is unchanged, wreaking havoc with files that depend on this one.
+	     *
+	     * I have decided it is better to make too much than to make too
+	     * little, so this stuff is commented out unless you're sure it's
+	     * ok.
+	     * -- ardeb 1/12/88
+	     */
+	    if (noExecute || Dir_MTime(gn) == 0) {
+		gn->mtime = now;
+	    }
+	    if (gn->cmtime > gn->mtime)
+		gn->mtime = gn->cmtime;
+	    DEBUGF(MAKE, ("update time: %s\n", Targ_FmtTime(gn->mtime)));
+#endif
+	    if (!(gn->type & OP_EXEC)) {
+		pgn->childMade = TRUE;
+		Make_TimeStamp(pgn, gn);
+	    }
+	} else if (keepgoing) {
+	    pgn->make = FALSE;
+	} else {
+	    char *p1;
+
+	    printf ("\n\nStop in %s.\n", Var_Value(".CURDIR", gn, &p1));
+	    efree(p1);
+	    exit (1);
+	}
+    } else if (gn->made == ERROR) {
+	/*
+	 * Already had an error when making this beastie. Tell the parent
+	 * to abort.
+	 */
+	pgn->make = FALSE;
+    } else {
+	if (Lst_Member (gn->iParents, pgn) != NULL) {
+	    char *p1;
+	    Var_Set (IMPSRC, Var_Value(TARGET, gn, &p1), pgn);
+	    efree(p1);
+	}
+	switch(gn->made) {
+	    case BEINGMADE:
+		Error("Graph cycles through %s\n", gn->name);
+		gn->made = ERROR;
+		pgn->make = FALSE;
+		break;
+	    case MADE:
+		if ((gn->type & OP_EXEC) == 0) {
+		    pgn->childMade = TRUE;
+		    Make_TimeStamp(pgn, gn);
+		}
+		break;
+	    case UPTODATE:
+		if ((gn->type & OP_EXEC) == 0) {
+		    Make_TimeStamp(pgn, gn);
+		}
+		break;
+	    default:
+		break;
+	}
+    }
+
+    return (0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Compat_Run --
+ *	Start making again, given a list of target nodes.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Compat_Run(Lst targs)
+{
+    char    	  *cp;	    /* Pointer to string of shell meta-characters */
+    GNode   	  *gn = NULL;/* Current root target */
+    int	    	  errors;   /* Number of targets not remade due to errors */
+
+    if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
+	signal(SIGINT, CompatInterrupt);
+    }
+    if (signal(SIGTERM, SIG_IGN) != SIG_IGN) {
+	signal(SIGTERM, CompatInterrupt);
+    }
+    if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
+	signal(SIGHUP, CompatInterrupt);
+    }
+    if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
+	signal(SIGQUIT, CompatInterrupt);
+    }
+
+    for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) {
+	meta[(unsigned char) *cp] = 1;
+    }
+    /*
+     * The null character serves as a sentinel in the string.
+     */
+    meta[0] = 1;
+
+    ENDNode = Targ_FindNode(".END", TARG_CREATE);
+    /*
+     * If the user has defined a .BEGIN target, execute the commands attached
+     * to it.
+     */
+    if (!queryFlag) {
+	gn = Targ_FindNode(".BEGIN", TARG_NOCREATE);
+	if (gn != NULL) {
+	    Lst_ForEach(gn->commands, CompatRunCommand, (void *)gn);
+            if (gn->made == ERROR) {
+                printf("\n\nStop.\n");
+                exit(1);
+            }
+	}
+    }
+
+    /*
+     * For each entry in the list of targets to create, call CompatMake on
+     * it to create the thing. CompatMake will leave the 'made' field of gn
+     * in one of several states:
+     *	    UPTODATE	    gn was already up-to-date
+     *	    MADE  	    gn was recreated successfully
+     *	    ERROR 	    An error occurred while gn was being created
+     *	    ABORTED	    gn was not remade because one of its inferiors
+     *	    	  	    could not be made due to errors.
+     */
+    errors = 0;
+    while (!Lst_IsEmpty (targs)) {
+	gn = (GNode *) Lst_DeQueue (targs);
+	CompatMake (gn, gn);
+
+	if (gn->made == UPTODATE) {
+	    printf ("`%s' is up to date.\n", gn->name);
+	} else if (gn->made == ABORTED) {
+	    printf ("`%s' not remade because of errors.\n", gn->name);
+	    errors += 1;
+	}
+    }
+
+    /*
+     * If the user has defined a .END target, run its commands.
+     */
+    if (errors == 0) {
+	Lst_ForEach(ENDNode->commands, CompatRunCommand, (void *)gn);
+    }
+}
Index: trunk/src/kmk/cond.c
===================================================================
--- trunk/src/kmk/cond.c	(revision 9)
+++ trunk/src/kmk/cond.c	(revision 9)
@@ -0,0 +1,1247 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)cond.c	8.2 (Berkeley) 1/2/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/cond.c,v 1.24 2002/10/09 03:42:09 jmallett Exp $");
+
+/*-
+ * cond.c --
+ *	Functions to handle conditionals in a makefile.
+ *
+ * Interface:
+ *	Cond_Eval 	Evaluate the conditional in the passed line.
+ *
+ */
+
+#include    <ctype.h>
+#include    <math.h>
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "buf.h"
+
+/*
+ * The parsing of conditional expressions is based on this grammar:
+ *	E -> F || E
+ *	E -> F
+ *	F -> T && F
+ *	F -> T
+ *	T -> defined(variable)
+ *	T -> make(target)
+ *	T -> exists(file)
+ *	T -> empty(varspec)
+ *	T -> target(name)
+ *	T -> symbol
+ *	T -> $(varspec) op value
+ *	T -> $(varspec) == "string"
+ *	T -> $(varspec) != "string"
+ *	T -> ( E )
+ *	T -> ! T
+ *	op -> == | != | > | < | >= | <=
+ *
+ * 'symbol' is some other symbol to which the default function (condDefProc)
+ * is applied.
+ *
+ * Tokens are scanned from the 'condExpr' string. The scanner (CondToken)
+ * will return And for '&' and '&&', Or for '|' and '||', Not for '!',
+ * LParen for '(', RParen for ')' and will evaluate the other terminal
+ * symbols, using either the default function or the function given in the
+ * terminal, and return the result as either True or False.
+ *
+ * All Non-Terminal functions (CondE, CondF and CondT) return Err on error.
+ */
+typedef enum {
+    And, Or, Not, True, False, LParen, RParen, EndOfFile, None, Err
+} Token;
+
+/*-
+ * Structures to handle elegantly the different forms of #if's. The
+ * last two fields are stored in condInvert and condDefProc, respectively.
+ */
+static void CondPushBack(Token);
+static int CondGetArg(char **, char **, char *, Boolean);
+static Boolean CondDoDefined(int, char *);
+static int CondStrMatch(void *, void *);
+static Boolean CondDoMake(int, char *);
+static Boolean CondDoExists(int, char *);
+static Boolean CondDoTarget(int, char *);
+static char * CondCvtArg(char *, double *);
+static Token CondToken(Boolean);
+static Token CondT(Boolean);
+static Token CondF(Boolean);
+static Token CondE(Boolean);
+
+static struct If {
+    char	*form;	      /* Form of if */
+    int		formlen;      /* Length of form */
+    Boolean	doNot;	      /* TRUE if default function should be negated */
+    Boolean	(*defProc)(int, char *); /* Default function to apply */
+} ifs[] = {
+    { "ifdef",	  5,	  FALSE,  CondDoDefined },
+    { "ifndef",	  6,	  TRUE,	  CondDoDefined },
+    { "ifmake",	  6,	  FALSE,  CondDoMake },
+    { "ifnmake",  7,	  TRUE,	  CondDoMake },
+    { "if",	  2,	  FALSE,  CondDoDefined },
+    { NULL,	  0,	  FALSE,  NULL }
+};
+
+static Boolean	  condInvert;	    	/* Invert the default function */
+static Boolean	  (*condDefProc)	/* Default function to apply */
+(int, char *);
+static char 	  *condExpr;	    	/* The expression to parse */
+static Token	  condPushBack=None;	/* Single push-back token used in
+					 * parsing */
+
+#define	MAXIF		30	  /* greatest depth of #if'ing */
+
+static Boolean	  condStack[MAXIF]; 	/* Stack of conditionals's values */
+static int  	  condTop = MAXIF;  	/* Top-most conditional */
+static int  	  skipIfLevel=0;    	/* Depth of skipped conditionals */
+static Boolean	  skipLine = FALSE; 	/* Whether the parse module is skipping
+					 * lines */
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondPushBack --
+ *	Push back the most recent token read. We only need one level of
+ *	this, so the thing is just stored in 'condPushback'.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	condPushback is overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CondPushBack (Token t)
+{
+    condPushBack = t;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondGetArg --
+ *	Find the argument of a built-in function.  parens is set to TRUE
+ *	if the arguments are bounded by parens.
+ *
+ * Results:
+ *	The length of the argument and the address of the argument.
+ *
+ * Side Effects:
+ *	The pointer is set to point to the closing parenthesis of the
+ *	function call.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CondGetArg (char **linePtr, char **argPtr, char *func, Boolean parens)
+{
+    char	  *cp;
+    int	    	  argLen;
+    Buffer	  buf;
+
+    cp = *linePtr;
+    if (parens) {
+	while (*cp != '(' && *cp != '\0') {
+	    cp++;
+	}
+	if (*cp == '(') {
+	    cp++;
+	}
+    }
+
+    if (*cp == '\0') {
+	/*
+	 * No arguments whatsoever. Because 'make' and 'defined' aren't really
+	 * "reserved words", we don't print a message. I think this is better
+	 * than hitting the user with a warning message every time s/he uses
+	 * the word 'make' or 'defined' at the beginning of a symbol...
+	 */
+	*argPtr = cp;
+	return (0);
+    }
+
+    while (*cp == ' ' || *cp == '\t') {
+	cp++;
+    }
+
+    /*
+     * Create a buffer for the argument and start it out at 16 characters
+     * long. Why 16? Why not?
+     */
+    buf = Buf_Init(16);
+
+    while ((strchr(" \t)&|", *cp) == (char *)NULL) && (*cp != '\0')) {
+	if (*cp == '$') {
+	    /*
+	     * Parse the variable spec and install it as part of the argument
+	     * if it's valid. We tell Var_Parse to complain on an undefined
+	     * variable, so we don't do it too. Nor do we return an error,
+	     * though perhaps we should...
+	     */
+	    char  	*cp2;
+	    int		len;
+	    Boolean	doFree;
+
+	    cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &doFree);
+
+	    Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2);
+	    if (doFree) {
+		free(cp2);
+	    }
+	    cp += len;
+	} else {
+	    Buf_AddByte(buf, (Byte)*cp);
+	    cp++;
+	}
+    }
+
+    Buf_AddByte(buf, (Byte)'\0');
+    *argPtr = (char *)Buf_GetAll(buf, &argLen);
+    Buf_Destroy(buf, FALSE);
+
+    while (*cp == ' ' || *cp == '\t') {
+	cp++;
+    }
+    if (parens && *cp != ')') {
+	Parse_Error (PARSE_WARNING, "Missing closing parenthesis for %s()",
+		     func);
+	return (0);
+    } else if (parens) {
+	/*
+	 * Advance pointer past close parenthesis.
+	 */
+	cp++;
+    }
+
+    *linePtr = cp;
+    return (argLen);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoDefined --
+ *	Handle the 'defined' function for conditionals.
+ *
+ * Results:
+ *	TRUE if the given variable is defined.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoDefined (int argLen, char *arg)
+{
+    char    savec = arg[argLen];
+    char    *p1;
+    Boolean result;
+
+    arg[argLen] = '\0';
+    if (Var_Value (arg, VAR_CMD, &p1) != (char *)NULL) {
+	result = TRUE;
+    } else {
+	result = FALSE;
+    }
+    efree(p1);
+    arg[argLen] = savec;
+    return (result);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondStrMatch --
+ *	Front-end for Str_Match so it returns 0 on match and non-zero
+ *	on mismatch. Callback function for CondDoMake via Lst_Find
+ *
+ * Results:
+ *	0 if string matches pattern
+ *
+ * Side Effects:
+ *	None
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+CondStrMatch(void *string, void *pattern)
+{
+    return(!Str_Match((char *) string,(char *) pattern));
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoMake --
+ *	Handle the 'make' function for conditionals.
+ *
+ * Results:
+ *	TRUE if the given target is being made.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoMake (int argLen, char *arg)
+{
+    char    savec = arg[argLen];
+    Boolean result;
+
+    arg[argLen] = '\0';
+    if (Lst_Find (create, (void *)arg, CondStrMatch) == NULL) {
+	result = FALSE;
+    } else {
+	result = TRUE;
+    }
+    arg[argLen] = savec;
+    return (result);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoExists --
+ *	See if the given file exists.
+ *
+ * Results:
+ *	TRUE if the file exists and FALSE if it does not.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoExists (int argLen, char *arg)
+{
+    char    savec = arg[argLen];
+    Boolean result;
+    char    *path;
+
+    arg[argLen] = '\0';
+    path = Dir_FindFile(arg, dirSearchPath);
+    if (path != (char *)NULL) {
+	result = TRUE;
+	free(path);
+    } else {
+	result = FALSE;
+    }
+    arg[argLen] = savec;
+    return (result);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoTarget --
+ *	See if the given node exists and is an actual target.
+ *
+ * Results:
+ *	TRUE if the node exists as a target and FALSE if it does not.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoTarget (int argLen, char *arg)
+{
+    char    savec = arg[argLen];
+    Boolean result;
+    GNode   *gn;
+
+    arg[argLen] = '\0';
+    gn = Targ_FindNode(arg, TARG_NOCREATE);
+    if ((gn != NULL) && !OP_NOP(gn->type)) {
+	result = TRUE;
+    } else {
+	result = FALSE;
+    }
+    arg[argLen] = savec;
+    return (result);
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondCvtArg --
+ *	Convert the given number into a double. If the number begins
+ *	with 0x, it is interpreted as a hexadecimal integer
+ *	and converted to a double from there. All other strings just have
+ *	strtod called on them.
+ *
+ * Results:
+ *	Sets 'value' to double value of string.
+ *	Returns address of the first character after the last valid
+ *	character of the converted number.
+ *
+ * Side Effects:
+ *	Can change 'value' even if string is not a valid number.
+ *
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+CondCvtArg(char *str, double *value)
+{
+    if ((*str == '0') && (str[1] == 'x')) {
+	long i;
+
+	for (str += 2, i = 0; ; str++) {
+	    int x;
+	    if (isdigit((unsigned char) *str))
+		x  = *str - '0';
+	    else if (isxdigit((unsigned char) *str))
+		x = 10 + *str - isupper((unsigned char) *str) ? 'A' : 'a';
+	    else {
+		*value = (double) i;
+		return str;
+	    }
+	    i = (i << 4) + x;
+	}
+    }
+    else {
+	char *eptr;
+	*value = strtod(str, &eptr);
+	return eptr;
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondToken --
+ *	Return the next token from the input.
+ *
+ * Results:
+ *	A Token for the next lexical token in the stream.
+ *
+ * Side Effects:
+ *	condPushback will be set back to None if it is used.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondToken(Boolean doEval)
+{
+    Token	  t;
+
+    if (condPushBack == None) {
+	while (*condExpr == ' ' || *condExpr == '\t') {
+	    condExpr++;
+	}
+	switch (*condExpr) {
+	    case '(':
+		t = LParen;
+		condExpr++;
+		break;
+	    case ')':
+		t = RParen;
+		condExpr++;
+		break;
+	    case '|':
+		if (condExpr[1] == '|') {
+		    condExpr++;
+		}
+		condExpr++;
+		t = Or;
+		break;
+	    case '&':
+		if (condExpr[1] == '&') {
+		    condExpr++;
+		}
+		condExpr++;
+		t = And;
+		break;
+	    case '!':
+		t = Not;
+		condExpr++;
+		break;
+	    case '\n':
+	    case '\0':
+		t = EndOfFile;
+		break;
+	    case '$': {
+		char	*lhs;
+		char	*rhs;
+		char	*op;
+		int	varSpecLen;
+		Boolean	doFree;
+
+		/*
+		 * Parse the variable spec and skip over it, saving its
+		 * value in lhs.
+		 */
+		t = Err;
+		lhs = Var_Parse(condExpr, VAR_CMD, doEval,&varSpecLen,&doFree);
+		if (lhs == var_Error) {
+		    /*
+		     * Even if !doEval, we still report syntax errors, which
+		     * is what getting var_Error back with !doEval means.
+		     */
+		    return(Err);
+		}
+		condExpr += varSpecLen;
+
+		if (!isspace((unsigned char) *condExpr) &&
+		    strchr("!=><", *condExpr) == NULL) {
+		    Buffer buf;
+		    char *cp;
+
+		    buf = Buf_Init(0);
+
+		    for (cp = lhs; *cp; cp++)
+			Buf_AddByte(buf, (Byte)*cp);
+
+		    if (doFree)
+			free(lhs);
+
+		    for (;*condExpr && !isspace((unsigned char) *condExpr);
+			 condExpr++)
+			Buf_AddByte(buf, (Byte)*condExpr);
+
+		    Buf_AddByte(buf, (Byte)'\0');
+		    lhs = (char *)Buf_GetAll(buf, &varSpecLen);
+		    Buf_Destroy(buf, FALSE);
+
+		    doFree = TRUE;
+		}
+
+		/*
+		 * Skip whitespace to get to the operator
+		 */
+		while (isspace((unsigned char) *condExpr))
+		    condExpr++;
+
+		/*
+		 * Make sure the operator is a valid one. If it isn't a
+		 * known relational operator, pretend we got a
+		 * != 0 comparison.
+		 */
+		op = condExpr;
+		switch (*condExpr) {
+		    case '!':
+		    case '=':
+		    case '<':
+		    case '>':
+			if (condExpr[1] == '=') {
+			    condExpr += 2;
+			} else {
+			    condExpr += 1;
+			}
+			break;
+		    default:
+			op = "!=";
+			rhs = "0";
+
+			goto do_compare;
+		}
+		while (isspace((unsigned char) *condExpr)) {
+		    condExpr++;
+		}
+		if (*condExpr == '\0') {
+		    Parse_Error(PARSE_WARNING,
+				"Missing right-hand-side of operator");
+		    goto error;
+		}
+		rhs = condExpr;
+do_compare:
+		if (*rhs == '"') {
+		    /*
+		     * Doing a string comparison. Only allow == and != for
+		     * operators.
+		     */
+		    char    *string;
+		    char    *cp, *cp2;
+		    int	    qt;
+		    Buffer  buf;
+
+do_string_compare:
+		    if (((*op != '!') && (*op != '=')) || (op[1] != '=')) {
+			Parse_Error(PARSE_WARNING,
+		"String comparison operator should be either == or !=");
+			goto error;
+		    }
+
+		    buf = Buf_Init(0);
+		    qt = *rhs == '"' ? 1 : 0;
+
+		    for (cp = &rhs[qt];
+			 ((qt && (*cp != '"')) ||
+			  (!qt && strchr(" \t)", *cp) == NULL)) &&
+			 (*cp != '\0'); cp++) {
+			if ((*cp == '\\') && (cp[1] != '\0')) {
+			    /*
+			     * Backslash escapes things -- skip over next
+			     * character, if it exists.
+			     */
+			    cp++;
+			    Buf_AddByte(buf, (Byte)*cp);
+			} else if (*cp == '$') {
+			    int	len;
+			    Boolean freeIt;
+
+			    cp2 = Var_Parse(cp, VAR_CMD, doEval,&len, &freeIt);
+			    if (cp2 != var_Error) {
+				Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2);
+				if (freeIt) {
+				    free(cp2);
+				}
+				cp += len - 1;
+			    } else {
+				Buf_AddByte(buf, (Byte)*cp);
+			    }
+			} else {
+			    Buf_AddByte(buf, (Byte)*cp);
+			}
+		    }
+
+		    Buf_AddByte(buf, (Byte)0);
+
+		    string = (char *)Buf_GetAll(buf, (int *)0);
+		    Buf_Destroy(buf, FALSE);
+
+		    DEBUGF(COND, ("lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
+			   lhs, string, op));
+		    /*
+		     * Null-terminate rhs and perform the comparison.
+		     * t is set to the result.
+		     */
+		    if (*op == '=') {
+			t = strcmp(lhs, string) ? False : True;
+		    } else {
+			t = strcmp(lhs, string) ? True : False;
+		    }
+		    free(string);
+		    if (rhs == condExpr) {
+		    	if (!qt && *cp == ')')
+			    condExpr = cp;
+			else
+			    condExpr = cp + 1;
+		    }
+		} else {
+		    /*
+		     * rhs is either a float or an integer. Convert both the
+		     * lhs and the rhs to a double and compare the two.
+		     */
+		    double  	left, right;
+		    char    	*string;
+
+		    if (*CondCvtArg(lhs, &left) != '\0')
+			goto do_string_compare;
+		    if (*rhs == '$') {
+			int 	len;
+			Boolean	freeIt;
+
+			string = Var_Parse(rhs, VAR_CMD, doEval,&len,&freeIt);
+			if (string == var_Error) {
+			    right = 0.0;
+			} else {
+			    if (*CondCvtArg(string, &right) != '\0') {
+				if (freeIt)
+				    free(string);
+				goto do_string_compare;
+			    }
+			    if (freeIt)
+				free(string);
+			    if (rhs == condExpr)
+				condExpr += len;
+			}
+		    } else {
+			char *c = CondCvtArg(rhs, &right);
+			if (*c != '\0' && !isspace((unsigned char) *c))
+			    goto do_string_compare;
+			if (rhs == condExpr) {
+			    /*
+			     * Skip over the right-hand side
+			     */
+			    while(!isspace((unsigned char) *condExpr) &&
+				  (*condExpr != '\0')) {
+				condExpr++;
+			    }
+			}
+		    }
+
+		    DEBUGF(COND, ("left = %f, right = %f, op = %.2s\n", left,
+			   right, op));
+		    switch(op[0]) {
+		    case '!':
+			if (op[1] != '=') {
+			    Parse_Error(PARSE_WARNING,
+					"Unknown operator");
+			    goto error;
+			}
+			t = (left != right ? True : False);
+			break;
+		    case '=':
+			if (op[1] != '=') {
+			    Parse_Error(PARSE_WARNING,
+					"Unknown operator");
+			    goto error;
+			}
+			t = (left == right ? True : False);
+			break;
+		    case '<':
+			if (op[1] == '=') {
+			    t = (left <= right ? True : False);
+			} else {
+			    t = (left < right ? True : False);
+			}
+			break;
+		    case '>':
+			if (op[1] == '=') {
+			    t = (left >= right ? True : False);
+			} else {
+			    t = (left > right ? True : False);
+			}
+			break;
+		    default:
+			break;
+		    }
+		}
+error:
+		if (doFree)
+		    free(lhs);
+		break;
+	    }
+	    default: {
+		Boolean (*evalProc)(int, char *);
+		Boolean invert = FALSE;
+		char	*arg;
+		int	arglen;
+
+		if (strncmp (condExpr, "defined", 7) == 0) {
+		    /*
+		     * Use CondDoDefined to evaluate the argument and
+		     * CondGetArg to extract the argument from the 'function
+		     * call'.
+		     */
+		    evalProc = CondDoDefined;
+		    condExpr += 7;
+		    arglen = CondGetArg (&condExpr, &arg, "defined", TRUE);
+		    if (arglen == 0) {
+			condExpr -= 7;
+			goto use_default;
+		    }
+		} else if (strncmp (condExpr, "make", 4) == 0) {
+		    /*
+		     * Use CondDoMake to evaluate the argument and
+		     * CondGetArg to extract the argument from the 'function
+		     * call'.
+		     */
+		    evalProc = CondDoMake;
+		    condExpr += 4;
+		    arglen = CondGetArg (&condExpr, &arg, "make", TRUE);
+		    if (arglen == 0) {
+			condExpr -= 4;
+			goto use_default;
+		    }
+		} else if (strncmp (condExpr, "exists", 6) == 0) {
+		    /*
+		     * Use CondDoExists to evaluate the argument and
+		     * CondGetArg to extract the argument from the
+		     * 'function call'.
+		     */
+		    evalProc = CondDoExists;
+		    condExpr += 6;
+		    arglen = CondGetArg(&condExpr, &arg, "exists", TRUE);
+		    if (arglen == 0) {
+			condExpr -= 6;
+			goto use_default;
+		    }
+		} else if (strncmp(condExpr, "empty", 5) == 0) {
+		    /*
+		     * Use Var_Parse to parse the spec in parens and return
+		     * True if the resulting string is empty.
+		     */
+		    int	    length;
+		    Boolean doFree;
+		    char    *val;
+
+		    condExpr += 5;
+
+		    for (arglen = 0;
+			 condExpr[arglen] != '(' && condExpr[arglen] != '\0';
+			 arglen += 1)
+			continue;
+
+		    if (condExpr[arglen] != '\0') {
+			val = Var_Parse(&condExpr[arglen - 1], VAR_CMD,
+					doEval, &length, &doFree);
+			if (val == var_Error) {
+			    t = Err;
+			} else {
+			    /*
+			     * A variable is empty when it just contains
+			     * spaces... 4/15/92, christos
+			     */
+			    char *p;
+			    for (p = val; *p && isspace((unsigned char)*p); p++)
+				continue;
+			    t = (*p == '\0') ? True : False;
+			}
+			if (doFree) {
+			    free(val);
+			}
+			/*
+			 * Advance condExpr to beyond the closing ). Note that
+			 * we subtract one from arglen + length b/c length
+			 * is calculated from condExpr[arglen - 1].
+			 */
+			condExpr += arglen + length - 1;
+		    } else {
+			condExpr -= 5;
+			goto use_default;
+		    }
+		    break;
+		} else if (strncmp (condExpr, "target", 6) == 0) {
+		    /*
+		     * Use CondDoTarget to evaluate the argument and
+		     * CondGetArg to extract the argument from the
+		     * 'function call'.
+		     */
+		    evalProc = CondDoTarget;
+		    condExpr += 6;
+		    arglen = CondGetArg(&condExpr, &arg, "target", TRUE);
+		    if (arglen == 0) {
+			condExpr -= 6;
+			goto use_default;
+		    }
+		} else {
+		    /*
+		     * The symbol is itself the argument to the default
+		     * function. We advance condExpr to the end of the symbol
+		     * by hand (the next whitespace, closing paren or
+		     * binary operator) and set to invert the evaluation
+		     * function if condInvert is TRUE.
+		     */
+		use_default:
+		    invert = condInvert;
+		    evalProc = condDefProc;
+		    arglen = CondGetArg(&condExpr, &arg, "", FALSE);
+		}
+
+		/*
+		 * Evaluate the argument using the set function. If invert
+		 * is TRUE, we invert the sense of the function.
+		 */
+		t = (!doEval || (* evalProc) (arglen, arg) ?
+		     (invert ? False : True) :
+		     (invert ? True : False));
+		free(arg);
+		break;
+	    }
+	}
+    } else {
+	t = condPushBack;
+	condPushBack = None;
+    }
+    return (t);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondT --
+ *	Parse a single term in the expression. This consists of a terminal
+ *	symbol or Not and a terminal symbol (not including the binary
+ *	operators):
+ *	    T -> defined(variable) | make(target) | exists(file) | symbol
+ *	    T -> ! T | ( E )
+ *
+ * Results:
+ *	True, False or Err.
+ *
+ * Side Effects:
+ *	Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondT(Boolean doEval)
+{
+    Token   t;
+
+    t = CondToken(doEval);
+
+    if (t == EndOfFile) {
+	/*
+	 * If we reached the end of the expression, the expression
+	 * is malformed...
+	 */
+	t = Err;
+    } else if (t == LParen) {
+	/*
+	 * T -> ( E )
+	 */
+	t = CondE(doEval);
+	if (t != Err) {
+	    if (CondToken(doEval) != RParen) {
+		t = Err;
+	    }
+	}
+    } else if (t == Not) {
+	t = CondT(doEval);
+	if (t == True) {
+	    t = False;
+	} else if (t == False) {
+	    t = True;
+	}
+    }
+    return (t);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondF --
+ *	Parse a conjunctive factor (nice name, wot?)
+ *	    F -> T && F | T
+ *
+ * Results:
+ *	True, False or Err
+ *
+ * Side Effects:
+ *	Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondF(Boolean doEval)
+{
+    Token   l, o;
+
+    l = CondT(doEval);
+    if (l != Err) {
+	o = CondToken(doEval);
+
+	if (o == And) {
+	    /*
+	     * F -> T && F
+	     *
+	     * If T is False, the whole thing will be False, but we have to
+	     * parse the r.h.s. anyway (to throw it away).
+	     * If T is True, the result is the r.h.s., be it an Err or no.
+	     */
+	    if (l == True) {
+		l = CondF(doEval);
+	    } else {
+		(void) CondF(FALSE);
+	    }
+	} else {
+	    /*
+	     * F -> T
+	     */
+	    CondPushBack (o);
+	}
+    }
+    return (l);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondE --
+ *	Main expression production.
+ *	    E -> F || E | F
+ *
+ * Results:
+ *	True, False or Err.
+ *
+ * Side Effects:
+ *	Tokens are, of course, consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondE(Boolean doEval)
+{
+    Token   l, o;
+
+    l = CondF(doEval);
+    if (l != Err) {
+	o = CondToken(doEval);
+
+	if (o == Or) {
+	    /*
+	     * E -> F || E
+	     *
+	     * A similar thing occurs for ||, except that here we make sure
+	     * the l.h.s. is False before we bother to evaluate the r.h.s.
+	     * Once again, if l is False, the result is the r.h.s. and once
+	     * again if l is True, we parse the r.h.s. to throw it away.
+	     */
+	    if (l == False) {
+		l = CondE(doEval);
+	    } else {
+		(void) CondE(FALSE);
+	    }
+	} else {
+	    /*
+	     * E -> F
+	     */
+	    CondPushBack (o);
+	}
+    }
+    return (l);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_Eval --
+ *	Evaluate the conditional in the passed line. The line
+ *	looks like this:
+ *	    #<cond-type> <expr>
+ *	where <cond-type> is any of if, ifmake, ifnmake, ifdef,
+ *	ifndef, elif, elifmake, elifnmake, elifdef, elifndef
+ *	and <expr> consists of &&, ||, !, make(target), defined(variable)
+ *	and parenthetical groupings thereof.
+ *
+ * Results:
+ *	COND_PARSE	if should parse lines after the conditional
+ *	COND_SKIP	if should skip lines after the conditional
+ *	COND_INVALID  	if not a valid conditional.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Cond_Eval (char *line)
+{
+    struct If	    *ifp;
+    Boolean 	    isElse;
+    Boolean 	    value = FALSE;
+    int	    	    level;  	/* Level at which to report errors. */
+
+    level = PARSE_FATAL;
+
+    for (line++; *line == ' ' || *line == '\t'; line++) {
+	continue;
+    }
+
+    /*
+     * Find what type of if we're dealing with. The result is left
+     * in ifp and isElse is set TRUE if it's an elif line.
+     */
+    if (line[0] == 'e' && line[1] == 'l') {
+	line += 2;
+	isElse = TRUE;
+    } else if (strncmp (line, "endif", 5) == 0) {
+	/*
+	 * End of a conditional section. If skipIfLevel is non-zero, that
+	 * conditional was skipped, so lines following it should also be
+	 * skipped. Hence, we return COND_SKIP. Otherwise, the conditional
+	 * was read so succeeding lines should be parsed (think about it...)
+	 * so we return COND_PARSE, unless this endif isn't paired with
+	 * a decent if.
+	 */
+	if (skipIfLevel != 0) {
+	    skipIfLevel -= 1;
+	    return (COND_SKIP);
+	} else {
+	    if (condTop == MAXIF) {
+		Parse_Error (level, "if-less endif");
+		return (COND_INVALID);
+	    } else {
+		skipLine = FALSE;
+		condTop += 1;
+		return (COND_PARSE);
+	    }
+	}
+    } else {
+	isElse = FALSE;
+    }
+
+    /*
+     * Figure out what sort of conditional it is -- what its default
+     * function is, etc. -- by looking in the table of valid "ifs"
+     */
+    for (ifp = ifs; ifp->form != (char *)0; ifp++) {
+	if (strncmp (ifp->form, line, ifp->formlen) == 0) {
+	    break;
+	}
+    }
+
+    if (ifp->form == (char *) 0) {
+	/*
+	 * Nothing fit. If the first word on the line is actually
+	 * "else", it's a valid conditional whose value is the inverse
+	 * of the previous if we parsed.
+	 */
+	if (isElse && (line[0] == 's') && (line[1] == 'e')) {
+	    if (condTop == MAXIF) {
+		Parse_Error (level, "if-less else");
+		return (COND_INVALID);
+	    } else if (skipIfLevel == 0) {
+		value = !condStack[condTop];
+	    } else {
+		return (COND_SKIP);
+	    }
+	} else {
+	    /*
+	     * Not a valid conditional type. No error...
+	     */
+	    return (COND_INVALID);
+	}
+    } else {
+	if (isElse) {
+	    if (condTop == MAXIF) {
+		Parse_Error (level, "if-less elif");
+		return (COND_INVALID);
+	    } else if (skipIfLevel != 0) {
+		/*
+		 * If skipping this conditional, just ignore the whole thing.
+		 * If we don't, the user might be employing a variable that's
+		 * undefined, for which there's an enclosing ifdef that
+		 * we're skipping...
+		 */
+		return(COND_SKIP);
+	    }
+	} else if (skipLine) {
+	    /*
+	     * Don't even try to evaluate a conditional that's not an else if
+	     * we're skipping things...
+	     */
+	    skipIfLevel += 1;
+	    return(COND_SKIP);
+	}
+
+	/*
+	 * Initialize file-global variables for parsing
+	 */
+	condDefProc = ifp->defProc;
+	condInvert = ifp->doNot;
+
+	line += ifp->formlen;
+
+	while (*line == ' ' || *line == '\t') {
+	    line++;
+	}
+
+	condExpr = line;
+	condPushBack = None;
+
+	switch (CondE(TRUE)) {
+	    case True:
+		if (CondToken(TRUE) == EndOfFile) {
+		    value = TRUE;
+		    break;
+		}
+		goto err;
+		/*FALLTHRU*/
+	    case False:
+		if (CondToken(TRUE) == EndOfFile) {
+		    value = FALSE;
+		    break;
+		}
+		/*FALLTHRU*/
+	    case Err:
+	    err:
+		Parse_Error (level, "Malformed conditional (%s)",
+			     line);
+		return (COND_INVALID);
+	    default:
+		break;
+	}
+    }
+    if (!isElse) {
+	condTop -= 1;
+    } else if ((skipIfLevel != 0) || condStack[condTop]) {
+	/*
+	 * If this is an else-type conditional, it should only take effect
+	 * if its corresponding if was evaluated and FALSE. If its if was
+	 * TRUE or skipped, we return COND_SKIP (and start skipping in case
+	 * we weren't already), leaving the stack unmolested so later elif's
+	 * don't screw up...
+	 */
+	skipLine = TRUE;
+	return (COND_SKIP);
+    }
+
+    if (condTop < 0) {
+	/*
+	 * This is the one case where we can definitely proclaim a fatal
+	 * error. If we don't, we're hosed.
+	 */
+	Parse_Error (PARSE_FATAL, "Too many nested if's. %d max.", MAXIF);
+	return (COND_INVALID);
+    } else {
+	condStack[condTop] = value;
+	skipLine = !value;
+	return (value ? COND_PARSE : COND_SKIP);
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_End --
+ *	Make sure everything's clean at the end of a makefile.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Parse_Error will be called if open conditionals are around.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Cond_End(void)
+{
+    if (condTop != MAXIF) {
+	Parse_Error(PARSE_FATAL, "%d open conditional%s", MAXIF-condTop,
+		    MAXIF-condTop == 1 ? "" : "s");
+    }
+    condTop = MAXIF;
+}
Index: trunk/src/kmk/config.h
===================================================================
--- trunk/src/kmk/config.h	(revision 9)
+++ trunk/src/kmk/config.h	(revision 9)
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)config.h	8.2 (Berkeley) 4/28/95
+ * $FreeBSD: src/usr.bin/make/config.h,v 1.12 2002/09/17 21:29:06 jmallett Exp $
+ */
+
+/*
+ * DEFMAXJOBS
+ * DEFMAXLOCAL
+ *	These control the default concurrency. On no occasion will more
+ *	than DEFMAXJOBS targets be created at once (locally or remotely)
+ *	DEFMAXLOCAL is the highest number of targets which will be
+ *	created on the local machine at once. Note that if you set this
+ *	to 0, nothing will ever happen...
+ */
+#define	DEFMAXJOBS	4
+#define	DEFMAXLOCAL	1
+
+/*
+ * INCLUDES
+ * LIBRARIES
+ *	These control the handling of the .INCLUDES and .LIBS variables.
+ *	If INCLUDES is defined, the .INCLUDES variable will be filled
+ *	from the search paths of those suffixes which are marked by
+ *	.INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS
+ *	See suff.c for more details.
+ */
+#define	INCLUDES
+#define	LIBRARIES
+
+/*
+ * LIBSUFF
+ *	Is the suffix used to denote libraries and is used by the Suff module
+ *	to find the search path on which to seek any -l<xx> targets.
+ *
+ * RECHECK
+ *	If defined, Make_Update will check a target for its current
+ *	modification time after it has been re-made, setting it to the
+ *	starting time of the make only if the target still doesn't exist.
+ *	Unfortunately, under NFS the modification time often doesn't
+ *	get updated in time, so a target will appear to not have been
+ *	re-made, causing later targets to appear up-to-date. On systems
+ *	that don't have this problem, you should defined this. Under
+ *	NFS you probably should not, unless you aren't exporting jobs.
+ */
+#define	LIBSUFF	".a"
+#define	RECHECK
+
+/*
+ * POSIX
+ *	Adhere to the POSIX 1003.2 draft for the make(1) program.
+ *	- Use MAKEFLAGS instead of MAKE to pick arguments from the
+ *	  environment.
+ *	- Allow empty command lines if starting with tab.
+ */
+#define	POSIX
+
+/*
+ * SYSVINCLUDE
+ *	Recognize system V like include directives [include "filename"]
+ * SYSVVARSUB
+ *	Recognize system V like ${VAR:x=y} variable substitutions
+ */
+#define	SYSVINCLUDE
+#define	SYSVVARSUB
+
+/*
+ * SUNSHCMD
+ *	Recognize SunOS and Solaris:
+ *		VAR :sh= CMD	# Assign VAR to the command substitution of CMD
+ *		${VAR:sh}	# Return the command substitution of the value
+ *				# of ${VAR}
+ */
+#define	SUNSHCMD
+
+#if !defined(__svr4__) && !defined(__SVR4) && !defined(__ELF__)
+# ifndef RANLIBMAG
+#  define RANLIBMAG "__.SYMDEF"
+# endif
+#else
+# ifndef RANLIBMAG
+#  define RANLIBMAG "/"
+# endif
+#endif
Index: trunk/src/kmk/dir.c
===================================================================
--- trunk/src/kmk/dir.c	(revision 9)
+++ trunk/src/kmk/dir.c	(revision 9)
@@ -0,0 +1,1222 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)dir.c	8.2 (Berkeley) 1/2/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/dir.c,v 1.30 2002/10/09 03:42:10 jmallett Exp $");
+
+/*-
+ * dir.c --
+ *	Directory searching using wildcards and/or normal names...
+ *	Used both for source wildcarding in the Makefile and for finding
+ *	implicit sources.
+ *
+ * The interface for this module is:
+ *	Dir_Init  	    Initialize the module.
+ *
+ *	Dir_End  	    Cleanup the module.
+ *
+ *	Dir_HasWildcards    Returns TRUE if the name given it needs to
+ *	    	  	    be wildcard-expanded.
+ *
+ *	Dir_Expand	    Given a pattern and a path, return a Lst of names
+ *	    	  	    which match the pattern on the search path.
+ *
+ *	Dir_FindFile	    Searches for a file on a given search path.
+ *	    	  	    If it exists, the entire path is returned.
+ *	    	  	    Otherwise NULL is returned.
+ *
+ *	Dir_MTime 	    Return the modification time of a node. The file
+ *	    	  	    is searched for along the default search path.
+ *	    	  	    The path and mtime fields of the node are filled
+ *	    	  	    in.
+ *
+ *	Dir_AddDir	    Add a directory to a search path.
+ *
+ *	Dir_MakeFlags	    Given a search path and a command flag, create
+ *	    	  	    a string with each of the directories in the path
+ *	    	  	    preceded by the command flag and all of them
+ *	    	  	    separated by a space.
+ *
+ *	Dir_Destroy	    Destroy an element of a search path. Frees up all
+ *	    	  	    things that can be freed for the element as long
+ *	    	  	    as the element is no longer referenced by any other
+ *	    	  	    search path.
+ *	Dir_ClearPath	    Resets a search path to the empty list.
+ *
+ * For debugging:
+ *	Dir_PrintDirectories	Print stats about the directory cache.
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <err.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+
+/*
+ *	A search path consists of a Lst of Path structures. A Path structure
+ *	has in it the name of the directory and a hash table of all the files
+ *	in the directory. This is used to cut down on the number of system
+ *	calls necessary to find implicit dependents and their like. Since
+ *	these searches are made before any actions are taken, we need not
+ *	worry about the directory changing due to creation commands. If this
+ *	hampers the style of some makefiles, they must be changed.
+ *
+ *	A list of all previously-read directories is kept in the
+ *	openDirectories Lst. This list is checked first before a directory
+ *	is opened.
+ *
+ *	The need for the caching of whole directories is brought about by
+ *	the multi-level transformation code in suff.c, which tends to search
+ *	for far more files than regular make does. In the initial
+ *	implementation, the amount of time spent performing "stat" calls was
+ *	truly astronomical. The problem with hashing at the start is,
+ *	of course, that pmake doesn't then detect changes to these directories
+ *	during the course of the make. Three possibilities suggest themselves:
+ *
+ *	    1) just use stat to test for a file's existence. As mentioned
+ *	       above, this is very inefficient due to the number of checks
+ *	       engendered by the multi-level transformation code.
+ *	    2) use readdir() and company to search the directories, keeping
+ *	       them open between checks. I have tried this and while it
+ *	       didn't slow down the process too much, it could severely
+ *	       affect the amount of parallelism available as each directory
+ *	       open would take another file descriptor out of play for
+ *	       handling I/O for another job. Given that it is only recently
+ *	       that UNIX OS's have taken to allowing more than 20 or 32
+ *	       file descriptors for a process, this doesn't seem acceptable
+ *	       to me.
+ *	    3) record the mtime of the directory in the Path structure and
+ *	       verify the directory hasn't changed since the contents were
+ *	       hashed. This will catch the creation or deletion of files,
+ *	       but not the updating of files. However, since it is the
+ *	       creation and deletion that is the problem, this could be
+ *	       a good thing to do. Unfortunately, if the directory (say ".")
+ *	       were fairly large and changed fairly frequently, the constant
+ *	       rehashing could seriously degrade performance. It might be
+ *	       good in such cases to keep track of the number of rehashes
+ *	       and if the number goes over a (small) limit, resort to using
+ *	       stat in its place.
+ *
+ *	An additional thing to consider is that pmake is used primarily
+ *	to create C programs and until recently pcc-based compilers refused
+ *	to allow you to specify where the resulting object file should be
+ *	placed. This forced all objects to be created in the current
+ *	directory. This isn't meant as a full excuse, just an explanation of
+ *	some of the reasons for the caching used here.
+ *
+ *	One more note: the location of a target's file is only performed
+ *	on the downward traversal of the graph and then only for terminal
+ *	nodes in the graph. This could be construed as wrong in some cases,
+ *	but prevents inadvertent modification of files when the "installed"
+ *	directory for a file is provided in the search path.
+ *
+ *	Another data structure maintained by this module is an mtime
+ *	cache used when the searching of cached directories fails to find
+ *	a file. In the past, Dir_FindFile would simply perform an access()
+ *	call in such a case to determine if the file could be found using
+ *	just the name given. When this hit, however, all that was gained
+ *	was the knowledge that the file existed. Given that an access() is
+ *	essentially a stat() without the copyout() call, and that the same
+ *	filesystem overhead would have to be incurred in Dir_MTime, it made
+ *	sense to replace the access() with a stat() and record the mtime
+ *	in a cache for when Dir_MTime was actually called.
+ */
+
+Lst          dirSearchPath;	/* main search path */
+
+static Lst   openDirectories;	/* the list of all open directories */
+
+/*
+ * Variables for gathering statistics on the efficiency of the hashing
+ * mechanism.
+ */
+static int    hits,	      /* Found in directory cache */
+	      misses,	      /* Sad, but not evil misses */
+	      nearmisses,     /* Found under search path */
+	      bigmisses;      /* Sought by itself */
+
+static Path    	  *dot;	    /* contents of current directory */
+static Hash_Table mtimes;   /* Results of doing a last-resort stat in
+			     * Dir_FindFile -- if we have to go to the
+			     * system to find the file, we might as well
+			     * have its mtime on record. XXX: If this is done
+			     * way early, there's a chance other rules will
+			     * have already updated the file, in which case
+			     * we'll update it again. Generally, there won't
+			     * be two rules to update a single file, so this
+			     * should be ok, but... */
+
+
+static int DirFindName(void *, void *);
+static int DirMatchFiles(char *, Path *, Lst);
+static void DirExpandCurly(char *, char *, Lst, Lst);
+static void DirExpandInt(char *, Lst, Lst);
+static int DirPrintWord(void *, void *);
+static int DirPrintDir(void *, void *);
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Init --
+ *	initialize things for this module
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	some directories may be opened.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Init (void)
+{
+    dirSearchPath = Lst_Init (FALSE);
+    openDirectories = Lst_Init (FALSE);
+    Hash_InitTable(&mtimes, 0);
+
+    /*
+     * Since the Path structure is placed on both openDirectories and
+     * the path we give Dir_AddDir (which in this case is openDirectories),
+     * we need to remove "." from openDirectories and what better time to
+     * do it than when we have to fetch the thing anyway?
+     */
+    Dir_AddDir (openDirectories, ".");
+    dot = (Path *) Lst_DeQueue (openDirectories);
+    if (dot == (Path *) NULL)
+	err(1, "cannot open current directory");
+
+    /*
+     * We always need to have dot around, so we increment its reference count
+     * to make sure it's not destroyed.
+     */
+    dot->refCount += 1;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_End --
+ *	cleanup things for this module
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	none
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_End(void)
+{
+    dot->refCount -= 1;
+    Dir_Destroy((void *) dot);
+    Dir_ClearPath(dirSearchPath);
+    Lst_Destroy(dirSearchPath, NOFREE);
+    Dir_ClearPath(openDirectories);
+    Lst_Destroy(openDirectories, NOFREE);
+    Hash_DeleteTable(&mtimes);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirFindName --
+ *	See if the Path structure describes the same directory as the
+ *	given one by comparing their names. Called from Dir_AddDir via
+ *	Lst_Find when searching the list of open directories.
+ *
+ * Results:
+ *	0 if it is the same. Non-zero otherwise
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static int
+DirFindName (void *p, void *dname)
+{
+    return (strcmp (((Path *)p)->name, (char *) dname));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_HasWildcards  --
+ *	See if the given name has any wildcard characters in it.
+ *
+ * Results:
+ *	returns TRUE if the word should be expanded, FALSE otherwise
+ *
+ * Side Effects:
+ *	none
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Dir_HasWildcards (char *name)
+{
+    char *cp;
+    int wild = 0, brace = 0, bracket = 0;
+
+    for (cp = name; *cp; cp++) {
+	switch(*cp) {
+	case '{':
+		brace++;
+		wild = 1;
+		break;
+	case '}':
+		brace--;
+		break;
+	case '[':
+		bracket++;
+		wild = 1;
+		break;
+	case ']':
+		bracket--;
+		break;
+	case '?':
+	case '*':
+		wild = 1;
+		break;
+	default:
+		break;
+	}
+    }
+    return wild && bracket == 0 && brace == 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirMatchFiles --
+ * 	Given a pattern and a Path structure, see if any files
+ *	match the pattern and add their names to the 'expansions' list if
+ *	any do. This is incomplete -- it doesn't take care of patterns like
+ *	src / *src / *.c properly (just *.c on any of the directories), but it
+ *	will do for now.
+ *
+ * Results:
+ *	Always returns 0
+ *
+ * Side Effects:
+ *	File names are added to the expansions lst. The directory will be
+ *	fully hashed when this is done.
+ *-----------------------------------------------------------------------
+ */
+static int
+DirMatchFiles (char *pattern, Path *p, Lst expansions)
+{
+    Hash_Search	  search;   	/* Index into the directory's table */
+    Hash_Entry	  *entry;   	/* Current entry in the table */
+    Boolean 	  isDot;    	/* TRUE if the directory being searched is . */
+
+    isDot = (*p->name == '.' && p->name[1] == '\0');
+
+    for (entry = Hash_EnumFirst(&p->files, &search);
+	 entry != (Hash_Entry *)NULL;
+	 entry = Hash_EnumNext(&search))
+    {
+	/*
+	 * See if the file matches the given pattern. Note we follow the UNIX
+	 * convention that dot files will only be found if the pattern
+	 * begins with a dot (note also that as a side effect of the hashing
+	 * scheme, .* won't match . or .. since they aren't hashed).
+	 */
+	if (Str_Match(entry->name, pattern) &&
+	    ((entry->name[0] != '.') ||
+	     (pattern[0] == '.')))
+	{
+	    (void)Lst_AtEnd(expansions,
+			    (isDot ? estrdup(entry->name) :
+			     str_concat(p->name, entry->name,
+					STR_ADDSLASH)));
+	}
+    }
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirExpandCurly --
+ *	Expand curly braces like the C shell. Does this recursively.
+ *	Note the special case: if after the piece of the curly brace is
+ *	done there are no wildcard characters in the result, the result is
+ *	placed on the list WITHOUT CHECKING FOR ITS EXISTENCE.  The
+ *	given arguments are the entire word to expand, the first curly
+ *	brace in the word, the search path, and the list to store the
+ *	expansions in.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The given list is filled with the expansions...
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandCurly(char *word, char *brace, Lst path, Lst expansions)
+{
+    char    	  *end;	    	/* Character after the closing brace */
+    char    	  *cp;	    	/* Current position in brace clause */
+    char    	  *start;   	/* Start of current piece of brace clause */
+    int	    	  bracelevel;	/* Number of braces we've seen. If we see a
+				 * right brace when this is 0, we've hit the
+				 * end of the clause. */
+    char    	  *file;    	/* Current expansion */
+    int	    	  otherLen; 	/* The length of the other pieces of the
+				 * expansion (chars before and after the
+				 * clause in 'word') */
+    char    	  *cp2;	    	/* Pointer for checking for wildcards in
+				 * expansion before calling Dir_Expand */
+
+    start = brace+1;
+
+    /*
+     * Find the end of the brace clause first, being wary of nested brace
+     * clauses.
+     */
+    for (end = start, bracelevel = 0; *end != '\0'; end++) {
+	if (*end == '{') {
+	    bracelevel++;
+	} else if ((*end == '}') && (bracelevel-- == 0)) {
+	    break;
+	}
+    }
+    if (*end == '\0') {
+	Error("Unterminated {} clause \"%s\"", start);
+	return;
+    } else {
+	end++;
+    }
+    otherLen = brace - word + strlen(end);
+
+    for (cp = start; cp < end; cp++) {
+	/*
+	 * Find the end of this piece of the clause.
+	 */
+	bracelevel = 0;
+	while (*cp != ',') {
+	    if (*cp == '{') {
+		bracelevel++;
+	    } else if ((*cp == '}') && (bracelevel-- <= 0)) {
+		break;
+	    }
+	    cp++;
+	}
+	/*
+	 * Allocate room for the combination and install the three pieces.
+	 */
+	file = emalloc(otherLen + cp - start + 1);
+	if (brace != word) {
+	    strncpy(file, word, brace-word);
+	}
+	if (cp != start) {
+	    strncpy(&file[brace-word], start, cp-start);
+	}
+	strcpy(&file[(brace-word)+(cp-start)], end);
+
+	/*
+	 * See if the result has any wildcards in it. If we find one, call
+	 * Dir_Expand right away, telling it to place the result on our list
+	 * of expansions.
+	 */
+	for (cp2 = file; *cp2 != '\0'; cp2++) {
+	    switch(*cp2) {
+	    case '*':
+	    case '?':
+	    case '{':
+	    case '[':
+		Dir_Expand(file, path, expansions);
+		goto next;
+	    default:
+		break;
+	    }
+	}
+	if (*cp2 == '\0') {
+	    /*
+	     * Hit the end w/o finding any wildcards, so stick the expansion
+	     * on the end of the list.
+	     */
+	    (void)Lst_AtEnd(expansions, file);
+	} else {
+	next:
+	    free(file);
+	}
+	start = cp+1;
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirExpandInt --
+ *	Internal expand routine. Passes through the directories in the
+ *	path one by one, calling DirMatchFiles for each. NOTE: This still
+ *	doesn't handle patterns in directories...  Works given a word to
+ *	expand, a path to look in, and a list to store expansions in.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Things are added to the expansions list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandInt(char *word, Lst path, Lst expansions)
+{
+    LstNode 	  ln;	    	/* Current node */
+    Path	  *p;	    	/* Directory in the node */
+
+    if (Lst_Open(path) == SUCCESS) {
+	while ((ln = Lst_Next(path)) != NULL) {
+	    p = (Path *)Lst_Datum(ln);
+	    DirMatchFiles(word, p, expansions);
+	}
+	Lst_Close(path);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirPrintWord --
+ *	Print a word in the list of expansions. Callback for Dir_Expand
+ *	when DEBUG(DIR), via Lst_ForEach.
+ *
+ * Results:
+ *	=== 0
+ *
+ * Side Effects:
+ *	The passed word is printed, followed by a space.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+DirPrintWord(void *word, void *dummy __unused)
+{
+    DEBUGF(DIR, ("%s ", (char *) word));
+
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Expand  --
+ *	Expand the given word into a list of words by globbing it looking
+ *	in the directories on the given search path.
+ *
+ * Results:
+ *	A list of words consisting of the files which exist along the search
+ *	path matching the given pattern is placed in expansions.
+ *
+ * Side Effects:
+ *	Directories may be opened. Who knows?
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Expand (char *word, Lst path, Lst expansions)
+{
+    char    	  *cp;
+
+    DEBUGF(DIR, ("expanding \"%s\"...", word));
+
+    cp = strchr(word, '{');
+    if (cp) {
+	DirExpandCurly(word, cp, path, expansions);
+    } else {
+	cp = strchr(word, '/');
+	if (cp) {
+	    /*
+	     * The thing has a directory component -- find the first wildcard
+	     * in the string.
+	     */
+	    for (cp = word; *cp; cp++) {
+		if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') {
+		    break;
+		}
+	    }
+	    if (*cp == '{') {
+		/*
+		 * This one will be fun.
+		 */
+		DirExpandCurly(word, cp, path, expansions);
+		return;
+	    } else if (*cp != '\0') {
+		/*
+		 * Back up to the start of the component
+		 */
+		char  *dirpath;
+
+		while (cp > word && *cp != '/') {
+		    cp--;
+		}
+		if (cp != word) {
+		    char sc;
+		    /*
+		     * If the glob isn't in the first component, try and find
+		     * all the components up to the one with a wildcard.
+		     */
+		    sc = cp[1];
+		    cp[1] = '\0';
+		    dirpath = Dir_FindFile(word, path);
+		    cp[1] = sc;
+		    /*
+		     * dirpath is null if can't find the leading component
+		     * XXX: Dir_FindFile won't find internal components.
+		     * i.e. if the path contains ../Etc/Object and we're
+		     * looking for Etc, it won't be found. Ah well.
+		     * Probably not important.
+		     */
+		    if (dirpath != (char *)NULL) {
+			char *dp = &dirpath[strlen(dirpath) - 1];
+			if (*dp == '/')
+			    *dp = '\0';
+			path = Lst_Init(FALSE);
+			Dir_AddDir(path, dirpath);
+			DirExpandInt(cp+1, path, expansions);
+			Lst_Destroy(path, NOFREE);
+		    }
+		} else {
+		    /*
+		     * Start the search from the local directory
+		     */
+		    DirExpandInt(word, path, expansions);
+		}
+	    } else {
+		/*
+		 * Return the file -- this should never happen.
+		 */
+		DirExpandInt(word, path, expansions);
+	    }
+	} else {
+	    /*
+	     * First the files in dot
+	     */
+	    DirMatchFiles(word, dot, expansions);
+
+	    /*
+	     * Then the files in every other directory on the path.
+	     */
+	    DirExpandInt(word, path, expansions);
+	}
+    }
+    if (DEBUG(DIR)) {
+	Lst_ForEach(expansions, DirPrintWord, (void *) 0);
+	DEBUGF(DIR, ("\n"));
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_FindFile  --
+ *	Find the file with the given name along the given search path.
+ *
+ * Results:
+ *	The path to the file or NULL. This path is guaranteed to be in a
+ *	different part of memory than name and so may be safely free'd.
+ *
+ * Side Effects:
+ *	If the file is found in a directory which is not on the path
+ *	already (either 'name' is absolute or it is a relative path
+ *	[ dir1/.../dirn/file ] which exists below one of the directories
+ *	already on the search path), its directory is added to the end
+ *	of the path on the assumption that there will be more files in
+ *	that directory later on. Sometimes this is true. Sometimes not.
+ *-----------------------------------------------------------------------
+ */
+char *
+Dir_FindFile (char *name, Lst path)
+{
+    char          *p1;	    /* pointer into p->name */
+    char          *p2;	    /* pointer into name */
+    LstNode       ln;	    /* a list element */
+    char          *file;    /* the current filename to check */
+    Path          *p;	    /* current path member */
+    char          *cp;	    /* index of first slash, if any */
+    Boolean	  hasSlash; /* true if 'name' contains a / */
+    struct stat	  stb;	    /* Buffer for stat, if necessary */
+    Hash_Entry	  *entry;   /* Entry for mtimes table */
+
+    /*
+     * Find the final component of the name and note whether it has a
+     * slash in it (the name, I mean)
+     */
+    cp = strrchr (name, '/');
+    if (cp) {
+	hasSlash = TRUE;
+	cp += 1;
+    } else {
+	hasSlash = FALSE;
+	cp = name;
+    }
+
+    DEBUGF(DIR, ("Searching for %s...", name));
+    /*
+     * No matter what, we always look for the file in the current directory
+     * before anywhere else and we *do not* add the ./ to it if it exists.
+     * This is so there are no conflicts between what the user specifies
+     * (fish.c) and what pmake finds (./fish.c).
+     */
+    if ((!hasSlash || (cp - name == 2 && *name == '.')) &&
+	(Hash_FindEntry (&dot->files, cp) != (Hash_Entry *)NULL)) {
+	    DEBUGF(DIR, ("in '.'\n"));
+	    hits += 1;
+	    dot->hits += 1;
+	    return (estrdup (name));
+    }
+
+    if (Lst_Open (path) == FAILURE) {
+	DEBUGF(DIR, ("couldn't open path, file not found\n"));
+	misses += 1;
+	return ((char *) NULL);
+    }
+
+    /*
+     * We look through all the directories on the path seeking one which
+     * contains the final component of the given name and whose final
+     * component(s) match the name's initial component(s). If such a beast
+     * is found, we concatenate the directory name and the final component
+     * and return the resulting string. If we don't find any such thing,
+     * we go on to phase two...
+     */
+    while ((ln = Lst_Next (path)) != NULL) {
+	p = (Path *) Lst_Datum (ln);
+	DEBUGF(DIR, ("%s...", p->name));
+	if (Hash_FindEntry (&p->files, cp) != (Hash_Entry *)NULL) {
+	    DEBUGF(DIR, ("here..."));
+	    if (hasSlash) {
+		/*
+		 * If the name had a slash, its initial components and p's
+		 * final components must match. This is false if a mismatch
+		 * is encountered before all of the initial components
+		 * have been checked (p2 > name at the end of the loop), or
+		 * we matched only part of one of the components of p
+		 * along with all the rest of them (*p1 != '/').
+		 */
+		p1 = p->name + strlen (p->name) - 1;
+		p2 = cp - 2;
+		while (p2 >= name && p1 >= p->name && *p1 == *p2) {
+		    p1 -= 1; p2 -= 1;
+		}
+		if (p2 >= name || (p1 >= p->name && *p1 != '/')) {
+		    DEBUGF(DIR, ("component mismatch -- continuing..."));
+		    continue;
+		}
+	    }
+	    file = str_concat (p->name, cp, STR_ADDSLASH);
+	    DEBUGF(DIR, ("returning %s\n", file));
+	    Lst_Close (path);
+	    p->hits += 1;
+	    hits += 1;
+	    return (file);
+	} else if (hasSlash) {
+	    /*
+	     * If the file has a leading path component and that component
+	     * exactly matches the entire name of the current search
+	     * directory, we assume the file doesn't exist and return NULL.
+	     */
+	    for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) {
+		continue;
+	    }
+	    if (*p1 == '\0' && p2 == cp - 1) {
+		DEBUGF(DIR, ("must be here but isn't -- returing NULL\n"));
+		Lst_Close (path);
+		return ((char *) NULL);
+	    }
+	}
+    }
+
+    /*
+     * We didn't find the file on any existing members of the directory.
+     * If the name doesn't contain a slash, that means it doesn't exist.
+     * If it *does* contain a slash, however, there is still hope: it
+     * could be in a subdirectory of one of the members of the search
+     * path. (eg. /usr/include and sys/types.h. The above search would
+     * fail to turn up types.h in /usr/include, but it *is* in
+     * /usr/include/sys/types.h) If we find such a beast, we assume there
+     * will be more (what else can we assume?) and add all but the last
+     * component of the resulting name onto the search path (at the
+     * end). This phase is only performed if the file is *not* absolute.
+     */
+    if (!hasSlash) {
+	DEBUGF(DIR, ("failed.\n"));
+	misses += 1;
+	return ((char *) NULL);
+    }
+
+    if (*name != '/') {
+	Boolean	checkedDot = FALSE;
+
+	DEBUGF(DIR, ("failed. Trying subdirectories..."));
+	(void) Lst_Open (path);
+	while ((ln = Lst_Next (path)) != NULL) {
+	    p = (Path *) Lst_Datum (ln);
+	    if (p != dot) {
+		file = str_concat (p->name, name, STR_ADDSLASH);
+	    } else {
+		/*
+		 * Checking in dot -- DON'T put a leading ./ on the thing.
+		 */
+		file = estrdup(name);
+		checkedDot = TRUE;
+	    }
+	    DEBUGF(DIR, ("checking %s...", file));
+
+	    if (stat (file, &stb) == 0) {
+		DEBUGF(DIR, ("got it.\n"));
+
+		Lst_Close (path);
+
+		/*
+		 * We've found another directory to search. We know there's
+		 * a slash in 'file' because we put one there. We nuke it after
+		 * finding it and call Dir_AddDir to add this new directory
+		 * onto the existing search path. Once that's done, we restore
+		 * the slash and triumphantly return the file name, knowing
+		 * that should a file in this directory every be referenced
+		 * again in such a manner, we will find it without having to do
+		 * numerous numbers of access calls. Hurrah!
+		 */
+		cp = strrchr (file, '/');
+		*cp = '\0';
+		Dir_AddDir (path, file);
+		*cp = '/';
+
+		/*
+		 * Save the modification time so if it's needed, we don't have
+		 * to fetch it again.
+		 */
+		DEBUGF(DIR, ("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), file));
+		entry = Hash_CreateEntry(&mtimes, (char *) file,
+					 (Boolean *)NULL);
+		Hash_SetValue(entry, (long)stb.st_mtime);
+		nearmisses += 1;
+		return (file);
+	    } else {
+		free (file);
+	    }
+	}
+
+	DEBUGF(DIR, ("failed. "));
+	Lst_Close (path);
+
+	if (checkedDot) {
+	    /*
+	     * Already checked by the given name, since . was in the path,
+	     * so no point in proceeding...
+	     */
+	    DEBUGF(DIR, ("Checked . already, returning NULL\n"));
+	    return(NULL);
+	}
+    }
+
+    /*
+     * Didn't find it that way, either. Sigh. Phase 3. Add its directory
+     * onto the search path in any case, just in case, then look for the
+     * thing in the hash table. If we find it, grand. We return a new
+     * copy of the name. Otherwise we sadly return a NULL pointer. Sigh.
+     * Note that if the directory holding the file doesn't exist, this will
+     * do an extra search of the final directory on the path. Unless something
+     * weird happens, this search won't succeed and life will be groovy.
+     *
+     * Sigh. We cannot add the directory onto the search path because
+     * of this amusing case:
+     * $(INSTALLDIR)/$(FILE): $(FILE)
+     *
+     * $(FILE) exists in $(INSTALLDIR) but not in the current one.
+     * When searching for $(FILE), we will find it in $(INSTALLDIR)
+     * b/c we added it here. This is not good...
+     */
+#ifdef notdef
+    cp[-1] = '\0';
+    Dir_AddDir (path, name);
+    cp[-1] = '/';
+
+    bigmisses += 1;
+    ln = Lst_Last (path);
+    if (ln == NULL) {
+	return ((char *) NULL);
+    } else {
+	p = (Path *) Lst_Datum (ln);
+    }
+
+    if (Hash_FindEntry (&p->files, cp) != (Hash_Entry *)NULL) {
+	return (estrdup (name));
+    } else {
+	return ((char *) NULL);
+    }
+#else /* !notdef */
+    DEBUGF(DIR, ("Looking for \"%s\"...", name));
+
+    bigmisses += 1;
+    entry = Hash_FindEntry(&mtimes, name);
+    if (entry != (Hash_Entry *)NULL) {
+	DEBUGF(DIR, ("got it (in mtime cache)\n"));
+	return (estrdup(name));
+    } else if (stat (name, &stb) == 0) {
+	entry = Hash_CreateEntry(&mtimes, name, (Boolean *)NULL);
+	DEBUGF(DIR, ("Caching %s for %s\n", Targ_FmtTime(stb.st_mtime), name));
+	Hash_SetValue(entry, (long)stb.st_mtime);
+	return (estrdup (name));
+    } else {
+	DEBUGF(DIR, ("failed. Returning NULL\n"));
+	return ((char *)NULL);
+    }
+#endif /* notdef */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MTime  --
+ *	Find the modification time of the file described by gn along the
+ *	search path dirSearchPath.
+ *
+ * Results:
+ *	The modification time or 0 if it doesn't exist
+ *
+ * Side Effects:
+ *	The modification time is placed in the node's mtime slot.
+ *	If the node didn't have a path entry before, and Dir_FindFile
+ *	found one for it, the full name is placed in the path slot.
+ *-----------------------------------------------------------------------
+ */
+int
+Dir_MTime (GNode *gn)
+{
+    char          *fullName;  /* the full pathname of name */
+    struct stat	  stb;	      /* buffer for finding the mod time */
+    Hash_Entry	  *entry;
+
+    if (gn->type & OP_ARCHV) {
+	return Arch_MTime (gn);
+    } else if (gn->path == (char *)NULL) {
+	fullName = Dir_FindFile (gn->name, dirSearchPath);
+    } else {
+	fullName = gn->path;
+    }
+
+    if (fullName == (char *)NULL) {
+	fullName = estrdup(gn->name);
+    }
+
+    entry = Hash_FindEntry(&mtimes, fullName);
+    if (entry != (Hash_Entry *)NULL) {
+	/*
+	 * Only do this once -- the second time folks are checking to
+	 * see if the file was actually updated, so we need to actually go
+	 * to the filesystem.
+	 */
+	DEBUGF(DIR, ("Using cached time %s for %s\n",
+	       Targ_FmtTime((time_t)(long)Hash_GetValue(entry)), fullName));
+	stb.st_mtime = (time_t)(long)Hash_GetValue(entry);
+	Hash_DeleteEntry(&mtimes, entry);
+    } else if (stat (fullName, &stb) < 0) {
+	if (gn->type & OP_MEMBER) {
+	    if (fullName != gn->path)
+		free(fullName);
+	    return Arch_MemMTime (gn);
+	} else {
+	    stb.st_mtime = 0;
+	}
+    }
+    if (fullName && gn->path == (char *)NULL) {
+	gn->path = fullName;
+    }
+
+    gn->mtime = stb.st_mtime;
+    return (gn->mtime);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_AddDir --
+ *	Add the given name to the end of the given path. The order of
+ *	the arguments is backwards so ParseDoDependency can do a
+ *	Lst_ForEach of its list of paths...
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	A structure is added to the list and the directory is
+ *	read and hashed.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_AddDir (Lst path, char *name)
+{
+    LstNode       ln;	      /* node in case Path structure is found */
+    Path          *p;	      /* pointer to new Path structure */
+    DIR     	  *d;	      /* for reading directory */
+    struct dirent *dp;	      /* entry in directory */
+
+    ln = Lst_Find (openDirectories, (void *)name, DirFindName);
+    if (ln != NULL) {
+	p = (Path *)Lst_Datum (ln);
+	if (Lst_Member(path, (void *)p) == NULL) {
+	    p->refCount += 1;
+	    (void)Lst_AtEnd (path, (void *)p);
+	}
+    } else {
+	DEBUGF(DIR, ("Caching %s...", name));
+
+	if ((d = opendir (name)) != (DIR *) NULL) {
+	    p = (Path *) emalloc (sizeof (Path));
+	    p->name = estrdup (name);
+	    p->hits = 0;
+	    p->refCount = 1;
+	    Hash_InitTable (&p->files, -1);
+
+	    while ((dp = readdir (d)) != (struct dirent *) NULL) {
+#if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define	for d_fileno */
+		/*
+		 * The sun directory library doesn't check for a 0 inode
+		 * (0-inode slots just take up space), so we have to do
+		 * it ourselves.
+		 */
+		if (dp->d_fileno == 0) {
+		    continue;
+		}
+#endif /* sun && d_ino */
+
+		/* Skip the '.' and '..' entries by checking for them
+		 * specifically instead of assuming readdir() reuturns them in
+		 * that order when first going through a directory.  This is
+		 * needed for XFS over NFS filesystems since SGI does not
+		 * guarantee that these are the first two entries returned
+		 * from readdir().
+		 */
+		if (ISDOT(dp->d_name) || ISDOTDOT(dp->d_name))
+			continue;
+
+		(void)Hash_CreateEntry(&p->files, dp->d_name, (Boolean *)NULL);
+	    }
+	    (void) closedir (d);
+	    (void)Lst_AtEnd (openDirectories, (void *)p);
+	    (void)Lst_AtEnd (path, (void *)p);
+	}
+	DEBUGF(DIR, ("done\n"));
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_CopyDir --
+ *	Callback function for duplicating a search path via Lst_Duplicate.
+ *	Ups the reference count for the directory.
+ *
+ * Results:
+ *	Returns the Path it was given.
+ *
+ * Side Effects:
+ *	The refCount of the path is incremented.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Dir_CopyDir(void *p)
+{
+    ((Path *) p)->refCount += 1;
+
+    return ((void *)p);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MakeFlags --
+ *	Make a string by taking all the directories in the given search
+ *	path and preceding them by the given flag. Used by the suffix
+ *	module to create variables for compilers based on suffix search
+ *	paths.
+ *
+ * Results:
+ *	The string mentioned above. Note that there is no space between
+ *	the given flag and each directory. The empty string is returned if
+ *	Things don't go well.
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+char *
+Dir_MakeFlags (char *flag, Lst path)
+{
+    char	  *str;	  /* the string which will be returned */
+    char	  *tstr;  /* the current directory preceded by 'flag' */
+    LstNode	  ln;	  /* the node of the current directory */
+    Path	  *p;	  /* the structure describing the current directory */
+
+    str = estrdup ("");
+
+    if (Lst_Open (path) == SUCCESS) {
+	while ((ln = Lst_Next (path)) != NULL) {
+	    p = (Path *) Lst_Datum (ln);
+	    tstr = str_concat (flag, p->name, 0);
+	    str = str_concat (str, tstr, STR_ADDSPACE | STR_DOFREE);
+	}
+	Lst_Close (path);
+    }
+
+    return (str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Destroy --
+ *	Nuke a directory descriptor, if possible. Callback procedure
+ *	for the suffixes module when destroying a search path.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	If no other path references this directory (refCount == 0),
+ *	the Path and all its data are freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Destroy (void *pp)
+{
+    Path    	  *p = (Path *) pp;
+    p->refCount -= 1;
+
+    if (p->refCount == 0) {
+	LstNode	ln;
+
+	ln = Lst_Member (openDirectories, (void *)p);
+	(void) Lst_Remove (openDirectories, ln);
+
+	Hash_DeleteTable (&p->files);
+	free(p->name);
+	free(p);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_ClearPath --
+ *	Clear out all elements of the given search path. This is different
+ *	from destroying the list, notice.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The path is set to the empty list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_ClearPath(Lst path)
+{
+    Path    *p;
+    while (!Lst_IsEmpty(path)) {
+	p = (Path *)Lst_DeQueue(path);
+	Dir_Destroy((void *) p);
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Concat --
+ *	Concatenate two paths, adding the second to the end of the first.
+ *	Makes sure to avoid duplicates.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Reference counts for added dirs are upped.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Concat(Lst path1, Lst path2)
+{
+    LstNode ln;
+    Path    *p;
+
+    for (ln = Lst_First(path2); ln != NULL; ln = Lst_Succ(ln)) {
+	p = (Path *)Lst_Datum(ln);
+	if (Lst_Member(path1, (void *)p) == NULL) {
+	    p->refCount += 1;
+	    (void)Lst_AtEnd(path1, (void *)p);
+	}
+    }
+}
+
+/********** DEBUG INFO **********/
+void
+Dir_PrintDirectories(void)
+{
+    LstNode	ln;
+    Path	*p;
+
+    printf ("#*** Directory Cache:\n");
+    printf ("# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n",
+	      hits, misses, nearmisses, bigmisses,
+	      (hits+bigmisses+nearmisses ?
+	       hits * 100 / (hits + bigmisses + nearmisses) : 0));
+    printf ("# %-20s referenced\thits\n", "directory");
+    if (Lst_Open (openDirectories) == SUCCESS) {
+	while ((ln = Lst_Next (openDirectories)) != NULL) {
+	    p = (Path *) Lst_Datum (ln);
+	    printf ("# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits);
+	}
+	Lst_Close (openDirectories);
+    }
+}
+
+static int
+DirPrintDir (void *p, void *dummy __unused)
+{
+    printf ("%s ", ((Path *) p)->name);
+
+    return (0);
+}
+
+void
+Dir_PrintPath (Lst path)
+{
+    Lst_ForEach (path, DirPrintDir, (void *)0);
+}
Index: trunk/src/kmk/dir.h
===================================================================
--- trunk/src/kmk/dir.h	(revision 9)
+++ trunk/src/kmk/dir.h	(revision 9)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)dir.h	8.2 (Berkeley) 4/28/95
+ * $FreeBSD: src/usr.bin/make/dir.h,v 1.10 2002/04/13 10:57:56 obrien Exp $
+ */
+
+/* dir.h --
+ */
+
+#ifndef	_DIR
+#define	_DIR
+
+typedef struct Path {
+    char         *name;	    	/* Name of directory */
+    int	    	  refCount; 	/* Number of paths with this directory */
+    int		  hits;	    	/* the number of times a file in this
+				 * directory has been found */
+    Hash_Table    files;    	/* Hash table of files in directory */
+} Path;
+
+void Dir_Init(void);
+void Dir_End(void);
+Boolean Dir_HasWildcards(char *);
+void Dir_Expand(char *, Lst, Lst);
+char *Dir_FindFile(char *, Lst);
+int Dir_MTime(GNode *);
+void Dir_AddDir(Lst, char *);
+char *Dir_MakeFlags(char *, Lst);
+void Dir_ClearPath(Lst);
+void Dir_Concat(Lst, Lst);
+void Dir_PrintDirectories(void);
+void Dir_PrintPath(Lst);
+void Dir_Destroy(void *);
+void * Dir_CopyDir(void *);
+
+#endif /* _DIR */
Index: trunk/src/kmk/for.c
===================================================================
--- trunk/src/kmk/for.c	(revision 9)
+++ trunk/src/kmk/for.c	(revision 9)
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Christos Zoulas.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)for.c	8.1 (Berkeley) 6/6/93
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/for.c,v 1.19 2002/10/09 03:42:10 jmallett Exp $");
+
+/*-
+ * for.c --
+ *	Functions to handle loops in a makefile.
+ *
+ * Interface:
+ *	For_Eval 	Evaluate the loop in the passed line.
+ *	For_Run		Run accumulated loop
+ *
+ */
+
+#include    <ctype.h>
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "buf.h"
+
+/*
+ * For statements are of the form:
+ *
+ * .for <variable> in <varlist>
+ * ...
+ * .endfor
+ *
+ * The trick is to look for the matching end inside for for loop
+ * To do that, we count the current nesting level of the for loops.
+ * and the .endfor statements, accumulating all the statements between
+ * the initial .for loop and the matching .endfor;
+ * then we evaluate the for loop for each variable in the varlist.
+ */
+
+static int  	  forLevel = 0;  	/* Nesting level	*/
+static char	 *forVar;		/* Iteration variable	*/
+static Buffer	  forBuf;		/* Commands in loop	*/
+static Lst	  forLst;		/* List of items	*/
+
+/*
+ * State of a for loop.
+ */
+typedef struct _For {
+    Buffer	  buf;			/* Unexpanded buffer	*/
+    char*	  var;			/* Index name		*/
+    Lst  	  lst;			/* List of variables	*/
+} For;
+
+static int ForExec(void *, void *);
+
+
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Eval --
+ *	Evaluate the for loop in the passed line. The line
+ *	looks like this:
+ *	    .for <variable> in <varlist>
+ *
+ * Results:
+ *	TRUE: We found a for loop, or we are inside a for loop
+ *	FALSE: We did not find a for loop, or we found the end of the for
+ *	       for loop.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+For_Eval (char *line)
+{
+    char	    *ptr = line, *sub, *wrd;
+    int	    	    level;  	/* Level at which to report errors. */
+
+    level = PARSE_FATAL;
+
+
+    if (forLevel == 0) {
+	Buffer	    buf;
+	int	    varlen;
+
+	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+	    continue;
+	/*
+	 * If we are not in a for loop quickly determine if the statement is
+	 * a for.
+	 */
+	if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' ||
+	    !isspace((unsigned char) ptr[3]))
+	    return FALSE;
+	ptr += 3;
+
+	/*
+	 * we found a for loop, and now we are going to parse it.
+	 */
+	while (*ptr && isspace((unsigned char) *ptr))
+	    ptr++;
+
+	/*
+	 * Grab the variable
+	 */
+	buf = Buf_Init(0);
+	for (wrd = ptr; *ptr && !isspace((unsigned char) *ptr); ptr++)
+	    continue;
+	Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd);
+
+	forVar = (char *) Buf_GetAll(buf, &varlen);
+	if (varlen == 0) {
+	    Parse_Error (level, "missing variable in for");
+	    return 0;
+	}
+	Buf_Destroy(buf, FALSE);
+
+	while (*ptr && isspace((unsigned char) *ptr))
+	    ptr++;
+
+	/*
+	 * Grab the `in'
+	 */
+	if (ptr[0] != 'i' || ptr[1] != 'n' ||
+	    !isspace((unsigned char) ptr[2])) {
+	    Parse_Error (level, "missing `in' in for");
+	    printf("%s\n", ptr);
+	    return 0;
+	}
+	ptr += 3;
+
+	while (*ptr && isspace((unsigned char) *ptr))
+	    ptr++;
+
+	/*
+	 * Make a list with the remaining words
+	 */
+	forLst = Lst_Init(FALSE);
+	buf = Buf_Init(0);
+	sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
+
+#define	ADDWORD() \
+	Buf_AddBytes(buf, ptr - wrd, (Byte *) wrd), \
+	Buf_AddByte(buf, (Byte) '\0'), \
+	Lst_AtFront(forLst, (void *) Buf_GetAll(buf, &varlen)), \
+	Buf_Destroy(buf, FALSE)
+
+	for (ptr = sub; *ptr && isspace((unsigned char) *ptr); ptr++)
+	    continue;
+
+	for (wrd = ptr; *ptr; ptr++)
+	    if (isspace((unsigned char) *ptr)) {
+		ADDWORD();
+		buf = Buf_Init(0);
+		while (*ptr && isspace((unsigned char) *ptr))
+		    ptr++;
+		wrd = ptr--;
+	    }
+	DEBUGF(FOR, ("For: Iterator %s List %s\n", forVar, sub));
+	if (ptr - wrd > 0)
+	    ADDWORD();
+	else
+	    Buf_Destroy(buf, TRUE);
+	free(sub);
+
+	forBuf = Buf_Init(0);
+	forLevel++;
+	return 1;
+    }
+    else if (*ptr == '.') {
+
+	for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+	    continue;
+
+	if (strncmp(ptr, "endfor", 6) == 0 &&
+	    (isspace((unsigned char) ptr[6]) || !ptr[6])) {
+	    DEBUGF(FOR, ("For: end for %d\n", forLevel));
+	    if (--forLevel < 0) {
+		Parse_Error (level, "for-less endfor");
+		return 0;
+	    }
+	}
+	else if (strncmp(ptr, "for", 3) == 0 &&
+		 isspace((unsigned char) ptr[3])) {
+	    forLevel++;
+	    DEBUGF(FOR, ("For: new loop %d\n", forLevel));
+	}
+    }
+
+    if (forLevel != 0) {
+	Buf_AddBytes(forBuf, strlen(line), (Byte *) line);
+	Buf_AddByte(forBuf, (Byte) '\n');
+	return 1;
+    }
+    else {
+	return 0;
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ForExec --
+ *	Expand the for loop for this index and push it in the Makefile
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ForExec(void *namep, void *argp)
+{
+    char *name = (char *) namep;
+    For *arg = (For *) argp;
+    int len;
+    Var_Set(arg->var, name, VAR_GLOBAL);
+    DEBUGF(FOR, ("--- %s = %s\n", arg->var, name));
+    Parse_FromString(Var_Subst(arg->var, (char *) Buf_GetAll(arg->buf, &len),
+			       VAR_GLOBAL, FALSE));
+    Var_Delete(arg->var, VAR_GLOBAL);
+
+    return 0;
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Run --
+ *	Run the for loop, immitating the actions of an include file
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+For_Run(void)
+{
+    For arg;
+
+    if (forVar == NULL || forBuf == NULL || forLst == NULL)
+	return;
+    arg.var = forVar;
+    arg.buf = forBuf;
+    arg.lst = forLst;
+    forVar = NULL;
+    forBuf = NULL;
+    forLst = NULL;
+
+    Lst_ForEach(arg.lst, ForExec, (void *) &arg);
+
+    free(arg.var);
+    Lst_Destroy(arg.lst, (void (*)(void *)) free);
+    Buf_Destroy(arg.buf, TRUE);
+}
Index: trunk/src/kmk/hash.c
===================================================================
--- trunk/src/kmk/hash.c	(revision 9)
+++ trunk/src/kmk/hash.c	(revision 9)
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)hash.c	8.1 (Berkeley) 6/6/93
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/hash.c,v 1.18 2002/10/09 03:42:10 jmallett Exp $");
+
+/* hash.c --
+ *
+ * 	This module contains routines to manipulate a hash table.
+ * 	See hash.h for a definition of the structure of the hash
+ * 	table.  Hash tables grow automatically as the amount of
+ * 	information increases.
+ */
+#include <unistd.h>
+#include "sprite.h"
+#include "make.h"
+#include "hash.h"
+
+/*
+ * Forward references to local procedures that are used before they're
+ * defined:
+ */
+
+static void RebuildTable(Hash_Table *);
+
+/*
+ * The following defines the ratio of # entries to # buckets
+ * at which we rebuild the table to make it larger.
+ */
+
+#define	rebuildLimit 8
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_InitTable --
+ * 
+ * 	Set up the hash table t with a given number of buckets, or a
+ * 	reasonable default if the number requested is less than or
+ * 	equal to zero.  Hash tables will grow in size as needed.
+ *
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Memory is allocated for the initial bucket area.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_InitTable(Hash_Table *t, int numBuckets)
+{
+	int i;
+	struct Hash_Entry **hp;
+
+	/*
+	 * Round up the size to a power of two.
+	 */
+	if (numBuckets <= 0)
+		i = 16;
+	else {
+		for (i = 2; i < numBuckets; i <<= 1)
+			 continue;
+	}
+	t->numEntries = 0;
+	t->size = i;
+	t->mask = i - 1;
+	t->bucketPtr = hp = (struct Hash_Entry **)emalloc(sizeof(*hp) * i);
+	while (--i >= 0)
+		*hp++ = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteTable --
+ *
+ *	This routine removes everything from a hash table
+ *	and frees up the memory space it occupied (except for
+ *	the space in the Hash_Table structure).
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Lots of memory is freed up.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteTable(Hash_Table *t)
+{
+	struct Hash_Entry **hp, *h, *nexth = NULL;
+	int i;
+
+	for (hp = t->bucketPtr, i = t->size; --i >= 0;) {
+		for (h = *hp++; h != NULL; h = nexth) {
+			nexth = h->next;
+			free((char *)h);
+		}
+	}
+	free((char *)t->bucketPtr);
+
+	/*
+	 * Set up the hash table to cause memory faults on any future access
+	 * attempts until re-initialization.
+	 */
+	t->bucketPtr = NULL;
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_FindEntry --
+ *
+ * 	Searches a hash table for an entry corresponding to key.
+ *
+ * Results:
+ *	The return value is a pointer to the entry for key,
+ *	if key was present in the table.  If key was not
+ *	present, NULL is returned.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_FindEntry(Hash_Table *t, char *key)
+{
+	Hash_Entry *e;
+	unsigned h;
+	char *p;
+
+	for (h = 0, p = key; *p;)
+		h = (h << 5) - h + *p++;
+	p = key;
+	for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next)
+		if (e->namehash == h && strcmp(e->name, p) == 0)
+			return (e);
+	return (NULL);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_CreateEntry --
+ *
+ *	Searches a hash table for an entry corresponding to
+ *	key.  If no entry is found, then one is created.
+ *
+ * Results:
+ *	The return value is a pointer to the entry.  If *newPtr
+ *	isn't NULL, then *newPtr is filled in with TRUE if a
+ *	new entry was created, and FALSE if an entry already existed
+ *	with the given key.
+ *
+ * Side Effects:
+ *	Memory may be allocated, and the hash buckets may be modified.
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_CreateEntry(Hash_Table *t, char *key, Boolean *newPtr)
+{
+	Hash_Entry *e;
+	unsigned int h;
+	char *p;
+	int keylen;
+	struct Hash_Entry **hp;
+
+	/*
+	 * Hash the key.  As a side effect, save the length (strlen) of the
+	 * key in case we need to create the entry.
+	 */
+	for (h = 0, p = key; *p;)
+		h = (h << 5) - h + *p++;
+	keylen = p - key;
+	p = key;
+	for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) {
+		if (e->namehash == h && strcmp(e->name, p) == 0) {
+			if (newPtr != NULL)
+				*newPtr = FALSE;
+			return (e);
+		}
+	}
+
+	/*
+	 * The desired entry isn't there.  Before allocating a new entry,
+	 * expand the table if necessary (and this changes the resulting
+	 * bucket chain).
+	 */
+	if (t->numEntries >= rebuildLimit * t->size)
+		RebuildTable(t);
+	e = (Hash_Entry *) emalloc(sizeof(*e) + keylen);
+	hp = &t->bucketPtr[h & t->mask];
+	e->next = *hp;
+	*hp = e;
+	e->clientData = NULL;
+	e->namehash = h;
+	(void) strcpy(e->name, p);
+	t->numEntries++;
+
+	if (newPtr != NULL)
+		*newPtr = TRUE;
+	return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_DeleteEntry --
+ *
+ * 	Delete the given hash table entry and free memory associated with
+ *	it.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Hash chain that entry lives in is modified and memory is freed.
+ *
+ *---------------------------------------------------------
+ */
+
+void
+Hash_DeleteEntry(Hash_Table *t, Hash_Entry *e)
+{
+	Hash_Entry **hp, *p;
+
+	if (e == NULL)
+		return;
+	for (hp = &t->bucketPtr[e->namehash & t->mask];
+	     (p = *hp) != NULL; hp = &p->next) {
+		if (p == e) {
+			*hp = p->next;
+			free((char *)p);
+			t->numEntries--;
+			return;
+		}
+	}
+	(void) write(STDERR_FILENO, "bad call to Hash_DeleteEntry\n", 29);
+	abort();
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumFirst --
+ *	This procedure sets things up for a complete search
+ *	of all entries recorded in the hash table.
+ *
+ * Results:
+ *	The return value is the address of the first entry in
+ *	the hash table, or NULL if the table is empty.
+ *
+ * Side Effects:
+ *	The information in searchPtr is initialized so that successive
+ *	calls to Hash_Next will return successive HashEntry's
+ *	from the table.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumFirst(Hash_Table *t, Hash_Search *searchPtr)
+{
+	searchPtr->tablePtr = t;
+	searchPtr->nextIndex = 0;
+	searchPtr->hashEntryPtr = NULL;
+	return Hash_EnumNext(searchPtr);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_EnumNext --
+ *    This procedure returns successive entries in the hash table.
+ *
+ * Results:
+ *    The return value is a pointer to the next HashEntry
+ *    in the table, or NULL when the end of the table is
+ *    reached.
+ *
+ * Side Effects:
+ *    The information in searchPtr is modified to advance to the
+ *    next entry.
+ *
+ *---------------------------------------------------------
+ */
+
+Hash_Entry *
+Hash_EnumNext(Hash_Search *searchPtr)
+{
+	Hash_Entry *e;
+	Hash_Table *t = searchPtr->tablePtr;
+
+	/*
+	 * The hashEntryPtr field points to the most recently returned
+	 * entry, or is NULL if we are starting up.  If not NULL, we have
+	 * to start at the next one in the chain.
+	 */
+	e = searchPtr->hashEntryPtr;
+	if (e != NULL)
+		e = e->next;
+	/*
+	 * If the chain ran out, or if we are starting up, we need to
+	 * find the next nonempty chain.
+	 */
+	while (e == NULL) {
+		if (searchPtr->nextIndex >= t->size)
+			return (NULL);
+		e = t->bucketPtr[searchPtr->nextIndex++];
+	}
+	searchPtr->hashEntryPtr = e;
+	return (e);
+}
+
+/*
+ *---------------------------------------------------------
+ *
+ * RebuildTable --
+ *	This local routine makes a new hash table that
+ *	is larger than the old one.
+ *
+ * Results:
+ * 	None.
+ *
+ * Side Effects:
+ *	The entire hash table is moved, so any bucket numbers
+ *	from the old table are invalid.
+ *
+ *---------------------------------------------------------
+ */
+
+static void
+RebuildTable(Hash_Table *t)
+{
+	Hash_Entry *e, *next = NULL, **hp, **xp;
+	int i, mask;
+        Hash_Entry **oldhp;
+	int oldsize;
+
+	oldhp = t->bucketPtr;
+	oldsize = i = t->size;
+	i <<= 1;
+	t->size = i;
+	t->mask = mask = i - 1;
+	t->bucketPtr = hp = (struct Hash_Entry **) emalloc(sizeof(*hp) * i);
+	while (--i >= 0)
+		*hp++ = NULL;
+	for (hp = oldhp, i = oldsize; --i >= 0;) {
+		for (e = *hp++; e != NULL; e = next) {
+			next = e->next;
+			xp = &t->bucketPtr[e->namehash & mask];
+			e->next = *xp;
+			*xp = e;
+		}
+	}
+	free((char *)oldhp);
+}
Index: trunk/src/kmk/hash.h
===================================================================
--- trunk/src/kmk/hash.h	(revision 9)
+++ trunk/src/kmk/hash.h	(revision 9)
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)hash.h	8.1 (Berkeley) 6/6/93
+ * $FreeBSD: src/usr.bin/make/hash.h,v 1.12 2002/09/17 21:29:06 jmallett Exp $
+ */
+
+/* hash.h --
+ *
+ * 	This file contains definitions used by the hash module,
+ * 	which maintains hash tables.
+ */
+
+#ifndef	_HASH
+#define	_HASH
+
+/*
+ * The following defines one entry in the hash table.
+ */
+
+typedef struct Hash_Entry {
+    struct Hash_Entry *next;		/* Used to link together all the
+    					 * entries associated with the same
+					 * bucket. */
+    void *	      clientData;	/* Arbitrary piece of data associated
+    					 * with key. */
+    unsigned	      namehash;		/* hash value of key */
+    char	      name[1];		/* key string */
+} Hash_Entry;
+
+typedef struct Hash_Table {
+    struct Hash_Entry **bucketPtr;/* Pointers to Hash_Entry, one
+    				 * for each bucket in the table. */
+    int 	size;		/* Actual size of array. */
+    int 	numEntries;	/* Number of entries in the table. */
+    int 	mask;		/* Used to select bits for hashing. */
+} Hash_Table;
+
+/*
+ * The following structure is used by the searching routines
+ * to record where we are in the search.
+ */
+
+typedef struct Hash_Search {
+    Hash_Table  *tablePtr;	/* Table being searched. */
+    int 	nextIndex;	/* Next bucket to check (after current). */
+    Hash_Entry 	*hashEntryPtr;	/* Next entry to check in current bucket. */
+} Hash_Search;
+
+/*
+ * Macros.
+ */
+
+/*
+ * void * Hash_GetValue(h)
+ *     Hash_Entry *h;
+ */
+
+#define	Hash_GetValue(h) ((h)->clientData)
+
+/*
+ * Hash_SetValue(h, val);
+ *     Hash_Entry *h;
+ *     char *val;
+ */
+
+#define	Hash_SetValue(h, val) ((h)->clientData = (void *) (val))
+
+/*
+ * Hash_Size(n) returns the number of words in an object of n bytes
+ */
+
+#define	Hash_Size(n)	(((n) + sizeof (int) - 1) / sizeof (int))
+
+void Hash_InitTable(Hash_Table *, int);
+void Hash_DeleteTable(Hash_Table *);
+Hash_Entry *Hash_FindEntry(Hash_Table *, char *);
+Hash_Entry *Hash_CreateEntry(Hash_Table *, char *, Boolean *);
+void Hash_DeleteEntry(Hash_Table *, Hash_Entry *);
+Hash_Entry *Hash_EnumFirst(Hash_Table *, Hash_Search *);
+Hash_Entry *Hash_EnumNext(Hash_Search *);
+
+#endif /* _HASH */
Index: trunk/src/kmk/job.c
===================================================================
--- trunk/src/kmk/job.c	(revision 9)
+++ trunk/src/kmk/job.c	(revision 9)
@@ -0,0 +1,2997 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)job.c	8.2 (Berkeley) 3/19/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/job.c,v 1.45 2002/10/09 03:42:10 jmallett Exp $");
+
+#ifndef OLD_JOKE
+#define	OLD_JOKE 0
+#endif /* OLD_JOKE */
+
+/*-
+ * job.c --
+ *	handle the creation etc. of our child processes.
+ *
+ * Interface:
+ *	Job_Make  	    	Start the creation of the given target.
+ *
+ *	Job_CatchChildren   	Check for and handle the termination of any
+ *	    	  	    	children. This must be called reasonably
+ *	    	  	    	frequently to keep the whole make going at
+ *	    	  	    	a decent clip, since job table entries aren't
+ *	    	  	    	removed until their process is caught this way.
+ *	    	  	    	Its single argument is TRUE if the function
+ *	    	  	    	should block waiting for a child to terminate.
+ *
+ *	Job_CatchOutput	    	Print any output our children have produced.
+ *	    	  	    	Should also be called fairly frequently to
+ *	    	  	    	keep the user informed of what's going on.
+ *	    	  	    	If no output is waiting, it will block for
+ *	    	  	    	a time given by the SEL_* constants, below,
+ *	    	  	    	or until output is ready.
+ *
+ *	Job_Init  	    	Called to intialize this module. in addition,
+ *	    	  	    	any commands attached to the .BEGIN target
+ *	    	  	    	are executed before this function returns.
+ *	    	  	    	Hence, the makefile must have been parsed
+ *	    	  	    	before this function is called.
+ *
+ *	Job_Full  	    	Return TRUE if the job table is filled.
+ *
+ *	Job_Empty 	    	Return TRUE if the job table is completely
+ *	    	  	    	empty.
+ *
+ *	Job_ParseShell	    	Given the line following a .SHELL target, parse
+ *	    	  	    	the line as a shell specification. Returns
+ *	    	  	    	FAILURE if the spec was incorrect.
+ *
+ *	Job_Finish	  	    	Perform any final processing which needs doing.
+ *	    	  	    	This includes the execution of any commands
+ *	    	  	    	which have been/were attached to the .END
+ *	    	  	    	target. It should only be called when the
+ *	    	  	    	job table is empty.
+ *
+ *	Job_AbortAll	    	Abort all currently running jobs. It doesn't
+ *	    	  	    	handle output or do anything for the jobs,
+ *	    	  	    	just kills them. It should only be called in
+ *	    	  	    	an emergency, as it were.
+ *
+ *	Job_CheckCommands   	Verify that the commands for a target are
+ *	    	  	    	ok. Provide them if necessary and possible.
+ *
+ *	Job_Touch 	    	Update a target without really updating it.
+ *
+ *	Job_Wait  	    	Wait for all currently-running jobs to finish.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/time.h>
+#include <sys/event.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <utime.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+#ifdef REMOTE
+#include "rmt.h"
+# define STATIC
+#else
+# define STATIC static
+#endif
+
+/*
+ * error handling variables
+ */
+static int     	errors = 0;	    /* number of errors reported */
+static int    	aborting = 0;	    /* why is the make aborting? */
+#define	ABORT_ERROR	1   	    /* Because of an error */
+#define	ABORT_INTERRUPT	2   	    /* Because it was interrupted */
+#define	ABORT_WAIT	3   	    /* Waiting for jobs to finish */
+
+/*
+ * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file
+ * is a char! So when we go above 127 we turn negative!
+ */
+#define	FILENO(a) ((unsigned) fileno(a))
+
+/*
+ * post-make command processing. The node postCommands is really just the
+ * .END target but we keep it around to avoid having to search for it
+ * all the time.
+ */
+static GNode   	  *postCommands;    /* node containing commands to execute when
+				     * everything else is done */
+static int     	  numCommands; 	    /* The number of commands actually printed
+				     * for a target. Should this number be
+				     * 0, no shell will be executed. */
+
+/*
+ * Return values from JobStart.
+ */
+#define	JOB_RUNNING	0   	/* Job is running */
+#define	JOB_ERROR 	1   	/* Error in starting the job */
+#define	JOB_FINISHED	2   	/* The job is already finished */
+#define	JOB_STOPPED	3   	/* The job is stopped */
+
+/*
+ * tfile is used to build temp file names to store shell commands to
+ * execute. 
+ */
+static char     tfile[sizeof(TMPPAT)];
+
+
+/*
+ * Descriptions for various shells.
+ */
+static Shell    shells[] = {
+    /*
+     * CSH description. The csh can do echo control by playing
+     * with the setting of the 'echo' shell variable. Sadly,
+     * however, it is unable to do error control nicely.
+     */
+{
+    "csh",
+    TRUE, "unset verbose", "set verbose", "unset verbose", 10,
+    FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"",
+    "v", "e",
+},
+    /*
+     * SH description. Echo control is also possible and, under
+     * sun UNIX anyway, one can even control error checking.
+     */
+{
+    "sh",
+    TRUE, "set -", "set -v", "set -", 5,
+    TRUE, "set -e", "set +e",
+#ifdef OLDBOURNESHELL
+    FALSE, "echo \"%s\"\n", "sh -c '%s || exit 0'\n",
+#endif
+    "v", "e",
+},
+    /*
+     * KSH description. The Korn shell has a superset of
+     * the Bourne shell's functionality.
+     */
+{
+    "ksh",
+    TRUE, "set -", "set -v", "set -", 5,
+    TRUE, "set -e", "set +e",
+    "v", "e",
+},
+    /*
+     * UNKNOWN.
+     */
+{
+    (char *) 0,
+    FALSE, (char *) 0, (char *) 0, (char *) 0, 0,
+    FALSE, (char *) 0, (char *) 0,
+    (char *) 0, (char *) 0,
+}
+};
+static Shell 	*commandShell = &shells[DEFSHELL];/* this is the shell to
+						   * which we pass all
+						   * commands in the Makefile.
+						   * It is set by the
+						   * Job_ParseShell function */
+static char   	*shellPath = NULL,		  /* full pathname of
+						   * executable image */
+               	*shellName;	      	      	  /* last component of shell */
+
+
+static int  	maxJobs;    	/* The most children we can run at once */
+static int  	maxLocal;    	/* The most local ones we can have */
+STATIC int     	nJobs;	    	/* The number of children currently running */
+STATIC int	nLocal;    	/* The number of local children */
+STATIC Lst     	jobs;		/* The structures that describe them */
+STATIC Boolean	jobFull;    	/* Flag to tell when the job table is full. It
+				 * is set TRUE when (1) the total number of
+				 * running jobs equals the maximum allowed or
+				 * (2) a job can only be run locally, but
+				 * nLocal equals maxLocal */
+#ifndef RMT_WILL_WATCH
+#ifdef USE_KQUEUE
+static int	kqfd;		/* File descriptor obtained by kqueue() */
+#else
+static fd_set  	outputs;    	/* Set of descriptors of pipes connected to
+				 * the output channels of children */
+#endif
+#endif
+
+STATIC GNode   	*lastNode;	/* The node for which output was most recently
+				 * produced. */
+STATIC char    	*targFmt;   	/* Format string to use to head output from a
+				 * job when it's not the most-recent job heard
+				 * from */
+
+#ifdef REMOTE
+# define TARG_FMT  "--- %s at %s ---\n" /* Default format */
+# define MESSAGE(fp, gn) \
+	(void) fprintf(fp, targFmt, gn->name, gn->rem.hname);
+#else
+# define TARG_FMT  "--- %s ---\n" /* Default format */
+# define MESSAGE(fp, gn) \
+	(void) fprintf(fp, targFmt, gn->name);
+#endif
+
+/*
+ * When JobStart attempts to run a job remotely but can't, and isn't allowed
+ * to run the job locally, or when Job_CatchChildren detects a job that has
+ * been migrated home, the job is placed on the stoppedJobs queue to be run
+ * when the next job finishes.
+ */
+STATIC Lst	stoppedJobs;	/* Lst of Job structures describing
+				 * jobs that were stopped due to concurrency
+				 * limits or migration home */
+
+
+#if defined(USE_PGRP) && defined(SYSV)
+# define KILL(pid, sig)		killpg(-(pid), (sig))
+#else
+# if defined(USE_PGRP)
+#  define KILL(pid, sig)	killpg((pid), (sig))
+# else
+#  define KILL(pid, sig)	kill((pid), (sig))
+# endif
+#endif
+
+/*
+ * Grmpf... There is no way to set bits of the wait structure
+ * anymore with the stupid W*() macros. I liked the union wait
+ * stuff much more. So, we devise our own macros... This is
+ * really ugly, use dramamine sparingly. You have been warned.
+ */
+#define	W_SETMASKED(st, val, fun)				\
+	{							\
+		int sh = (int) ~0;				\
+		int mask = fun(sh);				\
+								\
+		for (sh = 0; ((mask >> sh) & 1) == 0; sh++)	\
+			continue;				\
+		*(st) = (*(st) & ~mask) | ((val) << sh);	\
+	}
+
+#define	W_SETTERMSIG(st, val) W_SETMASKED(st, val, WTERMSIG)
+#define	W_SETEXITSTATUS(st, val) W_SETMASKED(st, val, WEXITSTATUS)
+
+
+static int JobCondPassSig(void *, void *);
+static void JobPassSig(int);
+static int JobCmpPid(void *, void *);
+static int JobPrintCommand(void *, void *);
+static int JobSaveCommand(void *, void *);
+static void JobClose(Job *);
+#ifdef REMOTE
+static int JobCmpRmtID(Job *, int);
+# ifdef RMT_WILL_WATCH
+static void JobLocalInput(int, Job *);
+# endif
+#else
+static void JobFinish(Job *, int *);
+static void JobExec(Job *, char **);
+#endif
+static void JobMakeArgv(Job *, char **);
+static void JobRestart(Job *);
+static int JobStart(GNode *, int, Job *);
+static char *JobOutput(Job *, char *, char *, int);
+static void JobDoOutput(Job *, Boolean);
+static Shell *JobMatchShell(char *);
+static void JobInterrupt(int, int);
+static void JobRestartJobs(void);
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobCondPassSig --
+ *	Pass a signal to a job if the job is remote or if USE_PGRP
+ *	is defined.
+ *
+ * Results:
+ *	=== 0
+ *
+ * Side Effects:
+ *	None, except the job may bite it.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+JobCondPassSig(void *jobp, void *signop)
+{
+    Job	*job = (Job *) jobp;
+    int	signo = *(int *) signop;
+#ifdef RMT_WANTS_SIGNALS
+    if (job->flags & JOB_REMOTE) {
+	(void) Rmt_Signal(job, signo);
+    } else {
+	KILL(job->pid, signo);
+    }
+#else
+    /*
+     * Assume that sending the signal to job->pid will signal any remote
+     * job as well.
+     */
+    DEBUGF(JOB, ("JobCondPassSig passing signal %d to child %d.\n", signo, job->pid));
+    KILL(job->pid, signo);
+#endif
+    return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPassSig --
+ *	Pass a signal on to all remote jobs and to all local jobs if
+ *	USE_PGRP is defined, then die ourselves.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	We die by the same signal.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobPassSig(int signo)
+{
+    sigset_t nmask, omask;
+    struct sigaction act;
+
+    DEBUGF(JOB, ("JobPassSig(%d) called.\n", signo));
+    Lst_ForEach(jobs, JobCondPassSig, (void *) &signo);
+
+    /*
+     * Deal with proper cleanup based on the signal received. We only run
+     * the .INTERRUPT target if the signal was in fact an interrupt. The other
+     * three termination signals are more of a "get out *now*" command.
+     */
+    if (signo == SIGINT) {
+	JobInterrupt(TRUE, signo);
+    } else if ((signo == SIGHUP) || (signo == SIGTERM) || (signo == SIGQUIT)) {
+	JobInterrupt(FALSE, signo);
+    }
+
+    /*
+     * Leave gracefully if SIGQUIT, rather than core dumping.
+     */
+    if (signo == SIGQUIT) {
+	signo = SIGINT;
+    }
+
+    /*
+     * Send ourselves the signal now we've given the message to everyone else.
+     * Note we block everything else possible while we're getting the signal.
+     * This ensures that all our jobs get continued when we wake up before
+     * we take any other signal.
+     */
+    sigemptyset(&nmask);
+    sigaddset(&nmask, signo);
+    sigprocmask(SIG_SETMASK, &nmask, &omask);
+    act.sa_handler = SIG_DFL;
+    sigemptyset(&act.sa_mask);
+    act.sa_flags = 0;
+    sigaction(signo, &act, NULL);
+
+    DEBUGF(JOB, ("JobPassSig passing signal to self, mask = %x.\n", ~0 & ~(1 << (signo-1))));
+    (void) signal(signo, SIG_DFL);
+
+    (void) KILL(getpid(), signo);
+
+    signo = SIGCONT;
+    Lst_ForEach(jobs, JobCondPassSig, (void *) &signo);
+
+    (void) sigprocmask(SIG_SETMASK, &omask, NULL);
+    sigprocmask(SIG_SETMASK, &omask, NULL);
+    act.sa_handler = JobPassSig;
+    sigaction(signo, &act, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobCmpPid  --
+ *	Compare the pid of the job with the given pid and return 0 if they
+ *	are equal. This function is called from Job_CatchChildren via
+ *	Lst_Find to find the job descriptor of the finished job.
+ *
+ * Results:
+ *	0 if the pid's match
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static int
+JobCmpPid(void *job, void *pid)
+{
+    return *(int *) pid - ((Job *) job)->pid;
+}
+
+#ifdef REMOTE
+/*-
+ *-----------------------------------------------------------------------
+ * JobCmpRmtID  --
+ *	Compare the rmtID of the job with the given rmtID and return 0 if they
+ *	are equal.
+ *
+ * Results:
+ *	0 if the rmtID's match
+ *
+ * Side Effects:
+ *	None.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobCmpRmtID(void *job, void *rmtID)
+{
+    return(*(int *) rmtID - *(int *) job->rmtID);
+}
+#endif
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPrintCommand  --
+ *	Put out another command for the given job. If the command starts
+ *	with an @ or a - we process it specially. In the former case,
+ *	so long as the -s and -n flags weren't given to make, we stick
+ *	a shell-specific echoOff command in the script. In the latter,
+ *	we ignore errors for the entire job, unless the shell has error
+ *	control.
+ *	If the command is just "..." we take all future commands for this
+ *	job to be commands to be executed once the entire graph has been
+ *	made and return non-zero to signal that the end of the commands
+ *	was reached. These commands are later attached to the postCommands
+ *	node and executed by Job_Finish when all things are done.
+ *	This function is called from JobStart via Lst_ForEach.
+ *
+ * Results:
+ *	Always 0, unless the command was "..."
+ *
+ * Side Effects:
+ *	If the command begins with a '-' and the shell has no error control,
+ *	the JOB_IGNERR flag is set in the job descriptor.
+ *	If the command is "..." and we're not ignoring such things,
+ *	tailCmds is set to the successor node of the cmd.
+ *	numCommands is incremented if the command is actually printed.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobPrintCommand(void *cmdp, void *jobp)
+{
+    Boolean	  noSpecials;	    /* true if we shouldn't worry about
+				     * inserting special commands into
+				     * the input stream. */
+    Boolean       shutUp = FALSE;   /* true if we put a no echo command
+				     * into the command file */
+    Boolean	  errOff = FALSE;   /* true if we turned error checking
+				     * off before printing the command
+				     * and need to turn it back on */
+    char       	  *cmdTemplate;	    /* Template to use when printing the
+				     * command */
+    char    	  *cmdStart;	    /* Start of expanded command */
+    LstNode 	  cmdNode;  	    /* Node for replacing the command */
+    char     	  *cmd = (char *) cmdp;
+    Job           *job = (Job *) jobp;
+
+    noSpecials = (noExecute && !(job->node->type & OP_MAKE));
+
+    if (strcmp(cmd, "...") == 0) {
+	job->node->type |= OP_SAVE_CMDS;
+	if ((job->flags & JOB_IGNDOTS) == 0) {
+	    job->tailCmds = Lst_Succ(Lst_Member(job->node->commands,
+						(void *)cmd));
+	    return 1;
+	}
+	return 0;
+    }
+
+#define	DBPRINTF(fmt, arg)  			\
+   DEBUGF(JOB, (fmt, arg));			\
+   (void) fprintf(job->cmdFILE, fmt, arg);	\
+   (void) fflush(job->cmdFILE);
+
+    numCommands += 1;
+
+    /*
+     * For debugging, we replace each command with the result of expanding
+     * the variables in the command.
+     */
+    cmdNode = Lst_Member(job->node->commands, (void *)cmd);
+    cmdStart = cmd = Var_Subst(NULL, cmd, job->node, FALSE);
+    Lst_Replace(cmdNode, (void *)cmdStart);
+
+    cmdTemplate = "%s\n";
+
+    /*
+     * Check for leading @' and -'s to control echoing and error checking.
+     */
+    while (*cmd == '@' || *cmd == '-') {
+	if (*cmd == '@') {
+	    shutUp = DEBUG(LOUD) ? FALSE : TRUE;
+	} else {
+	    errOff = TRUE;
+	}
+	cmd++;
+    }
+
+    while (isspace((unsigned char) *cmd))
+	cmd++;
+
+    if (shutUp) {
+	if (!(job->flags & JOB_SILENT) && !noSpecials &&
+	    commandShell->hasEchoCtl) {
+		DBPRINTF("%s\n", commandShell->echoOff);
+	} else {
+	    shutUp = FALSE;
+	}
+    }
+
+    if (errOff) {
+	if ( !(job->flags & JOB_IGNERR) && !noSpecials) {
+	    if (commandShell->hasErrCtl) {
+		/*
+		 * we don't want the error-control commands showing
+		 * up either, so we turn off echoing while executing
+		 * them. We could put another field in the shell
+		 * structure to tell JobDoOutput to look for this
+		 * string too, but why make it any more complex than
+		 * it already is?
+		 */
+		if (!(job->flags & JOB_SILENT) && !shutUp &&
+		    commandShell->hasEchoCtl) {
+			DBPRINTF("%s\n", commandShell->echoOff);
+			DBPRINTF("%s\n", commandShell->ignErr);
+			DBPRINTF("%s\n", commandShell->echoOn);
+		} else {
+		    DBPRINTF("%s\n", commandShell->ignErr);
+		}
+	    } else if (commandShell->ignErr &&
+		      (*commandShell->ignErr != '\0'))
+	    {
+		/*
+		 * The shell has no error control, so we need to be
+		 * weird to get it to ignore any errors from the command.
+		 * If echoing is turned on, we turn it off and use the
+		 * errCheck template to echo the command. Leave echoing
+		 * off so the user doesn't see the weirdness we go through
+		 * to ignore errors. Set cmdTemplate to use the weirdness
+		 * instead of the simple "%s\n" template.
+		 */
+		if (!(job->flags & JOB_SILENT) && !shutUp &&
+		    commandShell->hasEchoCtl) {
+			DBPRINTF("%s\n", commandShell->echoOff);
+			DBPRINTF(commandShell->errCheck, cmd);
+			shutUp = TRUE;
+		}
+		cmdTemplate = commandShell->ignErr;
+		/*
+		 * The error ignoration (hee hee) is already taken care
+		 * of by the ignErr template, so pretend error checking
+		 * is still on.
+		 */
+		errOff = FALSE;
+	    } else {
+		errOff = FALSE;
+	    }
+	} else {
+	    errOff = FALSE;
+	}
+    }
+
+    DBPRINTF(cmdTemplate, cmd);
+
+    if (errOff) {
+	/*
+	 * If echoing is already off, there's no point in issuing the
+	 * echoOff command. Otherwise we issue it and pretend it was on
+	 * for the whole command...
+	 */
+	if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){
+	    DBPRINTF("%s\n", commandShell->echoOff);
+	    shutUp = TRUE;
+	}
+	DBPRINTF("%s\n", commandShell->errCheck);
+    }
+    if (shutUp) {
+	DBPRINTF("%s\n", commandShell->echoOn);
+    }
+    return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobSaveCommand --
+ *	Save a command to be executed when everything else is done.
+ *	Callback function for JobFinish...
+ *
+ * Results:
+ *	Always returns 0
+ *
+ * Side Effects:
+ *	The command is tacked onto the end of postCommands's commands list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+JobSaveCommand(void *cmd, void *gn)
+{
+    cmd = (void *) Var_Subst(NULL, (char *) cmd, (GNode *) gn, FALSE);
+    (void) Lst_AtEnd(postCommands->commands, cmd);
+    return(0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobClose --
+ *	Called to close both input and output pipes when a job is finished.
+ *
+ * Results:
+ *	Nada
+ *
+ * Side Effects:
+ *	The file descriptors associated with the job are closed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobClose(Job *job)
+{
+    if (usePipes) {
+#ifdef RMT_WILL_WATCH
+	Rmt_Ignore(job->inPipe);
+#elif !defined(USE_KQUEUE)
+	FD_CLR(job->inPipe, &outputs);
+#endif
+	if (job->outPipe != job->inPipe) {
+	   (void) close(job->outPipe);
+	}
+	JobDoOutput(job, TRUE);
+	(void) close(job->inPipe);
+    } else {
+	(void) close(job->outFd);
+	JobDoOutput(job, TRUE);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobFinish  --
+ *	Do final processing for the given job including updating
+ *	parents and starting new jobs as available/necessary. Note
+ *	that we pay no attention to the JOB_IGNERR flag here.
+ *	This is because when we're called because of a noexecute flag
+ *	or something, jstat.w_status is 0 and when called from
+ *	Job_CatchChildren, the status is zeroed if it s/b ignored.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Some nodes may be put on the toBeMade queue.
+ *	Final commands for the job are placed on postCommands.
+ *
+ *	If we got an error and are aborting (aborting == ABORT_ERROR) and
+ *	the job list is now empty, we are done for the day.
+ *	If we recognized an error (errors !=0), we set the aborting flag
+ *	to ABORT_ERROR so no more jobs will be started.
+ *-----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+static void
+JobFinish(Job *job, int *status)
+{
+    Boolean 	 done;
+
+    if ((WIFEXITED(*status) &&
+	 (((WEXITSTATUS(*status) != 0) && !(job->flags & JOB_IGNERR)))) ||
+	(WIFSIGNALED(*status) && (WTERMSIG(*status) != SIGCONT)))
+    {
+	/*
+	 * If it exited non-zero and either we're doing things our
+	 * way or we're not ignoring errors, the job is finished.
+	 * Similarly, if the shell died because of a signal
+	 * the job is also finished. In these
+	 * cases, finish out the job's output before printing the exit
+	 * status...
+	 */
+#ifdef REMOTE
+	KILL(job->pid, SIGCONT);
+#endif
+	JobClose(job);
+	if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+	   (void) fclose(job->cmdFILE);
+	}
+	done = TRUE;
+#ifdef REMOTE
+	if (job->flags & JOB_REMOTE)
+	    Rmt_Done(job->rmtID, job->node);
+#endif
+    } else if (WIFEXITED(*status)) {
+	/*
+	 * Deal with ignored errors in -B mode. We need to print a message
+	 * telling of the ignored error as well as setting status.w_status
+	 * to 0 so the next command gets run. To do this, we set done to be
+	 * TRUE if in -B mode and the job exited non-zero.
+	 */
+	done = WEXITSTATUS(*status) != 0;
+	/*
+	 * Old comment said: "Note we don't
+	 * want to close down any of the streams until we know we're at the
+	 * end."
+	 * But we do. Otherwise when are we going to print the rest of the
+	 * stuff?
+	 */
+	JobClose(job);
+#ifdef REMOTE
+	if (job->flags & JOB_REMOTE)
+	    Rmt_Done(job->rmtID, job->node);
+#endif /* REMOTE */
+    } else {
+	/*
+	 * No need to close things down or anything.
+	 */
+	done = FALSE;
+    }
+
+    if (done ||
+	WIFSTOPPED(*status) ||
+	(WIFSIGNALED(*status) && (WTERMSIG(*status) == SIGCONT)) ||
+	DEBUG(JOB))
+    {
+	FILE	  *out;
+
+	if (compatMake && !usePipes && (job->flags & JOB_IGNERR)) {
+	    /*
+	     * If output is going to a file and this job is ignoring
+	     * errors, arrange to have the exit status sent to the
+	     * output file as well.
+	     */
+	    out = fdopen(job->outFd, "w");
+	    if (out == NULL)
+		Punt("Cannot fdopen");
+	} else {
+	    out = stdout;
+	}
+
+	if (WIFEXITED(*status)) {
+	    DEBUGF(JOB, ("Process %d exited.\n", job->pid));
+	    if (WEXITSTATUS(*status) != 0) {
+		if (usePipes && job->node != lastNode) {
+		    MESSAGE(out, job->node);
+		    lastNode = job->node;
+		}
+		(void) fprintf(out, "*** Error code %d%s\n",
+			       WEXITSTATUS(*status),
+			       (job->flags & JOB_IGNERR) ? "(ignored)" : "");
+
+		if (job->flags & JOB_IGNERR) {
+		    *status = 0;
+		}
+	    } else if (DEBUG(JOB)) {
+		if (usePipes && job->node != lastNode) {
+		    MESSAGE(out, job->node);
+		    lastNode = job->node;
+		}
+		(void) fprintf(out, "*** Completed successfully\n");
+	    }
+	} else if (WIFSTOPPED(*status)) {
+	    DEBUGF(JOB, ("Process %d stopped.\n", job->pid));
+	    if (usePipes && job->node != lastNode) {
+		MESSAGE(out, job->node);
+		lastNode = job->node;
+	    }
+	    if (!(job->flags & JOB_REMIGRATE)) {
+		(void) fprintf(out, "*** Stopped -- signal %d\n",
+		    WSTOPSIG(*status));
+	    }
+	    job->flags |= JOB_RESUME;
+	    (void)Lst_AtEnd(stoppedJobs, (void *)job);
+#ifdef REMOTE
+	    if (job->flags & JOB_REMIGRATE)
+		JobRestart(job);
+#endif
+	    (void) fflush(out);
+	    return;
+	} else if (WTERMSIG(*status) == SIGCONT) {
+	    /*
+	     * If the beastie has continued, shift the Job from the stopped
+	     * list to the running one (or re-stop it if concurrency is
+	     * exceeded) and go and get another child.
+	     */
+	    if (job->flags & (JOB_RESUME|JOB_REMIGRATE|JOB_RESTART)) {
+		if (usePipes && job->node != lastNode) {
+		    MESSAGE(out, job->node);
+		    lastNode = job->node;
+		}
+		(void) fprintf(out, "*** Continued\n");
+	    }
+	    if (!(job->flags & JOB_CONTINUING)) {
+		DEBUGF(JOB, ("Warning: process %d was not continuing.\n", job->pid));
+#ifdef notdef
+		/*
+		 * We don't really want to restart a job from scratch just
+		 * because it continued, especially not without killing the
+		 * continuing process!  That's why this is ifdef'ed out.
+		 * FD - 9/17/90
+		 */
+		JobRestart(job);
+#endif
+	    }
+	    job->flags &= ~JOB_CONTINUING;
+ 	    Lst_AtEnd(jobs, (void *)job);
+	    nJobs += 1;
+	    if (!(job->flags & JOB_REMOTE)) {
+		DEBUGF(JOB, ("Process %d is continuing locally.\n", job->pid));
+		nLocal += 1;
+	    }
+	    if (nJobs == maxJobs) {
+		jobFull = TRUE;
+		DEBUGF(JOB, ("Job queue is full.\n"));
+  	    }
+	    (void) fflush(out);
+  	    return;
+	} else {
+	    if (usePipes && job->node != lastNode) {
+		MESSAGE(out, job->node);
+		lastNode = job->node;
+	    }
+	    (void) fprintf(out, "*** Signal %d\n", WTERMSIG(*status));
+	}
+
+	(void) fflush(out);
+    }
+
+    /*
+     * Now handle the -B-mode stuff. If the beast still isn't finished,
+     * try and restart the job on the next command. If JobStart says it's
+     * ok, it's ok. If there's an error, this puppy is done.
+     */
+    if (compatMake && (WIFEXITED(*status) &&
+	!Lst_IsAtEnd(job->node->commands))) {
+	switch (JobStart(job->node, job->flags & JOB_IGNDOTS, job)) {
+	case JOB_RUNNING:
+	    done = FALSE;
+	    break;
+	case JOB_ERROR:
+	    done = TRUE;
+	    W_SETEXITSTATUS(status, 1);
+	    break;
+	case JOB_FINISHED:
+	    /*
+	     * If we got back a JOB_FINISHED code, JobStart has already
+	     * called Make_Update and freed the job descriptor. We set
+	     * done to false here to avoid fake cycles and double frees.
+	     * JobStart needs to do the update so we can proceed up the
+	     * graph when given the -n flag..
+	     */
+	    done = FALSE;
+	    break;
+	default:
+	    break;
+	}
+    } else {
+	done = TRUE;
+    }
+
+
+    if (done &&
+	(aborting != ABORT_ERROR) &&
+	(aborting != ABORT_INTERRUPT) &&
+	(*status == 0))
+    {
+	/*
+	 * As long as we aren't aborting and the job didn't return a non-zero
+	 * status that we shouldn't ignore, we call Make_Update to update
+	 * the parents. In addition, any saved commands for the node are placed
+	 * on the .END target.
+	 */
+	if (job->tailCmds != NULL) {
+	    Lst_ForEachFrom(job->node->commands, job->tailCmds,
+			     JobSaveCommand,
+			    (void *)job->node);
+	}
+	job->node->made = MADE;
+	Make_Update(job->node);
+	free(job);
+    } else if (*status != 0) {
+	errors += 1;
+	free(job);
+    }
+
+    JobRestartJobs();
+
+    /*
+     * Set aborting if any error.
+     */
+    if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) {
+	/*
+	 * If we found any errors in this batch of children and the -k flag
+	 * wasn't given, we set the aborting flag so no more jobs get
+	 * started.
+	 */
+	aborting = ABORT_ERROR;
+    }
+
+    if ((aborting == ABORT_ERROR) && Job_Empty())
+	/*
+	 * If we are aborting and the job table is now empty, we finish.
+	 */
+	Finish(errors);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Touch --
+ *	Touch the given target. Called by JobStart when the -t flag was
+ *	given.  Prints messages unless told to be silent.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The data modification of the file is changed. In addition, if the
+ *	file did not exist, it is created.
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Touch(GNode *gn, Boolean silent)
+{
+    int		  streamID;   	/* ID of stream opened to do the touch */
+    struct utimbuf times;	/* Times for utime() call */
+
+    if (gn->type & (OP_JOIN|OP_USE|OP_EXEC|OP_OPTIONAL)) {
+	/*
+	 * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
+	 * and, as such, shouldn't really be created.
+	 */
+	return;
+    }
+
+    if (!silent) {
+	(void) fprintf(stdout, "touch %s\n", gn->name);
+	(void) fflush(stdout);
+    }
+
+    if (noExecute) {
+	return;
+    }
+
+    if (gn->type & OP_ARCHV) {
+	Arch_Touch(gn);
+    } else if (gn->type & OP_LIB) {
+	Arch_TouchLib(gn);
+    } else {
+	char	*file = gn->path ? gn->path : gn->name;
+
+	times.actime = times.modtime = now;
+	if (utime(file, &times) < 0){
+	    streamID = open(file, O_RDWR | O_CREAT, 0666);
+
+	    if (streamID >= 0) {
+		char	c;
+
+		/*
+		 * Read and write a byte to the file to change the
+		 * modification time, then close the file.
+		 */
+		if (read(streamID, &c, 1) == 1) {
+		    (void) lseek(streamID, (off_t)0, SEEK_SET);
+		    (void) write(streamID, &c, 1);
+		}
+
+		(void) close(streamID);
+	    } else {
+		(void) fprintf(stdout, "*** couldn't touch %s: %s",
+			       file, strerror(errno));
+		(void) fflush(stdout);
+	    }
+	}
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CheckCommands --
+ *	Make sure the given node has all the commands it needs.
+ *
+ * Results:
+ *	TRUE if the commands list is/was ok.
+ *
+ * Side Effects:
+ *	The node will have commands from the .DEFAULT rule added to it
+ *	if it needs them.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...))
+{
+    if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) &&
+	(gn->type & OP_LIB) == 0) {
+	/*
+	 * No commands. Look for .DEFAULT rule from which we might infer
+	 * commands
+	 */
+	if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands)) {
+	    char *p1;
+	    /*
+	     * Make only looks for a .DEFAULT if the node was never the
+	     * target of an operator, so that's what we do too. If
+	     * a .DEFAULT was given, we substitute its commands for gn's
+	     * commands and set the IMPSRC variable to be the target's name
+	     * The DEFAULT node acts like a transformation rule, in that
+	     * gn also inherits any attributes or sources attached to
+	     * .DEFAULT itself.
+	     */
+	    Make_HandleUse(DEFAULT, gn);
+	    Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn);
+	    efree(p1);
+	} else if (Dir_MTime(gn) == 0) {
+	    /*
+	     * The node wasn't the target of an operator we have no .DEFAULT
+	     * rule to go on and the target doesn't already exist. There's
+	     * nothing more we can do for this branch. If the -k flag wasn't
+	     * given, we stop in our tracks, otherwise we just don't update
+	     * this node's parents so they never get examined.
+	     */
+	    static const char msg[] = "make: don't know how to make";
+
+	    if (gn->type & OP_OPTIONAL) {
+		(void) fprintf(stdout, "%s %s(ignored)\n", msg, gn->name);
+		(void) fflush(stdout);
+	    } else if (keepgoing) {
+		(void) fprintf(stdout, "%s %s(continuing)\n", msg, gn->name);
+		(void) fflush(stdout);
+  		return FALSE;
+	    } else {
+#if OLD_JOKE
+		if (strcmp(gn->name,"love") == 0)
+		    (*abortProc)("Not war.");
+		else
+#endif
+		    (*abortProc)("%s %s. Stop", msg, gn->name);
+		return FALSE;
+	    }
+	}
+    }
+    return TRUE;
+}
+#ifdef RMT_WILL_WATCH
+/*-
+ *-----------------------------------------------------------------------
+ * JobLocalInput --
+ *	Handle a pipe becoming readable. Callback function for Rmt_Watch
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	JobDoOutput is called.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*ARGSUSED*/
+static void
+JobLocalInput(int stream, Job *job)
+{
+    JobDoOutput(job, FALSE);
+}
+#endif /* RMT_WILL_WATCH */
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobExec --
+ *	Execute the shell for the given job. Called from JobStart and
+ *	JobRestart.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	A shell is executed, outputs is altered and the Job structure added
+ *	to the job table.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobExec(Job *job, char **argv)
+{
+    int	    	  cpid;	    	/* ID of new child */
+
+    if (DEBUG(JOB)) {
+	int 	  i;
+
+	DEBUGF(JOB, ("Running %s %sly\n", job->node->name,
+	       job->flags&JOB_REMOTE?"remote":"local"));
+	DEBUGF(JOB, ("\tCommand: "));
+	for (i = 0; argv[i] != NULL; i++) {
+	    DEBUGF(JOB, ("%s ", argv[i]));
+	}
+	DEBUGF(JOB, ("\n"));
+    }
+
+    /*
+     * Some jobs produce no output and it's disconcerting to have
+     * no feedback of their running (since they produce no output, the
+     * banner with their name in it never appears). This is an attempt to
+     * provide that feedback, even if nothing follows it.
+     */
+    if ((lastNode != job->node) && (job->flags & JOB_FIRST) &&
+	!(job->flags & JOB_SILENT)) {
+	MESSAGE(stdout, job->node);
+	lastNode = job->node;
+    }
+
+#ifdef RMT_NO_EXEC
+    if (job->flags & JOB_REMOTE) {
+	goto jobExecFinish;
+    }
+#endif /* RMT_NO_EXEC */
+
+    if ((cpid = vfork()) == -1) {
+	Punt("Cannot fork");
+    } else if (cpid == 0) {
+
+	/*
+	 * Must duplicate the input stream down to the child's input and
+	 * reset it to the beginning (again). Since the stream was marked
+	 * close-on-exec, we must clear that bit in the new input.
+	 */
+	if (dup2(FILENO(job->cmdFILE), 0) == -1)
+	    Punt("Cannot dup2: %s", strerror(errno));
+	(void) fcntl(0, F_SETFD, 0);
+	(void) lseek(0, (off_t)0, SEEK_SET);
+
+	if (usePipes) {
+	    /*
+	     * Set up the child's output to be routed through the pipe
+	     * we've created for it.
+	     */
+	    if (dup2(job->outPipe, 1) == -1)
+		Punt("Cannot dup2: %s", strerror(errno));
+	} else {
+	    /*
+	     * We're capturing output in a file, so we duplicate the
+	     * descriptor to the temporary file into the standard
+	     * output.
+	     */
+	    if (dup2(job->outFd, 1) == -1)
+		Punt("Cannot dup2: %s", strerror(errno));
+	}
+	/*
+	 * The output channels are marked close on exec. This bit was
+	 * duplicated by the dup2 (on some systems), so we have to clear
+	 * it before routing the shell's error output to the same place as
+	 * its standard output.
+	 */
+	(void) fcntl(1, F_SETFD, 0);
+	if (dup2(1, 2) == -1)
+	    Punt("Cannot dup2: %s", strerror(errno));
+
+#ifdef USE_PGRP
+	/*
+	 * We want to switch the child into a different process family so
+	 * we can kill it and all its descendants in one fell swoop,
+	 * by killing its process family, but not commit suicide.
+	 */
+# if defined(SYSV)
+	(void) setsid();
+# else
+	(void) setpgid(0, getpid());
+# endif
+#endif /* USE_PGRP */
+
+#ifdef REMOTE
+	if (job->flags & JOB_REMOTE) {
+	    Rmt_Exec(shellPath, argv, FALSE);
+	} else
+#endif /* REMOTE */
+	   (void) execv(shellPath, argv);
+
+	(void) write(STDERR_FILENO, "Could not execute shell\n",
+		     sizeof("Could not execute shell"));
+	_exit(1);
+    } else {
+#ifdef REMOTE
+	long omask = sigblock(sigmask(SIGCHLD));
+#endif
+	job->pid = cpid;
+
+	if (usePipes && (job->flags & JOB_FIRST) ) {
+	    /*
+	     * The first time a job is run for a node, we set the current
+	     * position in the buffer to the beginning and mark another
+	     * stream to watch in the outputs mask
+	     */
+#ifdef USE_KQUEUE
+	    struct kevent	kev[2];
+#endif
+	    job->curPos = 0;
+
+#ifdef RMT_WILL_WATCH
+	    Rmt_Watch(job->inPipe, JobLocalInput, job);
+#elif defined(USE_KQUEUE)
+	    EV_SET(&kev[0], job->inPipe, EVFILT_READ, EV_ADD, 0, 0, job);
+	    EV_SET(&kev[1], job->pid, EVFILT_PROC, EV_ADD | EV_ONESHOT,
+		NOTE_EXIT, 0, NULL);
+	    if (kevent(kqfd, kev, 2, NULL, 0, NULL) != 0) {
+		/* kevent() will fail if the job is already finished */
+		if (errno != EBADF && errno != ESRCH)
+		    Punt("kevent: %s", strerror(errno));
+	    }
+#else
+	    FD_SET(job->inPipe, &outputs);
+#endif /* RMT_WILL_WATCH */
+	}
+
+	if (job->flags & JOB_REMOTE) {
+#ifndef REMOTE
+	    job->rmtID = 0;
+#else
+	    job->rmtID = Rmt_LastID(job->pid);
+#endif /* REMOTE */
+	} else {
+	    nLocal += 1;
+	    /*
+	     * XXX: Used to not happen if REMOTE. Why?
+	     */
+	    if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+		(void) fclose(job->cmdFILE);
+		job->cmdFILE = NULL;
+	    }
+	}
+#ifdef REMOTE
+	(void) sigsetmask(omask);
+#endif
+    }
+
+#ifdef RMT_NO_EXEC
+jobExecFinish:
+#endif
+    /*
+     * Now the job is actually running, add it to the table.
+     */
+    nJobs += 1;
+    (void) Lst_AtEnd(jobs, (void *)job);
+    if (nJobs == maxJobs) {
+	jobFull = TRUE;
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMakeArgv --
+ *	Create the argv needed to execute the shell for a given job.
+ *
+ *
+ * Results:
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobMakeArgv(Job *job, char **argv)
+{
+    int	    	  argc;
+    static char	  args[10]; 	/* For merged arguments */
+
+    argv[0] = shellName;
+    argc = 1;
+
+    if ((commandShell->exit && (*commandShell->exit != '-')) ||
+	(commandShell->echo && (*commandShell->echo != '-')))
+    {
+	/*
+	 * At least one of the flags doesn't have a minus before it, so
+	 * merge them together. Have to do this because the *(&(@*#*&#$#
+	 * Bourne shell thinks its second argument is a file to source.
+	 * Grrrr. Note the ten-character limitation on the combined arguments.
+	 */
+	(void)sprintf(args, "-%s%s",
+		      ((job->flags & JOB_IGNERR) ? "" :
+		       (commandShell->exit ? commandShell->exit : "")),
+		      ((job->flags & JOB_SILENT) ? "" :
+		       (commandShell->echo ? commandShell->echo : "")));
+
+	if (args[1]) {
+	    argv[argc] = args;
+	    argc++;
+	}
+    } else {
+	if (!(job->flags & JOB_IGNERR) && commandShell->exit) {
+	    argv[argc] = commandShell->exit;
+	    argc++;
+	}
+	if (!(job->flags & JOB_SILENT) && commandShell->echo) {
+	    argv[argc] = commandShell->echo;
+	    argc++;
+	}
+    }
+    argv[argc] = NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobRestart --
+ *	Restart a job that stopped for some reason.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	jobFull will be set if the job couldn't be run.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobRestart(Job *job)
+{
+#ifdef REMOTE
+    int host;
+#endif
+
+    if (job->flags & JOB_REMIGRATE) {
+	if (
+#ifdef REMOTE
+	    verboseRemigrates ||
+#endif
+	    DEBUG(JOB)) {
+	   (void) fprintf(stdout, "*** remigrating %x(%s)\n",
+			   job->pid, job->node->name);
+	   (void) fflush(stdout);
+	}
+
+#ifdef REMOTE
+	if (!Rmt_ReExport(job->pid, job->node, &host)) {
+	    if (verboseRemigrates || DEBUG(JOB)) {
+		(void) fprintf(stdout, "*** couldn't migrate...\n");
+		(void) fflush(stdout);
+	    }
+#endif
+	    if (nLocal != maxLocal) {
+		/*
+		 * Job cannot be remigrated, but there's room on the local
+		 * machine, so resume the job and note that another
+		 * local job has started.
+		 */
+		if (
+#ifdef REMOTE
+		    verboseRemigrates ||
+#endif
+		    DEBUG(JOB)) {
+		    (void) fprintf(stdout, "*** resuming on local machine\n");
+		    (void) fflush(stdout);
+		}
+		KILL(job->pid, SIGCONT);
+		nLocal +=1;
+#ifdef REMOTE
+		job->flags &= ~(JOB_REMIGRATE|JOB_RESUME|JOB_REMOTE);
+		job->flags |= JOB_CONTINUING;
+#else
+		job->flags &= ~(JOB_REMIGRATE|JOB_RESUME);
+#endif
+	} else {
+		/*
+		 * Job cannot be restarted. Mark the table as full and
+		 * place the job back on the list of stopped jobs.
+		 */
+		if (
+#ifdef REMOTE
+		    verboseRemigrates ||
+#endif
+		    DEBUG(JOB)) {
+		   (void) fprintf(stdout, "*** holding\n");
+		   (void) fflush(stdout);
+  		}
+		(void)Lst_AtFront(stoppedJobs, (void *)job);
+		jobFull = TRUE;
+		DEBUGF(JOB, ("Job queue is full.\n"));
+		return;
+	    }
+#ifdef REMOTE
+	} else {
+	    /*
+	     * Clear out the remigrate and resume flags. Set the continuing
+	     * flag so we know later on that the process isn't exiting just
+	     * because of a signal.
+	     */
+	    job->flags &= ~(JOB_REMIGRATE|JOB_RESUME);
+	    job->flags |= JOB_CONTINUING;
+	    job->rmtID = host;
+	}
+#endif
+
+	(void)Lst_AtEnd(jobs, (void *)job);
+	nJobs += 1;
+	if (nJobs == maxJobs) {
+	    jobFull = TRUE;
+	    DEBUGF(JOB, ("Job queue is full.\n"));
+	}
+    } else if (job->flags & JOB_RESTART) {
+	/*
+	 * Set up the control arguments to the shell. This is based on the
+	 * flags set earlier for this job. If the JOB_IGNERR flag is clear,
+	 * the 'exit' flag of the commandShell is used to cause it to exit
+	 * upon receiving an error. If the JOB_SILENT flag is clear, the
+	 * 'echo' flag of the commandShell is used to get it to start echoing
+	 * as soon as it starts processing commands.
+	 */
+	char	  *argv[4];
+
+	JobMakeArgv(job, argv);
+
+	DEBUGF(JOB, ("Restarting %s...", job->node->name));
+#ifdef REMOTE
+	if ((job->node->type&OP_NOEXPORT) ||
+ 	    (nLocal < maxLocal && runLocalFirst)
+# ifdef RMT_NO_EXEC
+	    || !Rmt_Export(shellPath, argv, job)
+# else
+	    || !Rmt_Begin(shellPath, argv, job->node)
+# endif
+#endif
+	{
+	    if (((nLocal >= maxLocal) && !(job->flags & JOB_SPECIAL))) {
+		/*
+		 * Can't be exported and not allowed to run locally -- put it
+		 * back on the hold queue and mark the table full
+		 */
+		DEBUGF(JOB, ("holding\n"));
+		(void)Lst_AtFront(stoppedJobs, (void *)job);
+		jobFull = TRUE;
+		DEBUGF(JOB, ("Job queue is full.\n"));
+		return;
+	    } else {
+		/*
+		 * Job may be run locally.
+		 */
+		DEBUGF(JOB, ("running locally\n"));
+		job->flags &= ~JOB_REMOTE;
+	    }
+	}
+#ifdef REMOTE
+	else {
+	    /*
+	     * Can be exported. Hooray!
+	     */
+	    DEBUGF(JOB, ("exporting\n"));
+	    job->flags |= JOB_REMOTE;
+	}
+#endif
+	JobExec(job, argv);
+    } else {
+	/*
+	 * The job has stopped and needs to be restarted. Why it stopped,
+	 * we don't know...
+	 */
+	DEBUGF(JOB, ("Resuming %s...", job->node->name));
+	if (((job->flags & JOB_REMOTE) ||
+	    (nLocal < maxLocal) ||
+#ifdef REMOTE
+	    (((job->flags & JOB_SPECIAL) &&
+	      (job->node->type & OP_NOEXPORT)) &&
+	     (maxLocal == 0))) &&
+#else
+	    ((job->flags & JOB_SPECIAL) &&
+	     (maxLocal == 0))) &&
+#endif
+	   (nJobs != maxJobs))
+	{
+	    /*
+	     * If the job is remote, it's ok to resume it as long as the
+	     * maximum concurrency won't be exceeded. If it's local and
+	     * we haven't reached the local concurrency limit already (or the
+	     * job must be run locally and maxLocal is 0), it's also ok to
+	     * resume it.
+	     */
+	    Boolean error;
+	    int status;
+
+#ifdef RMT_WANTS_SIGNALS
+	    if (job->flags & JOB_REMOTE) {
+		error = !Rmt_Signal(job, SIGCONT);
+	    } else
+#endif	/* RMT_WANTS_SIGNALS */
+		error = (KILL(job->pid, SIGCONT) != 0);
+
+	    if (!error) {
+		/*
+		 * Make sure the user knows we've continued the beast and
+		 * actually put the thing in the job table.
+		 */
+		job->flags |= JOB_CONTINUING;
+		W_SETTERMSIG(&status, SIGCONT);
+		JobFinish(job, &status);
+
+		job->flags &= ~(JOB_RESUME|JOB_CONTINUING);
+		DEBUGF(JOB, ("done\n"));
+	    } else {
+		Error("couldn't resume %s: %s",
+		    job->node->name, strerror(errno));
+		status = 0;
+		W_SETEXITSTATUS(&status, 1);
+		JobFinish(job, &status);
+	    }
+	} else {
+	    /*
+	     * Job cannot be restarted. Mark the table as full and
+	     * place the job back on the list of stopped jobs.
+	     */
+	    DEBUGF(JOB, ("table full\n"));
+	    (void) Lst_AtFront(stoppedJobs, (void *)job);
+	    jobFull = TRUE;
+	    DEBUGF(JOB, ("Job queue is full.\n"));
+	}
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobStart  --
+ *	Start a target-creation process going for the target described
+ *	by the graph node gn.
+ *
+ * Results:
+ *	JOB_ERROR if there was an error in the commands, JOB_FINISHED
+ *	if there isn't actually anything left to do for the job and
+ *	JOB_RUNNING if the job has been started.
+ *
+ * Side Effects:
+ *	A new Job node is created and added to the list of running
+ *	jobs. PMake is forked and a child shell created.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobStart(GNode *gn, int flags, Job *previous)
+{
+    Job	  	  *job;       /* new job descriptor */
+    char	  *argv[4];   /* Argument vector to shell */
+    Boolean	  cmdsOK;     /* true if the nodes commands were all right */
+    Boolean 	  local;      /* Set true if the job was run locally */
+    Boolean 	  noExec;     /* Set true if we decide not to run the job */
+    int		  tfd;	      /* File descriptor for temp file */
+
+    if (previous != NULL) {
+	previous->flags &= ~(JOB_FIRST|JOB_IGNERR|JOB_SILENT|JOB_REMOTE);
+	job = previous;
+    } else {
+	job = (Job *) emalloc(sizeof(Job));
+	flags |= JOB_FIRST;
+    }
+
+    job->node = gn;
+    job->tailCmds = NULL;
+
+    /*
+     * Set the initial value of the flags for this job based on the global
+     * ones and the node's attributes... Any flags supplied by the caller
+     * are also added to the field.
+     */
+    job->flags = 0;
+    if (Targ_Ignore(gn)) {
+	job->flags |= JOB_IGNERR;
+    }
+    if (Targ_Silent(gn)) {
+	job->flags |= JOB_SILENT;
+    }
+    job->flags |= flags;
+
+    /*
+     * Check the commands now so any attributes from .DEFAULT have a chance
+     * to migrate to the node
+     */
+    if (!compatMake && job->flags & JOB_FIRST) {
+	cmdsOK = Job_CheckCommands(gn, Error);
+    } else {
+	cmdsOK = TRUE;
+    }
+
+    /*
+     * If the -n flag wasn't given, we open up OUR (not the child's)
+     * temporary file to stuff commands in it. The thing is rd/wr so we don't
+     * need to reopen it to feed it to the shell. If the -n flag *was* given,
+     * we just set the file to be stdout. Cute, huh?
+     */
+    if ((gn->type & OP_MAKE) || (!noExecute && !touchFlag)) {
+	/*
+	 * We're serious here, but if the commands were bogus, we're
+	 * also dead...
+	 */
+	if (!cmdsOK) {
+	    DieHorribly();
+	}
+
+	(void) strcpy(tfile, TMPPAT);
+	if ((tfd = mkstemp(tfile)) == -1)
+	    Punt("Cannot create temp file: %s", strerror(errno));
+	job->cmdFILE = fdopen(tfd, "w+");
+	eunlink(tfile);
+	if (job->cmdFILE == NULL) {
+	    close(tfd);
+	    Punt("Could not open %s", tfile);
+	}
+	(void) fcntl(FILENO(job->cmdFILE), F_SETFD, 1);
+	/*
+	 * Send the commands to the command file, flush all its buffers then
+	 * rewind and remove the thing.
+	 */
+	noExec = FALSE;
+
+	/*
+	 * used to be backwards; replace when start doing multiple commands
+	 * per shell.
+	 */
+	if (compatMake) {
+	    /*
+	     * Be compatible: If this is the first time for this node,
+	     * verify its commands are ok and open the commands list for
+	     * sequential access by later invocations of JobStart.
+	     * Once that is done, we take the next command off the list
+	     * and print it to the command file. If the command was an
+	     * ellipsis, note that there's nothing more to execute.
+	     */
+	    if ((job->flags&JOB_FIRST) && (Lst_Open(gn->commands) != SUCCESS)){
+		cmdsOK = FALSE;
+	    } else {
+		LstNode	ln = Lst_Next(gn->commands);
+
+		if ((ln == NULL) ||
+		    JobPrintCommand((void *) Lst_Datum(ln),
+				    (void *) job))
+		{
+		    noExec = TRUE;
+		    Lst_Close(gn->commands);
+		}
+		if (noExec && !(job->flags & JOB_FIRST)) {
+		    /*
+		     * If we're not going to execute anything, the job
+		     * is done and we need to close down the various
+		     * file descriptors we've opened for output, then
+		     * call JobDoOutput to catch the final characters or
+		     * send the file to the screen... Note that the i/o streams
+		     * are only open if this isn't the first job.
+		     * Note also that this could not be done in
+		     * Job_CatchChildren b/c it wasn't clear if there were
+		     * more commands to execute or not...
+		     */
+		    JobClose(job);
+		}
+	    }
+	} else {
+	    /*
+	     * We can do all the commands at once. hooray for sanity
+	     */
+	    numCommands = 0;
+	    Lst_ForEach(gn->commands, JobPrintCommand, (void *)job);
+
+	    /*
+	     * If we didn't print out any commands to the shell script,
+	     * there's not much point in executing the shell, is there?
+	     */
+	    if (numCommands == 0) {
+		noExec = TRUE;
+	    }
+	}
+    } else if (noExecute) {
+	/*
+	 * Not executing anything -- just print all the commands to stdout
+	 * in one fell swoop. This will still set up job->tailCmds correctly.
+	 */
+	if (lastNode != gn) {
+	    MESSAGE(stdout, gn);
+	    lastNode = gn;
+	}
+	job->cmdFILE = stdout;
+	/*
+	 * Only print the commands if they're ok, but don't die if they're
+	 * not -- just let the user know they're bad and keep going. It
+	 * doesn't do any harm in this case and may do some good.
+	 */
+	if (cmdsOK) {
+	    Lst_ForEach(gn->commands, JobPrintCommand, (void *)job);
+	}
+	/*
+	 * Don't execute the shell, thank you.
+	 */
+	noExec = TRUE;
+    } else {
+	/*
+	 * Just touch the target and note that no shell should be executed.
+	 * Set cmdFILE to stdout to make life easier. Check the commands, too,
+	 * but don't die if they're no good -- it does no harm to keep working
+	 * up the graph.
+	 */
+	job->cmdFILE = stdout;
+    	Job_Touch(gn, job->flags&JOB_SILENT);
+	noExec = TRUE;
+    }
+
+    /*
+     * If we're not supposed to execute a shell, don't.
+     */
+    if (noExec) {
+	/*
+	 * Unlink and close the command file if we opened one
+	 */
+	if (job->cmdFILE != stdout) {
+	    if (job->cmdFILE != NULL)
+		(void) fclose(job->cmdFILE);
+	} else {
+	     (void) fflush(stdout);
+	}
+
+	/*
+	 * We only want to work our way up the graph if we aren't here because
+	 * the commands for the job were no good.
+	 */
+	if (cmdsOK) {
+	    if (aborting == 0) {
+		if (job->tailCmds != NULL) {
+		    Lst_ForEachFrom(job->node->commands, job->tailCmds,
+				    JobSaveCommand,
+				   (void *)job->node);
+		}
+		job->node->made = MADE;
+		Make_Update(job->node);
+	    }
+	    free(job);
+	    return(JOB_FINISHED);
+	} else {
+	    free(job);
+	    return(JOB_ERROR);
+	}
+    } else {
+	(void) fflush(job->cmdFILE);
+    }
+
+    /*
+     * Set up the control arguments to the shell. This is based on the flags
+     * set earlier for this job.
+     */
+    JobMakeArgv(job, argv);
+
+    /*
+     * If we're using pipes to catch output, create the pipe by which we'll
+     * get the shell's output. If we're using files, print out that we're
+     * starting a job and then set up its temporary-file name.
+     */
+    if (!compatMake || (job->flags & JOB_FIRST)) {
+	if (usePipes) {
+	    int fd[2];
+	    if (pipe(fd) == -1)
+		Punt("Cannot create pipe: %s", strerror(errno));
+	    job->inPipe = fd[0];
+	    job->outPipe = fd[1];
+	    (void) fcntl(job->inPipe, F_SETFD, 1);
+	    (void) fcntl(job->outPipe, F_SETFD, 1);
+	} else {
+	    (void) fprintf(stdout, "Remaking `%s'\n", gn->name);
+  	    (void) fflush(stdout);
+	    (void) strcpy(job->outFile, TMPPAT);
+	    if ((job->outFd = mkstemp(job->outFile)) == -1)
+		Punt("cannot create temp file: %s", strerror(errno));
+	    (void) fcntl(job->outFd, F_SETFD, 1);
+	}
+    }
+
+#ifdef REMOTE
+    if (!(gn->type & OP_NOEXPORT) && !(runLocalFirst && nLocal < maxLocal)) {
+#ifdef RMT_NO_EXEC
+	local = !Rmt_Export(shellPath, argv, job);
+#else
+	local = !Rmt_Begin(shellPath, argv, job->node);
+#endif /* RMT_NO_EXEC */
+	if (!local) {
+	    job->flags |= JOB_REMOTE;
+	}
+    } else
+#endif
+	local = TRUE;
+
+    if (local && (((nLocal >= maxLocal) &&
+	!(job->flags & JOB_SPECIAL) &&
+#ifdef REMOTE
+	(!(gn->type & OP_NOEXPORT) || (maxLocal != 0))
+#else
+	(maxLocal != 0)
+#endif
+	)))
+    {
+	/*
+	 * The job can only be run locally, but we've hit the limit of
+	 * local concurrency, so put the job on hold until some other job
+	 * finishes. Note that the special jobs (.BEGIN, .INTERRUPT and .END)
+	 * may be run locally even when the local limit has been reached
+	 * (e.g. when maxLocal == 0), though they will be exported if at
+	 * all possible. In addition, any target marked with .NOEXPORT will
+	 * be run locally if maxLocal is 0.
+	 */
+	jobFull = TRUE;
+
+	DEBUGF(JOB, ("Can only run job locally.\n"));
+	job->flags |= JOB_RESTART;
+	(void) Lst_AtEnd(stoppedJobs, (void *)job);
+    } else {
+	if ((nLocal >= maxLocal) && local) {
+	    /*
+	     * If we're running this job locally as a special case (see above),
+	     * at least say the table is full.
+	     */
+	    jobFull = TRUE;
+	    DEBUGF(JOB, ("Local job queue is full.\n"));
+	}
+	JobExec(job, argv);
+    }
+    return(JOB_RUNNING);
+}
+
+static char *
+JobOutput(Job *job, char *cp, char *endp, int msg)
+{
+    char *ecp;
+
+    if (commandShell->noPrint) {
+	ecp = Str_FindSubstring(cp, commandShell->noPrint);
+	while (ecp != NULL) {
+	    if (cp != ecp) {
+		*ecp = '\0';
+		if (msg && job->node != lastNode) {
+		    MESSAGE(stdout, job->node);
+		    lastNode = job->node;
+		}
+		/*
+		 * The only way there wouldn't be a newline after
+		 * this line is if it were the last in the buffer.
+		 * however, since the non-printable comes after it,
+		 * there must be a newline, so we don't print one.
+		 */
+		(void) fprintf(stdout, "%s", cp);
+		(void) fflush(stdout);
+	    }
+	    cp = ecp + commandShell->noPLen;
+	    if (cp != endp) {
+		/*
+		 * Still more to print, look again after skipping
+		 * the whitespace following the non-printable
+		 * command....
+		 */
+		cp++;
+		while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
+		    cp++;
+		}
+		ecp = Str_FindSubstring(cp, commandShell->noPrint);
+	    } else {
+		return cp;
+	    }
+	}
+    }
+    return cp;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobDoOutput  --
+ *	This function is called at different times depending on
+ *	whether the user has specified that output is to be collected
+ *	via pipes or temporary files. In the former case, we are called
+ *	whenever there is something to read on the pipe. We collect more
+ *	output from the given job and store it in the job's outBuf. If
+ *	this makes up a line, we print it tagged by the job's identifier,
+ *	as necessary.
+ *	If output has been collected in a temporary file, we open the
+ *	file and read it line by line, transfering it to our own
+ *	output channel until the file is empty. At which point we
+ *	remove the temporary file.
+ *	In both cases, however, we keep our figurative eye out for the
+ *	'noPrint' line for the shell from which the output came. If
+ *	we recognize a line, we don't print it. If the command is not
+ *	alone on the line (the character after it is not \0 or \n), we
+ *	do print whatever follows it.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	curPos may be shifted as may the contents of outBuf.
+ *-----------------------------------------------------------------------
+ */
+STATIC void
+JobDoOutput(Job *job, Boolean finish)
+{
+    Boolean       gotNL = FALSE;  /* true if got a newline */
+    Boolean       fbuf;  	  /* true if our buffer filled up */
+    int		  nr;	      	  /* number of bytes read */
+    int		  i;	      	  /* auxiliary index into outBuf */
+    int		  max;	      	  /* limit for i (end of current data) */
+    int		  nRead;      	  /* (Temporary) number of bytes read */
+
+    FILE      	  *oFILE;	  /* Stream pointer to shell's output file */
+    char          inLine[132];
+
+
+    if (usePipes) {
+	/*
+	 * Read as many bytes as will fit in the buffer.
+	 */
+end_loop:
+	gotNL = FALSE;
+	fbuf = FALSE;
+
+	nRead = read(job->inPipe, &job->outBuf[job->curPos],
+			 JOB_BUFSIZE - job->curPos);
+	if (nRead < 0) {
+	    DEBUGF(JOB, ("JobDoOutput(piperead)"));
+	    nr = 0;
+	} else {
+	    nr = nRead;
+	}
+
+	/*
+	 * If we hit the end-of-file (the job is dead), we must flush its
+	 * remaining output, so pretend we read a newline if there's any
+	 * output remaining in the buffer.
+	 * Also clear the 'finish' flag so we stop looping.
+	 */
+	if ((nr == 0) && (job->curPos != 0)) {
+	    job->outBuf[job->curPos] = '\n';
+	    nr = 1;
+	    finish = FALSE;
+	} else if (nr == 0) {
+	    finish = FALSE;
+	}
+
+	/*
+	 * Look for the last newline in the bytes we just got. If there is
+	 * one, break out of the loop with 'i' as its index and gotNL set
+	 * TRUE.
+	 */
+	max = job->curPos + nr;
+	for (i = job->curPos + nr - 1; i >= job->curPos; i--) {
+	    if (job->outBuf[i] == '\n') {
+		gotNL = TRUE;
+		break;
+	    } else if (job->outBuf[i] == '\0') {
+		/*
+		 * Why?
+		 */
+		job->outBuf[i] = ' ';
+	    }
+	}
+
+	if (!gotNL) {
+	    job->curPos += nr;
+	    if (job->curPos == JOB_BUFSIZE) {
+		/*
+		 * If we've run out of buffer space, we have no choice
+		 * but to print the stuff. sigh.
+		 */
+		fbuf = TRUE;
+		i = job->curPos;
+	    }
+	}
+	if (gotNL || fbuf) {
+	    /*
+	     * Need to send the output to the screen. Null terminate it
+	     * first, overwriting the newline character if there was one.
+	     * So long as the line isn't one we should filter (according
+	     * to the shell description), we print the line, preceded
+	     * by a target banner if this target isn't the same as the
+	     * one for which we last printed something.
+	     * The rest of the data in the buffer are then shifted down
+	     * to the start of the buffer and curPos is set accordingly.
+	     */
+	    job->outBuf[i] = '\0';
+	    if (i >= job->curPos) {
+		char *cp;
+
+		cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE);
+
+		/*
+		 * There's still more in that thar buffer. This time, though,
+		 * we know there's no newline at the end, so we add one of
+		 * our own free will.
+		 */
+		if (*cp != '\0') {
+		    if (job->node != lastNode) {
+			MESSAGE(stdout, job->node);
+			lastNode = job->node;
+		    }
+		    (void) fprintf(stdout, "%s%s", cp, gotNL ? "\n" : "");
+		    (void) fflush(stdout);
+		}
+	    }
+	    if (i < max - 1) {
+		/* shift the remaining characters down */
+		(void) memcpy(job->outBuf, &job->outBuf[i + 1], max - (i + 1));
+		job->curPos = max - (i + 1);
+
+	    } else {
+		/*
+		 * We have written everything out, so we just start over
+		 * from the start of the buffer. No copying. No nothing.
+		 */
+		job->curPos = 0;
+	    }
+	}
+	if (finish) {
+	    /*
+	     * If the finish flag is true, we must loop until we hit
+	     * end-of-file on the pipe. This is guaranteed to happen
+	     * eventually since the other end of the pipe is now closed
+	     * (we closed it explicitly and the child has exited). When
+	     * we do get an EOF, finish will be set FALSE and we'll fall
+	     * through and out.
+	     */
+	    goto end_loop;
+	}
+    } else {
+	/*
+	 * We've been called to retrieve the output of the job from the
+	 * temporary file where it's been squirreled away. This consists of
+	 * opening the file, reading the output line by line, being sure not
+	 * to print the noPrint line for the shell we used, then close and
+	 * remove the temporary file. Very simple.
+	 *
+	 * Change to read in blocks and do FindSubString type things as for
+	 * pipes? That would allow for "@echo -n..."
+	 */
+	oFILE = fopen(job->outFile, "r");
+	if (oFILE != NULL) {
+	    (void) fprintf(stdout, "Results of making %s:\n", job->node->name);
+	    (void) fflush(stdout);
+	    while (fgets(inLine, sizeof(inLine), oFILE) != NULL) {
+		char	*cp, *endp, *oendp;
+
+		cp = inLine;
+		oendp = endp = inLine + strlen(inLine);
+		if (endp[-1] == '\n') {
+		    *--endp = '\0';
+		}
+		cp = JobOutput(job, inLine, endp, FALSE);
+
+		/*
+		 * There's still more in that thar buffer. This time, though,
+		 * we know there's no newline at the end, so we add one of
+		 * our own free will.
+		 */
+		(void) fprintf(stdout, "%s", cp);
+		(void) fflush(stdout);
+		if (endp != oendp) {
+		    (void) fprintf(stdout, "\n");
+		    (void) fflush(stdout);
+		}
+	    }
+	    (void) fclose(oFILE);
+	    (void) eunlink(job->outFile);
+	}
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchChildren --
+ *	Handle the exit of a child. Called from Make_Make.
+ *
+ * Results:
+ *	none.
+ *
+ * Side Effects:
+ *	The job descriptor is removed from the list of children.
+ *
+ * Notes:
+ *	We do waits, blocking or not, according to the wisdom of our
+ *	caller, until there are no more children to report. For each
+ *	job, call JobFinish to finish things off. This will take care of
+ *	putting jobs on the stoppedJobs queue.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_CatchChildren(Boolean block)
+{
+    int    	  pid;	    	/* pid of dead child */
+    Job		  *job;	    	/* job descriptor for dead child */
+    LstNode       jnode;    	/* list element for finding job */
+    int	  	  status;   	/* Exit/termination status */
+
+    /*
+     * Don't even bother if we know there's no one around.
+     */
+    if (nLocal == 0) {
+	return;
+    }
+
+    while ((pid = waitpid((pid_t) -1, &status,
+			  (block?0:WNOHANG)|WUNTRACED)) > 0)
+    {
+	DEBUGF(JOB, ("Process %d exited or stopped.\n", pid));
+
+	jnode = Lst_Find(jobs, (void *)&pid, JobCmpPid);
+
+	if (jnode == NULL) {
+	    if (WIFSIGNALED(status) && (WTERMSIG(status) == SIGCONT)) {
+		jnode = Lst_Find(stoppedJobs, (void *) &pid, JobCmpPid);
+		if (jnode == NULL) {
+		    Error("Resumed child (%d) not in table", pid);
+		    continue;
+		}
+		job = (Job *)Lst_Datum(jnode);
+		(void) Lst_Remove(stoppedJobs, jnode);
+	    } else {
+		Error("Child (%d) not in table?", pid);
+		continue;
+	    }
+	} else {
+	    job = (Job *) Lst_Datum(jnode);
+	    (void) Lst_Remove(jobs, jnode);
+	    nJobs -= 1;
+	    DEBUGF(JOB, ("Job queue is no longer full.\n"));
+	    jobFull = FALSE;
+#ifdef REMOTE
+	    if (!(job->flags & JOB_REMOTE)) {
+		DEBUGF(JOB, ("Job queue has one fewer local process.\n"));
+		nLocal -= 1;
+	    }
+#else
+	    nLocal -= 1;
+#endif
+	}
+
+	JobFinish(job, &status);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchOutput --
+ *	Catch the output from our children, if we're using
+ *	pipes do so. Otherwise just block time until we get a
+ *	signal (most likely a SIGCHLD) since there's no point in
+ *	just spinning when there's nothing to do and the reaping
+ *	of a child can wait for a while.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Output is read from pipes if we're piping.
+ * -----------------------------------------------------------------------
+ */
+void
+Job_CatchOutput(void)
+{
+    int           	  nfds;
+#ifdef USE_KQUEUE
+#define KEV_SIZE	4
+    struct kevent	  kev[KEV_SIZE];
+    int			  i;
+#else
+    struct timeval	  timeout;
+    fd_set           	  readfds;
+    LstNode		  ln;
+    Job		   	  *job;
+#endif
+#ifdef RMT_WILL_WATCH
+    int	    	  	  pnJobs;   	/* Previous nJobs */
+#endif
+
+    (void) fflush(stdout);
+#ifdef RMT_WILL_WATCH
+    pnJobs = nJobs;
+
+    /*
+     * It is possible for us to be called with nJobs equal to 0. This happens
+     * if all the jobs finish and a job that is stopped cannot be run
+     * locally (eg if maxLocal is 0) and cannot be exported. The job will
+     * be placed back on the stoppedJobs queue, Job_Empty() will return false,
+     * Make_Run will call us again when there's nothing for which to wait.
+     * nJobs never changes, so we loop forever. Hence the check. It could
+     * be argued that we should sleep for a bit so as not to swamp the
+     * exportation system with requests. Perhaps we should.
+     *
+     * NOTE: IT IS THE RESPONSIBILITY OF Rmt_Wait TO CALL Job_CatchChildren
+     * IN A TIMELY FASHION TO CATCH ANY LOCALLY RUNNING JOBS THAT EXIT.
+     * It may use the variable nLocal to determine if it needs to call
+     * Job_CatchChildren (if nLocal is 0, there's nothing for which to
+     * wait...)
+     */
+    while (nJobs != 0 && pnJobs == nJobs) {
+	Rmt_Wait();
+    }
+#else
+    if (usePipes) {
+#ifdef USE_KQUEUE
+	if ((nfds = kevent(kqfd, NULL, 0, kev, KEV_SIZE, NULL)) == -1) {
+	    Punt("kevent: %s", strerror(errno));
+	} else {
+	    for (i = 0; i < nfds; i++) {
+		if (kev[i].flags & EV_ERROR) {
+		    warnc(kev[i].data, "kevent");
+		    continue;
+		}
+		switch (kev[i].filter) {
+		case EVFILT_READ:
+		    JobDoOutput(kev[i].udata, FALSE);
+		    break;
+		case EVFILT_PROC:
+		    /* Just wake up and let Job_CatchChildren() collect the
+		     * terminated job. */
+		    break;
+		}
+	    }
+	}
+#else
+	readfds = outputs;
+	timeout.tv_sec = SEL_SEC;
+	timeout.tv_usec = SEL_USEC;
+
+	if ((nfds = select(FD_SETSIZE, &readfds, (fd_set *) 0,
+			   (fd_set *) 0, &timeout)) <= 0)
+	    return;
+	else {
+	    if (Lst_Open(jobs) == FAILURE) {
+		Punt("Cannot open job table");
+	    }
+	    while (nfds && (ln = Lst_Next(jobs)) != NULL) {
+		job = (Job *) Lst_Datum(ln);
+		if (FD_ISSET(job->inPipe, &readfds)) {
+		    JobDoOutput(job, FALSE);
+		    nfds -= 1;
+		}
+	    }
+	    Lst_Close(jobs);
+	}
+#endif /* !USE_KQUEUE */
+    }
+#endif /* RMT_WILL_WATCH */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Make --
+ *	Start the creation of a target. Basically a front-end for
+ *	JobStart used by the Make module.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Another job is started.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Make(GNode *gn)
+{
+    (void) JobStart(gn, 0, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Init --
+ *	Initialize the process module, given a maximum number of jobs, and
+ *	a maximum number of local jobs.
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	lists and counters are initialized
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Init(int maxproc, int maxlocal)
+{
+    GNode         *begin;     /* node for commands to do at the very start */
+
+    jobs =  	  Lst_Init(FALSE);
+    stoppedJobs = Lst_Init(FALSE);
+    maxJobs = 	  maxproc;
+    maxLocal = 	  maxlocal;
+    nJobs = 	  0;
+    nLocal = 	  0;
+    jobFull = 	  FALSE;
+
+    aborting = 	  0;
+    errors = 	  0;
+
+    lastNode =	  NULL;
+
+    if (maxJobs == 1 || beVerbose == 0
+#ifdef REMOTE
+	|| noMessages
+#endif
+		     ) {
+	/*
+	 * If only one job can run at a time, there's no need for a banner,
+	 * no is there?
+	 */
+	targFmt = "";
+    } else {
+	targFmt = TARG_FMT;
+    }
+
+    if (shellPath == NULL) {
+	/*
+	 * The user didn't specify a shell to use, so we are using the
+	 * default one... Both the absolute path and the last component
+	 * must be set. The last component is taken from the 'name' field
+	 * of the default shell description pointed-to by commandShell.
+	 * All default shells are located in _PATH_DEFSHELLDIR.
+	 */
+	shellName = commandShell->name;
+	shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH);
+    }
+
+    if (commandShell->exit == NULL) {
+	commandShell->exit = "";
+    }
+    if (commandShell->echo == NULL) {
+	commandShell->echo = "";
+    }
+
+    /*
+     * Catch the four signals that POSIX specifies if they aren't ignored.
+     * JobPassSig will take care of calling JobInterrupt if appropriate.
+     */
+    if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGINT, JobPassSig);
+    }
+    if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGHUP, JobPassSig);
+    }
+    if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGQUIT, JobPassSig);
+    }
+    if (signal(SIGTERM, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGTERM, JobPassSig);
+    }
+    /*
+     * There are additional signals that need to be caught and passed if
+     * either the export system wants to be told directly of signals or if
+     * we're giving each job its own process group (since then it won't get
+     * signals from the terminal driver as we own the terminal)
+     */
+#if defined(RMT_WANTS_SIGNALS) || defined(USE_PGRP)
+    if (signal(SIGTSTP, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGTSTP, JobPassSig);
+    }
+    if (signal(SIGTTOU, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGTTOU, JobPassSig);
+    }
+    if (signal(SIGTTIN, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGTTIN, JobPassSig);
+    }
+    if (signal(SIGWINCH, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGWINCH, JobPassSig);
+    }
+#endif
+
+#ifdef USE_KQUEUE
+    if ((kqfd = kqueue()) == -1) {
+	Punt("kqueue: %s", strerror(errno));
+    }
+#endif
+
+    begin = Targ_FindNode(".BEGIN", TARG_NOCREATE);
+
+    if (begin != NULL) {
+	JobStart(begin, JOB_SPECIAL, (Job *)0);
+	while (nJobs) {
+	    Job_CatchOutput();
+#ifndef RMT_WILL_WATCH
+	    Job_CatchChildren(!usePipes);
+#endif /* RMT_WILL_WATCH */
+	}
+    }
+    postCommands = Targ_FindNode(".END", TARG_CREATE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Full --
+ *	See if the job table is full. It is considered full if it is OR
+ *	if we are in the process of aborting OR if we have
+ *	reached/exceeded our local quota. This prevents any more jobs
+ *	from starting up.
+ *
+ * Results:
+ *	TRUE if the job table is full, FALSE otherwise
+ * Side Effects:
+ *	None.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Job_Full(void)
+{
+    return(aborting || jobFull);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Empty --
+ *	See if the job table is empty.  Because the local concurrency may
+ *	be set to 0, it is possible for the job table to become empty,
+ *	while the list of stoppedJobs remains non-empty. In such a case,
+ *	we want to restart as many jobs as we can.
+ *
+ * Results:
+ *	TRUE if it is. FALSE if it ain't.
+ *
+ * Side Effects:
+ *	None.
+ *
+ * -----------------------------------------------------------------------
+ */
+Boolean
+Job_Empty(void)
+{
+    if (nJobs == 0) {
+	if (!Lst_IsEmpty(stoppedJobs) && !aborting) {
+	    /*
+	     * The job table is obviously not full if it has no jobs in
+	     * it...Try and restart the stopped jobs.
+	     */
+	    jobFull = FALSE;
+	    JobRestartJobs();
+	    return(FALSE);
+	} else {
+	    return(TRUE);
+	}
+    } else {
+	return(FALSE);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMatchShell --
+ *	Find a matching shell in 'shells' given its final component.
+ *
+ * Results:
+ *	A pointer to the Shell structure.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Shell *
+JobMatchShell(char *name)
+{
+    Shell	  *sh;	      /* Pointer into shells table */
+    Shell	  *match;     /* Longest-matching shell */
+    char	  *cp1,
+		  *cp2;
+    char	  *eoname;
+
+    eoname = name + strlen(name);
+
+    match = NULL;
+
+    for (sh = shells; sh->name != NULL; sh++) {
+	for (cp1 = eoname - strlen(sh->name), cp2 = sh->name;
+	     *cp1 != '\0' && *cp1 == *cp2;
+	     cp1++, cp2++) {
+		 continue;
+	}
+	if (*cp1 != *cp2) {
+	    continue;
+	} else if (match == NULL || strlen(match->name) < strlen(sh->name)) {
+	   match = sh;
+	}
+    }
+    return(match == NULL ? sh : match);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_ParseShell --
+ *	Parse a shell specification and set up commandShell, shellPath
+ *	and shellName appropriately.
+ *
+ * Results:
+ *	FAILURE if the specification was incorrect.
+ *
+ * Side Effects:
+ *	commandShell points to a Shell structure (either predefined or
+ *	created from the shell spec), shellPath is the full path of the
+ *	shell described by commandShell, while shellName is just the
+ *	final component of shellPath.
+ *
+ * Notes:
+ *	A shell specification consists of a .SHELL target, with dependency
+ *	operator, followed by a series of blank-separated words. Double
+ *	quotes can be used to use blanks in words. A backslash escapes
+ *	anything (most notably a double-quote and a space) and
+ *	provides the functionality it does in C. Each word consists of
+ *	keyword and value separated by an equal sign. There should be no
+ *	unnecessary spaces in the word. The keywords are as follows:
+ *	    name  	    Name of shell.
+ *	    path  	    Location of shell. Overrides "name" if given
+ *	    quiet 	    Command to turn off echoing.
+ *	    echo  	    Command to turn echoing on
+ *	    filter	    Result of turning off echoing that shouldn't be
+ *	    	  	    printed.
+ *	    echoFlag	    Flag to turn echoing on at the start
+ *	    errFlag	    Flag to turn error checking on at the start
+ *	    hasErrCtl	    True if shell has error checking control
+ *	    check 	    Command to turn on error checking if hasErrCtl
+ *	    	  	    is TRUE or template of command to echo a command
+ *	    	  	    for which error checking is off if hasErrCtl is
+ *	    	  	    FALSE.
+ *	    ignore	    Command to turn off error checking if hasErrCtl
+ *	    	  	    is TRUE or template of command to execute a
+ *	    	  	    command so as to ignore any errors it returns if
+ *	    	  	    hasErrCtl is FALSE.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Job_ParseShell(char *line)
+{
+    char    	  **words;
+    int	    	  wordCount;
+    char	  **argv;
+    int		  argc;
+    char    	  *path;
+    Shell   	  newShell;
+    Boolean 	  fullSpec = FALSE;
+
+    while (isspace((unsigned char) *line)) {
+	line++;
+    }
+    words = brk_string(line, &wordCount, TRUE);
+
+    memset(&newShell, 0, sizeof(newShell));
+
+    /*
+     * Parse the specification by keyword
+     */
+    for (path = NULL, argc = wordCount - 1, argv = words + 1;
+	 argc != 0;
+	 argc--, argv++) {
+	     if (strncmp(*argv, "path=", 5) == 0) {
+		 path = &argv[0][5];
+	     } else if (strncmp(*argv, "name=", 5) == 0) {
+		 newShell.name = &argv[0][5];
+	     } else {
+		 if (strncmp(*argv, "quiet=", 6) == 0) {
+		     newShell.echoOff = &argv[0][6];
+		 } else if (strncmp(*argv, "echo=", 5) == 0) {
+		     newShell.echoOn = &argv[0][5];
+		 } else if (strncmp(*argv, "filter=", 7) == 0) {
+		     newShell.noPrint = &argv[0][7];
+		     newShell.noPLen = strlen(newShell.noPrint);
+		 } else if (strncmp(*argv, "echoFlag=", 9) == 0) {
+		     newShell.echo = &argv[0][9];
+		 } else if (strncmp(*argv, "errFlag=", 8) == 0) {
+		     newShell.exit = &argv[0][8];
+		 } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) {
+		     char c = argv[0][10];
+		     newShell.hasErrCtl = !((c != 'Y') && (c != 'y') &&
+					   (c != 'T') && (c != 't'));
+		 } else if (strncmp(*argv, "check=", 6) == 0) {
+		     newShell.errCheck = &argv[0][6];
+		 } else if (strncmp(*argv, "ignore=", 7) == 0) {
+		     newShell.ignErr = &argv[0][7];
+		 } else {
+		     Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"",
+				  *argv);
+		     return(FAILURE);
+		 }
+		 fullSpec = TRUE;
+	     }
+    }
+
+    if (path == NULL) {
+	/*
+	 * If no path was given, the user wants one of the pre-defined shells,
+	 * yes? So we find the one s/he wants with the help of JobMatchShell
+	 * and set things up the right way. shellPath will be set up by
+	 * Job_Init.
+	 */
+	if (newShell.name == NULL) {
+	    Parse_Error(PARSE_FATAL, "Neither path nor name specified");
+	    return(FAILURE);
+	} else {
+	    commandShell = JobMatchShell(newShell.name);
+	    shellName = newShell.name;
+	}
+    } else {
+	/*
+	 * The user provided a path. If s/he gave nothing else (fullSpec is
+	 * FALSE), try and find a matching shell in the ones we know of.
+	 * Else we just take the specification at its word and copy it
+	 * to a new location. In either case, we need to record the
+	 * path the user gave for the shell.
+	 */
+	shellPath = path;
+	path = strrchr(path, '/');
+	if (path == NULL) {
+	    path = shellPath;
+	} else {
+	    path += 1;
+	}
+	if (newShell.name != NULL) {
+	    shellName = newShell.name;
+	} else {
+	    shellName = path;
+	}
+	if (!fullSpec) {
+	    commandShell = JobMatchShell(shellName);
+	} else {
+	    commandShell = (Shell *) emalloc(sizeof(Shell));
+	    *commandShell = newShell;
+	}
+    }
+
+    if (commandShell->echoOn && commandShell->echoOff) {
+	commandShell->hasEchoCtl = TRUE;
+    }
+
+    if (!commandShell->hasErrCtl) {
+	if (commandShell->errCheck == NULL) {
+	    commandShell->errCheck = "";
+	}
+	if (commandShell->ignErr == NULL) {
+	    commandShell->ignErr = "%s\n";
+	}
+    }
+
+    return SUCCESS;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobInterrupt --
+ *	Handle the receipt of an interrupt.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	All children are killed. Another job will be started if the
+ *	.INTERRUPT target was given.
+ *-----------------------------------------------------------------------
+ */
+static void
+JobInterrupt(int runINTERRUPT, int signo)
+{
+    LstNode 	  ln;		/* element in job table */
+    Job           *job = NULL;	/* job descriptor in that element */
+    GNode         *interrupt;	/* the node describing the .INTERRUPT target */
+
+    aborting = ABORT_INTERRUPT;
+
+    (void) Lst_Open(jobs);
+    while ((ln = Lst_Next(jobs)) != NULL) {
+	job = (Job *) Lst_Datum(ln);
+
+	if (!Targ_Precious(job->node)) {
+	    char  	*file = (job->node->path == NULL ?
+				 job->node->name :
+				 job->node->path);
+	    if (!noExecute && eunlink(file) != -1) {
+		Error("*** %s removed", file);
+	    }
+	}
+#ifdef RMT_WANTS_SIGNALS
+	if (job->flags & JOB_REMOTE) {
+	    /*
+	     * If job is remote, let the Rmt module do the killing.
+	     */
+	    if (!Rmt_Signal(job, signo)) {
+		/*
+		 * If couldn't kill the thing, finish it out now with an
+		 * error code, since no exit report will come in likely.
+		 */
+		int status;
+
+		status.w_status = 0;
+		status.w_retcode = 1;
+		JobFinish(job, &status);
+	    }
+	} else if (job->pid) {
+	    KILL(job->pid, signo);
+	}
+#else
+	if (job->pid) {
+	    DEBUGF(JOB, ("JobInterrupt passing signal to child %d.\n",
+		   job->pid));
+	    KILL(job->pid, signo);
+	}
+#endif /* RMT_WANTS_SIGNALS */
+    }
+
+#ifdef REMOTE
+    (void)Lst_Open(stoppedJobs);
+    while ((ln = Lst_Next(stoppedJobs)) != NULL) {
+	job = (Job *) Lst_Datum(ln);
+
+	if (job->flags & JOB_RESTART) {
+	    DEBUGF(JOB, "JobInterrupt skipping job on stopped queue"
+		   "-- it was waiting to be restarted.\n");
+	    continue;
+	}
+	if (!Targ_Precious(job->node)) {
+	    char  	*file = (job->node->path == NULL ?
+				 job->node->name :
+				 job->node->path);
+	    if (eunlink(file) == 0) {
+		Error("*** %s removed", file);
+	    }
+	}
+	/*
+	 * Resume the thing so it will take the signal.
+	 */
+	DEBUGF(JOB, ("JobInterrupt passing CONT to stopped child %d.\n", job->pid));
+	KILL(job->pid, SIGCONT);
+#ifdef RMT_WANTS_SIGNALS
+	if (job->flags & JOB_REMOTE) {
+	    /*
+	     * If job is remote, let the Rmt module do the killing.
+	     */
+	    if (!Rmt_Signal(job, SIGINT)) {
+		/*
+		 * If couldn't kill the thing, finish it out now with an
+		 * error code, since no exit report will come in likely.
+		 */
+		int status;
+		status.w_status = 0;
+		status.w_retcode = 1;
+		JobFinish(job, &status);
+	    }
+	} else if (job->pid) {
+	    DEBUGF(JOB, "JobInterrupt passing interrupt to stopped child %d.\n",
+		   job->pid);
+	    KILL(job->pid, SIGINT);
+	}
+#endif /* RMT_WANTS_SIGNALS */
+    }
+#endif
+    Lst_Close(stoppedJobs);
+
+    if (runINTERRUPT && !touchFlag) {
+	interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+	if (interrupt != NULL) {
+	    ignoreErrors = FALSE;
+
+	    JobStart(interrupt, JOB_IGNDOTS, (Job *)0);
+	    while (nJobs) {
+		Job_CatchOutput();
+#ifndef RMT_WILL_WATCH
+		Job_CatchChildren(!usePipes);
+#endif /* RMT_WILL_WATCH */
+	    }
+	}
+    }
+}
+
+/*
+ *-----------------------------------------------------------------------
+ * Job_Finish --
+ *	Do final processing such as the running of the commands
+ *	attached to the .END target.
+ *
+ * Results:
+ *	Number of errors reported.
+ *-----------------------------------------------------------------------
+ */
+int
+Job_Finish(void)
+{
+    if (postCommands != NULL && !Lst_IsEmpty(postCommands->commands)) {
+	if (errors) {
+	    Error("Errors reported so .END ignored");
+	} else {
+	    JobStart(postCommands, JOB_SPECIAL | JOB_IGNDOTS, NULL);
+
+	    while (nJobs) {
+		Job_CatchOutput();
+#ifndef RMT_WILL_WATCH
+		Job_CatchChildren(!usePipes);
+#endif /* RMT_WILL_WATCH */
+	    }
+	}
+    }
+    return(errors);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Wait --
+ *	Waits for all running jobs to finish and returns. Sets 'aborting'
+ *	to ABORT_WAIT to prevent other jobs from starting.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Currently running jobs finish.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Wait(void)
+{
+    aborting = ABORT_WAIT;
+    while (nJobs != 0) {
+	Job_CatchOutput();
+#ifndef RMT_WILL_WATCH
+	Job_CatchChildren(!usePipes);
+#endif /* RMT_WILL_WATCH */
+    }
+    aborting = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_AbortAll --
+ *	Abort all currently running jobs without handling output or anything.
+ *	This function is to be called only in the event of a major
+ *	error. Most definitely NOT to be called from JobInterrupt.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	All children are killed, not just the firstborn
+ *-----------------------------------------------------------------------
+ */
+void
+Job_AbortAll(void)
+{
+    LstNode           	ln;	/* element in job table */
+    Job            	*job;	/* the job descriptor in that element */
+    int     	  	foo;
+
+    aborting = ABORT_ERROR;
+
+    if (nJobs) {
+
+	(void) Lst_Open(jobs);
+	while ((ln = Lst_Next(jobs)) != NULL) {
+	    job = (Job *) Lst_Datum(ln);
+
+	    /*
+	     * kill the child process with increasingly drastic signals to make
+	     * darn sure it's dead.
+	     */
+#ifdef RMT_WANTS_SIGNALS
+	    if (job->flags & JOB_REMOTE) {
+		Rmt_Signal(job, SIGINT);
+		Rmt_Signal(job, SIGKILL);
+	    } else {
+		KILL(job->pid, SIGINT);
+		KILL(job->pid, SIGKILL);
+	    }
+#else
+	    KILL(job->pid, SIGINT);
+	    KILL(job->pid, SIGKILL);
+#endif /* RMT_WANTS_SIGNALS */
+	}
+    }
+
+    /*
+     * Catch as many children as want to report in at first, then give up
+     */
+    while (waitpid((pid_t) -1, &foo, WNOHANG) > 0)
+	continue;
+}
+
+#ifdef REMOTE
+/*-
+ *-----------------------------------------------------------------------
+ * JobFlagForMigration --
+ *	Handle the eviction of a child. Called from RmtStatusChange.
+ *	Flags the child as remigratable and then suspends it.  Takes
+ *	the ID of the host we used, for matching children.
+ *
+ * Results:
+ *	none.
+ *
+ * Side Effects:
+ *	The job descriptor is flagged for remigration.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+JobFlagForMigration(int hostID)
+{
+    Job		  *job;	    	/* job descriptor for dead child */
+    LstNode       jnode;    	/* list element for finding job */
+
+    DEBUGF(JOB, ("JobFlagForMigration(%d) called.\n", hostID));
+    jnode = Lst_Find(jobs, (void *)hostID, JobCmpRmtID);
+
+    if (jnode == NULL) {
+	jnode = Lst_Find(stoppedJobs, (void *)hostID, JobCmpRmtID);
+		if (jnode == NULL) {
+		    if (DEBUG(JOB)) {
+			Error("Evicting host(%d) not in table", hostID);
+		    }
+		    return;
+		}
+    }
+    job = (Job *) Lst_Datum(jnode);
+
+    DEBUGF(JOB, ("JobFlagForMigration(%d) found job '%s'.\n", hostID, job->node->name));
+
+    KILL(job->pid, SIGSTOP);
+
+    job->flags |= JOB_REMIGRATE;
+}
+
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobRestartJobs --
+ *	Tries to restart stopped jobs if there are slots available.
+ *	Note that this tries to restart them regardless of pending errors.
+ *	It's not good to leave stopped jobs lying around!
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Resumes(and possibly migrates) jobs.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobRestartJobs(void)
+{
+    while (!jobFull && !Lst_IsEmpty(stoppedJobs)) {
+	DEBUGF(JOB, ("Job queue is not full. Restarting a stopped job.\n"));
+	JobRestart((Job *)Lst_DeQueue(stoppedJobs));
+    }
+}
Index: trunk/src/kmk/job.h
===================================================================
--- trunk/src/kmk/job.h	(revision 9)
+++ trunk/src/kmk/job.h	(revision 9)
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)job.h	8.1 (Berkeley) 6/6/93
+ * $FreeBSD: src/usr.bin/make/job.h,v 1.20 2002/10/09 01:56:02 jmallett Exp $
+ */
+
+/*-
+ * job.h --
+ *	Definitions pertaining to the running of jobs in parallel mode.
+ *	Exported from job.c for the use of remote-execution modules.
+ */
+#ifndef _JOB_H_
+#define	_JOB_H_
+
+#define	TMPPAT	"/tmp/makeXXXXXXXXXX"
+
+#ifndef USE_KQUEUE
+/*
+ * The SEL_ constants determine the maximum amount of time spent in select
+ * before coming out to see if a child has finished. SEL_SEC is the number of
+ * seconds and SEL_USEC is the number of micro-seconds
+ */
+#define	SEL_SEC		0
+#define	SEL_USEC	100000
+#endif /* !USE_KQUEUE */
+
+
+
+/*-
+ * Job Table definitions.
+ *
+ * Each job has several things associated with it:
+ *	1) The process id of the child shell
+ *	2) The graph node describing the target being made by this job
+ *	3) A LstNode for the first command to be saved after the job
+ *	   completes. This is NULL if there was no "..." in the job's
+ *	   commands.
+ *	4) An FILE* for writing out the commands. This is only
+ *	   used before the job is actually started.
+ *	5) A union of things used for handling the shell's output. Different
+ *	   parts of the union are used based on the value of the usePipes
+ *	   flag. If it is true, the output is being caught via a pipe and
+ *	   the descriptors of our pipe, an array in which output is line
+ *	   buffered and the current position in that buffer are all
+ *	   maintained for each job. If, on the other hand, usePipes is false,
+ *	   the output is routed to a temporary file and all that is kept
+ *	   is the name of the file and the descriptor open to the file.
+ *	6) An identifier provided by and for the exclusive use of the
+ *	   Rmt module.
+ *	7) A word of flags which determine how the module handles errors,
+ *	   echoing, etc. for the job
+ *
+ * The job "table" is kept as a linked Lst in 'jobs', with the number of
+ * active jobs maintained in the 'nJobs' variable. At no time will this
+ * exceed the value of 'maxJobs', initialized by the Job_Init function.
+ *
+ * When a job is finished, the Make_Update function is called on each of the
+ * parents of the node which was just remade. This takes care of the upward
+ * traversal of the dependency graph.
+ */
+#define	JOB_BUFSIZE	1024
+typedef struct Job {
+    int       	pid;	    /* The child's process ID */
+    char	tfile[sizeof(TMPPAT)];
+			    /* Temporary file to use for job */
+    GNode    	*node;      /* The target the child is making */
+    LstNode 	tailCmds;   /* The node of the first command to be
+			     * saved when the job has been run */
+    FILE 	*cmdFILE;   /* When creating the shell script, this is
+			     * where the commands go */
+    int    	rmtID;     /* ID returned from Rmt module */
+    short      	flags;	    /* Flags to control treatment of job */
+#define	JOB_IGNERR	0x001	/* Ignore non-zero exits */
+#define	JOB_SILENT	0x002	/* no output */
+#define	JOB_SPECIAL	0x004	/* Target is a special one. i.e. run it locally
+				 * if we can't export it and maxLocal is 0 */
+#define	JOB_IGNDOTS	0x008  	/* Ignore "..." lines when processing
+				 * commands */
+#define	JOB_REMOTE	0x010	/* Job is running remotely */
+#define	JOB_FIRST	0x020	/* Job is first job for the node */
+#define	JOB_REMIGRATE	0x040	/* Job needs to be remigrated */
+#define	JOB_RESTART	0x080	/* Job needs to be completely restarted */
+#define	JOB_RESUME	0x100	/* Job needs to be resumed b/c it stopped,
+				 * for some reason */
+#define	JOB_CONTINUING	0x200	/* We are in the process of resuming this job.
+				 * Used to avoid infinite recursion between
+				 * JobFinish and JobRestart */
+    union {
+	struct {
+	    int	  	op_inPipe;	/* Input side of pipe associated
+					 * with job's output channel */
+	    int   	op_outPipe;	/* Output side of pipe associated with
+					 * job's output channel */
+	    char  	op_outBuf[JOB_BUFSIZE + 1];
+	    	  	    	    	/* Buffer for storing the output of the
+					 * job, line by line */
+	    int   	op_curPos;	/* Current position in op_outBuf */
+	}   	    o_pipe;	    /* data used when catching the output via
+				     * a pipe */
+	struct {
+	    char  	of_outFile[sizeof(TMPPAT)];
+	    	  	    	    	/* Name of file to which shell output
+					 * was rerouted */
+	    int	    	of_outFd;	/* Stream open to the output
+					 * file. Used to funnel all
+					 * from a single job to one file
+					 * while still allowing
+					 * multiple shell invocations */
+	}   	    o_file;	    /* Data used when catching the output in
+				     * a temporary file */
+    }       	output;	    /* Data for tracking a shell's output */
+} Job;
+
+#define	outPipe	  	output.o_pipe.op_outPipe
+#define	inPipe	  	output.o_pipe.op_inPipe
+#define	outBuf		output.o_pipe.op_outBuf
+#define	curPos		output.o_pipe.op_curPos
+#define	outFile		output.o_file.of_outFile
+#define	outFd	  	output.o_file.of_outFd
+
+
+
+/*-
+ * Shell Specifications:
+ * Each shell type has associated with it the following information:
+ *	1) The string which must match the last character of the shell name
+ *	   for the shell to be considered of this type. The longest match
+ *	   wins.
+ *	2) A command to issue to turn off echoing of command lines
+ *	3) A command to issue to turn echoing back on again
+ *	4) What the shell prints, and its length, when given the echo-off
+ *	   command. This line will not be printed when received from the shell
+ *	5) A boolean to tell if the shell has the ability to control
+ *	   error checking for individual commands.
+ *	6) The string to turn this checking on.
+ *	7) The string to turn it off.
+ *	8) The command-flag to give to cause the shell to start echoing
+ *	   commands right away.
+ *	9) The command-flag to cause the shell to Lib_Exit when an error is
+ *	   detected in one of the commands.
+ *
+ * Some special stuff goes on if a shell doesn't have error control. In such
+ * a case, errCheck becomes a printf template for echoing the command,
+ * should echoing be on and ignErr becomes another printf template for
+ * executing the command while ignoring the return status. If either of these
+ * strings is empty when hasErrCtl is FALSE, the command will be executed
+ * anyway as is and if it causes an error, so be it.
+ */
+typedef struct Shell {
+    char	  *name;	/* the name of the shell. For Bourne and C
+				 * shells, this is used only to find the
+				 * shell description when used as the single
+				 * source of a .SHELL target. For user-defined
+				 * shells, this is the full path of the shell.
+				 */
+    Boolean 	  hasEchoCtl;	/* True if both echoOff and echoOn defined */
+    char          *echoOff;	/* command to turn off echo */
+    char          *echoOn;	/* command to turn it back on again */
+    char          *noPrint;	/* command to skip when printing output from
+				 * shell. This is usually the command which
+				 * was executed to turn off echoing */
+    int           noPLen;	/* length of noPrint command */
+    Boolean	  hasErrCtl;	/* set if can control error checking for
+				 * individual commands */
+    char	  *errCheck;	/* string to turn error checking on */
+    char	  *ignErr;	/* string to turn off error checking */
+    /*
+     * command-line flags
+     */
+    char          *echo;	/* echo commands */
+    char          *exit;	/* exit on error */
+}               Shell;
+
+/*
+ * If REMOTE is defined then these things need exposed, otherwise they are
+ * static to job.c!
+ */
+#ifdef REMOTE
+extern char 	*targFmt;   	/* Format string for banner that separates
+				 * output from multiple jobs. Contains a
+				 * single %s where the name of the node being
+				 * made should be put. */
+extern GNode	*lastNode;  	/* Last node for which a banner was printed.
+				 * If Rmt module finds it necessary to print
+				 * a banner, it should set this to the node
+				 * for which the banner was printed */
+extern int  	nJobs;	    	/* Number of jobs running (local and remote) */
+extern int  	nLocal;	    	/* Number of jobs running locally */
+extern Lst  	jobs;	    	/* List of active job descriptors */
+extern Lst  	stoppedJobs;	/* List of jobs that are stopped or didn't
+				 * quite get started */
+extern Boolean	jobFull;    	/* Non-zero if no more jobs should/will start*/
+#endif
+
+extern int	maxJobs;	/* Number of jobs that may run */
+
+
+void Job_Touch(GNode *, Boolean);
+Boolean Job_CheckCommands(GNode *, void (*abortProc)(const char *, ...));
+void Job_CatchChildren(Boolean);
+void Job_CatchOutput(void);
+void Job_Make(GNode *);
+void Job_Init(int, int);
+Boolean Job_Full(void);
+Boolean Job_Empty(void);
+ReturnStatus Job_ParseShell(char *);
+int Job_Finish(void);
+void Job_Wait(void);
+void Job_AbortAll(void);
+void JobFlagForMigration(int);
+
+#endif /* _JOB_H_ */
Index: trunk/src/kmk/list.h
===================================================================
--- trunk/src/kmk/list.h	(revision 9)
+++ trunk/src/kmk/list.h	(revision 9)
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)list.h	8.2 (Berkeley) 4/28/95
+ * $FreeBSD: src/usr.bin/make/list.h,v 1.11 2002/09/17 21:29:06 jmallett Exp $
+ */
+
+/*
+ * list.h --
+ *
+ * Structures, macros, and routines exported by the List module.
+ */
+
+#ifndef _LIST
+#define	_LIST
+
+#ifndef _SPRITE
+#include "sprite.h"
+#endif _SPRITE
+
+/*
+ * This module defines the list abstraction, which enables one to link
+ * together arbitrary data structures.  Lists are doubly-linked and
+ * circular.  A list contains a header followed by its real members, if
+ * any.  (An empty list therefore consists of a single element, the
+ * header,  whose nextPtr and prevPtr fields point to itself).  To refer
+ * to a list as a whole, the user keeps a pointer to the header; that
+ * header is initialized by a call to List_Init(), which creates an empty
+ * list given a pointer to a List_Links structure (described below).
+ *
+ * The links are contained in a two-element structure called List_Links.
+ * A list joins List_Links records (that is, each List_Links structure
+ * points to other List_Links structures), but if the List_Links is the
+ * first field within a larger structure, then the larger structures are
+ * effectively linked together as follows:
+ *
+ *	      header
+ *	  (List_Links)		   first elt.		    second elt.
+ *	-----------------	-----------------	-----------------
+ * ..->	|    nextPtr	| ---->	|  List_Links	| ---->	|  List_Links	|----..
+ *	| - - - - - - -	|	|		|	|		|
+ * ..--	|    prevPtr	| <----	|		| <----	|		|<---..
+ *	-----------------	- ---  ---  ---	-	- ---  ---  ---	-
+ *				|    rest of	|	|    rest of	|
+ *				|   structure	|	|   structure	|
+ *				|		|	|		|
+ *				|      ...	|	|      ...	|
+ *				-----------------	-----------------
+ *
+ * It is possible to link structures through List_Links fields that are
+ * not at the beginning of the larger structure, but it is then necessary
+ * to perform pointer arithmetic to find the beginning of the larger
+ * structure, given a pointer to some point within it.
+ *
+ * A typical structure might be something like:
+ *
+ *      typedef struct {
+ *                  List_Links links;
+ *                  char ch;
+ *                  integer flags;
+ *      } EditChar;
+ *
+ * Before an element is inserted in a list for the first time, it must
+ * be initialized by calling the macro List_InitElement().
+ */
+
+
+
+/*
+ * data structure for lists
+ */
+
+typedef struct List_Links {
+    struct List_Links *prevPtr;
+    struct List_Links *nextPtr;
+} List_Links;
+
+/*
+ * procedures
+ */
+
+void	List_Init();    /* initialize a header to a list */
+void    List_Insert();  /* insert an element into a list */
+void 	List_Remove();  /* remove an element from a list */
+void 	List_Move();    /* move an element elsewhere in a list */
+
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_InitElement --
+ *
+ *      Initialize a list element.  Must be called before an element is first
+ *	inserted into a list.
+ *
+ * ----------------------------------------------------------------------------
+ */
+#define	List_InitElement(elementPtr) \
+    (elementPtr)->prevPtr = (List_Links *) NULL; \
+    (elementPtr)->nextPtr = (List_Links *) NULL;
+
+/*
+ * Macros for stepping through or selecting parts of lists
+ */
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * LIST_FORALL --
+ *
+ *      Macro to loop through a list and perform an operation on each member.
+ *
+ *      Usage: LIST_FORALL(headerPtr, itemPtr) {
+ *                 / *
+ *                   * operation on itemPtr, which points to successive members
+ *                   * of the list
+ *                   *
+ *                   * It may be appropriate to first assign
+ *                   *          foobarPtr = (Foobar *) itemPtr;
+ *                   * to refer to the entire Foobar structure.
+ *                   * /
+ *             }
+ *
+ *      Note: itemPtr must be a List_Links pointer variable, and headerPtr
+ *      must evaluate to a pointer to a List_Links structure.
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define	LIST_FORALL(headerPtr, itemPtr) \
+        for (itemPtr = List_First(headerPtr); \
+             !List_IsAtEnd((headerPtr),itemPtr); \
+             itemPtr = List_Next(itemPtr))
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_IsEmpty --
+ *
+ *      Macro: Boolean value, TRUE if the given list does not contain any
+ *      members.
+ *
+ *      Usage: if (List_IsEmpty(headerPtr)) ...
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define	List_IsEmpty(headerPtr) \
+        ((headerPtr) == (headerPtr)->nextPtr)
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_IsAtEnd --
+ *
+ *      Macro: Boolean value, TRUE if itemPtr is after the end of headerPtr
+ *      (i.e., itemPtr is the header of the list).
+ *
+ *      Usage: if (List_IsAtEnd(headerPtr, itemPtr)) ...
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+
+#define	List_IsAtEnd(headerPtr, itemPtr) \
+        ((itemPtr) == (headerPtr))
+
+
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_First --
+ *
+ *      Macro to return the first member in a list, which is the header if
+ *      the list is empty.
+ *
+ *      Usage: firstPtr = List_First(headerPtr);
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define	List_First(headerPtr) ((headerPtr)->nextPtr)
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_Last --
+ *
+ *      Macro to return the last member in a list, which is the header if
+ *      the list is empty.
+ *
+ *      Usage: lastPtr = List_Last(headerPtr);
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define	List_Last(headerPtr) ((headerPtr)->prevPtr)
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_Prev --
+ *
+ *      Macro to return the member preceding the given member in its list.
+ *      If the given list member is the first element in the list, List_Prev
+ *      returns the list header.
+ *
+ *      Usage: prevPtr = List_Prev(itemPtr);
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define	List_Prev(itemPtr) ((itemPtr)->prevPtr)
+
+/*
+ * ----------------------------------------------------------------------------
+ *
+ * List_Next --
+ *
+ *      Macro to return the member following the given member in its list.
+ *      If the given list member is the last element in the list, List_Next
+ *      returns the list header.
+ *
+ *      Usage: nextPtr = List_Next(itemPtr);
+ *
+ * ----------------------------------------------------------------------------
+ */
+
+#define	List_Next(itemPtr) ((itemPtr)->nextPtr)
+
+
+
+/*
+ * ----------------------------------------------------------------------------
+ *      The List_Insert procedure takes two arguments.  The first argument
+ *      is a pointer to the structure to be inserted into a list, and
+ *      the second argument is a pointer to the list member after which
+ *      the new element is to be inserted.  Macros are used to determine
+ *      which existing member will precede the new one.
+ *
+ *      The List_Move procedure takes a destination argument with the same
+ *      semantics as List_Insert.
+ *
+ *      The following macros define where to insert the new element
+ *      in the list:
+ *
+ *      LIST_AFTER(itemPtr)     --      insert after itemPtr
+ *      LIST_BEFORE(itemPtr)    --      insert before itemPtr
+ *      LIST_ATFRONT(headerPtr) --      insert at front of list
+ *      LIST_ATREAR(headerPtr)  --      insert at end of list
+ *
+ *      For example,
+ *
+ *              List_Insert(itemPtr, LIST_AFTER(otherPtr));
+ *
+ *      will insert itemPtr following otherPtr in the list containing otherPtr.
+ * ----------------------------------------------------------------------------
+ */
+
+#define	LIST_AFTER(itemPtr) ((List_Links *) itemPtr)
+
+#define	LIST_BEFORE(itemPtr) (((List_Links *) itemPtr)->prevPtr)
+
+#define	LIST_ATFRONT(headerPtr) ((List_Links *) headerPtr)
+
+#define	LIST_ATREAR(headerPtr) (((List_Links *) headerPtr)->prevPtr)
+
+#endif /* _LIST */
Index: trunk/src/kmk/lst.h
===================================================================
--- trunk/src/kmk/lst.h	(revision 9)
+++ trunk/src/kmk/lst.h	(revision 9)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1988, 1989 by Adam de Boor
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)lst.h	8.2 (Berkeley) 4/28/95
+ * $FreeBSD: src/usr.bin/make/lst.h,v 1.15 2002/09/17 21:29:06 jmallett Exp $
+ */
+
+/*-
+ * lst.h --
+ *	Header for using the list library
+ */
+#ifndef _LST_H_
+#define	_LST_H_
+
+#include	<sys/param.h>
+#include	<stdlib.h>
+#include	"sprite.h"
+
+/*
+ * basic typedef. This is what the Lst_ functions handle
+ */
+
+typedef	struct	Lst	*Lst;
+typedef	struct	LstNode	*LstNode;
+
+/*
+ * NOFREE can be used as the freeProc to Lst_Destroy when the elements are
+ *	not to be freed.
+ * NOCOPY performs similarly when given as the copyProc to Lst_Duplicate.
+ */
+#define	NOFREE		((void (*)(void *)) 0)
+#define	NOCOPY		((void * (*)(void *)) 0)
+
+#define	LST_CONCNEW	0   /* create new LstNode's when using Lst_Concat */
+#define	LST_CONCLINK	1   /* relink LstNode's when using Lst_Concat */
+
+/*
+ * Creation/destruction functions
+ */
+/* Create a new list */
+Lst		Lst_Init(Boolean);
+/* Duplicate an existing list */
+Lst		Lst_Duplicate(Lst, void * (*)(void *));
+/* Destroy an old one */
+void		Lst_Destroy(Lst, void (*)(void *));
+/* True if list is empty */
+Boolean		Lst_IsEmpty(Lst);
+
+/*
+ * Functions to modify a list
+ */
+/* Insert an element before another */
+ReturnStatus	Lst_Insert(Lst, LstNode, void *);
+/* Insert an element after another */
+ReturnStatus	Lst_Append(Lst, LstNode, void *);
+/* Place an element at the front of a lst. */
+ReturnStatus	Lst_AtFront(Lst, void *);
+/* Place an element at the end of a lst. */
+ReturnStatus	Lst_AtEnd(Lst, void *);
+/* Remove an element */
+ReturnStatus	Lst_Remove(Lst, LstNode);
+/* Replace a node with a new value */
+ReturnStatus	Lst_Replace(LstNode, void *);
+/* Concatenate two lists */
+ReturnStatus	Lst_Concat(Lst, Lst, int);
+
+/*
+ * Node-specific functions
+ */
+/* Return first element in list */
+LstNode		Lst_First(Lst);
+/* Return last element in list */
+LstNode		Lst_Last(Lst);
+/* Return successor to given element */
+LstNode		Lst_Succ(LstNode);
+/* Get datum from LstNode */
+void *	Lst_Datum(LstNode);
+
+/*
+ * Functions for entire lists
+ */
+/* Find an element in a list */
+LstNode		Lst_Find(Lst, void *, int (*)(void *, void *));
+/* Find an element starting from somewhere */
+LstNode		Lst_FindFrom(Lst, LstNode, void *, int (*cProc)(void *, void *));
+/*
+ * See if the given datum is on the list. Returns the LstNode containing
+ * the datum
+ */
+LstNode		Lst_Member(Lst, void *);
+/* Apply a function to all elements of a lst */
+void		Lst_ForEach(Lst, int (*)(void *, void *), void *);
+/*
+ * Apply a function to all elements of a lst starting from a certain point.
+ * If the list is circular, the application will wrap around to the
+ * beginning of the list again.
+ */
+void		Lst_ForEachFrom(Lst, LstNode, int (*)(void *, void *), void *);
+/*
+ * these functions are for dealing with a list as a table, of sorts.
+ * An idea of the "current element" is kept and used by all the functions
+ * between Lst_Open() and Lst_Close().
+ */
+/* Open the list */
+ReturnStatus	Lst_Open(Lst);
+/* Next element please */
+LstNode		Lst_Next(Lst);
+/* Done yet? */
+Boolean		Lst_IsAtEnd(Lst);
+/* Finish table access */
+void		Lst_Close(Lst);
+
+/*
+ * for using the list as a queue
+ */
+/* Place an element at tail of queue */
+ReturnStatus	Lst_EnQueue(Lst, void *);
+/* Remove an element from head of queue */
+void *	Lst_DeQueue(Lst);
+
+#endif /* _LST_H_ */
Index: trunk/src/kmk/lst.lib/lstAppend.c
===================================================================
--- trunk/src/kmk/lst.lib/lstAppend.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstAppend.c	(revision 9)
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstAppend.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstAppend.c,v 1.10 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstAppend.c --
+ *	Add a new node with a new datum after an existing node
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Append --
+ *	Create a new node and add it to the given list after the given node.
+ *
+ * Results:
+ *	SUCCESS if all went well.
+ *
+ * Side Effects:
+ *	A new ListNode is created and linked in to the List. The lastPtr
+ *	field of the List will be altered if ln is the last node in the
+ *	list. lastPtr and firstPtr will alter if the list was empty and
+ *	ln was NULL.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Append (l, ln, d)
+    Lst	  	l;	/* affected list */
+    LstNode	ln;	/* node after which to append the datum */
+    void *	d;	/* said datum */
+{
+    register List 	list;
+    register ListNode	lNode;
+    register ListNode	nLNode;
+
+    if (LstValid (l) && (ln == NULL && LstIsEmpty (l))) {
+	goto ok;
+    }
+
+    if (!LstValid (l) || LstIsEmpty (l)  || ! LstNodeValid (ln, l)) {
+	return (FAILURE);
+    }
+    ok:
+
+    list = (List)l;
+    lNode = (ListNode)ln;
+
+    PAlloc (nLNode, ListNode);
+    nLNode->datum = d;
+    nLNode->useCount = nLNode->flags = 0;
+
+    if (lNode == NULL) {
+	if (list->isCirc) {
+	    nLNode->nextPtr = nLNode->prevPtr = nLNode;
+	} else {
+	    nLNode->nextPtr = nLNode->prevPtr = NULL;
+	}
+	list->firstPtr = list->lastPtr = nLNode;
+    } else {
+	nLNode->prevPtr = lNode;
+	nLNode->nextPtr = lNode->nextPtr;
+
+	lNode->nextPtr = nLNode;
+	if (nLNode->nextPtr != NULL) {
+	    nLNode->nextPtr->prevPtr = nLNode;
+	}
+
+	if (lNode == list->lastPtr) {
+	    list->lastPtr = nLNode;
+	}
+    }
+
+    return (SUCCESS);
+}
+
Index: trunk/src/kmk/lst.lib/lstAtEnd.c
===================================================================
--- trunk/src/kmk/lst.lib/lstAtEnd.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstAtEnd.c	(revision 9)
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstAtEnd.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstAtEnd.c,v 1.9 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstAtEnd.c --
+ *	Add a node at the end of the list
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_AtEnd --
+ *	Add a node to the end of the given list
+ *
+ * Results:
+ *	SUCCESS if life is good.
+ *
+ * Side Effects:
+ *	A new ListNode is created and added to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtEnd (l, d)
+    Lst		l;	/* List to which to add the datum */
+    void *	d;	/* Datum to add */
+{
+    register LstNode	end;
+
+    end = Lst_Last (l);
+    return (Lst_Append (l, end, d));
+}
Index: trunk/src/kmk/lst.lib/lstAtFront.c
===================================================================
--- trunk/src/kmk/lst.lib/lstAtFront.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstAtFront.c	(revision 9)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstAtFront.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstAtFront.c,v 1.9 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstAtFront.c --
+ *	Add a node at the front of the list
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_AtFront --
+ *	Place a piece of data at the front of a list
+ *
+ * Results:
+ *	SUCCESS or FAILURE
+ *
+ * Side Effects:
+ *	A new ListNode is created and stuck at the front of the list.
+ *	hence, firstPtr (and possible lastPtr) in the list are altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtFront (l, d)
+    Lst		l;
+    void *	d;
+{
+    register LstNode	front;
+
+    front = Lst_First (l);
+    return (Lst_Insert (l, front, d));
+}
Index: trunk/src/kmk/lst.lib/lstClose.c
===================================================================
--- trunk/src/kmk/lst.lib/lstClose.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstClose.c	(revision 9)
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstClose.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstClose.c,v 1.8 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstClose.c --
+ *	Close a list for sequential access.
+ *	The sequential functions access the list in a slightly different way.
+ *	CurPtr points to their idea of the current node in the list and they
+ *	access the list based on it. Because the list is circular, Lst_Next
+ *	and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ *	used to determine when to stop.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Close --
+ *	Close a list which was opened for sequential access.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The list is closed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Close (l)
+    Lst	    l;	  	/* The list to close */
+{
+    register List 	list = (List) l;
+
+    if (LstValid(l) == TRUE) {
+	list->isOpen = FALSE;
+	list->atEnd = Unknown;
+    }
+}
+
Index: trunk/src/kmk/lst.lib/lstConcat.c
===================================================================
--- trunk/src/kmk/lst.lib/lstConcat.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstConcat.c	(revision 9)
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstConcat.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstConcat.c,v 1.11 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * listConcat.c --
+ *	Function to concatentate two lists.
+ */
+
+#include    "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Concat --
+ *	Concatenate two lists. New elements are created to hold the data
+ *	elements, if specified, but the elements themselves are not copied.
+ *	If the elements should be duplicated to avoid confusion with another
+ *	list, the Lst_Duplicate function should be called first.
+ *	If LST_CONCLINK is specified, the second list is destroyed since
+ *	its pointers have been corrupted and the list is no longer useable.
+ *
+ * Results:
+ *	SUCCESS if all went well. FAILURE otherwise.
+ *
+ * Side Effects:
+ *	New elements are created and appended the the first list.
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Concat (l1, l2, flags)
+    Lst    	  	l1; 	/* The list to which l2 is to be appended */
+    Lst    	  	l2; 	/* The list to append to l1 */
+    int	   	  	flags;  /* LST_CONCNEW if LstNode's should be duplicated
+				 * LST_CONCLINK if should just be relinked */
+{
+    register ListNode  	ln;     /* original LstNode */
+    register ListNode  	nln;    /* new LstNode */
+    register ListNode  	last;   /* the last element in the list. Keeps
+				 * bookkeeping until the end */
+    register List 	list1 = (List)l1;
+    register List 	list2 = (List)l2;
+
+    if (!LstValid (l1) || !LstValid (l2)) {
+	return (FAILURE);
+    }
+
+    if (flags == LST_CONCLINK) {
+	if (list2->firstPtr != NULL) {
+	    /*
+	     * We set the nextPtr of the
+	     * last element of list two to be NULL to make the loop easier and
+	     * so we don't need an extra case should the first list turn
+	     * out to be non-circular -- the final element will already point
+	     * to NULL space and the first element will be untouched if it
+	     * existed before and will also point to NULL space if it didn't.
+	     */
+	    list2->lastPtr->nextPtr = NULL;
+	    /*
+	     * So long as the second list isn't empty, we just link the
+	     * first element of the second list to the last element of the
+	     * first list. If the first list isn't empty, we then link the
+	     * last element of the list to the first element of the second list
+	     * The last element of the second list, if it exists, then becomes
+	     * the last element of the first list.
+	     */
+	    list2->firstPtr->prevPtr = list1->lastPtr;
+	    if (list1->lastPtr != NULL) {
+ 		list1->lastPtr->nextPtr = list2->firstPtr;
+	    } else {
+		list1->firstPtr = list2->firstPtr;
+	    }
+	    list1->lastPtr = list2->lastPtr;
+	}
+	if (list1->isCirc && list1->firstPtr != NULL) {
+	    /*
+	     * If the first list is supposed to be circular and it is (now)
+	     * non-empty, we must make sure it's circular by linking the
+	     * first element to the last and vice versa
+	     */
+	    list1->firstPtr->prevPtr = list1->lastPtr;
+	    list1->lastPtr->nextPtr = list1->firstPtr;
+	}
+	free (l2);
+    } else if (list2->firstPtr != NULL) {
+	/*
+	 * We set the nextPtr of the last element of list 2 to be NULL to make
+	 * the loop less difficult. The loop simply goes through the entire
+	 * second list creating new LstNodes and filling in the nextPtr, and
+	 * prevPtr to fit into l1 and its datum field from the
+	 * datum field of the corresponding element in l2. The 'last' node
+	 * follows the last of the new nodes along until the entire l2 has
+	 * been appended. Only then does the bookkeeping catch up with the
+	 * changes. During the first iteration of the loop, if 'last' is NULL,
+	 * the first list must have been empty so the newly-created node is
+	 * made the first node of the list.
+	 */
+	list2->lastPtr->nextPtr = NULL;
+	for (last = list1->lastPtr, ln = list2->firstPtr;
+	     ln != NULL;
+	     ln = ln->nextPtr)
+	{
+	    PAlloc (nln, ListNode);
+	    nln->datum = ln->datum;
+	    if (last != NULL) {
+		last->nextPtr = nln;
+	    } else {
+		list1->firstPtr = nln;
+	    }
+	    nln->prevPtr = last;
+	    nln->flags = nln->useCount = 0;
+	    last = nln;
+	}
+
+	/*
+	 * Finish bookkeeping. The last new element becomes the last element
+	 * of list one.
+	 */
+	list1->lastPtr = last;
+
+	/*
+	 * The circularity of both list one and list two must be corrected
+	 * for -- list one because of the new nodes added to it; list two
+	 * because of the alteration of list2->lastPtr's nextPtr to ease the
+	 * above for loop.
+	 */
+	if (list1->isCirc) {
+	    list1->lastPtr->nextPtr = list1->firstPtr;
+	    list1->firstPtr->prevPtr = list1->lastPtr;
+	} else {
+	    last->nextPtr = NULL;
+	}
+
+	if (list2->isCirc) {
+	    list2->lastPtr->nextPtr = list2->firstPtr;
+	}
+    }
+
+    return (SUCCESS);
+}
+
Index: trunk/src/kmk/lst.lib/lstDatum.c
===================================================================
--- trunk/src/kmk/lst.lib/lstDatum.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstDatum.c	(revision 9)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstDatum.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstDatum.c,v 1.9 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstDatum.c --
+ *	Return the datum associated with a list node.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Datum --
+ *	Return the datum stored in the given node.
+ *
+ * Results:
+ *	The datum or (ick!) NULL if the node is invalid.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Lst_Datum (ln)
+    LstNode	ln;
+{
+    if (ln != NULL) {
+	return (((ListNode)ln)->datum);
+    } else {
+	return ((void *) NULL);
+    }
+}
+
Index: trunk/src/kmk/lst.lib/lstDeQueue.c
===================================================================
--- trunk/src/kmk/lst.lib/lstDeQueue.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstDeQueue.c	(revision 9)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstDeQueue.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstDeQueue.c,v 1.10 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstDeQueue.c --
+ *	Remove the node and return its datum from the head of the list
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_DeQueue --
+ *	Remove and return the datum at the head of the given list.
+ *
+ * Results:
+ *	The datum in the node at the head or (ick) NULL if the list
+ *	is empty.
+ *
+ * Side Effects:
+ *	The head node is removed from the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Lst_DeQueue (l)
+    Lst	    	  l;
+{
+    void *	  rd;
+    register ListNode	tln;
+
+    tln = (ListNode) Lst_First (l);
+    if (tln == NULL) {
+	return ((void *) NULL);
+    }
+
+    rd = tln->datum;
+    if (Lst_Remove (l, (LstNode)tln) == FAILURE) {
+	return ((void *) NULL);
+    } else {
+	return (rd);
+    }
+}
+
Index: trunk/src/kmk/lst.lib/lstDestroy.c
===================================================================
--- trunk/src/kmk/lst.lib/lstDestroy.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstDestroy.c	(revision 9)
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstDestroy.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstDestroy.c,v 1.12 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstDestroy.c --
+ *	Nuke a list and all its resources
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Destroy --
+ *	Destroy a list and free all its resources. If the freeProc is
+ *	given, it is called with the datum from each node in turn before
+ *	the node is freed.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The given list is freed in its entirety.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Destroy (l, freeProc)
+    Lst	    	  	l;
+    register void	(*freeProc)(void *);
+{
+    register ListNode	ln;
+    register ListNode	tln = NULL;
+    register List 	list = (List)l;
+
+    if (l == NULL || ! l) {
+	/*
+	 * Note the check for l == (Lst)0 to catch uninitialized static Lst's.
+	 * Gross, but useful.
+	 */
+	return;
+    }
+
+    /* To ease scanning */
+    if (list->lastPtr != NULL)
+	list->lastPtr->nextPtr = NULL;
+    else {
+	free (l);
+	return;
+    }
+
+    if (freeProc) {
+	for (ln = list->firstPtr; ln != NULL; ln = tln) {
+	     tln = ln->nextPtr;
+	     (*freeProc) (ln->datum);
+	     free (ln);
+	}
+    } else {
+	for (ln = list->firstPtr; ln != NULL; ln = tln) {
+	     tln = ln->nextPtr;
+	     free (ln);
+	}
+    }
+
+    free (l);
+}
Index: trunk/src/kmk/lst.lib/lstDupl.c
===================================================================
--- trunk/src/kmk/lst.lib/lstDupl.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstDupl.c	(revision 9)
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstDupl.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstDupl.c,v 1.12 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * listDupl.c --
+ *	Duplicate a list. This includes duplicating the individual
+ *	elements.
+ */
+
+#include    "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Duplicate --
+ *	Duplicate an entire list. If a function to copy a void * is
+ *	given, the individual client elements will be duplicated as well.
+ *
+ * Results:
+ *	The new Lst structure or NULL if failure.
+ *
+ * Side Effects:
+ *	A new list is created.
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Duplicate (l, copyProc)
+    Lst     	  l;	    	 /* the list to duplicate */
+    /* A function to duplicate each void * */
+    void *	  (*copyProc)(void *);
+{
+    register Lst 	nl;
+    register ListNode  	ln;
+    register List 	list = (List)l;
+
+    if (!LstValid (l)) {
+	return (NULL);
+    }
+
+    nl = Lst_Init (list->isCirc);
+    if (nl == NULL) {
+	return (NULL);
+    }
+
+    ln = list->firstPtr;
+    while (ln != NULL) {
+	if (copyProc != NOCOPY) {
+	    if (Lst_AtEnd (nl, (*copyProc) (ln->datum)) == FAILURE) {
+		return (NULL);
+	    }
+	} else if (Lst_AtEnd (nl, ln->datum) == FAILURE) {
+	    return (NULL);
+	}
+
+	if (list->isCirc && ln == list->lastPtr) {
+	    ln = NULL;
+	} else {
+	    ln = ln->nextPtr;
+	}
+    }
+
+    return (nl);
+}
Index: trunk/src/kmk/lst.lib/lstEnQueue.c
===================================================================
--- trunk/src/kmk/lst.lib/lstEnQueue.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstEnQueue.c	(revision 9)
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstEnQueue.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstEnQueue.c,v 1.9 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstEnQueue.c--
+ *	Treat the list as a queue and place a datum at its end
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_EnQueue --
+ *	Add the datum to the tail of the given list.
+ *
+ * Results:
+ *	SUCCESS or FAILURE as returned by Lst_Append.
+ *
+ * Side Effects:
+ *	the lastPtr field is altered all the time and the firstPtr field
+ *	will be altered if the list used to be empty.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_EnQueue (l, d)
+    Lst	    	  l;
+    void *	  d;
+{
+    if (LstValid (l) == FALSE) {
+	return (FAILURE);
+    }
+
+    return (Lst_Append (l, Lst_Last(l), d));
+}
+
Index: trunk/src/kmk/lst.lib/lstFind.c
===================================================================
--- trunk/src/kmk/lst.lib/lstFind.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstFind.c	(revision 9)
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstFind.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstFind.c,v 1.11 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstFind.c --
+ *	Find a node on a list.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Find --
+ *	Find a node on the given list using the given comparison function
+ *	and the given datum.
+ *
+ * Results:
+ *	The found node or NULL if none matches.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Find (l, d, cProc)
+    Lst		l;
+    void *	d;
+    int		(*cProc)(void *, void *);
+{
+    return (Lst_FindFrom (l, Lst_First(l), d, cProc));
+}
+
Index: trunk/src/kmk/lst.lib/lstFindFrom.c
===================================================================
--- trunk/src/kmk/lst.lib/lstFindFrom.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstFindFrom.c	(revision 9)
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstFindFrom.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstFindFrom.c,v 1.12 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstFindFrom.c --
+ *	Find a node on a list from a given starting point. Used by Lst_Find.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_FindFrom --
+ *	Search for a node starting and ending with the given one on the
+ *	given list using the passed datum and comparison function to
+ *	determine when it has been found.
+ *
+ * Results:
+ *	The found node or NULL
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_FindFrom (l, ln, d, cProc)
+    Lst		      	l;
+    register LstNode    ln;
+    register void * d;
+    register int	(*cProc)(void *, void *);
+{
+    register ListNode	tln;
+    Boolean		found = FALSE;
+
+    if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+	return (NULL);
+    }
+
+    tln = (ListNode)ln;
+
+    do {
+	if ((*cProc) (tln->datum, d) == 0) {
+	    found = TRUE;
+	    break;
+	} else {
+	    tln = tln->nextPtr;
+	}
+    } while (tln != (ListNode)ln && tln != NULL);
+
+    if (found) {
+	return ((LstNode)tln);
+    } else {
+	return (NULL);
+    }
+}
+
Index: trunk/src/kmk/lst.lib/lstFirst.c
===================================================================
--- trunk/src/kmk/lst.lib/lstFirst.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstFirst.c	(revision 9)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstFirst.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstFirst.c,v 1.8 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstFirst.c --
+ *	Return the first node of a list
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_First --
+ *	Return the first node on the given list.
+ *
+ * Results:
+ *	The first node or NULL if the list is empty.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_First (l)
+    Lst	l;
+{
+    if (!LstValid (l) || LstIsEmpty (l)) {
+	return (NULL);
+    } else {
+	return ((LstNode)((List)l)->firstPtr);
+    }
+}
+
Index: trunk/src/kmk/lst.lib/lstForEach.c
===================================================================
--- trunk/src/kmk/lst.lib/lstForEach.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstForEach.c	(revision 9)
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstForEach.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstForEach.c,v 1.10 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstForeach.c --
+ *	Perform a given function on all elements of a list.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_ForEach --
+ *	Apply the given function to each element of the given list. The
+ *	function should return 0 if Lst_ForEach should continue and non-
+ *	zero if it should abort.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Only those created by the passed-in function.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*VARARGS2*/
+void
+Lst_ForEach (l, proc, d)
+    Lst	    	  	l;
+    register int	(*proc)(void *, void *);
+    register void *	d;
+{
+    Lst_ForEachFrom(l, Lst_First(l), proc, d);
+}
+
Index: trunk/src/kmk/lst.lib/lstForEachFrom.c
===================================================================
--- trunk/src/kmk/lst.lib/lstForEachFrom.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstForEachFrom.c	(revision 9)
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstForEachFrom.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstForEachFrom.c,v 1.12 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * lstForEachFrom.c --
+ *	Perform a given function on all elements of a list starting from
+ *	a given point.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_ForEachFrom --
+ *	Apply the given function to each element of the given list. The
+ *	function should return 0 if traversal should continue and non-
+ *	zero if it should abort.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Only those created by the passed-in function.
+ *
+ *-----------------------------------------------------------------------
+ */
+/*VARARGS2*/
+void
+Lst_ForEachFrom (l, ln, proc, d)
+    Lst	    	    	l;
+    LstNode    	  	ln;
+    register int	(*proc)(void *, void *);
+    register void *	d;
+{
+    register ListNode	tln = (ListNode)ln;
+    register List 	list = (List)l;
+    register ListNode	next;
+    Boolean 	    	done;
+    int     	    	result;
+
+    if (!LstValid (list) || LstIsEmpty (list)) {
+	return;
+    }
+
+    do {
+	/*
+	 * Take care of having the current element deleted out from under
+	 * us.
+	 */
+
+	next = tln->nextPtr;
+
+	(void) tln->useCount++;
+	result = (*proc) (tln->datum, d);
+	(void) tln->useCount--;
+
+	/*
+	 * We're done with the traversal if
+	 *  - nothing's been added after the current node and
+	 *  - the next node to examine is the first in the queue or
+	 *    doesn't exist.
+	 */
+	done = (next == tln->nextPtr &&
+		(next == NULL || next == list->firstPtr));
+
+	next = tln->nextPtr;
+
+	if (tln->flags & LN_DELETED) {
+	    free((char *)tln);
+	}
+	tln = next;
+    } while (!result && !LstIsEmpty(list) && !done);
+
+}
+
Index: trunk/src/kmk/lst.lib/lstInit.c
===================================================================
--- trunk/src/kmk/lst.lib/lstInit.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstInit.c	(revision 9)
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstInit.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstInit.c,v 1.9 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * init.c --
+ *	Initialize a new linked list.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Init --
+ *	Create and initialize a new list.
+ *
+ * Results:
+ *	The created list.
+ *
+ * Side Effects:
+ *	A list is created, what else?
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Init(circ)
+    Boolean		circ;	/* TRUE if the list should be made circular */
+{
+    register List	nList;
+
+    PAlloc (nList, List);
+
+    nList->firstPtr = NULL;
+    nList->lastPtr = NULL;
+    nList->isOpen = FALSE;
+    nList->isCirc = circ;
+    nList->atEnd = Unknown;
+
+    return ((Lst)nList);
+}
Index: trunk/src/kmk/lst.lib/lstInsert.c
===================================================================
--- trunk/src/kmk/lst.lib/lstInsert.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstInsert.c	(revision 9)
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstInsert.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstInsert.c,v 1.10 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstInsert.c --
+ *	Insert a new datum before an old one
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Insert --
+ *	Insert a new node with the given piece of data before the given
+ *	node in the given list.
+ *
+ * Results:
+ *	SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ *	the firstPtr field will be changed if ln is the first node in the
+ *	list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Insert (l, ln, d)
+    Lst	    	  	l;	/* list to manipulate */
+    LstNode	  	ln;	/* node before which to insert d */
+    void *	  	d;	/* datum to be inserted */
+{
+    register ListNode	nLNode;	/* new lnode for d */
+    register ListNode	lNode = (ListNode)ln;
+    register List 	list = (List)l;
+
+
+    /*
+     * check validity of arguments
+     */
+    if (LstValid (l) && (LstIsEmpty (l) && ln == NULL))
+	goto ok;
+
+    if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+	return (FAILURE);
+    }
+
+    ok:
+    PAlloc (nLNode, ListNode);
+
+    nLNode->datum = d;
+    nLNode->useCount = nLNode->flags = 0;
+
+    if (ln == NULL) {
+	if (list->isCirc) {
+	    nLNode->prevPtr = nLNode->nextPtr = nLNode;
+	} else {
+	    nLNode->prevPtr = nLNode->nextPtr = NULL;
+	}
+	list->firstPtr = list->lastPtr = nLNode;
+    } else {
+	nLNode->prevPtr = lNode->prevPtr;
+	nLNode->nextPtr = lNode;
+
+	if (nLNode->prevPtr != NULL) {
+	    nLNode->prevPtr->nextPtr = nLNode;
+	}
+	lNode->prevPtr = nLNode;
+
+	if (lNode == list->firstPtr) {
+	    list->firstPtr = nLNode;
+	}
+    }
+
+    return (SUCCESS);
+}
+
Index: trunk/src/kmk/lst.lib/lstInt.h
===================================================================
--- trunk/src/kmk/lst.lib/lstInt.h	(revision 9)
+++ trunk/src/kmk/lst.lib/lstInt.h	(revision 9)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	from: @(#)lstInt.h	8.1 (Berkeley) 6/6/93
+ * $FreeBSD: src/usr.bin/make/lst.lib/lstInt.h,v 1.10 2002/07/15 06:57:25 jmallett Exp $
+ */
+
+/*-
+ * lstInt.h --
+ *	Internals for the list library
+ */
+#ifndef _LSTINT_H_
+#define _LSTINT_H_
+
+#include	  "make.h"
+#include	  "lst.h"
+
+typedef struct ListNode {
+	struct ListNode	*prevPtr;   /* previous element in list */
+	struct ListNode	*nextPtr;   /* next in list */
+	int	    	useCount:8, /* Count of functions using the node.
+				     * node may not be deleted until count
+				     * goes to 0 */
+ 	    	    	flags:8;    /* Node status flags */
+	void *	datum;	    /* datum associated with this element */
+} *ListNode;
+/*
+ * Flags required for synchronization
+ */
+#define LN_DELETED  	0x0001      /* List node should be removed when done */
+
+typedef enum {
+    Head, Middle, Tail, Unknown
+} Where;
+
+typedef struct	{
+	ListNode  	firstPtr; /* first node in list */
+	ListNode  	lastPtr;  /* last node in list */
+	Boolean	  	isCirc;	  /* true if the list should be considered
+				   * circular */
+/*
+ * fields for sequential access
+ */
+	Where	  	atEnd;	  /* Where in the list the last access was */
+	Boolean	  	isOpen;	  /* true if list has been Lst_Open'ed */
+	ListNode  	curPtr;	  /* current node, if open. NULL if
+				   * *just* opened */
+	ListNode  	prevPtr;  /* Previous node, if open. Used by
+				   * Lst_Remove */
+} *List;
+
+/*
+ * PAlloc (var, ptype) --
+ *	Allocate a pointer-typedef structure 'ptype' into the variable 'var'
+ */
+#define	PAlloc(var,ptype)	var = (ptype) emalloc (sizeof (*var))
+
+/*
+ * LstValid (l) --
+ *	Return TRUE if the list l is valid
+ */
+#define LstValid(l)	(((Lst)l == NULL) ? FALSE : TRUE)
+
+/*
+ * LstNodeValid (ln, l) --
+ *	Return TRUE if the LstNode ln is valid with respect to l
+ */
+#define LstNodeValid(ln, l)	((((LstNode)ln) == NULL) ? FALSE : TRUE)
+
+/*
+ * LstIsEmpty (l) --
+ *	TRUE if the list l is empty.
+ */
+#define LstIsEmpty(l)	(((List)l)->firstPtr == NULL)
+
+#endif /* _LSTINT_H_ */
Index: trunk/src/kmk/lst.lib/lstIsAtEnd.c
===================================================================
--- trunk/src/kmk/lst.lib/lstIsAtEnd.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstIsAtEnd.c	(revision 9)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstIsAtEnd.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstIsAtEnd.c,v 1.7 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstIsAtEnd.c --
+ *	Tell if the current node is at the end of the list.
+ *	The sequential functions access the list in a slightly different way.
+ *	CurPtr points to their idea of the current node in the list and they
+ *	access the list based on it. Because the list is circular, Lst_Next
+ *	and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ *	used to determine when to stop.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_IsAtEnd --
+ *	Return true if have reached the end of the given list.
+ *
+ * Results:
+ *	TRUE if at the end of the list (this includes the list not being
+ *	open or being invalid) or FALSE if not. We return TRUE if the list
+ *	is invalid or unopend so as to cause the caller to exit its loop
+ *	asap, the assumption being that the loop is of the form
+ *	    while (!Lst_IsAtEnd (l)) {
+ *	    	  ...
+ *	    }
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsAtEnd (l)
+    Lst	    l;
+{
+    register List list = (List) l;
+
+    return (!LstValid (l) || !list->isOpen ||
+	    (list->atEnd == Head) || (list->atEnd == Tail));
+}
+
Index: trunk/src/kmk/lst.lib/lstIsEmpty.c
===================================================================
--- trunk/src/kmk/lst.lib/lstIsEmpty.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstIsEmpty.c	(revision 9)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstIsEmpty.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstIsEmpty.c,v 1.8 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstIsEmpty.c --
+ *	A single function to decide if a list is empty
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_IsEmpty --
+ *	Return TRUE if the given list is empty.
+ *
+ * Results:
+ *	TRUE if the list is empty, FALSE otherwise.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *	A list is considered empty if its firstPtr == NULL (or if
+ *	the list itself is NULLLIST).
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsEmpty (l)
+    Lst	l;
+{
+    return ( ! LstValid (l) || LstIsEmpty(l));
+}
+
Index: trunk/src/kmk/lst.lib/lstLast.c
===================================================================
--- trunk/src/kmk/lst.lib/lstLast.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstLast.c	(revision 9)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstLast.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstLast.c,v 1.8 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstLast.c --
+ *	Return the last element of a list
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Last --
+ *	Return the last node on the list l.
+ *
+ * Results:
+ *	The requested node or NULL if the list is empty.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Last (l)
+    Lst	    l;
+{
+    if (!LstValid(l) || LstIsEmpty (l)) {
+	return (NULL);
+    } else {
+	return ((LstNode)((List)l)->lastPtr);
+    }
+}
+
Index: trunk/src/kmk/lst.lib/lstMember.c
===================================================================
--- trunk/src/kmk/lst.lib/lstMember.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstMember.c	(revision 9)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstMember.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstMember.c,v 1.10 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * lstMember.c --
+ *	See if a given datum is on a given list.
+ */
+
+#include    "lstInt.h"
+
+LstNode
+Lst_Member (l, d)
+    Lst	    	  	l;
+    void *	  	d;
+{
+    List    	  	list = (List) l;
+    register ListNode	lNode;
+
+    lNode = list->firstPtr;
+    if (lNode == NULL) {
+	return NULL;
+    }
+
+    do {
+	if (lNode->datum == d) {
+	    return (LstNode)lNode;
+	}
+	lNode = lNode->nextPtr;
+    } while (lNode != NULL && lNode != list->firstPtr);
+
+    return NULL;
+}
Index: trunk/src/kmk/lst.lib/lstNext.c
===================================================================
--- trunk/src/kmk/lst.lib/lstNext.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstNext.c	(revision 9)
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstNext.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstNext.c,v 1.9 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstNext.c --
+ *	Return the next node for a list.
+ *	The sequential functions access the list in a slightly different way.
+ *	CurPtr points to their idea of the current node in the list and they
+ *	access the list based on it. Because the list is circular, Lst_Next
+ *	and Lst_Prev will go around the list forever. Lst_IsAtEnd must be
+ *	used to determine when to stop.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Next --
+ *	Return the next node for the given list.
+ *
+ * Results:
+ *	The next node or NULL if the list has yet to be opened. Also
+ *	if the list is non-circular and the end has been reached, NULL
+ *	is returned.
+ *
+ * Side Effects:
+ *	the curPtr field is updated.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Next (l)
+    Lst	    	  l;
+{
+    register ListNode	tln;
+    register List 	list = (List)l;
+
+    if ((LstValid (l) == FALSE) ||
+	(list->isOpen == FALSE)) {
+	    return (NULL);
+    }
+
+    list->prevPtr = list->curPtr;
+
+    if (list->curPtr == NULL) {
+	if (list->atEnd == Unknown) {
+	    /*
+	     * If we're just starting out, atEnd will be Unknown.
+	     * Then we want to start this thing off in the right
+	     * direction -- at the start with atEnd being Middle.
+	     */
+	    list->curPtr = tln = list->firstPtr;
+	    list->atEnd = Middle;
+	} else {
+	    tln = NULL;
+	    list->atEnd = Tail;
+	}
+    } else {
+	tln = list->curPtr->nextPtr;
+	list->curPtr = tln;
+
+	if (tln == list->firstPtr || tln == NULL) {
+	    /*
+	     * If back at the front, then we've hit the end...
+	     */
+	    list->atEnd = Tail;
+	} else {
+	    /*
+	     * Reset to Middle if gone past first.
+	     */
+	    list->atEnd = Middle;
+	}
+    }
+
+    return ((LstNode)tln);
+}
+
Index: trunk/src/kmk/lst.lib/lstOpen.c
===================================================================
--- trunk/src/kmk/lst.lib/lstOpen.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstOpen.c	(revision 9)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstOpen.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstOpen.c,v 1.8 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstOpen.c --
+ *	Open a list for sequential access. The sequential functions access the
+ *	list in a slightly different way. CurPtr points to their idea of the
+ *	current node in the list and they access the list based on it.
+ *	If the list is circular, Lst_Next and Lst_Prev will go around
+ *	the list forever. Lst_IsAtEnd must be used to determine when to stop.
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Open --
+ *	Open a list for sequential access. A list can still be searched,
+ *	etc., without confusing these functions.
+ *
+ * Results:
+ *	SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ *	isOpen is set TRUE and curPtr is set to NULL so the
+ *	other sequential functions no it was just opened and can choose
+ *	the first element accessed based on this.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Open (l)
+	register Lst	l;
+{
+	if (LstValid (l) == FALSE) {
+		return (FAILURE);
+	}
+	((List) l)->isOpen = TRUE;
+	((List) l)->atEnd = LstIsEmpty (l) ? Head : Unknown;
+	((List) l)->curPtr = NULL;
+
+	return (SUCCESS);
+}
+
Index: trunk/src/kmk/lst.lib/lstRemove.c
===================================================================
--- trunk/src/kmk/lst.lib/lstRemove.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstRemove.c	(revision 9)
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstRemove.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstRemove.c,v 1.10 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstRemove.c --
+ *	Remove an element from a list
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Remove --
+ *	Remove the given node from the given list.
+ *
+ * Results:
+ *	SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ *	The list's firstPtr will be set to NULL if ln is the last
+ *	node on the list. firsPtr and lastPtr will be altered if ln is
+ *	either the first or last node, respectively, on the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Remove (l, ln)
+    Lst	    	  	l;
+    LstNode	  	ln;
+{
+    register List 	list = (List) l;
+    register ListNode	lNode = (ListNode) ln;
+
+    if (!LstValid (l) ||
+	!LstNodeValid (ln, l)) {
+	    return (FAILURE);
+    }
+
+    /*
+     * unlink it from the list
+     */
+    if (lNode->nextPtr != NULL) {
+	lNode->nextPtr->prevPtr = lNode->prevPtr;
+    }
+    if (lNode->prevPtr != NULL) {
+	lNode->prevPtr->nextPtr = lNode->nextPtr;
+    }
+
+    /*
+     * if either the firstPtr or lastPtr of the list point to this node,
+     * adjust them accordingly
+     */
+    if (list->firstPtr == lNode) {
+	list->firstPtr = lNode->nextPtr;
+    }
+    if (list->lastPtr == lNode) {
+	list->lastPtr = lNode->prevPtr;
+    }
+
+    /*
+     * Sequential access stuff. If the node we're removing is the current
+     * node in the list, reset the current node to the previous one. If the
+     * previous one was non-existent (prevPtr == NULL), we set the
+     * end to be Unknown, since it is.
+     */
+    if (list->isOpen && (list->curPtr == lNode)) {
+	list->curPtr = list->prevPtr;
+	if (list->curPtr == NULL) {
+	    list->atEnd = Unknown;
+	}
+    }
+
+    /*
+     * the only way firstPtr can still point to ln is if ln is the last
+     * node on the list (the list is circular, so lNode->nextptr == lNode in
+     * this case). The list is, therefore, empty and is marked as such
+     */
+    if (list->firstPtr == lNode) {
+	list->firstPtr = NULL;
+    }
+
+    /*
+     * note that the datum is unmolested. The caller must free it as
+     * necessary and as expected.
+     */
+    if (lNode->useCount == 0) {
+	free (ln);
+    } else {
+	lNode->flags |= LN_DELETED;
+    }
+
+    return (SUCCESS);
+}
+
Index: trunk/src/kmk/lst.lib/lstReplace.c
===================================================================
--- trunk/src/kmk/lst.lib/lstReplace.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstReplace.c	(revision 9)
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstReplace.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstReplace.c,v 1.9 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstReplace.c --
+ *	Replace the datum in a node with a new datum
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Replace --
+ *	Replace the datum in the given node with the new datum
+ *
+ * Results:
+ *	SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ *	The datum field fo the node is altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Replace (ln, d)
+    register LstNode	ln;
+    void *	  	d;
+{
+    if (ln == NULL) {
+	return (FAILURE);
+    } else {
+	((ListNode) ln)->datum = d;
+	return (SUCCESS);
+    }
+}
+
Index: trunk/src/kmk/lst.lib/lstSucc.c
===================================================================
--- trunk/src/kmk/lst.lib/lstSucc.c	(revision 9)
+++ trunk/src/kmk/lst.lib/lstSucc.c	(revision 9)
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)lstSucc.c	8.1 (Berkeley) 6/6/93
+ */
+
+#ifndef lint
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/lst.lib/lstSucc.c,v 1.8 2002/10/09 02:00:22 jmallett Exp $");
+#endif /* not lint */
+
+/*-
+ * LstSucc.c --
+ *	return the successor to a given node
+ */
+
+#include	"lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Succ --
+ *	Return the sucessor to the given node on its list.
+ *
+ * Results:
+ *	The successor of the node, if it exists (note that on a circular
+ *	list, if the node is the only one in the list, it is its own
+ *	successor).
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Succ (ln)
+    LstNode	ln;
+{
+    if (ln == NULL) {
+	return (NULL);
+    } else {
+	return ((LstNode) ((ListNode) ln)->nextPtr);
+    }
+}
+
Index: trunk/src/kmk/main.c
===================================================================
--- trunk/src/kmk/main.c	(revision 9)
+++ trunk/src/kmk/main.c	(revision 9)
@@ -0,0 +1,1139 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)main.c      8.3 (Berkeley) 3/19/94
+ */
+
+#ifndef lint
+#if 0
+static char copyright[] =
+"@(#) Copyright (c) 1988, 1989, 1990, 1993\n\
+	The Regents of the University of California.  All rights reserved.\n";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/main.c,v 1.79 2002/10/10 19:27:48 jmallett Exp $");
+
+/*-
+ * main.c --
+ *	The main file for this entire program. Exit routines etc
+ *	reside here.
+ *
+ * Utility functions defined in this file:
+ *	Main_ParseArgLine	Takes a line of arguments, breaks them and
+ *				treats them as if they were given when first
+ *				invoked. Used by the parse module to implement
+ *				the .MFLAGS target.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#if defined(__i386__)
+#include <sys/sysctl.h>
+#endif
+#ifndef MACHINE
+#include <sys/utsname.h>
+#endif
+#include <sys/wait.h>
+#include <err.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sysexits.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+
+#define WANT_ENV_MKLVL	1
+
+#ifndef	DEFMAXLOCAL
+#define	DEFMAXLOCAL DEFMAXJOBS
+#endif	/* DEFMAXLOCAL */
+
+#define	MAKEFLAGS	".MAKEFLAGS"
+
+Lst			create;		/* Targets to be made */
+time_t			now;		/* Time at start of make */
+GNode			*DEFAULT;	/* .DEFAULT node */
+Boolean			allPrecious;	/* .PRECIOUS given on line by itself */
+
+static Boolean		noBuiltins;	/* -r flag */
+static Lst		makefiles;	/* ordered list of makefiles to read */
+static Boolean		expandVars;	/* fully expand printed variables */
+static Lst		variables;	/* list of variables to print */
+int			maxJobs;	/* -j argument */
+static Boolean          forceJobs;      /* -j argument given */
+static int		maxLocal;	/* -L argument */
+Boolean			compatMake;	/* -B argument */
+Boolean			debug;		/* -d flag */
+Boolean			noExecute;	/* -n flag */
+Boolean			keepgoing;	/* -k flag */
+Boolean			queryFlag;	/* -q flag */
+Boolean			touchFlag;	/* -t flag */
+Boolean			usePipes;	/* !-P flag */
+Boolean			ignoreErrors;	/* -i flag */
+Boolean			beSilent;	/* -s flag */
+Boolean			beVerbose;	/* -v flag */
+Boolean			oldVars;	/* variable substitution style */
+Boolean			checkEnvFirst;	/* -e flag */
+Lst			envFirstVars;	/* (-E) vars to override from env */
+Boolean			jobsRunning;	/* TRUE if the jobs might be running */
+
+static void		MainParseArgs(int, char **);
+char *			chdir_verify_path(char *, char *);
+static int		ReadMakefile(void *, void *);
+static void		usage(void);
+
+static char *curdir;			/* startup directory */
+static char *objdir;			/* where we chdir'ed to */
+
+/*-
+ * MainParseArgs --
+ *	Parse a given argument vector. Called from main() and from
+ *	Main_ParseArgLine() when the .MAKEFLAGS target is used.
+ *
+ *	XXX: Deal with command line overriding .MAKEFLAGS in makefile
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Various global and local flags will be set depending on the flags
+ *	given
+ */
+static void
+MainParseArgs(int argc, char **argv)
+{
+	char *p;
+	int c;
+
+	optind = 1;	/* since we're called more than once */
+#ifdef REMOTE
+# define OPTFLAGS "BC:D:E:I:L:PSV:Xd:ef:ij:km:nqrstv"
+#else
+# define OPTFLAGS "BC:D:E:I:PSV:Xd:ef:ij:km:nqrstv"
+#endif
+rearg:	while((c = getopt(argc, argv, OPTFLAGS)) != -1) {
+		switch(c) {
+		case 'C':
+			chdir(optarg);
+			break;
+		case 'D':
+			Var_Set(optarg, "1", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+			break;
+		case 'I':
+			Parse_AddIncludeDir(optarg);
+			Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+			break;
+		case 'V':
+			(void)Lst_AtEnd(variables, (void *)optarg);
+			Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+			break;
+		case 'X':
+			expandVars = FALSE;
+			break;
+		case 'B':
+			compatMake = TRUE;
+			Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL);
+			break;
+#ifdef REMOTE
+		case 'L': {
+			char *endptr;
+
+			maxLocal = strtol(optarg, &endptr, 10);
+			if (maxLocal < 0 || *endptr != '\0') {
+				warnx("illegal number, -L argument -- %s",
+				    optarg);
+				usage();
+			}
+			Var_Append(MAKEFLAGS, "-L", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+			break;
+		}
+#endif
+		case 'P':
+			usePipes = FALSE;
+			Var_Append(MAKEFLAGS, "-P", VAR_GLOBAL);
+			break;
+		case 'S':
+			keepgoing = FALSE;
+			Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL);
+			break;
+		case 'd': {
+			char *modules = optarg;
+
+			for (; *modules; ++modules)
+				switch (*modules) {
+				case 'A':
+					debug = ~0;
+					break;
+				case 'a':
+					debug |= DEBUG_ARCH;
+					break;
+				case 'c':
+					debug |= DEBUG_COND;
+					break;
+				case 'd':
+					debug |= DEBUG_DIR;
+					break;
+				case 'f':
+					debug |= DEBUG_FOR;
+					break;
+				case 'g':
+					if (modules[1] == '1') {
+						debug |= DEBUG_GRAPH1;
+						++modules;
+					}
+					else if (modules[1] == '2') {
+						debug |= DEBUG_GRAPH2;
+						++modules;
+					}
+					break;
+				case 'j':
+					debug |= DEBUG_JOB;
+					break;
+				case 'l':
+					debug |= DEBUG_LOUD;
+					break;
+				case 'm':
+					debug |= DEBUG_MAKE;
+					break;
+				case 's':
+					debug |= DEBUG_SUFF;
+					break;
+				case 't':
+					debug |= DEBUG_TARG;
+					break;
+				case 'v':
+					debug |= DEBUG_VAR;
+					break;
+				default:
+					warnx("illegal argument to d option -- %c", *modules);
+					usage();
+				}
+			Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+			break;
+		}
+		case 'E':
+			p = emalloc(strlen(optarg) + 1);
+			(void)strcpy(p, optarg);
+			(void)Lst_AtEnd(envFirstVars, (void *)p);
+			Var_Append(MAKEFLAGS, "-E", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+			break;
+		case 'e':
+			checkEnvFirst = TRUE;
+			Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL);
+			break;
+		case 'f':
+			(void)Lst_AtEnd(makefiles, (void *)optarg);
+			break;
+		case 'i':
+			ignoreErrors = TRUE;
+			Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL);
+			break;
+		case 'j': {
+			char *endptr;
+
+			forceJobs = TRUE;
+			maxJobs = strtol(optarg, &endptr, 10);
+			if (maxJobs <= 0 || *endptr != '\0') {
+				warnx("illegal number, -j argument -- %s",
+				    optarg);
+				usage();
+			}
+#ifndef REMOTE
+			maxLocal = maxJobs;
+#endif
+			Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+			break;
+		}
+		case 'k':
+			keepgoing = TRUE;
+			Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL);
+			break;
+		case 'm':
+			Dir_AddDir(sysIncPath, optarg);
+			Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL);
+			Var_Append(MAKEFLAGS, optarg, VAR_GLOBAL);
+			break;
+		case 'n':
+			noExecute = TRUE;
+			Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL);
+			break;
+		case 'q':
+			queryFlag = TRUE;
+			/* Kind of nonsensical, wot? */
+			Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL);
+			break;
+		case 'r':
+			noBuiltins = TRUE;
+			Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL);
+			break;
+		case 's':
+			beSilent = TRUE;
+			Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL);
+			break;
+		case 't':
+			touchFlag = TRUE;
+			Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL);
+			break;
+		case 'v':
+			beVerbose = TRUE;
+			Var_Append(MAKEFLAGS, "-v", VAR_GLOBAL);
+			break;
+		default:
+		case '?':
+			usage();
+		}
+	}
+
+	oldVars = TRUE;
+
+	/*
+	 * See if the rest of the arguments are variable assignments and
+	 * perform them if so. Else take them to be targets and stuff them
+	 * on the end of the "create" list.
+	 */
+	for (argv += optind, argc -= optind; *argv; ++argv, --argc)
+		if (Parse_IsVar(*argv))
+			Parse_DoVar(*argv, VAR_CMD);
+		else {
+			if (!**argv)
+				Punt("illegal (null) argument.");
+			if (**argv == '-') {
+				if ((*argv)[1])
+					optind = 0;     /* -flag... */
+				else
+					optind = 1;     /* - */
+				goto rearg;
+			}
+			(void)Lst_AtEnd(create, (void *)estrdup(*argv));
+		}
+}
+
+/*-
+ * Main_ParseArgLine --
+ *  	Used by the parse module when a .MFLAGS or .MAKEFLAGS target
+ *	is encountered and by main() when reading the .MAKEFLAGS envariable.
+ *	Takes a line of arguments and breaks it into its
+ * 	component words and passes those words and the number of them to the
+ *	MainParseArgs function.
+ *	The line should have all its leading whitespace removed.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Only those that come from the various arguments.
+ */
+void
+Main_ParseArgLine(char *line)
+{
+	char **argv;			/* Manufactured argument vector */
+	int argc;			/* Number of arguments in argv */
+
+	if (line == NULL)
+		return;
+	for (; *line == ' '; ++line)
+		continue;
+	if (!*line)
+		return;
+
+	argv = brk_string(line, &argc, TRUE);
+	MainParseArgs(argc, argv);
+}
+
+char *
+chdir_verify_path(char *path, char *obpath)
+{
+	struct stat sb;
+
+	if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
+		if (chdir(path) == -1 || getcwd(obpath, MAXPATHLEN) == NULL) {
+			warn("warning: %s", path);
+			return 0;
+		}
+		return obpath;
+	}
+
+	return 0;
+}
+
+
+/*-
+ * main --
+ *	The main function, for obvious reasons. Initializes variables
+ *	and a few modules, then parses the arguments give it in the
+ *	environment and on the command line. Reads the system makefile
+ *	followed by either Makefile, makefile or the file given by the
+ *	-f argument. Sets the .MAKEFLAGS PMake variable based on all the
+ *	flags it has received by then uses either the Make or the Compat
+ *	module to create the initial list of targets.
+ *
+ * Results:
+ *	If -q was given, exits -1 if anything was out-of-date. Else it exits
+ *	0.
+ *
+ * Side Effects:
+ *	The program exits when done. Targets are created. etc. etc. etc.
+ */
+int
+main(int argc, char **argv)
+{
+	Lst targs;	/* target nodes to create -- passed to Make_Init */
+	Boolean outOfDate = TRUE; 	/* FALSE if all targets up to date */
+	struct stat sa;
+	char *p, *p1, *path, *pathp;
+#ifdef WANT_ENV_MKLVL
+#define	MKLVL_MAXVAL	500
+#define	MKLVL_ENVVAR	"__MKLVL__"
+	int iMkLvl = 0;
+	char *szMkLvl = getenv(MKLVL_ENVVAR);
+#endif	/* WANT_ENV_MKLVL */
+	char mdpath[MAXPATHLEN];
+	char obpath[MAXPATHLEN];
+	char cdpath[MAXPATHLEN];
+    	char *machine = getenv("MACHINE");
+	char *machine_arch = getenv("MACHINE_ARCH");
+	char *machine_cpu = getenv("MACHINE_CPU");
+	Lst sysMkPath;			/* Path of sys.mk */
+	char *cp = NULL, *start;
+					/* avoid faults on read-only strings */
+	static char syspath[] = _PATH_DEFSYSPATH;
+
+#ifdef WANT_ENV_MKLVL
+	if ((iMkLvl = szMkLvl ? atoi(szMkLvl) : 0) < 0) {
+	  iMkLvl = 0;
+	}
+	if (iMkLvl++ > MKLVL_MAXVAL) {
+	  errc(2, EAGAIN, 
+	       "Max recursion level (%d) exceeded.", MKLVL_MAXVAL);
+	}
+	bzero(szMkLvl = emalloc(32), 32);
+	sprintf(szMkLvl, "%d", iMkLvl);
+	setenv(MKLVL_ENVVAR, szMkLvl, 1);
+#endif /* WANT_ENV_MKLVL */
+
+#if DEFSHELL == 2
+	/*
+	 * Turn off ENV to make ksh happier.
+	 */
+	unsetenv("ENV");
+#endif
+
+#ifdef RLIMIT_NOFILE
+	/*
+	 * get rid of resource limit on file descriptors
+	 */
+	{
+		struct rlimit rl;
+		if (getrlimit(RLIMIT_NOFILE, &rl) != -1 &&
+		    rl.rlim_cur != rl.rlim_max) {
+			rl.rlim_cur = rl.rlim_max;
+			(void) setrlimit(RLIMIT_NOFILE, &rl);
+		}
+	}
+#endif
+	/*
+	 * Find where we are...
+	 * All this code is so that we know where we are when we start up
+	 * on a different machine with pmake.
+	 */
+	curdir = cdpath;
+	if (getcwd(curdir, MAXPATHLEN) == NULL)
+		err(2, NULL);
+
+	if (stat(curdir, &sa) == -1)
+	    err(2, "%s", curdir);
+
+#if defined(__i386__) && defined(__FreeBSD_version) && \
+    __FreeBSD_version > 300003
+	/*
+	 * PC-98 kernel sets the `i386' string to the utsname.machine and
+	 * it cannot be distinguished from IBM-PC by uname(3).  Therefore,
+	 * we check machine.ispc98 and adjust the machine variable before
+	 * using usname(3) below.
+	 * NOTE: machdep.ispc98 was defined on 1998/8/31. At that time,
+	 * __FreeBSD_version was defined as 300003. So, this check can
+	 * safely be done with any kernel with version > 300003.
+	 */
+	if (!machine) {
+		int	ispc98;
+		size_t	len;
+
+		len = sizeof(ispc98);
+		if (!sysctlbyname("machdep.ispc98", &ispc98, &len, NULL, 0)) {
+			if (ispc98)
+				machine = "pc98";
+		}
+	}
+#endif
+
+	/*
+	 * Get the name of this type of MACHINE from utsname
+	 * so we can share an executable for similar machines.
+	 * (i.e. m68k: amiga hp300, mac68k, sun3, ...)
+	 *
+	 * Note that while MACHINE is decided at run-time,
+	 * MACHINE_ARCH is always known at compile time.
+	 */
+	if (!machine) {
+#ifndef MACHINE
+	    struct utsname utsname;
+
+	    if (uname(&utsname) == -1)
+		    err(2, "uname");
+	    machine = utsname.machine;
+#else
+	    machine = MACHINE;
+#endif
+	}
+
+	if (!machine_arch) {
+#ifndef MACHINE_ARCH
+		machine_arch = "unknown";
+#else
+		machine_arch = MACHINE_ARCH;
+#endif
+	}
+
+	/*
+	 * Set machine_cpu to the minumum supported CPU revision based
+	 * on the target architecture, if not already set.
+	 */
+	if (!machine_cpu) {
+		if (!strcmp(machine_arch, "i386"))
+			machine_cpu = "i386";
+		else if (!strcmp(machine_arch, "alpha"))
+			machine_cpu = "ev4";
+		else
+			machine_cpu = "unknown";
+	}
+	
+	/*
+	 * The object directory location is determined using the
+	 * following order of preference:
+	 *
+	 *	1. MAKEOBJDIRPREFIX`cwd`
+	 *	2. MAKEOBJDIR
+	 *	3. _PATH_OBJDIR.${MACHINE}
+	 *	4. _PATH_OBJDIR
+	 *	5. _PATH_OBJDIRPREFIX`cwd`
+	 *
+	 * If one of the first two fails, use the current directory.
+	 * If the remaining three all fail, use the current directory.
+	 *
+	 * Once things are initted,
+	 * have to add the original directory to the search path,
+	 * and modify the paths for the Makefiles apropriately.  The
+	 * current directory is also placed as a variable for make scripts.
+	 */
+	if (!(pathp = getenv("MAKEOBJDIRPREFIX"))) {
+		if (!(path = getenv("MAKEOBJDIR"))) {
+			path = _PATH_OBJDIR;
+			pathp = _PATH_OBJDIRPREFIX;
+			(void) snprintf(mdpath, MAXPATHLEN, "%s.%s",
+					path, machine);
+			if (!(objdir = chdir_verify_path(mdpath, obpath)))
+				if (!(objdir=chdir_verify_path(path, obpath))) {
+					(void) snprintf(mdpath, MAXPATHLEN,
+							"%s%s", pathp, curdir);
+					if (!(objdir=chdir_verify_path(mdpath,
+								       obpath)))
+						objdir = curdir;
+				}
+		}
+		else if (!(objdir = chdir_verify_path(path, obpath)))
+			objdir = curdir;
+	}
+	else {
+		(void) snprintf(mdpath, MAXPATHLEN, "%s%s", pathp, curdir);
+		if (!(objdir = chdir_verify_path(mdpath, obpath)))
+			objdir = curdir;
+	}
+
+	create = Lst_Init(FALSE);
+	makefiles = Lst_Init(FALSE);
+	envFirstVars = Lst_Init(FALSE);
+	expandVars = TRUE;
+	variables = Lst_Init(FALSE);
+	beSilent = FALSE;		/* Print commands as executed */
+	ignoreErrors = FALSE;		/* Pay attention to non-zero returns */
+	noExecute = FALSE;		/* Execute all commands */
+	keepgoing = FALSE;		/* Stop on error */
+	allPrecious = FALSE;		/* Remove targets when interrupted */
+	queryFlag = FALSE;		/* This is not just a check-run */
+	noBuiltins = FALSE;		/* Read the built-in rules */
+	touchFlag = FALSE;		/* Actually update targets */
+	usePipes = TRUE;		/* Catch child output in pipes */
+	debug = 0;			/* No debug verbosity, please. */
+	jobsRunning = FALSE;
+
+	maxLocal = DEFMAXLOCAL;		/* Set default local max concurrency */
+#ifdef REMOTE
+	maxJobs = DEFMAXJOBS;		/* Set default max concurrency */
+#else
+	maxJobs = maxLocal;
+#endif
+	forceJobs = FALSE;              /* No -j flag */
+	compatMake = FALSE;		/* No compat mode */
+
+
+	/*
+	 * Initialize the parsing, directory and variable modules to prepare
+	 * for the reading of inclusion paths and variable settings on the
+	 * command line
+	 */
+	Dir_Init();		/* Initialize directory structures so -I flags
+				 * can be processed correctly */
+	Parse_Init();		/* Need to initialize the paths of #include
+				 * directories */
+	Var_Init();		/* As well as the lists of variables for
+				 * parsing arguments */
+        str_init();
+	if (objdir != curdir)
+		Dir_AddDir(dirSearchPath, curdir);
+	Var_Set(".CURDIR", curdir, VAR_GLOBAL);
+	Var_Set(".OBJDIR", objdir, VAR_GLOBAL);
+
+	/*
+	 * Initialize various variables.
+	 *	MAKE also gets this name, for compatibility
+	 *	.MAKEFLAGS gets set to the empty string just in case.
+	 *	MFLAGS also gets initialized empty, for compatibility.
+	 */
+	Var_Set("MAKE", argv[0], VAR_GLOBAL);
+	Var_Set(MAKEFLAGS, "", VAR_GLOBAL);
+	Var_Set("MFLAGS", "", VAR_GLOBAL);
+	Var_Set("MACHINE", machine, VAR_GLOBAL);
+	Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL);
+	Var_Set("MACHINE_CPU", machine_cpu, VAR_GLOBAL);
+#ifdef MAKE_VERSION
+	Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL);
+#endif
+
+	/*
+	 * First snag any flags out of the MAKE environment variable.
+	 * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's
+	 * in a different format).
+	 */
+#ifdef POSIX
+	Main_ParseArgLine(getenv("MAKEFLAGS"));
+#else
+	Main_ParseArgLine(getenv("MAKE"));
+#endif
+
+	MainParseArgs(argc, argv);
+
+	/*
+	 * Be compatible if user did not specify -j and did not explicitly
+	 * turned compatibility on
+	 */
+	if (!compatMake && !forceJobs)
+		compatMake = TRUE;
+
+	/*
+	 * Initialize archive, target and suffix modules in preparation for
+	 * parsing the makefile(s)
+	 */
+	Arch_Init();
+	Targ_Init();
+	Suff_Init();
+
+	DEFAULT = NULL;
+	(void)time(&now);
+
+	/*
+	 * Set up the .TARGETS variable to contain the list of targets to be
+	 * created. If none specified, make the variable empty -- the parser
+	 * will fill the thing in with the default or .MAIN target.
+	 */
+	if (!Lst_IsEmpty(create)) {
+		LstNode ln;
+
+		for (ln = Lst_First(create); ln != NULL;
+		    ln = Lst_Succ(ln)) {
+			char *name = (char *)Lst_Datum(ln);
+
+			Var_Append(".TARGETS", name, VAR_GLOBAL);
+		}
+	} else
+		Var_Set(".TARGETS", "", VAR_GLOBAL);
+
+
+	/*
+	 * If no user-supplied system path was given (through the -m option)
+	 * add the directories from the DEFSYSPATH (more than one may be given
+	 * as dir1:...:dirn) to the system include path.
+	 */
+	if (Lst_IsEmpty(sysIncPath)) {
+		for (start = syspath; *start != '\0'; start = cp) {
+			for (cp = start; *cp != '\0' && *cp != ':'; cp++)
+				continue;
+			if (*cp == '\0') {
+				Dir_AddDir(sysIncPath, start);
+			} else {
+				*cp++ = '\0';
+				Dir_AddDir(sysIncPath, start);
+			}
+		}
+	}
+
+	/*
+	 * Read in the built-in rules first, followed by the specified
+	 * makefile, if it was (makefile != (char *) NULL), or the default
+	 * Makefile and makefile, in that order, if it wasn't.
+	 */
+	if (!noBuiltins) {
+		LstNode ln;
+
+		sysMkPath = Lst_Init (FALSE);
+		Dir_Expand (_PATH_DEFSYSMK, sysIncPath, sysMkPath);
+		if (Lst_IsEmpty(sysMkPath))
+			Fatal("make: no system rules (%s).", _PATH_DEFSYSMK);
+		ln = Lst_Find(sysMkPath, (void *)NULL, ReadMakefile);
+		if (ln != NULL)
+			Fatal("make: cannot open %s.", (char *)Lst_Datum(ln));
+	}
+
+	if (!Lst_IsEmpty(makefiles)) {
+		LstNode ln;
+
+		ln = Lst_Find(makefiles, (void *)NULL, ReadMakefile);
+		if (ln != NULL)
+			Fatal("make: cannot open %s.", (char *)Lst_Datum(ln));
+	} else if (!ReadMakefile("BSDmakefile", NULL))
+	    if (!ReadMakefile("makefile", NULL))
+		(void)ReadMakefile("Makefile", NULL);
+
+	(void)ReadMakefile(".depend", NULL);
+
+	Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL);
+	efree(p1);
+
+	/* Install all the flags into the MAKE envariable. */
+	if (((p = Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1)) != NULL) && *p)
+#ifdef POSIX
+		setenv("MAKEFLAGS", p, 1);
+#else
+		setenv("MAKE", p, 1);
+#endif
+	efree(p1);
+
+	/*
+	 * For compatibility, look at the directories in the VPATH variable
+	 * and add them to the search path, if the variable is defined. The
+	 * variable's value is in the same format as the PATH envariable, i.e.
+	 * <directory>:<directory>:<directory>...
+	 */
+	if (Var_Exists("VPATH", VAR_CMD)) {
+		char *vpath, savec;
+		/*
+		 * GCC stores string constants in read-only memory, but
+		 * Var_Subst will want to write this thing, so store it
+		 * in an array
+		 */
+		static char VPATH[] = "${VPATH}";
+
+		vpath = Var_Subst(NULL, VPATH, VAR_CMD, FALSE);
+		path = vpath;
+		do {
+			/* skip to end of directory */
+			for (cp = path; *cp != ':' && *cp != '\0'; cp++)
+				continue;
+			/* Save terminator character so know when to stop */
+			savec = *cp;
+			*cp = '\0';
+			/* Add directory to search path */
+			Dir_AddDir(dirSearchPath, path);
+			*cp = savec;
+			path = cp + 1;
+		} while (savec == ':');
+		(void)free(vpath);
+	}
+
+	/*
+	 * Now that all search paths have been read for suffixes et al, it's
+	 * time to add the default search path to their lists...
+	 */
+	Suff_DoPaths();
+
+	/* print the initial graph, if the user requested it */
+	if (DEBUG(GRAPH1))
+		Targ_PrintGraph(1);
+
+	/* print the values of any variables requested by the user */
+	if (!Lst_IsEmpty(variables)) {
+		LstNode ln;
+
+		for (ln = Lst_First(variables); ln != NULL;
+		    ln = Lst_Succ(ln)) {
+			char *value;
+			if (expandVars) {
+				p1 = emalloc(strlen((char *)Lst_Datum(ln)) + 1 + 3);
+				/* This sprintf is safe, because of the malloc above */
+				(void)sprintf(p1, "${%s}", (char *)Lst_Datum(ln));
+				value = Var_Subst(NULL, p1, VAR_GLOBAL, FALSE);
+			} else {
+				value = Var_Value((char *)Lst_Datum(ln),
+						  VAR_GLOBAL, &p1);
+			}
+			printf("%s\n", value ? value : "");
+			if (p1)
+				free(p1);
+		}
+	} else {
+
+		/*
+		 * Have now read the entire graph and need to make a list of targets
+		 * to create. If none was given on the command line, we consult the
+		 * parsing module to find the main target(s) to create.
+		 */
+		if (Lst_IsEmpty(create))
+			targs = Parse_MainName();
+		else
+			targs = Targ_FindList(create, TARG_CREATE);
+
+		if (!compatMake) {
+			/*
+			 * Initialize job module before traversing the graph, now that
+			 * any .BEGIN and .END targets have been read.  This is done
+			 * only if the -q flag wasn't given (to prevent the .BEGIN from
+			 * being executed should it exist).
+			 */
+			if (!queryFlag) {
+				if (maxLocal == -1)
+					maxLocal = maxJobs;
+				Job_Init(maxJobs, maxLocal);
+				jobsRunning = TRUE;
+			}
+
+			/* Traverse the graph, checking on all the targets */
+			outOfDate = Make_Run(targs);
+		} else {
+			/*
+			 * Compat_Init will take care of creating all the targets as
+			 * well as initializing the module.
+			 */
+			Compat_Run(targs);
+		}
+		Lst_Destroy(targs, NOFREE);
+	}
+
+	Lst_Destroy(variables, NOFREE);
+	Lst_Destroy(makefiles, NOFREE);
+	Lst_Destroy(create, (void (*)(void *)) free);
+
+	/* print the graph now it's been processed if the user requested it */
+	if (DEBUG(GRAPH2))
+		Targ_PrintGraph(2);
+
+	Suff_End();
+        Targ_End();
+	Arch_End();
+	str_end();
+	Var_End();
+	Parse_End();
+	Dir_End();
+
+	if (queryFlag && outOfDate)
+		return(1);
+	else
+		return(0);
+}
+
+/*-
+ * ReadMakefile  --
+ *	Open and parse the given makefile.
+ *
+ * Results:
+ *	TRUE if ok. FALSE if couldn't open file.
+ *
+ * Side Effects:
+ *	lots
+ */
+static Boolean
+ReadMakefile(void *p, void *q __unused)
+{
+	char *fname;			/* makefile to read */
+	FILE *stream;
+	char *name, path[MAXPATHLEN];
+	char *MAKEFILE;
+	int setMAKEFILE;
+
+	fname = p;
+
+	if (!strcmp(fname, "-")) {
+		Parse_File("(stdin)", stdin);
+		Var_Set("MAKEFILE", "", VAR_GLOBAL);
+	} else {
+		setMAKEFILE = strcmp(fname, ".depend");
+
+		/* if we've chdir'd, rebuild the path name */
+		if (curdir != objdir && *fname != '/') {
+			(void)snprintf(path, MAXPATHLEN, "%s/%s", curdir, fname);
+			/*
+			 * XXX The realpath stuff breaks relative includes
+			 * XXX in some cases.   The problem likely is in
+			 * XXX parse.c where it does special things in
+			 * XXX ParseDoInclude if the file is relateive
+			 * XXX or absolute and not a system file.  There
+			 * XXX it assumes that if the current file that's
+			 * XXX being included is absolute, that any files
+			 * XXX that it includes shouldn't do the -I path
+			 * XXX stuff, which is inconsistant with historical
+			 * XXX behavior.  However, I can't pentrate the mists
+			 * XXX further, so I'm putting this workaround in
+			 * XXX here until such time as the underlying bug
+			 * XXX can be fixed.
+			 */
+#if THIS_BREAKS_THINGS
+			if (realpath(path, path) != NULL &&
+			    (stream = fopen(path, "r")) != NULL) {
+				MAKEFILE = fname;
+				fname = path;
+				goto found;
+			}
+		} else if (realpath(fname, path) != NULL) {
+			MAKEFILE = fname;
+			fname = path;
+			if ((stream = fopen(fname, "r")) != NULL)
+				goto found;
+		}
+#else
+			if ((stream = fopen(path, "r")) != NULL) {
+				MAKEFILE = fname;
+				fname = path;
+				goto found;
+			}
+		} else {
+			MAKEFILE = fname;
+			if ((stream = fopen(fname, "r")) != NULL)
+				goto found;
+		}
+#endif
+		/* look in -I and system include directories. */
+		name = Dir_FindFile(fname, parseIncPath);
+		if (!name)
+			name = Dir_FindFile(fname, sysIncPath);
+		if (!name || !(stream = fopen(name, "r")))
+			return(FALSE);
+		MAKEFILE = fname = name;
+		/*
+		 * set the MAKEFILE variable desired by System V fans -- the
+		 * placement of the setting here means it gets set to the last
+		 * makefile specified, as it is set by SysV make.
+		 */
+found:
+		if (setMAKEFILE)
+			Var_Set("MAKEFILE", MAKEFILE, VAR_GLOBAL);
+		Parse_File(fname, stream);
+		(void)fclose(stream);
+	}
+	return(TRUE);
+}
+
+/*-
+ * Cmd_Exec --
+ *	Execute the command in cmd, and return the output of that command
+ *	in a string.
+ *
+ * Results:
+ *	A string containing the output of the command, or the empty string
+ *	If error is not NULL, it contains the reason for the command failure
+ *
+ * Side Effects:
+ *	The string must be freed by the caller.
+ */
+char *
+Cmd_Exec(char *cmd, char **error)
+{
+    char	*args[4];   	/* Args for invoking the shell */
+    int 	fds[2];	    	/* Pipe streams */
+    int 	cpid;	    	/* Child PID */
+    int 	pid;	    	/* PID from wait() */
+    char	*res;		/* result */
+    int		status;		/* command exit status */
+    Buffer	buf;		/* buffer to store the result */
+    char	*cp;
+    int		cc;
+
+    *error = NULL;
+
+    /*
+     * Set up arguments for shell
+     */
+    args[0] = "sh";
+    args[1] = "-c";
+    args[2] = cmd;
+    args[3] = NULL;
+
+    /*
+     * Open a pipe for fetching its output
+     */
+    if (pipe(fds) == -1) {
+	*error = "Couldn't create pipe for \"%s\"";
+	goto bad;
+    }
+
+    /*
+     * Fork
+     */
+    switch (cpid = vfork()) {
+    case 0:
+	/*
+	 * Close input side of pipe
+	 */
+	(void) close(fds[0]);
+
+	/*
+	 * Duplicate the output stream to the shell's output, then
+	 * shut the extra thing down. Note we don't fetch the error
+	 * stream...why not? Why?
+	 */
+	(void) dup2(fds[1], 1);
+	(void) close(fds[1]);
+
+#if defined(DEFSHELL) && DEFSHELL == 0
+	(void) execv("/bin/csh", args);
+#elif DEFSHELL == 1
+	(void) execv("/bin/sh", args);
+#elif DEFSHELL == 2
+	(void) execv("/bin/ksh", args);
+#else
+#error "DEFSHELL must be 1 or 2."
+#endif
+	_exit(1);
+	/*NOTREACHED*/
+
+    case -1:
+	*error = "Couldn't exec \"%s\"";
+	goto bad;
+
+    default:
+	/*
+	 * No need for the writing half
+	 */
+	(void) close(fds[1]);
+
+	buf = Buf_Init (MAKE_BSIZE);
+
+	do {
+	    char   result[BUFSIZ];
+	    cc = read(fds[0], result, sizeof(result));
+	    if (cc > 0)
+		Buf_AddBytes(buf, cc, (Byte *) result);
+	}
+	while (cc > 0 || (cc == -1 && errno == EINTR));
+
+	/*
+	 * Close the input side of the pipe.
+	 */
+	(void) close(fds[0]);
+
+	/*
+	 * Wait for the process to exit.
+	 */
+	while(((pid = wait(&status)) != cpid) && (pid >= 0))
+	    continue;
+
+	if (cc == -1)
+	    *error = "Error reading shell's output for \"%s\"";
+
+	res = (char *)Buf_GetAll (buf, &cc);
+	Buf_Destroy (buf, FALSE);
+
+	if (status)
+	    *error = "\"%s\" returned non-zero status";
+
+	/*
+	 * Null-terminate the result, convert newlines to spaces and
+	 * install it in the variable.
+	 */
+	res[cc] = '\0';
+	cp = &res[cc] - 1;
+
+	if (*cp == '\n') {
+	    /*
+	     * A final newline is just stripped
+	     */
+	    *cp-- = '\0';
+	}
+	while (cp >= res) {
+	    if (*cp == '\n') {
+		*cp = ' ';
+	    }
+	    cp--;
+	}
+	break;
+    }
+    return res;
+bad:
+    res = emalloc(1);
+    *res = '\0';
+    return res;
+}
+
+/*
+ * usage --
+ *	exit with usage message
+ */
+static void
+usage(void)
+{
+	(void)fprintf(stderr, "%s\n%s\n%s\n",
+"usage: make [-Beiknqrstv] [-D variable] [-d flags] [-E variable] [-f makefile]",
+"            [-I directory] [-j max_jobs] [-m directory] [-V variable]",
+"            [variable=value] [target ...]");
+	exit(2);
+}
Index: trunk/src/kmk/make.1
===================================================================
--- trunk/src/kmk/make.1	(revision 9)
+++ trunk/src/kmk/make.1	(revision 9)
@@ -0,0 +1,1278 @@
+.\" Copyright (c) 1990, 1993
+.\"	The Regents of the University of California.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\"    must display the following acknowledgement:
+.\"	This product includes software developed by the University of
+.\"	California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"	@(#)make.1	8.8 (Berkeley) 6/13/95
+.\" $FreeBSD: src/usr.bin/make/make.1,v 1.59 2002/09/27 09:21:07 ru Exp $
+.\"
+.Dd June 13, 1995
+.Dt MAKE 1
+.Os
+.Sh NAME
+.Nm make
+.Nd maintain program dependencies
+.Sh SYNOPSIS
+.Nm
+.Op Fl BPSXeiknqrstv
+.Op Fl C Ar directory
+.Op Fl D Ar variable
+.Op Fl d Ar flags
+.Op Fl E Ar variable
+.Op Fl f Ar makefile
+.Op Fl I Ar directory
+.Bk -words
+.Op Fl j Ar max_jobs
+.Op Fl m Ar directory
+.Ek
+.Op Fl V Ar variable
+.Op Ar variable Ns No = Ns Ar value
+.Op Ar target ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is a program designed to simplify the maintenance of other programs.
+Its input is a list of specifications
+describing dependency relationships between the generation of
+files and programs.
+The first of
+.Ql Pa BSDmakefile ,
+.Ql Pa makefile
+and
+.Ql Pa Makefile
+that can be found in either the current directory or a special object directory
+(see
+.Ql Va .OBJDIR )
+will be read for this list of specifications.
+If the file
+.Ql Pa .depend
+can be found, it is also read (see
+.Xr mkdep 1 ) .
+.Pp
+This manual page is intended as a reference document only.
+For a more thorough introduction to
+.Nm
+and makefiles, please refer to
+.%T "Make \- A Tutorial" .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl B
+Try to be backwards compatible by executing a single shell per command and
+by executing the commands to make the sources of a dependency line in sequence.
+This is turned on by default unless
+.Fl j
+is used.
+.It Fl C Ar directory
+Change to
+.Ar directory
+while running.
+.It Fl D Ar variable
+Define
+.Ar variable
+to be 1, in the global context.
+.It Fl d Ar flags
+Turn on debugging, and specify which portions of
+.Nm
+are to print debugging information.
+Argument
+.Ar flags
+is one or more of the following:
+.Bl -tag -width Ds
+.It Ar A
+Print all possible debugging information;
+equivalent to specifying all of the debugging flags.
+.It Ar a
+Print debugging information about archive searching and caching.
+.It Ar c
+Print debugging information about conditional evaluation.
+.It Ar d
+Print debugging information about directory searching and caching.
+.It Ar f
+Print debugging information about the execution of for loops.
+Currently a
+no-op.
+.It Ar "g1"
+Print the input graph before making anything.
+.It Ar "g2"
+Print the input graph after making everything, or before exiting
+on error.
+.It Ar j
+Print debugging information about running multiple shells.
+.It Ar l
+Print commands in Makefiles regardless of whether or not they are prefixed
+by @ or other "quiet" flags.
+Also known as "loud" behavior.
+.It Ar m
+Print debugging information about making targets, including modification
+dates.
+.It Ar s
+Print debugging information about suffix-transformation rules.
+.It Ar t
+Print debugging information about target list maintenance.
+.It Ar v
+Print debugging information about variable assignment.
+.El
+.It Fl E Ar variable
+Specify a variable whose environment value (if any) will override
+macro assignments within makefiles.
+.It Fl e
+Specify that environment values override macro assignments within
+makefiles for all variables.
+.It Fl f Ar makefile
+Specify a makefile to read instead of the default
+.Ql Pa makefile
+and
+.Ql Pa Makefile .
+If
+.Ar makefile
+is
+.Ql \- ,
+standard input is read.
+Multiple makefiles may be specified, and are read in the order specified.
+.It Fl I Ar directory
+Specify a directory in which to search for makefiles and included makefiles.
+The system makefile directory (or directories, see the
+.Fl m
+option) is automatically included as part of this list.
+.It Fl i
+Ignore non-zero exit of shell commands in the makefile.
+Equivalent to specifying
+.Ql \-
+before each command line in the makefile.
+.It Fl j Ar max_jobs
+Specify the maximum number of jobs that
+.Nm
+may have running at any one time.
+Turns compatibility mode off, unless the
+.Ar B
+flag is also specified.
+.It Fl k
+Continue processing after errors are encountered, but only on those targets
+that do not depend on the target whose creation caused the error.
+.It Fl m Ar directory
+Specify a directory in which to search for
+.Pa sys.mk
+and makefiles included via the <...> style.
+Multiple directories can be added to form a search path.
+This path will override the default system include path:
+.Pa /usr/share/mk .
+Furthermore, the system include path will be appended to the search path used
+for "..."-style inclusions (see the
+.Fl I
+option).
+.It Fl n
+Display the commands that would have been executed, but do not actually
+execute them.
+.It Fl P
+Collate the output of a given job and display it only when the job finishes,
+instead of mixing the output of parallel jobs together.
+This option has no effect unless
+.Fl j
+is used too.
+.It Fl q
+Do not execute any commands, but exit 0 if the specified targets are
+up-to-date and 1, otherwise.
+.It Fl r
+Do not use the built-in rules specified in the system makefile.
+.It Fl S
+Stop processing when an error is encountered.
+Default behaviour.
+This is needed to negate the
+.Fl k
+option during recursive builds.
+.It Fl s
+Do not echo any commands as they are executed.
+Equivalent to specifying
+.Ql Ic @
+before each command line in the makefile.
+.It Fl t
+Rather than re-building a target as specified in the makefile, create it
+or update its modification time to make it appear up-to-date.
+.It Fl V Ar variable
+Print
+.Nm Ns 's
+idea of the value of
+.Ar variable ,
+in the global context.
+Do not build any targets.
+Multiple instances of this option may be specified;
+the variables will be printed one per line,
+with a blank line for each null or undefined variable.
+.It Fl v
+Be extra verbose.
+For multi-job makes, this will cause file banners to be generated.
+.It Fl X
+When using the
+.Fl V
+option to print the values of variables,
+do not recursively expand the values.
+.It Ar variable Ns No = Ns Ar value
+Set the value of the variable
+.Ar variable
+to
+.Ar value .
+.El
+.Pp
+There are seven different types of lines in a makefile: file dependency
+specifications, shell commands, variable assignments, include statements,
+conditional directives, for loops, and comments.
+.Pp
+In general, lines may be continued from one line to the next by ending
+them with a backslash
+.Pq Ql \e .
+The trailing newline character and initial whitespace on the following
+line are compressed into a single space.
+.Sh FILE DEPENDENCY SPECIFICATIONS
+Dependency lines consist of one or more targets, an operator, and zero
+or more sources.
+This creates a relationship where the targets
+.Dq depend
+on the sources
+and are usually created from them.
+The exact relationship between the target and the source is determined
+by the operator that separates them.
+The three operators are as follows:
+.Bl -tag -width flag
+.It Ic \&:
+A target is considered out-of-date if its modification time is less than
+those of any of its sources.
+Sources for a target accumulate over dependency lines when this operator
+is used.
+The target is removed if
+.Nm
+is interrupted.
+.It Ic \&!
+Targets are always re-created, but not until all sources have been
+examined and re-created as necessary.
+Sources for a target accumulate over dependency lines when this operator
+is used.
+The target is removed if
+.Nm
+is interrupted.
+.It Ic \&::
+If no sources are specified, the target is always re-created.
+Otherwise, a target is considered out-of-date if any of its sources has
+been modified more recently than the target.
+Sources for a target do not accumulate over dependency lines when this
+operator is used.
+The target will not be removed if
+.Nm
+is interrupted.
+.El
+.Pp
+Targets and sources may contain the shell wildcard expressions
+.Ql \&? ,
+.Ql * ,
+.Ql []
+and
+.Ql {} .
+The expressions
+.Ql \&? ,
+.Ql *
+and
+.Ql []
+may only be used as part of the final
+component of the target or source, and must be used to describe existing
+files.
+The expression
+.Ql {}
+need not necessarily be used to describe existing files.
+Expansion is in directory order, not alphabetically as done in the shell.
+.Sh SHELL COMMANDS
+Each target may have associated with it a series of shell commands, normally
+used to create the target.
+Each of the commands in this script
+.Em must
+be preceded by a tab.
+While any target may appear on a dependency line, only one of these
+dependencies may be followed by a creation script, unless the
+.Ql Ic ::
+operator is used.
+.Pp
+If the first or first two characters of the command line are
+.Ql Ic @
+and/or
+.Ql Ic \- ,
+the command is treated specially.
+A
+.Ql Ic @
+causes the command not to be echoed before it is executed.
+A
+.Ql Ic \-
+causes any non-zero exit status of the command line to be ignored.
+.Sh VARIABLE ASSIGNMENTS
+Variables in
+.Nm
+are much like variables in the shell, and, by tradition,
+consist of all upper-case letters.
+The five operators that can be used to assign values to variables are as
+follows:
+.Bl -tag -width Ds
+.It Ic \&=
+Assign the value to the variable.
+Any previous value is overridden.
+.It Ic \&+=
+Append the value to the current value of the variable.
+.It Ic \&?=
+Assign the value to the variable if it is not already defined.
+.It Ic \&:=
+Assign with expansion, i.e. expand the value before assigning it
+to the variable.
+Normally, expansion is not done until the variable is referenced.
+.It Ic \&!=
+Expand the value and pass it to the shell for execution and assign
+the result to the variable.
+Any newlines in the result are replaced with spaces.
+.El
+.Pp
+Any whitespace before the assigned
+.Ar value
+is removed; if the value is being appended, a single space is inserted
+between the previous contents of the variable and the appended value.
+.Pp
+Variables are expanded by surrounding the variable name with either
+curly braces
+.Pq Ql {}
+or parentheses
+.Pq Ql ()
+and preceding it with
+a dollar sign
+.Pq Ql \&$ .
+If the variable name contains only a single letter, the surrounding
+braces or parentheses are not required.
+This shorter form is not recommended.
+.Pp
+Variable substitution occurs at two distinct times, depending on where
+the variable is being used.
+Variables in dependency lines are expanded as the line is read.
+Variables in shell commands are expanded when the shell command is
+executed.
+.Pp
+The four different classes of variables (in order of increasing precedence)
+are:
+.Bl -tag -width Ds
+.It Environment variables
+Variables defined as part of
+.Nm Ns 's
+environment.
+.It Global variables
+Variables defined in the makefile or in included makefiles.
+.It Command line variables
+Variables defined as part of the command line.
+.It Local variables
+Variables that are defined specific to a certain target.
+The seven local variables are as follows:
+.Bl -tag -width ".ARCHIVE"
+.It Va .ALLSRC
+The list of all sources for this target; also known as
+.Ql Va \&> .
+.It Va .ARCHIVE
+The name of the archive file; also known as
+.Ql Va \&! .
+.It Va .IMPSRC
+The name/path of the source from which the target is to be transformed
+(the
+.Dq implied
+source); also known as
+.Ql Va \&< .
+.It Va .MEMBER
+The name of the archive member; also known as
+.Ql Va \&% .
+.It Va .OODATE
+The list of sources for this target that were deemed out-of-date; also
+known as
+.Ql Va \&? .
+.It Va .PREFIX
+The file prefix of the file, containing only the file portion, no suffix
+or preceding directory components; also known as
+.Ql Va * .
+.It Va .TARGET
+The name of the target; also known as
+.Ql Va @ .
+.El
+.Pp
+The shorter forms
+.Ql Va @ ,
+.Ql Va \&! ,
+.Ql Va \&< ,
+.Ql Va \&% ,
+.Ql Va \&? ,
+.Ql Va \&> ,
+and
+.Ql Va *
+are permitted for backward
+compatibility and are not recommended.
+The six variables
+.Ql Va "@F" ,
+.Ql Va "@D" ,
+.Ql Va "<F" ,
+.Ql Va "<D" ,
+.Ql Va "*F" ,
+and
+.Ql Va "*D"
+are
+permitted for compatibility with
+.At V
+makefiles and are not recommended.
+.Pp
+Four of the local variables may be used in sources on dependency lines
+because they expand to the proper value for each target on the line.
+These variables are
+.Ql Va .TARGET ,
+.Ql Va .PREFIX ,
+.Ql Va .ARCHIVE ,
+and
+.Ql Va .MEMBER .
+.El
+.Pp
+In addition,
+.Nm
+sets or knows about the following internal variables or environment
+variables:
+.Bl -tag -width MAKEFLAGS
+.It Va \&$
+A single dollar sign
+.Ql \&$ ,
+i.e.\&
+.Ql \&$$
+expands to a single dollar
+sign.
+.It Va MAKE
+The name that
+.Nm
+was executed with
+.Pq Va argv Ns Op 0 .
+.It Va .CURDIR
+A path to the directory where
+.Nm
+was executed.
+The
+.Nm
+utility sets
+.Va .CURDIR
+to the canonical path given by
+.Xr getcwd 3 .
+.It Va .OBJDIR
+A path to the directory where the targets are built.
+At startup,
+.Nm
+searches for an alternate directory to place target files.
+It will attempt to change into this special directory
+and will search this directory for makefiles
+not found in the current directory.
+The following directories are tried in order:
+.Pp
+.Bl -enum -compact
+.It
+${MAKEOBJDIRPREFIX}/`pwd`
+.It
+${MAKEOBJDIR}
+.It
+obj.${MACHINE}
+.It
+obj
+.It
+/usr/obj/`pwd`
+.El
+.Pp
+The first directory that
+.Nm
+successfully changes into is used.
+If either
+.Ev MAKEOBJDIRPREFIX
+or
+.Ev MAKEOBJDIR
+is set in the environment but
+.Nm
+is unable to change into the corresponding directory,
+then the current directory is used
+without checking the remainder of the list.
+If they are undefined and
+.Nm
+is unable to change into any of the remaining three directories,
+then the current directory is used.
+.Pp
+The
+.Nm
+utility sets
+.Va .OBJDIR
+to the canonical path given by
+.Xr getcwd 3 .
+.It Va .MAKEFLAGS
+The environment variable
+.Ev MAKEFLAGS
+may contain anything that
+may be specified on
+.Nm Ns 's
+command line.
+Its contents are stored in
+.Nm Ns 's
+.Va .MAKEFLAGS
+variable.
+Anything specified on
+.Nm Ns 's
+command line is appended to the
+.Va .MAKEFLAGS
+variable which is then
+entered into the environment as
+.Ev MAKEFLAGS
+for all programs which
+.Nm
+executes.
+.It Va MFLAGS
+A synonym for
+.Va .MAKEFLAGS
+provided for backward compatibility.
+.It Va .TARGETS
+List of targets
+.Nm
+is currently building.
+.It Va .INCLUDES
+See
+.Ic .INCLUDES
+special target.
+.It Va .LIBS
+See
+.Ic .LIBS
+special target.
+.It Va MACHINE
+Name of the machine architecture
+.Nm
+is running on, obtained from the
+.Ev MACHINE
+environment variable, or through
+.Xr uname 3
+if not defined.
+.It Va MACHINE_ARCH
+Name of the machine architecture
+.Nm
+was compiled for, defined at compilation time.
+.It Va VPATH
+Makefiles may assign a colon-delimited list of directories to
+.Va VPATH .
+These directories will be searched for source files by
+.Nm
+after it has finished parsing all input makefiles.
+.El
+.Pp
+Variable expansion may be modified to select or modify each word of the
+variable (where a
+.Dq word
+is whitespace-delimited sequence of characters).
+The general format of a variable expansion is as follows:
+.Pp
+.Dl {variable[:modifier[:...]]}
+.Pp
+Each modifier begins with a colon and one of the following
+special characters.
+The colon may be escaped with a backslash
+.Pq Ql \e .
+.Bl -tag -width Cm
+.Sm off
+.It Cm C No \&/ Ar pattern Xo
+.No \&/ Ar replacement
+.No \&/ Op Cm 1g
+.Xc
+.Sm on
+The
+.Cm C
+modifier is just like the
+.Cm S
+modifier except that the old and new strings, instead of being
+simple strings, are an extended regular expression (see
+.Xr re_format 7 )
+and an
+.Xr ed 1 Ns \-style
+replacement string.
+Normally, the first occurrence of the pattern in
+each word of the value is changed.
+The
+.Ql 1
+modifier causes the substitution to apply to at most one word; the
+.Ql g
+modifier causes the substitution to apply to as many instances of the
+search pattern as occur in the word or words it is found in.
+Note that
+.Ql 1
+and
+.Ql g
+are orthogonal; the former specifies whether multiple words are
+potentially affected, the latter whether multiple substitutions can
+potentially occur within each affected word.
+.It Cm E
+Replaces each word in the variable with its suffix.
+.It Cm H
+Replaces each word in the variable with everything but the last component.
+.It Cm L
+Converts variable to lower-case letters.
+.It Cm M Ns Ar pattern
+Select only those words that match the rest of the modifier.
+The standard shell wildcard characters
+.Pf ( Ql * ,
+.Ql \&? ,
+and
+.Ql Op )
+may
+be used.
+The wildcard characters may be escaped with a backslash
+.Pq Ql \e .
+.It Cm N Ns Ar pattern
+This is identical to
+.Cm M ,
+but selects all words which do not match
+the rest of the modifier.
+.It Cm Q
+Quotes every shell meta-character in the variable, so that it can be passed
+safely through recursive invocations of
+.Nm .
+.It Cm R
+Replaces each word in the variable with everything but its suffix.
+.Sm off
+.It Cm S No \&/ Ar old_string Xo
+.No \&/ Ar new_string
+.No \&/ Op Cm g
+.Xc
+.Sm on
+Modify the first occurrence of
+.Ar old_string
+in each word of the variable's value, replacing it with
+.Ar new_string .
+If a
+.Ql g
+is appended to the last slash of the pattern, all occurrences
+in each word are replaced.
+If
+.Ar old_string
+begins with a caret
+.Pq Ql ^ ,
+.Ar old_string
+is anchored at the beginning of each word.
+If
+.Ar old_string
+ends with a dollar sign
+.Pq Ql \&$ ,
+it is anchored at the end of each word.
+Inside
+.Ar new_string ,
+an ampersand
+.Pq Ql &
+is replaced by
+.Ar old_string .
+Any character may be used as a delimiter for the parts of the modifier
+string.
+The anchoring, ampersand, and delimiter characters may be escaped with a
+backslash
+.Pq Ql \e .
+.Pp
+Variable expansion occurs in the normal fashion inside both
+.Ar old_string
+and
+.Ar new_string
+with the single exception that a backslash is used to prevent the expansion
+of a dollar sign
+.Pq Ql \&$ ,
+not a preceding dollar sign as is usual.
+.It Ar old_string=new_string
+This is the
+.At V
+style variable substitution.
+It must be the last modifier specified.
+If
+.Ar old_string
+or
+.Ar new_string
+do not contain the pattern matching character
+.Ar %
+then it is assumed that they are
+anchored at the end of each word, so only suffixes or entire
+words may be replaced.
+Otherwise
+.Ar %
+is the substring of
+.Ar old_string
+to be replaced in
+.Ar new_string
+.It Cm T
+Replaces each word in the variable with its last component.
+.It Cm U
+Converts variable to upper-case letters.
+.El
+.Sh DIRECTIVES, CONDITIONALS, AND FOR LOOPS
+Directives, conditionals, and for loops reminiscent
+of the C programming language are provided in
+.Nm .
+All such structures are identified by a line beginning with a single
+dot
+.Pq Ql \&.
+character.
+The following directives are supported:
+.Bl -tag -width Ds
+.It Ic \&.include Ar <file>
+.It Ic \&.include Ar \*qfile\*q
+Include the specified makefile.
+Variables between the angle brackets
+or double quotes are expanded to form the file name.
+If angle brackets
+are used, the included makefile is expected to be in the system
+makefile directory.
+If double quotes are used, the including
+makefile's directory and any directories specified using the
+.Fl I
+option are searched before the system
+makefile directory.
+.It Ic \&.undef Ar variable
+Un-define the specified global variable.
+Only global variables may be un-defined.
+.It Ic \&.error Ar message
+Terminate processing of the makefile immediately.
+The filename of the
+makefile, the line on which the error was encountered and the specified
+message are printed to standard output and
+.Nm
+terminates with exit code 1.
+Variables in the message are expanded.
+.El
+.Pp
+Conditionals are used to determine which parts of the Makefile
+to process.
+They are used similarly to the conditionals supported
+by the C pre-processor.
+The following conditionals are supported:
+.Bl -tag -width Ds
+.It Xo
+.Ic \&.if
+.Oo \&! Oc Ns Ar expression
+.Op Ar operator expression ...
+.Xc
+Test the value of an expression.
+.It Xo
+.Ic .ifdef
+.Oo \&! Oc Ns Ar variable
+.Op Ar operator variable ...
+.Xc
+Test the value of a variable.
+.It Xo
+.Ic .ifndef
+.Oo \&! Oc Ns Ar variable
+.Op Ar operator variable ...
+.Xc
+Test the value of a variable.
+.It Xo
+.Ic .ifmake
+.Oo \&! Oc Ns Ar target
+.Op Ar operator target ...
+.Xc
+Test the target being built.
+.It Xo
+.Ic .ifnmake
+.Oo \&! Oc Ns Ar target
+.Op Ar operator target ...
+.Xc
+Test the target being built.
+.It Ic .else
+Reverse the sense of the last conditional.
+.It Xo
+.Ic .elif
+.Oo \&! Oc Ns Ar expression
+.Op Ar operator expression ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .if .
+.It Xo
+.Ic .elifdef
+.Oo \&! Oc Ns Ar variable
+.Op Ar operator variable ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifdef .
+.It Xo
+.Ic .elifndef
+.Oo \&! Oc Ns Ar variable
+.Op Ar operator variable ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifndef .
+.It Xo
+.Ic .elifmake
+.Oo \&! Oc Ns Ar target
+.Op Ar operator target ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifmake .
+.It Xo
+.Ic .elifnmake
+.Oo \&! Oc Ns Ar target
+.Op Ar operator target ...
+.Xc
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifnmake .
+.It Ic .endif
+End the body of the conditional.
+.El
+.Pp
+The
+.Ar operator
+may be any one of the following:
+.Bl -tag -width "Cm XX"
+.It Cm \&|\&|
+logical
+.Tn OR
+.It Cm \&&&
+Logical
+.Tn AND ;
+of higher precedence than
+.Ql Ic || .
+.El
+.Pp
+As in C,
+.Nm
+will only evaluate a conditional as far as is necessary to determine
+its value.
+Parentheses may be used to change the order of evaluation.
+The boolean operator
+.Ql Ic !\&
+may be used to logically negate an entire
+conditional.
+It is of higher precedence than
+.Ql Ic \&&& .
+.Pp
+The value of
+.Ar expression
+may be any of the following:
+.Bl -tag -width Ic
+.It Ic defined
+Takes a variable name as an argument and evaluates to true if the variable
+has been defined.
+.It Ic make
+Takes a target name as an argument and evaluates to true if the target
+was specified as part of
+.Nm Ns 's
+command line or was declared the default target (either implicitly or
+explicitly, see
+.Va .MAIN )
+before the line containing the conditional.
+.It Ic empty
+Takes a variable, with possible modifiers, and evaluates to true if
+the expansion of the variable would result in an empty string.
+.It Ic exists
+Takes a file name as an argument and evaluates to true if the file exists.
+The file is searched for on the system search path (see
+.Va .PATH ) .
+.It Ic target
+Takes a target name as an argument and evaluates to true if the target
+has been defined.
+.El
+.Pp
+An
+.Ar expression
+may also be an arithmetic or string comparison.
+Variable expansion is
+performed on both sides of the comparison, after which the integral
+values are compared.
+A value is interpreted as hexadecimal if it is
+preceded by 0x, otherwise it is decimal; octal numbers are not supported.
+The standard C relational operators are all supported.
+If after
+variable expansion, either the left or right hand side of a
+.Ql Ic ==
+or
+.Ql Ic "!="
+operator is not an integral value, then
+string comparison is performed between the expanded
+variables.
+If no relational operator is given, it is assumed that the expanded
+variable is being compared against 0.
+.Pp
+When
+.Nm
+is evaluating one of these conditional expressions, and it encounters
+a word it doesn't recognize, either the
+.Dq make
+or
+.Dq defined
+expression is applied to it, depending on the form of the conditional.
+If the form is
+.Ql Ic .ifdef
+or
+.Ql Ic .ifndef ,
+the
+.Dq defined
+expression is applied.
+Similarly, if the form is
+.Ql Ic .ifmake
+or
+.Ql Ic .ifnmake ,
+the
+.Dq make
+expression is applied.
+.Pp
+If the conditional evaluates to true the parsing of the makefile continues
+as before.
+If it evaluates to false, the following lines are skipped.
+In both cases this continues until a
+.Ql Ic .else
+or
+.Ql Ic .endif
+is found.
+.Pp
+For loops are typically used to apply a set of rules to a list of files.
+The syntax of a for loop is:
+.Pp
+.Bl -tag -width indent -compact
+.It Ic .for Ar variable Ic in Ar expression
+.It <make-rules>
+.It Ic \&.endfor
+.El
+.Pp
+After the for
+.Ar expression
+is evaluated, it is split into words.
+The
+iteration
+.Ar variable
+is successively set to each word, and substituted in the
+.Ic make-rules
+inside the body of the for loop.
+.Sh COMMENTS
+Comments begin with a hash
+.Pq Ql \&#
+character, anywhere but in a shell
+command line, and continue to the end of the line.
+.Sh SPECIAL SOURCES
+.Bl -tag -width Ic
+.It Ic .IGNORE
+Ignore any errors from the commands associated with this target, exactly
+as if they all were preceded by a dash
+.Pq Ql \- .
+.It Ic .MAKE
+Execute the commands associated with this target even if the
+.Fl n
+or
+.Fl t
+options were specified.
+Normally used to mark recursive
+.Nm Ns 's .
+.It Ic .NOTMAIN
+Normally
+.Nm
+selects the first target it encounters as the default target to be built
+if no target was specified.
+This source prevents this target from being selected.
+.It Ic .OPTIONAL
+If a target is marked with this attribute and
+.Nm
+can't figure out how to create it, it will ignore this fact and assume
+the file isn't needed or already exists.
+.It Ic .PRECIOUS
+When
+.Nm
+is interrupted, it removes any partially made targets.
+This source prevents the target from being removed.
+.It Ic .SILENT
+Do not echo any of the commands associated with this target, exactly
+as if they all were preceded by an at sign
+.Pq Ql @ .
+.It Ic .USE
+Turn the target into
+.Nm Ns 's
+version of a macro.
+When the target is used as a source for another target, the other target
+acquires the commands, sources, and attributes (except for
+.Ic .USE )
+of the
+source.
+If the target already has commands, the
+.Ic .USE
+target's commands are appended
+to them.
+.It Ic .WAIT
+If special
+.Ic .WAIT
+source is appears in a dependency line, the sources that precede it are
+made before the sources that succeed it in the line.
+Loops are not being
+detected and targets that form loops will be silently ignored.
+.El
+.Sh "SPECIAL TARGETS"
+Special targets may not be included with other targets, i.e. they must be
+the only target specified.
+.Bl -tag -width Ic
+.It Ic .BEGIN
+Any command lines attached to this target are executed before anything
+else is done.
+.It Ic .DEFAULT
+This is sort of a
+.Ic .USE
+rule for any target (that was used only as a
+source) that
+.Nm
+can't figure out any other way to create.
+Only the shell script is used.
+The
+.Ic .IMPSRC
+variable of a target that inherits
+.Ic .DEFAULT Ns 's
+commands is set
+to the target's own name.
+.It Ic .END
+Any command lines attached to this target are executed after everything
+else is done.
+.It Ic .IGNORE
+Mark each of the sources with the
+.Ic .IGNORE
+attribute.
+If no sources are specified, this is the equivalent of specifying the
+.Fl i
+option.
+.It Ic .INCLUDES
+A list of suffixes that indicate files that can be included in a source
+file.
+The suffix must have already been declared with
+.Ic .SUFFIXES ;
+any suffix so declared will have the directories on its search path (see
+.Ic .PATH )
+placed in the
+.Va .INCLUDES
+special variable, each preceded by a
+.Fl I
+flag.
+.It Ic .INTERRUPT
+If
+.Nm
+is interrupted, the commands for this target will be executed.
+.It Ic .LIBS
+This does for libraries what
+.Ic .INCLUDES
+does for include files, except that the flag used is
+.Fl L .
+.It Ic .MAIN
+If no target is specified when
+.Nm
+is invoked, this target will be built.
+This is always set, either
+explicitly, or implicitly when
+.Nm
+selects the default target, to give the user a way to refer to the default
+target on the command line.
+.It Ic .MAKEFLAGS
+This target provides a way to specify flags for
+.Nm
+when the makefile is used.
+The flags are as if typed to the shell, though the
+.Fl f
+option will have
+no effect.
+.\" XXX: NOT YET!!!!
+.\" .It Ic .NOTPARALLEL
+.\" The named targets are executed in non parallel mode. If no targets are
+.\" specified, then all targets are executed in non parallel mode.
+.It Ic .NOTPARALLEL
+Disable parallel mode.
+.It Ic .NO_PARALLEL
+Same as above, for compatibility with other
+.Nm pmake
+variants.
+.It Ic .ORDER
+The named targets are made in sequence.
+.\" XXX: NOT YET!!!!
+.\" .It Ic .PARALLEL
+.\" The named targets are executed in parallel mode. If no targets are
+.\" specified, then all targets are executed in parallel mode.
+.It Ic .PATH
+The sources are directories which are to be searched for files not
+found in the current directory.
+If no sources are specified, any previously specified directories are
+deleted.
+Where possible, use of
+.Ic .PATH
+is preferred over use of the
+.Va VPATH
+variable.
+.It Ic .PATH\fIsuffix\fR
+The sources are directories which are to be searched for suffixed files
+not found in the current directory.
+The
+.Nm
+utility
+first searches the suffixed search path, before reverting to the default
+path if the file is not found there.
+This form is required for
+.Ic .LIBS
+and
+.Ic .INCLUDES
+to work.
+.It Ic .PHONY
+Apply the
+.Ic .PHONY
+attribute to any specified sources.
+Targets with this attribute are always
+considered to be out of date.
+.It Ic .PRECIOUS
+Apply the
+.Ic .PRECIOUS
+attribute to any specified sources.
+If no sources are specified, the
+.Ic .PRECIOUS
+attribute is applied to every
+target in the file.
+.It Ic .SILENT
+Apply the
+.Ic .SILENT
+attribute to any specified sources.
+If no sources are specified, the
+.Ic .SILENT
+attribute is applied to every
+command in the file.
+.It Ic .SUFFIXES
+Each source specifies a suffix to
+.Nm .
+If no sources are specified, any previous specified suffices are deleted.
+.El
+.Sh COMPATIBILITY
+Older versions of
+.Nm
+used
+.Ev MAKE
+instead of
+.Ev MAKEFLAGS .
+This was removed for POSIX compatibility.
+The internal variable
+.Va MAKE
+is set to the same value as
+.Va .MAKE ;
+support for this may be removed in the future.
+.Pp
+Most of the more esoteric features of
+.Nm
+should probably be avoided for greater compatibility.
+.Sh ENVIRONMENT
+The
+.Nm
+utility uses the following environment variables, if they exist:
+.Ev MACHINE ,
+.Ev MAKE ,
+.Ev MAKEFLAGS ,
+.Ev MAKEOBJDIR ,
+and
+.Ev MAKEOBJDIRPREFIX .
+.Sh FILES
+.Bl -tag -width /usr/share/doc/psd/12.make -compact
+.It Pa .depend
+list of dependencies
+.It Pa Makefile
+list of dependencies
+.It Pa makefile
+list of dependencies
+.It obj
+object directory
+.It Pa sys.mk
+system makefile (processed before any other file, including
+.Pa makefile
+and
+.Pa Makefile )
+.It Pa /usr/share/mk
+system makefile directory
+.It /usr/share/doc/psd/12.make
+PMake tutorial
+.It Pa /usr/obj
+default
+.Ev MAKEOBJDIRPREFIX
+directory.
+.El
+.Sh BUGS
+The determination of
+.Va .OBJDIR
+is contorted to the point of absurdity.
+.Pp
+In the presence of several
+.Ic .MAIN
+special targets,
+.Nm
+silently ignores all but the first.
+.Pp
+.Va .TARGETS
+is not set to the default target when
+.Nm
+is invoked without a target name and no
+.Ic .MAIN
+special target exists.
+.Pp
+The evaluation of
+.Ar expression
+in a test is very simple-minded.
+Currently, the only form that works is
+.Ql .if ${VAR} op something
+For instance, you should write tests as
+.Ql .if ${VAR} = "string"
+not the other way around, which doesn't work.
+.Pp
+For loops are expanded before tests, so a fragment such as:
+.Bd -literal -offset indent
+\&.for TMACHINE in ${SHARED_ARCHS}
+\&.if ${TMACHINE} = ${MACHINE}
+     ...
+\&.endif
+\&.endfor
+.Ed
+.Pp
+won't work, and should be rewritten the other way around.
+.Pp
+The parsing code is broken with respect to handling a semicolon
+after a colon, so a fragment like this will fail:
+.Bd -literal -offset indent
+HDRS=   foo.h bar.h
+
+all:
+\&.for h in ${HDRS:S;^;${.CURDIR}/;}
+     ...
+\&.endfor
+.Ed
+.Sh SEE ALSO
+.Xr mkdep 1 ,
+.Xr make.conf 5
+.Rs
+.%T "PMake - A Tutorial"
+.Re
+in
+.Pa /usr/share/doc/psd/12.make
+.Sh HISTORY
+A
+.Nm
+command appeared in PWB UNIX.
Index: trunk/src/kmk/make.c
===================================================================
--- trunk/src/kmk/make.c	(revision 9)
+++ trunk/src/kmk/make.c	(revision 9)
@@ -0,0 +1,867 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)make.c	8.1 (Berkeley) 6/6/93
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/make.c,v 1.23 2002/10/09 03:42:10 jmallett Exp $");
+
+/*-
+ * make.c --
+ *	The functions which perform the examination of targets and
+ *	their suitability for creation
+ *
+ * Interface:
+ *	Make_Run 	    	Initialize things for the module and recreate
+ *	    	  	    	whatever needs recreating. Returns TRUE if
+ *	    	    	    	work was (or would have been) done and FALSE
+ *	    	  	    	otherwise.
+ *
+ *	Make_Update	    	Update all parents of a given child. Performs
+ *	    	  	    	various bookkeeping chores like the updating
+ *	    	  	    	of the cmtime field of the parent, filling
+ *	    	  	    	of the IMPSRC context variable, etc. It will
+ *	    	  	    	place the parent on the toBeMade queue if it
+ *	    	  	    	should be.
+ *
+ *	Make_TimeStamp	    	Function to set the parent's cmtime field
+ *	    	  	    	based on a child's modification time.
+ *
+ *	Make_DoAllVar	    	Set up the various local variables for a
+ *	    	  	    	target, including the .ALLSRC variable, making
+ *	    	  	    	sure that any variable that needs to exist
+ *	    	  	    	at the very least has the empty value.
+ *
+ *	Make_OODate 	    	Determine if a target is out-of-date.
+ *
+ *	Make_HandleUse		See if a child is a .USE node for a parent
+ *				and perform the .USE actions if so.
+ */
+
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "job.h"
+
+static Lst     	toBeMade;	/* The current fringe of the graph. These
+				 * are nodes which await examination by
+				 * MakeOODate. It is added to by
+				 * Make_Update and subtracted from by
+				 * MakeStartJobs */
+static int  	numNodes;   	/* Number of nodes to be processed. If this
+				 * is non-zero when Job_Empty() returns
+				 * TRUE, there's a cycle in the graph */
+
+static int MakeAddChild(void *, void *);
+static int MakeAddAllSrc(void *, void *);
+static int MakeTimeStamp(void *, void *);
+static int MakeHandleUse(void *, void *);
+static Boolean MakeStartJobs(void);
+static int MakePrintStatus(void *, void *);
+/*-
+ *-----------------------------------------------------------------------
+ * Make_TimeStamp --
+ *	Set the cmtime field of a parent node based on the mtime stamp in its
+ *	child. Called from MakeOODate via Lst_ForEach.
+ *
+ * Results:
+ *	Always returns 0.
+ *
+ * Side Effects:
+ *	The cmtime of the parent node will be changed if the mtime
+ *	field of the child is greater than it.
+ *-----------------------------------------------------------------------
+ */
+int
+Make_TimeStamp (GNode *pgn, GNode *cgn)
+{
+    if (cgn->mtime > pgn->cmtime) {
+	pgn->cmtime = cgn->mtime;
+    }
+    return (0);
+}
+
+static int
+MakeTimeStamp (void *pgn, void *cgn)
+{
+    return Make_TimeStamp((GNode *) pgn, (GNode *) cgn);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_OODate --
+ *	See if a given node is out of date with respect to its sources.
+ *	Used by Make_Run when deciding which nodes to place on the
+ *	toBeMade queue initially and by Make_Update to screen out USE and
+ *	EXEC nodes. In the latter case, however, any other sort of node
+ *	must be considered out-of-date since at least one of its children
+ *	will have been recreated.
+ *
+ * Results:
+ *	TRUE if the node is out of date. FALSE otherwise.
+ *
+ * Side Effects:
+ *	The mtime field of the node and the cmtime field of its parents
+ *	will/may be changed.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Make_OODate (GNode *gn)
+{
+    Boolean         oodate;
+
+    /*
+     * Certain types of targets needn't even be sought as their datedness
+     * doesn't depend on their modification time...
+     */
+    if ((gn->type & (OP_JOIN|OP_USE|OP_EXEC)) == 0) {
+	(void) Dir_MTime (gn);
+	if (gn->mtime != 0) {
+	    DEBUGF(MAKE, ("modified %s...", Targ_FmtTime(gn->mtime)));
+	} else {
+	    DEBUGF(MAKE, ("non-existent..."));
+	}
+    }
+
+    /*
+     * A target is remade in one of the following circumstances:
+     *	its modification time is smaller than that of its youngest child
+     *	    and it would actually be run (has commands or type OP_NOP)
+     *	it's the object of a force operator
+     *	it has no children, was on the lhs of an operator and doesn't exist
+     *	    already.
+     *
+     * Libraries are only considered out-of-date if the archive module says
+     * they are.
+     *
+     * These weird rules are brought to you by Backward-Compatability and
+     * the strange people who wrote 'Make'.
+     */
+    if (gn->type & OP_USE) {
+	/*
+	 * If the node is a USE node it is *never* out of date
+	 * no matter *what*.
+	 */
+	DEBUGF(MAKE, (".USE node..."));
+	oodate = FALSE;
+    } else if (gn->type & OP_LIB) {
+	DEBUGF(MAKE, ("library..."));
+
+	/*
+	 * always out of date if no children and :: target
+	 */
+
+	oodate = Arch_LibOODate (gn) ||
+	    ((gn->cmtime == 0) && (gn->type & OP_DOUBLEDEP));
+    } else if (gn->type & OP_JOIN) {
+	/*
+	 * A target with the .JOIN attribute is only considered
+	 * out-of-date if any of its children was out-of-date.
+	 */
+	DEBUGF(MAKE, (".JOIN node..."));
+	oodate = gn->childMade;
+    } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) {
+	/*
+	 * A node which is the object of the force (!) operator or which has
+	 * the .EXEC attribute is always considered out-of-date.
+	 */
+	if (gn->type & OP_FORCE) {
+	    DEBUGF(MAKE, ("! operator..."));
+	} else if (gn->type & OP_PHONY) {
+	    DEBUGF(MAKE, (".PHONY node..."));
+	} else {
+	    DEBUGF(MAKE, (".EXEC node..."));
+	}
+	oodate = TRUE;
+    } else if ((gn->mtime < gn->cmtime) ||
+	       ((gn->cmtime == 0) &&
+		((gn->mtime==0) || (gn->type & OP_DOUBLEDEP))))
+    {
+	/*
+	 * A node whose modification time is less than that of its
+	 * youngest child or that has no children (cmtime == 0) and
+	 * either doesn't exist (mtime == 0) or was the object of a
+	 * :: operator is out-of-date. Why? Because that's the way Make does
+	 * it.
+	 */
+	if (gn->mtime < gn->cmtime) {
+	    DEBUGF(MAKE, ("modified before source..."));
+	} else if (gn->mtime == 0) {
+	    DEBUGF(MAKE, ("non-existent and no sources..."));
+	} else {
+	    DEBUGF(MAKE, (":: operator and no sources..."));
+	}
+	oodate = TRUE;
+    } else
+	oodate = FALSE;
+
+    /*
+     * If the target isn't out-of-date, the parents need to know its
+     * modification time. Note that targets that appear to be out-of-date
+     * but aren't, because they have no commands and aren't of type OP_NOP,
+     * have their mtime stay below their children's mtime to keep parents from
+     * thinking they're out-of-date.
+     */
+    if (!oodate) {
+	Lst_ForEach (gn->parents, MakeTimeStamp, (void *)gn);
+    }
+
+    return (oodate);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeAddChild  --
+ *	Function used by Make_Run to add a child to the list l.
+ *	It will only add the child if its make field is FALSE.
+ *
+ * Results:
+ *	Always returns 0
+ *
+ * Side Effects:
+ *	The given list is extended
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeAddChild (void *gnp, void *lp)
+{
+    GNode          *gn = (GNode *) gnp;
+    Lst            l = (Lst) lp;
+
+    if (!gn->make && !(gn->type & OP_USE)) {
+	(void)Lst_EnQueue (l, (void *)gn);
+    }
+    return (0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_HandleUse --
+ *	Function called by Make_Run and SuffApplyTransform on the downward
+ *	pass to handle .USE and transformation nodes. A callback function
+ *	for Lst_ForEach, it implements the .USE and transformation
+ *	functionality by copying the node's commands, type flags
+ *	and children to the parent node. Should be called before the
+ *	children are enqueued to be looked at by MakeAddChild.
+ *
+ *	A .USE node is much like an explicit transformation rule, except
+ *	its commands are always added to the target node, even if the
+ *	target already has commands.
+ *
+ * Results:
+ *	returns 0.
+ *
+ * Side Effects:
+ *	Children and commands may be added to the parent and the parent's
+ *	type may be changed.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Make_HandleUse (GNode *cgn, GNode *pgn)
+{
+    GNode	*gn;	 	/* A child of the .USE node */
+    LstNode	ln;	 	/* An element in the children list */
+
+    if (cgn->type & (OP_USE|OP_TRANSFORM)) {
+	if ((cgn->type & OP_USE) || Lst_IsEmpty(pgn->commands)) {
+	    /*
+	     * .USE or transformation and target has no commands -- append
+	     * the child's commands to the parent.
+	     */
+	    (void) Lst_Concat (pgn->commands, cgn->commands, LST_CONCNEW);
+	}
+
+	if (Lst_Open (cgn->children) == SUCCESS) {
+	    while ((ln = Lst_Next (cgn->children)) != NULL) {
+		gn = (GNode *)Lst_Datum (ln);
+
+		if (Lst_Member (pgn->children, gn) == NULL) {
+		    (void) Lst_AtEnd (pgn->children, gn);
+		    (void) Lst_AtEnd (gn->parents, pgn);
+		    pgn->unmade += 1;
+		}
+	    }
+	    Lst_Close (cgn->children);
+	}
+
+	pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_TRANSFORM);
+
+	/*
+	 * This child node is now "made", so we decrement the count of
+	 * unmade children in the parent... We also remove the child
+	 * from the parent's list to accurately reflect the number of decent
+	 * children the parent has. This is used by Make_Run to decide
+	 * whether to queue the parent or examine its children...
+	 */
+	if (cgn->type & OP_USE) {
+	    pgn->unmade--;
+	}
+    }
+    return (0);
+}
+static int
+MakeHandleUse (void *pgn, void *cgn)
+{
+    return Make_HandleUse((GNode *) pgn, (GNode *) cgn);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Update  --
+ *	Perform update on the parents of a node. Used by JobFinish once
+ *	a node has been dealt with and by MakeStartJobs if it finds an
+ *	up-to-date node.
+ *
+ * Results:
+ *	Always returns 0
+ *
+ * Side Effects:
+ *	The unmade field of pgn is decremented and pgn may be placed on
+ *	the toBeMade queue if this field becomes 0.
+ *
+ * 	If the child was made, the parent's childMade field will be set true
+ *	and its cmtime set to now.
+ *
+ *	If the child wasn't made, the cmtime field of the parent will be
+ *	altered if the child's mtime is big enough.
+ *
+ *	Finally, if the child is the implied source for the parent, the
+ *	parent's IMPSRC variable is set appropriately.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Make_Update (GNode *cgn)
+{
+    GNode 	*pgn;		/* the parent node */
+    char  	*cname;		/* the child's name */
+    LstNode	ln;	 	/* Element in parents and iParents lists */
+    char	*p1;
+
+    cname = Var_Value (TARGET, cgn, &p1);
+    efree(p1);
+
+    /*
+     * If the child was actually made, see what its modification time is
+     * now -- some rules won't actually update the file. If the file still
+     * doesn't exist, make its mtime now.
+     */
+    if (cgn->made != UPTODATE) {
+#ifndef RECHECK
+	/*
+	 * We can't re-stat the thing, but we can at least take care of rules
+	 * where a target depends on a source that actually creates the
+	 * target, but only if it has changed, e.g.
+	 *
+	 * parse.h : parse.o
+	 *
+	 * parse.o : parse.y
+	 *  	yacc -d parse.y
+	 *  	cc -c y.tab.c
+	 *  	mv y.tab.o parse.o
+	 *  	cmp -s y.tab.h parse.h || mv y.tab.h parse.h
+	 *
+	 * In this case, if the definitions produced by yacc haven't changed
+	 * from before, parse.h won't have been updated and cgn->mtime will
+	 * reflect the current modification time for parse.h. This is
+	 * something of a kludge, I admit, but it's a useful one..
+	 * XXX: People like to use a rule like
+	 *
+	 * FRC:
+	 *
+	 * To force things that depend on FRC to be made, so we have to
+	 * check for gn->children being empty as well...
+	 */
+	if (!Lst_IsEmpty(cgn->commands) || Lst_IsEmpty(cgn->children)) {
+	    cgn->mtime = now;
+	}
+#else
+	/*
+	 * This is what Make does and it's actually a good thing, as it
+	 * allows rules like
+	 *
+	 *	cmp -s y.tab.h parse.h || cp y.tab.h parse.h
+	 *
+	 * to function as intended. Unfortunately, thanks to the stateless
+	 * nature of NFS (by which I mean the loose coupling of two clients
+	 * using the same file from a common server), there are times
+	 * when the modification time of a file created on a remote
+	 * machine will not be modified before the local stat() implied by
+	 * the Dir_MTime occurs, thus leading us to believe that the file
+	 * is unchanged, wreaking havoc with files that depend on this one.
+	 *
+	 * I have decided it is better to make too much than to make too
+	 * little, so this stuff is commented out unless you're sure it's ok.
+	 * -- ardeb 1/12/88
+	 */
+	/*
+	 * Christos, 4/9/92: If we are  saving commands pretend that
+	 * the target is made now. Otherwise archives with ... rules
+	 * don't work!
+	 */
+	if (noExecute || (cgn->type & OP_SAVE_CMDS) || Dir_MTime(cgn) == 0) {
+	    cgn->mtime = now;
+	}
+	DEBUGF(MAKE, ("update time: %s\n", Targ_FmtTime(cgn->mtime)));
+#endif
+    }
+
+    if (Lst_Open (cgn->parents) == SUCCESS) {
+	while ((ln = Lst_Next (cgn->parents)) != NULL) {
+	    pgn = (GNode *)Lst_Datum (ln);
+	    if (pgn->make) {
+		pgn->unmade -= 1;
+
+		if ( ! (cgn->type & (OP_EXEC|OP_USE))) {
+		    if (cgn->made == MADE) {
+			pgn->childMade = TRUE;
+			if (pgn->cmtime < cgn->mtime) {
+			    pgn->cmtime = cgn->mtime;
+			}
+		    } else {
+			(void)Make_TimeStamp (pgn, cgn);
+		    }
+		}
+		if (pgn->unmade == 0) {
+		    /*
+		     * Queue the node up -- any unmade predecessors will
+		     * be dealt with in MakeStartJobs.
+		     */
+		    (void)Lst_EnQueue (toBeMade, (void *)pgn);
+		} else if (pgn->unmade < 0) {
+		    Error ("Graph cycles through %s", pgn->name);
+		}
+	    }
+	}
+	Lst_Close (cgn->parents);
+    }
+    /*
+     * Deal with successor nodes. If any is marked for making and has an unmade
+     * count of 0, has not been made and isn't in the examination queue,
+     * it means we need to place it in the queue as it restrained itself
+     * before.
+     */
+    for (ln = Lst_First(cgn->successors); ln != NULL; ln = Lst_Succ(ln)) {
+	GNode	*succ = (GNode *)Lst_Datum(ln);
+
+	if (succ->make && succ->unmade == 0 && succ->made == UNMADE &&
+	    Lst_Member(toBeMade, (void *)succ) == NULL)
+	{
+	    (void)Lst_EnQueue(toBeMade, (void *)succ);
+	}
+    }
+
+    /*
+     * Set the .PREFIX and .IMPSRC variables for all the implied parents
+     * of this node.
+     */
+    if (Lst_Open (cgn->iParents) == SUCCESS) {
+	char    *ptr;
+	char	*cpref = Var_Value(PREFIX, cgn, &ptr);
+
+	while ((ln = Lst_Next (cgn->iParents)) != NULL) {
+	    pgn = (GNode *)Lst_Datum (ln);
+	    if (pgn->make) {
+		Var_Set (IMPSRC, cname, pgn);
+		Var_Set (PREFIX, cpref, pgn);
+	    }
+	}
+	efree(ptr);
+	Lst_Close (cgn->iParents);
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeAddAllSrc --
+ *	Add a child's name to the ALLSRC and OODATE variables of the given
+ *	node. Called from Make_DoAllVar via Lst_ForEach. A child is added only
+ *	if it has not been given the .EXEC, .USE or .INVISIBLE attributes.
+ *	.EXEC and .USE children are very rarely going to be files, so...
+ *	A child is added to the OODATE variable if its modification time is
+ *	later than that of its parent, as defined by Make, except if the
+ *	parent is a .JOIN node. In that case, it is only added to the OODATE
+ *	variable if it was actually made (since .JOIN nodes don't have
+ *	modification times, the comparison is rather unfair...)..
+ *
+ * Results:
+ *	Always returns 0
+ *
+ * Side Effects:
+ *	The ALLSRC variable for the given node is extended.
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeAddAllSrc (void *cgnp, void *pgnp)
+{
+    GNode	*cgn = (GNode *) cgnp;
+    GNode	*pgn = (GNode *) pgnp;
+    if ((cgn->type & (OP_EXEC|OP_USE|OP_INVISIBLE)) == 0) {
+	char *child;
+	char *p1 = NULL;
+
+	if (OP_NOP(cgn->type)) {
+	    /*
+	     * this node is only source; use the specific pathname for it
+	     */
+	    child = cgn->path ? cgn->path : cgn->name;
+	}
+	else
+	    child = Var_Value(TARGET, cgn, &p1);
+	Var_Append (ALLSRC, child, pgn);
+	if (pgn->type & OP_JOIN) {
+	    if (cgn->made == MADE) {
+		Var_Append(OODATE, child, pgn);
+	    }
+	} else if ((pgn->mtime < cgn->mtime) ||
+		   (cgn->mtime >= now && cgn->made == MADE))
+	{
+	    /*
+	     * It goes in the OODATE variable if the parent is younger than the
+	     * child or if the child has been modified more recently than
+	     * the start of the make. This is to keep pmake from getting
+	     * confused if something else updates the parent after the
+	     * make starts (shouldn't happen, I know, but sometimes it
+	     * does). In such a case, if we've updated the kid, the parent
+	     * is likely to have a modification time later than that of
+	     * the kid and anything that relies on the OODATE variable will
+	     * be hosed.
+	     *
+	     * XXX: This will cause all made children to go in the OODATE
+	     * variable, even if they're not touched, if RECHECK isn't defined,
+	     * since cgn->mtime is set to now in Make_Update. According to
+	     * some people, this is good...
+	     */
+	    Var_Append(OODATE, child, pgn);
+	}
+	efree(p1);
+    }
+    return (0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_DoAllVar --
+ *	Set up the ALLSRC and OODATE variables. Sad to say, it must be
+ *	done separately, rather than while traversing the graph. This is
+ *	because Make defined OODATE to contain all sources whose modification
+ *	times were later than that of the target, *not* those sources that
+ *	were out-of-date. Since in both compatibility and native modes,
+ *	the modification time of the parent isn't found until the child
+ *	has been dealt with, we have to wait until now to fill in the
+ *	variable. As for ALLSRC, the ordering is important and not
+ *	guaranteed when in native mode, so it must be set here, too.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The ALLSRC and OODATE variables of the given node is filled in.
+ *	If the node is a .JOIN node, its TARGET variable will be set to
+ * 	match its ALLSRC variable.
+ *-----------------------------------------------------------------------
+ */
+void
+Make_DoAllVar (GNode *gn)
+{
+    Lst_ForEach (gn->children, MakeAddAllSrc, (void *) gn);
+
+    if (!Var_Exists (OODATE, gn)) {
+	Var_Set (OODATE, "", gn);
+    }
+    if (!Var_Exists (ALLSRC, gn)) {
+	Var_Set (ALLSRC, "", gn);
+    }
+
+    if (gn->type & OP_JOIN) {
+	char *p1;
+	Var_Set (TARGET, Var_Value (ALLSRC, gn, &p1), gn);
+	efree(p1);
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeStartJobs --
+ *	Start as many jobs as possible.
+ *
+ * Results:
+ *	If the query flag was given to pmake, no job will be started,
+ *	but as soon as an out-of-date target is found, this function
+ *	returns TRUE. At all other times, this function returns FALSE.
+ *
+ * Side Effects:
+ *	Nodes are removed from the toBeMade queue and job table slots
+ *	are filled.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+MakeStartJobs (void)
+{
+    GNode	*gn;
+
+    while (!Job_Full() && !Lst_IsEmpty (toBeMade)) {
+	gn = (GNode *) Lst_DeQueue (toBeMade);
+	DEBUGF(MAKE, ("Examining %s...", gn->name));
+	/*
+	 * Make sure any and all predecessors that are going to be made,
+	 * have been.
+	 */
+	if (!Lst_IsEmpty(gn->preds)) {
+	    LstNode ln;
+
+	    for (ln = Lst_First(gn->preds); ln != NULL; ln = Lst_Succ(ln)){
+		GNode	*pgn = (GNode *)Lst_Datum(ln);
+
+		if (pgn->make && pgn->made == UNMADE) {
+		    DEBUGF(MAKE, ("predecessor %s not made yet.\n", pgn->name));
+		    break;
+		}
+	    }
+	    /*
+	     * If ln isn't NULL, there's a predecessor as yet unmade, so we
+	     * just drop this node on the floor. When the node in question
+	     * has been made, it will notice this node as being ready to
+	     * make but as yet unmade and will place the node on the queue.
+	     */
+	    if (ln != NULL) {
+		continue;
+	    }
+	}
+
+	numNodes--;
+	if (Make_OODate (gn)) {
+	    DEBUGF(MAKE, ("out-of-date\n"));
+	    if (queryFlag) {
+		return (TRUE);
+	    }
+	    Make_DoAllVar (gn);
+	    Job_Make (gn);
+	} else {
+	    DEBUGF(MAKE, ("up-to-date\n"));
+	    gn->made = UPTODATE;
+	    if (gn->type & OP_JOIN) {
+		/*
+		 * Even for an up-to-date .JOIN node, we need it to have its
+		 * context variables so references to it get the correct
+		 * value for .TARGET when building up the context variables
+		 * of its parent(s)...
+		 */
+		Make_DoAllVar (gn);
+	    }
+
+	    Make_Update (gn);
+	}
+    }
+    return (FALSE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakePrintStatus --
+ *	Print the status of a top-level node, viz. it being up-to-date
+ *	already or not created due to an error in a lower level.
+ *	Callback function for Make_Run via Lst_ForEach.  If gn->unmade is
+ *	nonzero and that is meant to imply a cycle in the graph, then
+ *	cycle is TRUE.
+ *
+ * Results:
+ *	Always returns 0.
+ *
+ * Side Effects:
+ *	A message may be printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+MakePrintStatus(void *gnp, void *cyclep)
+{
+    GNode   	*gn = (GNode *) gnp;
+    Boolean 	cycle = *(Boolean *) cyclep;
+    if (gn->made == UPTODATE) {
+	printf ("`%s' is up to date.\n", gn->name);
+    } else if (gn->unmade != 0) {
+	if (cycle) {
+	    Boolean t = TRUE;
+	    /*
+	     * If printing cycles and came to one that has unmade children,
+	     * print out the cycle by recursing on its children. Note a
+	     * cycle like:
+	     *	a : b
+	     *	b : c
+	     *	c : b
+	     * will cause this to erroneously complain about a being in
+	     * the cycle, but this is a good approximation.
+	     */
+	    if (gn->made == CYCLE) {
+		Error("Graph cycles through `%s'", gn->name);
+		gn->made = ENDCYCLE;
+		Lst_ForEach(gn->children, MakePrintStatus, (void *) &t);
+		gn->made = UNMADE;
+	    } else if (gn->made != ENDCYCLE) {
+		gn->made = CYCLE;
+		Lst_ForEach(gn->children, MakePrintStatus, (void *) &t);
+	    }
+	} else {
+	    printf ("`%s' not remade because of errors.\n", gn->name);
+	}
+    }
+    return (0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Run --
+ *	Initialize the nodes to remake and the list of nodes which are
+ *	ready to be made by doing a breadth-first traversal of the graph
+ *	starting from the nodes in the given list. Once this traversal
+ *	is finished, all the 'leaves' of the graph are in the toBeMade
+ *	queue.
+ *	Using this queue and the Job module, work back up the graph,
+ *	calling on MakeStartJobs to keep the job table as full as
+ *	possible.
+ *
+ * Results:
+ *	TRUE if work was done. FALSE otherwise.
+ *
+ * Side Effects:
+ *	The make field of all nodes involved in the creation of the given
+ *	targets is set to 1. The toBeMade list is set to contain all the
+ *	'leaves' of these subgraphs.
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Make_Run (Lst targs)
+{
+    GNode	    *gn;	/* a temporary pointer */
+    Lst		    examine; 	/* List of targets to examine */
+    int	    	    errors; 	/* Number of errors the Job module reports */
+
+    toBeMade = Lst_Init (FALSE);
+
+    examine = Lst_Duplicate(targs, NOCOPY);
+    numNodes = 0;
+
+    /*
+     * Make an initial downward pass over the graph, marking nodes to be made
+     * as we go down. We call Suff_FindDeps to find where a node is and
+     * to get some children for it if it has none and also has no commands.
+     * If the node is a leaf, we stick it on the toBeMade queue to
+     * be looked at in a minute, otherwise we add its children to our queue
+     * and go on about our business.
+     */
+    while (!Lst_IsEmpty (examine)) {
+	gn = (GNode *) Lst_DeQueue (examine);
+
+	if (!gn->make) {
+	    gn->make = TRUE;
+	    numNodes++;
+
+	    /*
+	     * Apply any .USE rules before looking for implicit dependencies
+	     * to make sure everything has commands that should...
+	     */
+	    Lst_ForEach (gn->children, MakeHandleUse, (void *)gn);
+	    Suff_FindDeps (gn);
+
+	    if (gn->unmade != 0) {
+		Lst_ForEach (gn->children, MakeAddChild, (void *)examine);
+	    } else {
+		(void)Lst_EnQueue (toBeMade, (void *)gn);
+	    }
+	}
+    }
+
+    Lst_Destroy (examine, NOFREE);
+
+    if (queryFlag) {
+	/*
+	 * We wouldn't do any work unless we could start some jobs in the
+	 * next loop... (we won't actually start any, of course, this is just
+	 * to see if any of the targets was out of date)
+	 */
+	return (MakeStartJobs());
+    } else {
+	/*
+	 * Initialization. At the moment, no jobs are running and until some
+	 * get started, nothing will happen since the remaining upward
+	 * traversal of the graph is performed by the routines in job.c upon
+	 * the finishing of a job. So we fill the Job table as much as we can
+	 * before going into our loop.
+	 */
+	(void) MakeStartJobs();
+    }
+
+    /*
+     * Main Loop: The idea here is that the ending of jobs will take
+     * care of the maintenance of data structures and the waiting for output
+     * will cause us to be idle most of the time while our children run as
+     * much as possible. Because the job table is kept as full as possible,
+     * the only time when it will be empty is when all the jobs which need
+     * running have been run, so that is the end condition of this loop.
+     * Note that the Job module will exit if there were any errors unless the
+     * keepgoing flag was given.
+     */
+    while (!Job_Empty ()) {
+	Job_CatchOutput ();
+	Job_CatchChildren (!usePipes);
+	(void)MakeStartJobs();
+    }
+
+    errors = Job_Finish();
+
+    /*
+     * Print the final status of each target. E.g. if it wasn't made
+     * because some inferior reported an error.
+     */
+    errors = ((errors == 0) && (numNodes != 0));
+    Lst_ForEach(targs, MakePrintStatus, (void *) &errors);
+
+    return (TRUE);
+}
Index: trunk/src/kmk/make.h
===================================================================
--- trunk/src/kmk/make.h	(revision 9)
+++ trunk/src/kmk/make.h	(revision 9)
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)make.h	8.3 (Berkeley) 6/13/95
+ * $FreeBSD: src/usr.bin/make/make.h,v 1.23 2002/10/10 19:27:48 jmallett Exp $
+ */
+
+/*-
+ * make.h --
+ *	The global definitions for pmake
+ */
+
+#ifndef _MAKE_H_
+#define	_MAKE_H_
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include "sprite.h"
+#include "lst.h"
+#include "config.h"
+#include "buf.h"
+
+/*-
+ * The structure for an individual graph node. Each node has several
+ * pieces of data associated with it.
+ *	1) the name of the target it describes
+ *	2) the location of the target file in the filesystem.
+ *	3) the type of operator used to define its sources (qv. parse.c)
+ *	4) whether it is involved in this invocation of make
+ *	5) whether the target has been remade
+ *	6) whether any of its children has been remade
+ *	7) the number of its children that are, as yet, unmade
+ *	8) its modification time
+ *	9) the modification time of its youngest child (qv. make.c)
+ *	10) a list of nodes for which this is a source
+ *	11) a list of nodes on which this depends
+ *	12) a list of nodes that depend on this, as gleaned from the
+ *	    transformation rules.
+ *	13) a list of nodes of the same name created by the :: operator
+ *	14) a list of nodes that must be made (if they're made) before
+ *	    this node can be, but that do no enter into the datedness of
+ *	    this node.
+ *	15) a list of nodes that must be made (if they're made) after
+ *	    this node is, but that do not depend on this node, in the
+ *	    normal sense.
+ *	16) a Lst of ``local'' variables that are specific to this target
+ *	   and this target only (qv. var.c [$@ $< $?, etc.])
+ *	17) a Lst of strings that are commands to be given to a shell
+ *	   to create this target.
+ */
+typedef struct GNode {
+    char            *name;     	/* The target's name */
+    char    	    *path;     	/* The full pathname of the file */
+    int             type;      	/* Its type (see the OP flags, below) */
+    int		    order;	/* Its wait weight */
+
+    Boolean         make;      	/* TRUE if this target needs to be remade */
+    enum {
+	UNMADE, BEINGMADE, MADE, UPTODATE, ERROR, ABORTED,
+	CYCLE, ENDCYCLE
+    }	    	    made;    	/* Set to reflect the state of processing
+				 * on this node:
+				 *  UNMADE - Not examined yet
+				 *  BEINGMADE - Target is already being made.
+				 *  	Indicates a cycle in the graph. (compat
+				 *  	mode only)
+				 *  MADE - Was out-of-date and has been made
+				 *  UPTODATE - Was already up-to-date
+				 *  ERROR - An error occured while it was being
+				 *  	made (used only in compat mode)
+				 *  ABORTED - The target was aborted due to
+				 *  	an error making an inferior (compat).
+				 *  CYCLE - Marked as potentially being part of
+				 *  	a graph cycle. If we come back to a
+				 *  	node marked this way, it is printed
+				 *  	and 'made' is changed to ENDCYCLE.
+				 *  ENDCYCLE - the cycle has been completely
+				 *  	printed. Go back and unmark all its
+				 *  	members.
+				 */
+    Boolean 	    childMade; 	/* TRUE if one of this target's children was
+				 * made */
+    int             unmade;    	/* The number of unmade children */
+
+    int             mtime;     	/* Its modification time */
+    int        	    cmtime;    	/* The modification time of its youngest
+				 * child */
+
+    Lst     	    iParents;  	/* Links to parents for which this is an
+				 * implied source, if any */
+    Lst	    	    cohorts;  	/* Other nodes for the :: operator */
+    Lst             parents;   	/* Nodes that depend on this one */
+    Lst             children;  	/* Nodes on which this one depends */
+    Lst	    	    successors;	/* Nodes that must be made after this one */
+    Lst	    	    preds;  	/* Nodes that must be made before this one */
+
+    Lst             context;   	/* The local variables */
+    Lst             commands;  	/* Creation commands */
+
+    struct _Suff    *suffix;	/* Suffix for the node (determined by
+				 * Suff_FindDeps and opaque to everyone
+				 * but the Suff module) */
+} GNode;
+
+/*
+ * The OP_ constants are used when parsing a dependency line as a way of
+ * communicating to other parts of the program the way in which a target
+ * should be made. These constants are bitwise-OR'ed together and
+ * placed in the 'type' field of each node. Any node that has
+ * a 'type' field which satisfies the OP_NOP function was never never on
+ * the lefthand side of an operator, though it may have been on the
+ * righthand side...
+ */
+#define	OP_DEPENDS	0x00000001  /* Execution of commands depends on
+				     * kids (:) */
+#define	OP_FORCE	0x00000002  /* Always execute commands (!) */
+#define	OP_DOUBLEDEP	0x00000004  /* Execution of commands depends on kids
+				     * per line (::) */
+#define	OP_OPMASK	(OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP)
+
+#define	OP_OPTIONAL	0x00000008  /* Don't care if the target doesn't
+				     * exist and can't be created */
+#define	OP_USE		0x00000010  /* Use associated commands for parents */
+#define	OP_EXEC	  	0x00000020  /* Target is never out of date, but always
+				     * execute commands anyway. Its time
+				     * doesn't matter, so it has none...sort
+				     * of */
+#define	OP_IGNORE	0x00000040  /* Ignore errors when creating the node */
+#define	OP_PRECIOUS	0x00000080  /* Don't remove the target when
+				     * interrupted */
+#define	OP_SILENT	0x00000100  /* Don't echo commands when executed */
+#define	OP_MAKE		0x00000200  /* Target is a recurrsive make so its
+				     * commands should always be executed when
+				     * it is out of date, regardless of the
+				     * state of the -n or -t flags */
+#define	OP_JOIN 	0x00000400  /* Target is out-of-date only if any of its
+				     * children was out-of-date */
+#define	OP_INVISIBLE	0x00004000  /* The node is invisible to its parents.
+				     * I.e. it doesn't show up in the parents's
+				     * local variables. */
+#define	OP_NOTMAIN	0x00008000  /* The node is exempt from normal 'main
+				     * target' processing in parse.c */
+#define	OP_PHONY	0x00010000  /* Not a file target; run always */
+/* Attributes applied by PMake */
+#define	OP_TRANSFORM	0x80000000  /* The node is a transformation rule */
+#define	OP_MEMBER 	0x40000000  /* Target is a member of an archive */
+#define	OP_LIB	  	0x20000000  /* Target is a library */
+#define	OP_ARCHV  	0x10000000  /* Target is an archive construct */
+#define	OP_HAS_COMMANDS	0x08000000  /* Target has all the commands it should.
+				     * Used when parsing to catch multiple
+				     * commands for a target */
+#define	OP_SAVE_CMDS	0x04000000  /* Saving commands on .END (Compat) */
+#define	OP_DEPS_FOUND	0x02000000  /* Already processed by Suff_FindDeps */
+
+/*
+ * OP_NOP will return TRUE if the node with the given type was not the
+ * object of a dependency operator
+ */
+#define	OP_NOP(t)	(((t) & OP_OPMASK) == 0x00000000)
+
+/*
+ * The TARG_ constants are used when calling the Targ_FindNode and
+ * Targ_FindList functions in targ.c. They simply tell the functions what to
+ * do if the desired node(s) is (are) not found. If the TARG_CREATE constant
+ * is given, a new, empty node will be created for the target, placed in the
+ * table of all targets and its address returned. If TARG_NOCREATE is given,
+ * a NULL pointer will be returned.
+ */
+#define	TARG_CREATE	0x01	  /* create node if not found */
+#define	TARG_NOCREATE	0x00	  /* don't create it */
+
+/*
+ * There are several places where expandable buffers are used (parse.c and
+ * var.c). This constant is merely the starting point for those buffers. If
+ * lines tend to be much shorter than this, it would be best to reduce BSIZE.
+ * If longer, it should be increased. Reducing it will cause more copying to
+ * be done for longer lines, but will save space for shorter ones. In any
+ * case, it ought to be a power of two simply because most storage allocation
+ * schemes allocate in powers of two.
+ */
+#define	MAKE_BSIZE		256	/* starting size for expandable buffers */
+
+/*
+ * These constants are all used by the Str_Concat function to decide how the
+ * final string should look. If STR_ADDSPACE is given, a space will be
+ * placed between the two strings. If STR_ADDSLASH is given, a '/' will
+ * be used instead of a space. If neither is given, no intervening characters
+ * will be placed between the two strings in the final output. If the
+ * STR_DOFREE bit is set, the two input strings will be freed before
+ * Str_Concat returns.
+ */
+#define	STR_ADDSPACE	0x01	/* add a space when Str_Concat'ing */
+#define	STR_DOFREE	0x02	/* free source strings after concatenation */
+#define	STR_ADDSLASH	0x04	/* add a slash when Str_Concat'ing */
+
+/*
+ * Error levels for parsing. PARSE_FATAL means the process cannot continue
+ * once the makefile has been parsed. PARSE_WARNING means it can. Passed
+ * as the first argument to Parse_Error.
+ */
+#define	PARSE_WARNING	2
+#define	PARSE_FATAL	1
+
+/*
+ * Values returned by Cond_Eval.
+ */
+#define	COND_PARSE	0   	/* Parse the next lines */
+#define	COND_SKIP 	1   	/* Skip the next lines */
+#define	COND_INVALID	2   	/* Not a conditional statement */
+
+/*
+ * Definitions for the "local" variables. Used only for clarity.
+ */
+#define	TARGET	  	  "@" 	/* Target of dependency */
+#define	OODATE	  	  "?" 	/* All out-of-date sources */
+#define	ALLSRC	  	  ">" 	/* All sources */
+#define	IMPSRC	  	  "<" 	/* Source implied by transformation */
+#define	PREFIX	  	  "*" 	/* Common prefix */
+#define	ARCHIVE	  	  "!" 	/* Archive in "archive(member)" syntax */
+#define	MEMBER	  	  "%" 	/* Member in "archive(member)" syntax */
+
+#define	FTARGET           "@F"  /* file part of TARGET */
+#define	DTARGET           "@D"  /* directory part of TARGET */
+#define	FIMPSRC           "<F"  /* file part of IMPSRC */
+#define	DIMPSRC           "<D"  /* directory part of IMPSRC */
+#define	FPREFIX           "*F"  /* file part of PREFIX */
+#define	DPREFIX           "*D"  /* directory part of PREFIX */
+
+/*
+ * Global Variables
+ */
+extern Lst  	create;	    	/* The list of target names specified on the
+				 * command line. used to resolve #if
+				 * make(...) statements */
+extern Lst     	dirSearchPath; 	/* The list of directories to search when
+				 * looking for targets */
+extern Lst	parseIncPath;	/* The list of directories to search when
+				 * looking for includes */
+
+extern Boolean	jobsRunning;	/* True if jobs are running */
+extern Boolean	compatMake;	/* True if we are make compatible */
+extern Boolean	ignoreErrors;  	/* True if should ignore all errors */
+extern Boolean  beSilent;    	/* True if should print no commands */
+extern Boolean	beVerbose;	/* True if should print extra cruft */
+extern Boolean  noExecute;    	/* True if should execute nothing */
+extern Boolean  allPrecious;   	/* True if every target is precious */
+extern Boolean  keepgoing;    	/* True if should continue on unaffected
+				 * portions of the graph when have an error
+				 * in one portion */
+extern Boolean 	touchFlag;    	/* TRUE if targets should just be 'touched'
+				 * if out of date. Set by the -t flag */
+extern Boolean  usePipes;    	/* TRUE if should capture the output of
+				 * subshells by means of pipes. Otherwise it
+				 * is routed to temporary files from which it
+				 * is retrieved when the shell exits */
+extern Boolean 	queryFlag;    	/* TRUE if we aren't supposed to really make
+				 * anything, just see if the targets are out-
+				 * of-date */
+
+extern Boolean	checkEnvFirst;	/* TRUE if environment should be searched for
+				 * all variables before the global context */
+extern Lst	envFirstVars;	/* List of specific variables for which the
+				 * environment should be searched before the
+				 * global context */
+
+extern GNode    *DEFAULT;    	/* .DEFAULT rule */
+
+extern GNode    *VAR_GLOBAL;   	/* Variables defined in a global context, e.g
+				 * in the Makefile itself */
+extern GNode    *VAR_CMD;    	/* Variables defined on the command line */
+extern char    	var_Error[];   	/* Value returned by Var_Parse when an error
+				 * is encountered. It actually points to
+				 * an empty string, so naive callers needn't
+				 * worry about it. */
+
+extern time_t 	now;	    	/* The time at the start of this whole
+				 * process */
+
+extern Boolean	oldVars;    	/* Do old-style variable substitution */
+
+extern Lst	sysIncPath;	/* The system include path. */
+
+/*
+ * debug control:
+ *	There is one bit per module.  It is up to the module what debug
+ *	information to print.
+ */
+extern int debug;
+#define	DEBUG_ARCH	0x0001
+#define	DEBUG_COND	0x0002
+#define	DEBUG_DIR	0x0004
+#define	DEBUG_GRAPH1	0x0008
+#define	DEBUG_GRAPH2	0x0010
+#define	DEBUG_JOB	0x0020
+#define	DEBUG_MAKE	0x0040
+#define	DEBUG_SUFF	0x0080
+#define	DEBUG_TARG	0x0100
+#define	DEBUG_VAR	0x0200
+#define	DEBUG_FOR	0x0400
+#define	DEBUG_LOUD	0x0800
+
+#define	CONCAT(a,b)	a##b
+
+#define	DEBUG(module)	(debug & CONCAT(DEBUG_,module))
+#define	DEBUGF(module,args)		\
+do {						\
+	if (DEBUG(module)) {			\
+		Debug args ;			\
+	}					\
+} while (0)
+#define	ISDOT(c) ((c)[0] == '.' && (((c)[1] == '\0') || ((c)[1] == '/')))
+#define	ISDOTDOT(c) ((c)[0] == '.' && ISDOT(&((c)[1])))
+
+/*
+ * Since there are so many, all functions that return non-integer values are
+ * extracted by means of a sed script or two and stuck in the file "nonints.h"
+ */
+#include "nonints.h"
+
+int Make_TimeStamp(GNode *, GNode *);
+Boolean Make_OODate(GNode *);
+int Make_HandleUse(GNode *, GNode *);
+void Make_Update(GNode *);
+void Make_DoAllVar(GNode *);
+Boolean Make_Run(Lst);
+
+#endif /* _MAKE_H_ */
Index: trunk/src/kmk/nonints.h
===================================================================
--- trunk/src/kmk/nonints.h	(revision 9)
+++ trunk/src/kmk/nonints.h	(revision 9)
@@ -0,0 +1,148 @@
+/*-
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)nonints.h	8.4 (Berkeley) 4/28/95
+ * $FreeBSD: src/usr.bin/make/nonints.h,v 1.17 2002/09/18 16:13:03 jmallett Exp $
+ */
+
+/* arch.c */
+ReturnStatus Arch_ParseArchive(char **, Lst, GNode *);
+void Arch_Touch(GNode *);
+void Arch_TouchLib(GNode *);
+int Arch_MTime(GNode *);
+int Arch_MemMTime(GNode *);
+void Arch_FindLib(GNode *, Lst);
+Boolean Arch_LibOODate(GNode *);
+void Arch_Init(void);
+void Arch_End(void);
+
+/* compat.c */
+void Compat_Run(Lst);
+
+/* cond.c */
+int Cond_Eval(char *);
+void Cond_End(void);
+
+/* for.c */
+int For_Eval(char *);
+void For_Run(void);
+
+/* main.c */
+void Main_ParseArgLine(char *);
+char *Cmd_Exec(char *, char **);
+void Debug(const char *, ...);
+void Error(const char *, ...);
+void Fatal(const char *, ...);
+void Punt(const char *, ...);
+void DieHorribly(void);
+int PrintAddr(void *, void *);
+void Finish(int);
+char *estrdup(const char *);
+void *emalloc(size_t);
+/* efree(x) works when x==NULL. STDC behavior, may need some different
+ * definition for cross-builds on deficient systems */
+#define	efree	free
+void *erealloc(void *, size_t);
+void enomem(void);
+int eunlink(const char *);
+
+/* parse.c */
+void Parse_Error(int, const char *, ...);
+Boolean Parse_AnyExport(void);
+Boolean Parse_IsVar(char *);
+void Parse_DoVar(char *, GNode *);
+void Parse_AddIncludeDir(char *);
+void Parse_File(char *, FILE *);
+void Parse_Init(void);
+void Parse_End(void);
+void Parse_FromString(char *);
+Lst Parse_MainName(void);
+
+/* str.c */
+void str_init(void);
+void str_end(void);
+char *str_concat(char *, char *, int);
+char **brk_string(char *, int *, Boolean);
+char *Str_FindSubstring(char *, char *);
+int Str_Match(char *, char *);
+char *Str_SYSVMatch(char *, char *, int *len);
+void Str_SYSVSubst(Buffer, char *, char *, int);
+
+/* suff.c */
+void Suff_ClearSuffixes(void);
+Boolean Suff_IsTransform(char *);
+GNode *Suff_AddTransform(char *);
+int Suff_EndTransform(void *, void *);
+void Suff_AddSuffix(char *);
+Lst Suff_GetPath(char *);
+void Suff_DoPaths(void);
+void Suff_AddInclude(char *);
+void Suff_AddLib(char *);
+void Suff_FindDeps(GNode *);
+void Suff_SetNull(char *);
+void Suff_Init(void);
+void Suff_End(void);
+void Suff_PrintAll(void);
+
+/* targ.c */
+void Targ_Init(void);
+void Targ_End(void);
+GNode *Targ_NewGN(char *);
+GNode *Targ_FindNode(char *, int);
+Lst Targ_FindList(Lst, int);
+Boolean Targ_Ignore(GNode *);
+Boolean Targ_Silent(GNode *);
+Boolean Targ_Precious(GNode *);
+void Targ_SetMain(GNode *);
+int Targ_PrintCmd(void *, void *);
+char *Targ_FmtTime(time_t);
+void Targ_PrintType(int);
+void Targ_PrintGraph(int);
+
+/* var.c */
+void Var_Delete(char *, GNode *);
+void Var_Set(char *, char *, GNode *);
+void Var_Append(char *, char *, GNode *);
+Boolean Var_Exists(char *, GNode *);
+char *Var_Value(char *, GNode *, char **);
+char *Var_Parse(char *, GNode *, Boolean, int *, Boolean *);
+char *Var_Subst(char *, char *, GNode *, Boolean);
+char *Var_GetTail(char *);
+char *Var_GetHead(char *);
+void Var_Init(void);
+void Var_End(void);
+void Var_Dump(GNode *);
Index: trunk/src/kmk/parse.c
===================================================================
--- trunk/src/kmk/parse.c	(revision 9)
+++ trunk/src/kmk/parse.c	(revision 9)
@@ -0,0 +1,2623 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)parse.c	8.3 (Berkeley) 3/19/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/parse.c,v 1.48 2002/10/09 03:42:10 jmallett Exp $");
+
+/*-
+ * parse.c --
+ *	Functions to parse a makefile.
+ *
+ *	One function, Parse_Init, must be called before any functions
+ *	in this module are used. After that, the function Parse_File is the
+ *	main entry point and controls most of the other functions in this
+ *	module.
+ *
+ *	Most important structures are kept in Lsts. Directories for
+ *	the #include "..." function are kept in the 'parseIncPath' Lst, while
+ *	those for the #include <...> are kept in the 'sysIncPath' Lst. The
+ *	targets currently being defined are kept in the 'targets' Lst.
+ *
+ *	The variables 'fname' and 'lineno' are used to track the name
+ *	of the current file and the line number in that file so that error
+ *	messages can be more meaningful.
+ *
+ * Interface:
+ *	Parse_Init	    	    Initialization function which must be
+ *	    	  	    	    called before anything else in this module
+ *	    	  	    	    is used.
+ *
+ *	Parse_End		    Cleanup the module
+ *
+ *	Parse_File	    	    Function used to parse a makefile. It must
+ *	    	  	    	    be given the name of the file, which should
+ *	    	  	    	    already have been opened, and a function
+ *	    	  	    	    to call to read a character from the file.
+ *
+ *	Parse_IsVar	    	    Returns TRUE if the given line is a
+ *	    	  	    	    variable assignment. Used by MainParseArgs
+ *	    	  	    	    to determine if an argument is a target
+ *	    	  	    	    or a variable assignment. Used internally
+ *	    	  	    	    for pretty much the same thing...
+ *
+ *	Parse_Error	    	    Function called when an error occurs in
+ *	    	  	    	    parsing. Used by the variable and
+ *	    	  	    	    conditional modules.
+ *	Parse_MainName	    	    Returns a Lst of the main target to create.
+ */
+
+#include <stdarg.h>
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "buf.h"
+#include "pathnames.h"
+
+/*
+ * These values are returned by ParseEOF to tell Parse_File whether to
+ * CONTINUE parsing, i.e. it had only reached the end of an include file,
+ * or if it's DONE.
+ */
+#define	CONTINUE	1
+#define	DONE		0
+static Lst     	    targets;	/* targets we're working on */
+static Lst     	    targCmds;	/* command lines for targets */
+static Boolean	    inLine;	/* true if currently in a dependency
+				 * line or its commands */
+typedef struct {
+    char *str;
+    char *ptr;
+} PTR;
+
+static char    	    *fname;	/* name of current file (for errors) */
+static int          lineno;	/* line number in current file */
+static FILE   	    *curFILE = NULL; 	/* current makefile */
+
+static PTR 	    *curPTR = NULL; 	/* current makefile */
+
+static int	    fatals = 0;
+
+static GNode	    *mainNode;	/* The main target to create. This is the
+				 * first target on the first dependency
+				 * line in the first makefile */
+/*
+ * Definitions for handling #include specifications
+ */
+typedef struct IFile {
+    char           *fname;	    /* name of previous file */
+    int             lineno;	    /* saved line number */
+    FILE *          F;		    /* the open stream */
+    PTR *	    p;	    	    /* the char pointer */
+} IFile;
+
+static Lst      includes;  	/* stack of IFiles generated by
+				 * #includes */
+Lst         	parseIncPath;	/* list of directories for "..." includes */
+Lst         	sysIncPath;	/* list of directories for <...> includes */
+
+/*-
+ * specType contains the SPECial TYPE of the current target. It is
+ * Not if the target is unspecial. If it *is* special, however, the children
+ * are linked as children of the parent but not vice versa. This variable is
+ * set in ParseDoDependency
+ */
+typedef enum {
+    Begin,  	    /* .BEGIN */
+    Default,	    /* .DEFAULT */
+    End,    	    /* .END */
+    Ignore,	    /* .IGNORE */
+    Includes,	    /* .INCLUDES */
+    Interrupt,	    /* .INTERRUPT */
+    Libs,	    /* .LIBS */
+    MFlags,	    /* .MFLAGS or .MAKEFLAGS */
+    Main,	    /* .MAIN and we don't have anything user-specified to
+		     * make */
+    NoExport,	    /* .NOEXPORT */
+    Not,	    /* Not special */
+    NotParallel,    /* .NOTPARALELL */
+    Null,   	    /* .NULL */
+    Order,  	    /* .ORDER */
+    Parallel,	    /* .PARALLEL */
+    ExPath,	    /* .PATH */
+    Phony,	    /* .PHONY */
+#ifdef POSIX
+    Posix,	    /* .POSIX */
+#endif
+    Precious,	    /* .PRECIOUS */
+    ExShell,	    /* .SHELL */
+    Silent,	    /* .SILENT */
+    SingleShell,    /* .SINGLESHELL */
+    Suffixes,	    /* .SUFFIXES */
+    Wait,	    /* .WAIT */
+    Attribute	    /* Generic attribute */
+} ParseSpecial;
+
+static ParseSpecial specType;
+static int waiting;
+
+/*
+ * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER
+ * seen, then set to each successive source on the line.
+ */
+static GNode	*predecessor;
+
+/*
+ * The parseKeywords table is searched using binary search when deciding
+ * if a target or source is special. The 'spec' field is the ParseSpecial
+ * type of the keyword ("Not" if the keyword isn't special as a target) while
+ * the 'op' field is the operator to apply to the list of targets if the
+ * keyword is used as a source ("0" if the keyword isn't special as a source)
+ */
+static struct {
+    char    	  *name;    	/* Name of keyword */
+    ParseSpecial  spec;	    	/* Type when used as a target */
+    int	    	  op;	    	/* Operator when used as a source */
+} parseKeywords[] = {
+{ ".BEGIN", 	  Begin,    	0 },
+{ ".DEFAULT",	  Default,  	0 },
+{ ".END",   	  End,	    	0 },
+{ ".EXEC",	  Attribute,   	OP_EXEC },
+{ ".IGNORE",	  Ignore,   	OP_IGNORE },
+{ ".INCLUDES",	  Includes, 	0 },
+{ ".INTERRUPT",	  Interrupt,	0 },
+{ ".INVISIBLE",	  Attribute,   	OP_INVISIBLE },
+{ ".JOIN",  	  Attribute,   	OP_JOIN },
+{ ".LIBS",  	  Libs,	    	0 },
+{ ".MAIN",	  Main,		0 },
+{ ".MAKE",  	  Attribute,   	OP_MAKE },
+{ ".MAKEFLAGS",	  MFlags,   	0 },
+{ ".MFLAGS",	  MFlags,   	0 },
+{ ".NOTMAIN",	  Attribute,   	OP_NOTMAIN },
+{ ".NOTPARALLEL", NotParallel,	0 },
+{ ".NO_PARALLEL", NotParallel,	0 },
+{ ".NULL",  	  Null,	    	0 },
+{ ".OPTIONAL",	  Attribute,   	OP_OPTIONAL },
+{ ".ORDER", 	  Order,    	0 },
+{ ".PARALLEL",	  Parallel,	0 },
+{ ".PATH",	  ExPath,	0 },
+{ ".PHONY",	  Phony,	OP_PHONY },
+#ifdef POSIX
+{ ".POSIX",	  Posix,	0 },
+#endif
+{ ".PRECIOUS",	  Precious, 	OP_PRECIOUS },
+{ ".RECURSIVE",	  Attribute,	OP_MAKE },
+{ ".SHELL", 	  ExShell,    	0 },
+{ ".SILENT",	  Silent,   	OP_SILENT },
+{ ".SINGLESHELL", SingleShell,	0 },
+{ ".SUFFIXES",	  Suffixes, 	0 },
+{ ".USE",   	  Attribute,   	OP_USE },
+{ ".WAIT",	  Wait, 	0 },
+};
+
+static int ParseFindKeyword(char *);
+static int ParseLinkSrc(void *, void *);
+static int ParseDoOp(void *, void *);
+static int ParseAddDep(void *, void *);
+static void ParseDoSrc(int, char *, Lst);
+static int ParseFindMain(void *, void *);
+static int ParseAddDir(void *, void *);
+static int ParseClearPath(void *, void *);
+static void ParseDoDependency(char *);
+static int ParseAddCmd(void *, void *);
+static int ParseReadc(void);
+static void ParseUnreadc(int);
+static void ParseHasCommands(void *);
+static void ParseDoInclude(char *);
+static void ParseDoError(char *);
+#ifdef SYSVINCLUDE
+static void ParseTraditionalInclude(char *);
+#endif
+static int ParseEOF(int);
+static char *ParseReadLine(void);
+static char *ParseSkipLine(int);
+static void ParseFinishLine(void);
+
+/*-
+ *----------------------------------------------------------------------
+ * ParseFindKeyword --
+ *	Look in the table of keywords for one matching the given string.
+ *
+ * Results:
+ *	The index of the keyword, or -1 if it isn't there.
+ *
+ * Side Effects:
+ *	None
+ *----------------------------------------------------------------------
+ */
+static int
+ParseFindKeyword (char *str)
+{
+    int		    start,
+		    end,
+		    cur;
+    int		    diff;
+
+    start = 0;
+    end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1;
+
+    do {
+	cur = start + ((end - start) / 2);
+	diff = strcmp (str, parseKeywords[cur].name);
+
+	if (diff == 0) {
+	    return (cur);
+	} else if (diff < 0) {
+	    end = cur - 1;
+	} else {
+	    start = cur + 1;
+	}
+    } while (start <= end);
+    return (-1);
+}
+
+/*-
+ * Parse_Error  --
+ *	Error message abort function for parsing. Prints out the context
+ *	of the error (line number and file) as well as the message with
+ *	two optional arguments.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	"fatals" is incremented if the level is PARSE_FATAL.
+ */
+/* VARARGS */
+void
+Parse_Error(int type, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	(void)fprintf(stderr, "\"%s\", line %d: ", fname, lineno);
+	if (type == PARSE_WARNING)
+		(void)fprintf(stderr, "warning: ");
+	(void)vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	(void)fprintf(stderr, "\n");
+	(void)fflush(stderr);
+	if (type == PARSE_FATAL)
+		fatals += 1;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseLinkSrc  --
+ *	Link the parent node to its new child. Used in a Lst_ForEach by
+ *	ParseDoDependency. If the specType isn't 'Not', the parent
+ *	isn't linked as a parent of the child.
+ *
+ * Results:
+ *	Always = 0
+ *
+ * Side Effects:
+ *	New elements are added to the parents list of cgn and the
+ *	children list of cgn. the unmade field of pgn is updated
+ *	to reflect the additional child.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseLinkSrc (void *pgnp, void *cgnp)
+{
+    GNode          *pgn = (GNode *) pgnp;
+    GNode          *cgn = (GNode *) cgnp;
+    if (Lst_Member (pgn->children, (void *)cgn) == NULL) {
+	(void)Lst_AtEnd (pgn->children, (void *)cgn);
+	if (specType == Not) {
+	    (void)Lst_AtEnd (cgn->parents, (void *)pgn);
+	}
+	pgn->unmade += 1;
+    }
+    return (0);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoOp  --
+ *	Apply the parsed operator to the given target node. Used in a
+ *	Lst_ForEach call by ParseDoDependency once all targets have
+ *	been found and their operator parsed. If the previous and new
+ *	operators are incompatible, a major error is taken.
+ *
+ * Results:
+ *	Always 0
+ *
+ * Side Effects:
+ *	The type field of the node is altered to reflect any new bits in
+ *	the op.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseDoOp (void *gnp, void *opp)
+{
+    GNode          *gn = (GNode *) gnp;
+    int             op = *(int *) opp;
+    /*
+     * If the dependency mask of the operator and the node don't match and
+     * the node has actually had an operator applied to it before, and
+     * the operator actually has some dependency information in it, complain.
+     */
+    if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) &&
+	!OP_NOP(gn->type) && !OP_NOP(op))
+    {
+	Parse_Error (PARSE_FATAL, "Inconsistent operator for %s", gn->name);
+	return (1);
+    }
+
+    if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) {
+	/*
+	 * If the node was the object of a :: operator, we need to create a
+	 * new instance of it for the children and commands on this dependency
+	 * line. The new instance is placed on the 'cohorts' list of the
+	 * initial one (note the initial one is not on its own cohorts list)
+	 * and the new instance is linked to all parents of the initial
+	 * instance.
+	 */
+	GNode		*cohort;
+	LstNode	    	ln;
+
+	cohort = Targ_NewGN(gn->name);
+	/*
+	 * Duplicate links to parents so graph traversal is simple. Perhaps
+	 * some type bits should be duplicated?
+	 *
+	 * Make the cohort invisible as well to avoid duplicating it into
+	 * other variables. True, parents of this target won't tend to do
+	 * anything with their local variables, but better safe than
+	 * sorry.
+	 */
+	Lst_ForEach(gn->parents, ParseLinkSrc, (void *)cohort);
+	cohort->type = OP_DOUBLEDEP|OP_INVISIBLE;
+	(void)Lst_AtEnd(gn->cohorts, (void *)cohort);
+
+	/*
+	 * Replace the node in the targets list with the new copy
+	 */
+	ln = Lst_Member(targets, (void *)gn);
+	Lst_Replace(ln, (void *)cohort);
+	gn = cohort;
+    }
+    /*
+     * We don't want to nuke any previous flags (whatever they were) so we
+     * just OR the new operator into the old
+     */
+    gn->type |= op;
+
+    return (0);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseAddDep  --
+ *	Check if the pair of GNodes given needs to be synchronized.
+ *	This has to be when two nodes are on different sides of a
+ *	.WAIT directive.
+ *
+ * Results:
+ *	Returns 1 if the two targets need to be ordered, 0 otherwise.
+ *	If it returns 1, the search can stop
+ *
+ * Side Effects:
+ *	A dependency can be added between the two nodes.
+ *
+ *---------------------------------------------------------------------
+ */
+static int
+ParseAddDep(void *pp, void *sp)
+{
+    GNode *p = (GNode *) pp;
+    GNode *s = (GNode *) sp;
+
+    if (p->order < s->order) {
+	/*
+	 * XXX: This can cause loops, and loops can cause unmade targets,
+	 * but checking is tedious, and the debugging output can show the
+	 * problem
+	 */
+	(void)Lst_AtEnd(p->successors, (void *)s);
+	(void)Lst_AtEnd(s->preds, (void *)p);
+	return 0;
+    }
+    else
+	return 1;
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoSrc  --
+ *	Given the name of a source, figure out if it is an attribute
+ *	and apply it to the targets if it is. Else decide if there is
+ *	some attribute which should be applied *to* the source because
+ *	of some special target and apply it if so. Otherwise, make the
+ *	source be a child of the targets in the list 'targets'
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Operator bits may be added to the list of targets or to the source.
+ *	The targets may have a new source added to their lists of children.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoSrc (int tOp, char *src, Lst allsrc)
+{
+    GNode	*gn = NULL;
+
+    if (*src == '.' && isupper ((unsigned char) src[1])) {
+	int keywd = ParseFindKeyword(src);
+	if (keywd != -1) {
+	    int op = parseKeywords[keywd].op;
+	    if (op != 0) {
+		Lst_ForEach (targets, ParseDoOp, (void *)&op);
+		return;
+	    }
+	    if (parseKeywords[keywd].spec == Wait) {
+		waiting++;
+		return;
+	    }
+	}
+    }
+
+    switch (specType) {
+    case Main:
+	/*
+	 * If we have noted the existence of a .MAIN, it means we need
+	 * to add the sources of said target to the list of things
+	 * to create. The string 'src' is likely to be free, so we
+	 * must make a new copy of it. Note that this will only be
+	 * invoked if the user didn't specify a target on the command
+	 * line. This is to allow #ifmake's to succeed, or something...
+	 */
+	(void) Lst_AtEnd (create, (void *)estrdup(src));
+	/*
+	 * Add the name to the .TARGETS variable as well, so the user cna
+	 * employ that, if desired.
+	 */
+	Var_Append(".TARGETS", src, VAR_GLOBAL);
+	return;
+
+    case Order:
+	/*
+	 * Create proper predecessor/successor links between the previous
+	 * source and the current one.
+	 */
+	gn = Targ_FindNode(src, TARG_CREATE);
+	if (predecessor != NULL) {
+	    (void)Lst_AtEnd(predecessor->successors, (void *)gn);
+	    (void)Lst_AtEnd(gn->preds, (void *)predecessor);
+	}
+	/*
+	 * The current source now becomes the predecessor for the next one.
+	 */
+	predecessor = gn;
+	break;
+
+    default:
+	/*
+	 * If the source is not an attribute, we need to find/create
+	 * a node for it. After that we can apply any operator to it
+	 * from a special target or link it to its parents, as
+	 * appropriate.
+	 *
+	 * In the case of a source that was the object of a :: operator,
+	 * the attribute is applied to all of its instances (as kept in
+	 * the 'cohorts' list of the node) or all the cohorts are linked
+	 * to all the targets.
+	 */
+	gn = Targ_FindNode (src, TARG_CREATE);
+	if (tOp) {
+	    gn->type |= tOp;
+	} else {
+	    Lst_ForEach (targets, ParseLinkSrc, (void *)gn);
+	}
+	if ((gn->type & OP_OPMASK) == OP_DOUBLEDEP) {
+	    GNode  	*cohort;
+	    LstNode	ln;
+
+	    for (ln=Lst_First(gn->cohorts); ln != NULL; ln = Lst_Succ(ln)){
+		cohort = (GNode *)Lst_Datum(ln);
+		if (tOp) {
+		    cohort->type |= tOp;
+		} else {
+		    Lst_ForEach(targets, ParseLinkSrc, (void *)cohort);
+		}
+	    }
+	}
+	break;
+    }
+
+    gn->order = waiting;
+    (void)Lst_AtEnd(allsrc, (void *)gn);
+    if (waiting) {
+	Lst_ForEach(allsrc, ParseAddDep, (void *)gn);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseFindMain --
+ *	Find a real target in the list and set it to be the main one.
+ *	Called by ParseDoDependency when a main target hasn't been found
+ *	yet.
+ *
+ * Results:
+ *	0 if main not found yet, 1 if it is.
+ *
+ * Side Effects:
+ *	mainNode is changed and Targ_SetMain is called.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseFindMain(void *gnp, void *dummy __unused)
+{
+    GNode   	  *gn = (GNode *) gnp;
+    if ((gn->type & (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM)) == 0) {
+	mainNode = gn;
+	Targ_SetMain(gn);
+	return (1);
+    } else {
+	return (0);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseAddDir --
+ *	Front-end for Dir_AddDir to make sure Lst_ForEach keeps going
+ *
+ * Results:
+ *	=== 0
+ *
+ * Side Effects:
+ *	See Dir_AddDir.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseAddDir(void *path, void *name)
+{
+    Dir_AddDir((Lst) path, (char *) name);
+    return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseClearPath --
+ *	Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going
+ *
+ * Results:
+ *	=== 0
+ *
+ * Side Effects:
+ *	See Dir_ClearPath
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ParseClearPath(void *path, void *dummy __unused)
+{
+    Dir_ClearPath((Lst) path);
+    return (0);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoDependency  --
+ *	Parse the dependency line in line.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The nodes of the sources are linked as children to the nodes of the
+ *	targets. Some nodes may be created.
+ *
+ *	We parse a dependency line by first extracting words from the line and
+ * finding nodes in the list of all targets with that name. This is done
+ * until a character is encountered which is an operator character. Currently
+ * these are only ! and :. At this point the operator is parsed and the
+ * pointer into the line advanced until the first source is encountered.
+ * 	The parsed operator is applied to each node in the 'targets' list,
+ * which is where the nodes found for the targets are kept, by means of
+ * the ParseDoOp function.
+ *	The sources are read in much the same way as the targets were except
+ * that now they are expanded using the wildcarding scheme of the C-Shell
+ * and all instances of the resulting words in the list of all targets
+ * are found. Each of the resulting nodes is then linked to each of the
+ * targets as one of its children.
+ *	Certain targets are handled specially. These are the ones detailed
+ * by the specType variable.
+ *	The storing of transformation rules is also taken care of here.
+ * A target is recognized as a transformation rule by calling
+ * Suff_IsTransform. If it is a transformation rule, its node is gotten
+ * from the suffix module via Suff_AddTransform rather than the standard
+ * Targ_FindNode in the target module.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoDependency (char *line)
+{
+    char  	   *cp;		/* our current position */
+    GNode 	   *gn;		/* a general purpose temporary node */
+    int             op;		/* the operator on the line */
+    char            savec;	/* a place to save a character */
+    Lst    	    paths;   	/* List of search paths to alter when parsing
+				 * a list of .PATH targets */
+    int	    	    tOp;    	/* operator from special target */
+    Lst	    	    sources;	/* list of archive source names after
+				 * expansion */
+    Lst 	    curTargs;	/* list of target names to be found and added
+				 * to the targets list */
+    Lst		    curSrcs;	/* list of sources in order */
+
+    tOp = 0;
+
+    specType = Not;
+    waiting = 0;
+    paths = (Lst)NULL;
+
+    curTargs = Lst_Init(FALSE);
+    curSrcs = Lst_Init(FALSE);
+
+    do {
+	for (cp = line;
+	     *cp && !isspace ((unsigned char) *cp) && *cp != '(';
+	     cp++)
+	{
+	    if (*cp == '$') {
+		/*
+		 * Must be a dynamic source (would have been expanded
+		 * otherwise), so call the Var module to parse the puppy
+		 * so we can safely advance beyond it...There should be
+		 * no errors in this, as they would have been discovered
+		 * in the initial Var_Subst and we wouldn't be here.
+		 */
+		int 	length;
+		Boolean	freeIt;
+		char	*result;
+
+		result=Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt);
+
+		if (freeIt) {
+		    free(result);
+		}
+		cp += length-1;
+	    } else if (*cp == '!' || *cp == ':') {
+		/*
+		 * We don't want to end a word on ':' or '!' if there is a
+		 * better match later on in the string.  By "better" I mean
+		 * one that is followed by whitespace.	This allows the user
+		 * to have targets like:
+		 *    fie::fi:fo: fum
+		 * where "fie::fi:fo" is the target.  In real life this is used
+		 * for perl5 library man pages where "::" separates an object
+		 * from its class.  Ie: "File::Spec::Unix".  This behaviour
+		 * is also consistent with other versions of make.
+		 */
+		char *p = cp + 1;
+
+		if (*cp == ':' && *p == ':')
+		    p++;
+
+		/* Found the best match already. */
+		if (*p == '\0' || isspace(*p))
+		    break;
+
+		do {
+		    p += strcspn(p, "!:");
+		    if (*p == '\0')
+			break;
+		} while (!isspace(*++p));
+
+		/* No better match later on... */
+		if (*p == '\0')
+		    break;
+	    }
+	    continue;
+	}
+	if (*cp == '(') {
+	    /*
+	     * Archives must be handled specially to make sure the OP_ARCHV
+	     * flag is set in their 'type' field, for one thing, and because
+	     * things like "archive(file1.o file2.o file3.o)" are permissible.
+	     * Arch_ParseArchive will set 'line' to be the first non-blank
+	     * after the archive-spec. It creates/finds nodes for the members
+	     * and places them on the given list, returning SUCCESS if all
+	     * went well and FAILURE if there was an error in the
+	     * specification. On error, line should remain untouched.
+	     */
+	    if (Arch_ParseArchive (&line, targets, VAR_CMD) != SUCCESS) {
+		Parse_Error (PARSE_FATAL,
+			     "Error in archive specification: \"%s\"", line);
+		return;
+	    } else {
+		continue;
+	    }
+	}
+	savec = *cp;
+
+	if (!*cp) {
+	    /*
+	     * Ending a dependency line without an operator is a Bozo
+	     * no-no.  As a heuristic, this is also often triggered by
+	     * undetected conflicts from cvs/rcs merges.
+	     */
+	    if ((strncmp(line, "<<<<<<", 6) == 0) ||
+		(strncmp(line, "======", 6) == 0) ||
+		(strncmp(line, ">>>>>>", 6) == 0))
+		Parse_Error (PARSE_FATAL,
+		    "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts");
+	    else
+	        Parse_Error (PARSE_FATAL, "Need an operator");
+	    return;
+	}
+	*cp = '\0';
+	/*
+	 * Have a word in line. See if it's a special target and set
+	 * specType to match it.
+	 */
+	if (*line == '.' && isupper ((unsigned char) line[1])) {
+	    /*
+	     * See if the target is a special target that must have it
+	     * or its sources handled specially.
+	     */
+	    int keywd = ParseFindKeyword(line);
+	    if (keywd != -1) {
+		if (specType == ExPath && parseKeywords[keywd].spec != ExPath) {
+		    Parse_Error(PARSE_FATAL, "Mismatched special targets");
+		    return;
+		}
+
+		specType = parseKeywords[keywd].spec;
+		tOp = parseKeywords[keywd].op;
+
+		/*
+		 * Certain special targets have special semantics:
+		 *	.PATH		Have to set the dirSearchPath
+		 *			variable too
+		 *	.MAIN		Its sources are only used if
+		 *			nothing has been specified to
+		 *			create.
+		 *	.DEFAULT    	Need to create a node to hang
+		 *			commands on, but we don't want
+		 *			it in the graph, nor do we want
+		 *			it to be the Main Target, so we
+		 *			create it, set OP_NOTMAIN and
+		 *			add it to the list, setting
+		 *			DEFAULT to the new node for
+		 *			later use. We claim the node is
+		 *	    	    	A transformation rule to make
+		 *	    	    	life easier later, when we'll
+		 *	    	    	use Make_HandleUse to actually
+		 *	    	    	apply the .DEFAULT commands.
+		 *	.PHONY		The list of targets
+		 *	.BEGIN
+		 *	.END
+		 *	.INTERRUPT  	Are not to be considered the
+		 *			main target.
+		 *  	.NOTPARALLEL	Make only one target at a time.
+		 *  	.SINGLESHELL	Create a shell for each command.
+		 *  	.ORDER	    	Must set initial predecessor to NULL
+		 */
+		switch (specType) {
+		    case ExPath:
+			if (paths == NULL) {
+			    paths = Lst_Init(FALSE);
+			}
+			(void)Lst_AtEnd(paths, (void *)dirSearchPath);
+			break;
+		    case Main:
+			if (!Lst_IsEmpty(create)) {
+			    specType = Not;
+			}
+			break;
+		    case Begin:
+		    case End:
+		    case Interrupt:
+			gn = Targ_FindNode(line, TARG_CREATE);
+			gn->type |= OP_NOTMAIN;
+			(void)Lst_AtEnd(targets, (void *)gn);
+			break;
+		    case Default:
+			gn = Targ_NewGN(".DEFAULT");
+			gn->type |= (OP_NOTMAIN|OP_TRANSFORM);
+			(void)Lst_AtEnd(targets, (void *)gn);
+			DEFAULT = gn;
+			break;
+		    case NotParallel:
+		    {
+			maxJobs = 1;
+			break;
+		    }
+		    case SingleShell:
+			compatMake = 1;
+			break;
+		    case Order:
+			predecessor = NULL;
+			break;
+		    default:
+			break;
+		}
+	    } else if (strncmp (line, ".PATH", 5) == 0) {
+		/*
+		 * .PATH<suffix> has to be handled specially.
+		 * Call on the suffix module to give us a path to
+		 * modify.
+		 */
+		Lst 	path;
+
+		specType = ExPath;
+		path = Suff_GetPath (&line[5]);
+		if (path == NULL) {
+		    Parse_Error (PARSE_FATAL,
+				 "Suffix '%s' not defined (yet)",
+				 &line[5]);
+		    return;
+		} else {
+		    if (paths == (Lst)NULL) {
+			paths = Lst_Init(FALSE);
+		    }
+		    (void)Lst_AtEnd(paths, (void *)path);
+		}
+	    }
+	}
+
+	/*
+	 * Have word in line. Get or create its node and stick it at
+	 * the end of the targets list
+	 */
+	if ((specType == Not) && (*line != '\0')) {
+	    if (Dir_HasWildcards(line)) {
+		/*
+		 * Targets are to be sought only in the current directory,
+		 * so create an empty path for the thing. Note we need to
+		 * use Dir_Destroy in the destruction of the path as the
+		 * Dir module could have added a directory to the path...
+		 */
+		Lst	    emptyPath = Lst_Init(FALSE);
+
+		Dir_Expand(line, emptyPath, curTargs);
+
+		Lst_Destroy(emptyPath, Dir_Destroy);
+	    } else {
+		/*
+		 * No wildcards, but we want to avoid code duplication,
+		 * so create a list with the word on it.
+		 */
+		(void)Lst_AtEnd(curTargs, (void *)line);
+	    }
+
+	    while(!Lst_IsEmpty(curTargs)) {
+		char	*targName = (char *)Lst_DeQueue(curTargs);
+
+		if (!Suff_IsTransform (targName)) {
+		    gn = Targ_FindNode (targName, TARG_CREATE);
+		} else {
+		    gn = Suff_AddTransform (targName);
+		}
+
+		(void)Lst_AtEnd (targets, (void *)gn);
+	    }
+	} else if (specType == ExPath && *line != '.' && *line != '\0') {
+	    Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line);
+	}
+
+	*cp = savec;
+	/*
+	 * If it is a special type and not .PATH, it's the only target we
+	 * allow on this line...
+	 */
+	if (specType != Not && specType != ExPath) {
+	    Boolean warn = FALSE;
+
+	    while ((*cp != '!') && (*cp != ':') && *cp) {
+		if (*cp != ' ' && *cp != '\t') {
+		    warn = TRUE;
+		}
+		cp++;
+	    }
+	    if (warn) {
+		Parse_Error(PARSE_WARNING, "Extra target ignored");
+	    }
+	} else {
+	    while (*cp && isspace ((unsigned char) *cp)) {
+		cp++;
+	    }
+	}
+	line = cp;
+    } while ((*line != '!') && (*line != ':') && *line);
+
+    /*
+     * Don't need the list of target names anymore...
+     */
+    Lst_Destroy(curTargs, NOFREE);
+
+    if (!Lst_IsEmpty(targets)) {
+	switch(specType) {
+	    default:
+		Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored");
+		break;
+	    case Default:
+	    case Begin:
+	    case End:
+	    case Interrupt:
+		/*
+		 * These four create nodes on which to hang commands, so
+		 * targets shouldn't be empty...
+		 */
+	    case Not:
+		/*
+		 * Nothing special here -- targets can be empty if it wants.
+		 */
+		break;
+	}
+    }
+
+    /*
+     * Have now parsed all the target names. Must parse the operator next. The
+     * result is left in  op .
+     */
+    if (*cp == '!') {
+	op = OP_FORCE;
+    } else if (*cp == ':') {
+	if (cp[1] == ':') {
+	    op = OP_DOUBLEDEP;
+	    cp++;
+	} else {
+	    op = OP_DEPENDS;
+	}
+    } else {
+	Parse_Error (PARSE_FATAL, "Missing dependency operator");
+	return;
+    }
+
+    cp++;			/* Advance beyond operator */
+
+    Lst_ForEach (targets, ParseDoOp, (void *)&op);
+
+    /*
+     * Get to the first source
+     */
+    while (*cp && isspace ((unsigned char) *cp)) {
+	cp++;
+    }
+    line = cp;
+
+    /*
+     * Several special targets take different actions if present with no
+     * sources:
+     *	a .SUFFIXES line with no sources clears out all old suffixes
+     *	a .PRECIOUS line makes all targets precious
+     *	a .IGNORE line ignores errors for all targets
+     *	a .SILENT line creates silence when making all targets
+     *	a .PATH removes all directories from the search path(s).
+     */
+    if (!*line) {
+	switch (specType) {
+	    case Suffixes:
+		Suff_ClearSuffixes ();
+		break;
+	    case Precious:
+		allPrecious = TRUE;
+		break;
+	    case Ignore:
+		ignoreErrors = TRUE;
+		break;
+	    case Silent:
+		beSilent = TRUE;
+		break;
+	    case ExPath:
+		Lst_ForEach(paths, ParseClearPath, (void *)NULL);
+		break;
+#ifdef POSIX
+	    case Posix:
+		Var_Set("%POSIX", "1003.2", VAR_GLOBAL);
+		break;
+#endif
+	    default:
+		break;
+	}
+    } else if (specType == MFlags) {
+	/*
+	 * Call on functions in main.c to deal with these arguments and
+	 * set the initial character to a null-character so the loop to
+	 * get sources won't get anything
+	 */
+	Main_ParseArgLine (line);
+	*line = '\0';
+    } else if (specType == ExShell) {
+	if (Job_ParseShell (line) != SUCCESS) {
+	    Parse_Error (PARSE_FATAL, "improper shell specification");
+	    return;
+	}
+	*line = '\0';
+    } else if ((specType == NotParallel) || (specType == SingleShell)) {
+	*line = '\0';
+    }
+
+    /*
+     * NOW GO FOR THE SOURCES
+     */
+    if ((specType == Suffixes) || (specType == ExPath) ||
+	(specType == Includes) || (specType == Libs) ||
+	(specType == Null))
+    {
+	while (*line) {
+	    /*
+	     * If the target was one that doesn't take files as its sources
+	     * but takes something like suffixes, we take each
+	     * space-separated word on the line as a something and deal
+	     * with it accordingly.
+	     *
+	     * If the target was .SUFFIXES, we take each source as a
+	     * suffix and add it to the list of suffixes maintained by the
+	     * Suff module.
+	     *
+	     * If the target was a .PATH, we add the source as a directory
+	     * to search on the search path.
+	     *
+	     * If it was .INCLUDES, the source is taken to be the suffix of
+	     * files which will be #included and whose search path should
+	     * be present in the .INCLUDES variable.
+	     *
+	     * If it was .LIBS, the source is taken to be the suffix of
+	     * files which are considered libraries and whose search path
+	     * should be present in the .LIBS variable.
+	     *
+	     * If it was .NULL, the source is the suffix to use when a file
+	     * has no valid suffix.
+	     */
+	    char  savech;
+	    while (*cp && !isspace ((unsigned char) *cp)) {
+		cp++;
+	    }
+	    savech = *cp;
+	    *cp = '\0';
+	    switch (specType) {
+		case Suffixes:
+		    Suff_AddSuffix (line);
+		    break;
+		case ExPath:
+		    Lst_ForEach(paths, ParseAddDir, (void *)line);
+		    break;
+		case Includes:
+		    Suff_AddInclude (line);
+		    break;
+		case Libs:
+		    Suff_AddLib (line);
+		    break;
+		case Null:
+		    Suff_SetNull (line);
+		    break;
+		default:
+		    break;
+	    }
+	    *cp = savech;
+	    if (savech != '\0') {
+		cp++;
+	    }
+	    while (*cp && isspace ((unsigned char) *cp)) {
+		cp++;
+	    }
+	    line = cp;
+	}
+	if (paths) {
+	    Lst_Destroy(paths, NOFREE);
+	}
+    } else {
+	while (*line) {
+	    /*
+	     * The targets take real sources, so we must beware of archive
+	     * specifications (i.e. things with left parentheses in them)
+	     * and handle them accordingly.
+	     */
+	    while (*cp && !isspace ((unsigned char) *cp)) {
+		if ((*cp == '(') && (cp > line) && (cp[-1] != '$')) {
+		    /*
+		     * Only stop for a left parenthesis if it isn't at the
+		     * start of a word (that'll be for variable changes
+		     * later) and isn't preceded by a dollar sign (a dynamic
+		     * source).
+		     */
+		    break;
+		} else {
+		    cp++;
+		}
+	    }
+
+	    if (*cp == '(') {
+		GNode	  *gnp;
+
+		sources = Lst_Init (FALSE);
+		if (Arch_ParseArchive (&line, sources, VAR_CMD) != SUCCESS) {
+		    Parse_Error (PARSE_FATAL,
+				 "Error in source archive spec \"%s\"", line);
+		    return;
+		}
+
+		while (!Lst_IsEmpty (sources)) {
+		    gnp = (GNode *) Lst_DeQueue (sources);
+		    ParseDoSrc (tOp, gnp->name, curSrcs);
+		}
+		Lst_Destroy (sources, NOFREE);
+		cp = line;
+	    } else {
+		if (*cp) {
+		    *cp = '\0';
+		    cp += 1;
+		}
+
+		ParseDoSrc (tOp, line, curSrcs);
+	    }
+	    while (*cp && isspace ((unsigned char) *cp)) {
+		cp++;
+	    }
+	    line = cp;
+	}
+    }
+
+    if (mainNode == NULL) {
+	/*
+	 * If we have yet to decide on a main target to make, in the
+	 * absence of any user input, we want the first target on
+	 * the first dependency line that is actually a real target
+	 * (i.e. isn't a .USE or .EXEC rule) to be made.
+	 */
+	Lst_ForEach (targets, ParseFindMain, (void *)0);
+    }
+
+    /*
+     * Finally, destroy the list of sources
+     */
+    Lst_Destroy(curSrcs, NOFREE);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_IsVar  --
+ *	Return TRUE if the passed line is a variable assignment. A variable
+ *	assignment consists of a single word followed by optional whitespace
+ *	followed by either a += or an = operator.
+ *	This function is used both by the Parse_File function and main when
+ *	parsing the command-line arguments.
+ *
+ * Results:
+ *	TRUE if it is. FALSE if it ain't
+ *
+ * Side Effects:
+ *	none
+ *---------------------------------------------------------------------
+ */
+Boolean
+Parse_IsVar (char *line)
+{
+    Boolean wasSpace = FALSE;	/* set TRUE if found a space */
+    Boolean haveName = FALSE;	/* Set TRUE if have a variable name */
+    int level = 0;
+#define	ISEQOPERATOR(c) \
+	(((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!'))
+
+    /*
+     * Skip to variable name
+     */
+    for (;(*line == ' ') || (*line == '\t'); line++)
+	continue;
+
+    for (; *line != '=' || level != 0; line++)
+	switch (*line) {
+	case '\0':
+	    /*
+	     * end-of-line -- can't be a variable assignment.
+	     */
+	    return FALSE;
+
+	case ' ':
+	case '\t':
+	    /*
+	     * there can be as much white space as desired so long as there is
+	     * only one word before the operator
+	     */
+	    wasSpace = TRUE;
+	    break;
+
+	case '(':
+	case '{':
+	    level++;
+	    break;
+
+	case '}':
+	case ')':
+	    level--;
+	    break;
+
+	default:
+	    if (wasSpace && haveName) {
+		    if (ISEQOPERATOR(*line)) {
+			/*
+			 * We must have a finished word
+			 */
+			if (level != 0)
+			    return FALSE;
+
+			/*
+			 * When an = operator [+?!:] is found, the next
+			 * character must be an = or it ain't a valid
+			 * assignment.
+			 */
+			if (line[1] == '=')
+			    return haveName;
+#ifdef SUNSHCMD
+			/*
+			 * This is a shell command
+			 */
+			if (strncmp(line, ":sh", 3) == 0)
+			    return haveName;
+#endif
+		    }
+		    /*
+		     * This is the start of another word, so not assignment.
+		     */
+		    return FALSE;
+	    }
+	    else {
+		haveName = TRUE;
+		wasSpace = FALSE;
+	    }
+	    break;
+	}
+
+    return haveName;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_DoVar  --
+ *	Take the variable assignment in the passed line and do it in the
+ *	global context.
+ *
+ *	Note: There is a lexical ambiguity with assignment modifier characters
+ *	in variable names. This routine interprets the character before the =
+ *	as a modifier. Therefore, an assignment like
+ *	    C++=/usr/bin/CC
+ *	is interpreted as "C+ +=" instead of "C++ =".
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	the variable structure of the given variable name is altered in the
+ *	global context.
+ *---------------------------------------------------------------------
+ */
+void
+Parse_DoVar (char *line, GNode *ctxt)
+{
+    char	   *cp;	/* pointer into line */
+    enum {
+	VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL
+    }	    	    type;   	/* Type of assignment */
+    char            *opc;	/* ptr to operator character to
+				 * null-terminate the variable name */
+    /*
+     * Avoid clobbered variable warnings by forcing the compiler
+     * to ``unregister'' variables
+     */
+#if __GNUC__
+    (void) &cp;
+    (void) &line;
+#endif
+
+    /*
+     * Skip to variable name
+     */
+    while ((*line == ' ') || (*line == '\t')) {
+	line++;
+    }
+
+    /*
+     * Skip to operator character, nulling out whitespace as we go
+     */
+    for (cp = line + 1; *cp != '='; cp++) {
+	if (isspace ((unsigned char) *cp)) {
+	    *cp = '\0';
+	}
+    }
+    opc = cp-1;		/* operator is the previous character */
+    *cp++ = '\0';	/* nuke the = */
+
+    /*
+     * Check operator type
+     */
+    switch (*opc) {
+	case '+':
+	    type = VAR_APPEND;
+	    *opc = '\0';
+	    break;
+
+	case '?':
+	    /*
+	     * If the variable already has a value, we don't do anything.
+	     */
+	    *opc = '\0';
+	    if (Var_Exists(line, ctxt)) {
+		return;
+	    } else {
+		type = VAR_NORMAL;
+	    }
+	    break;
+
+	case ':':
+	    type = VAR_SUBST;
+	    *opc = '\0';
+	    break;
+
+	case '!':
+	    type = VAR_SHELL;
+	    *opc = '\0';
+	    break;
+
+	default:
+#ifdef SUNSHCMD
+	    while (*opc != ':')
+		if (opc == line)
+		    break;
+		else
+		    --opc;
+
+	    if (strncmp(opc, ":sh", 3) == 0) {
+		type = VAR_SHELL;
+		*opc = '\0';
+		break;
+	    }
+#endif
+	    type = VAR_NORMAL;
+	    break;
+    }
+
+    while (isspace ((unsigned char) *cp)) {
+	cp++;
+    }
+
+    if (type == VAR_APPEND) {
+	Var_Append (line, cp, ctxt);
+    } else if (type == VAR_SUBST) {
+	/*
+	 * Allow variables in the old value to be undefined, but leave their
+	 * invocation alone -- this is done by forcing oldVars to be false.
+	 * XXX: This can cause recursive variables, but that's not hard to do,
+	 * and this allows someone to do something like
+	 *
+	 *  CFLAGS = $(.INCLUDES)
+	 *  CFLAGS := -I.. $(CFLAGS)
+	 *
+	 * And not get an error.
+	 */
+	Boolean	  oldOldVars = oldVars;
+
+	oldVars = FALSE;
+
+	/*
+	 * make sure that we set the variable the first time to nothing
+	 * so that it gets substituted!
+	 */
+	if (!Var_Exists(line, ctxt))
+	    Var_Set(line, "", ctxt);
+
+	cp = Var_Subst(NULL, cp, ctxt, FALSE);
+	oldVars = oldOldVars;
+
+	Var_Set(line, cp, ctxt);
+	free(cp);
+    } else if (type == VAR_SHELL) {
+	Boolean	freeCmd = FALSE; /* TRUE if the command needs to be freed, i.e.
+				  * if any variable expansion was performed */
+	char *res, *error;
+
+	if (strchr(cp, '$') != NULL) {
+	    /*
+	     * There's a dollar sign in the command, so perform variable
+	     * expansion on the whole thing. The resulting string will need
+	     * freeing when we're done, so set freeCmd to TRUE.
+	     */
+	    cp = Var_Subst(NULL, cp, VAR_CMD, TRUE);
+	    freeCmd = TRUE;
+	}
+
+	res = Cmd_Exec(cp, &error);
+	Var_Set(line, res, ctxt);
+	free(res);
+
+	if (error)
+	    Parse_Error(PARSE_WARNING, error, cp);
+
+	if (freeCmd)
+	    free(cp);
+    } else {
+	/*
+	 * Normal assignment -- just do it.
+	 */
+	Var_Set(line, cp, ctxt);
+    }
+}
+
+
+/*-
+ * ParseAddCmd  --
+ *	Lst_ForEach function to add a command line to all targets
+ *
+ * Results:
+ *	Always 0
+ *
+ * Side Effects:
+ *	A new element is added to the commands list of the node.
+ */
+static int
+ParseAddCmd(void *gnp, void *cmd)
+{
+    GNode *gn = (GNode *) gnp;
+    /* if target already supplied, ignore commands */
+    if (!(gn->type & OP_HAS_COMMANDS))
+	(void)Lst_AtEnd(gn->commands, cmd);
+    else
+	Parse_Error(PARSE_WARNING,
+		    "duplicate script for target \"%s\" ignored",
+		    gn->name);
+    return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseHasCommands --
+ *	Callback procedure for Parse_File when destroying the list of
+ *	targets on the last dependency line. Marks a target as already
+ *	having commands if it does, to keep from having shell commands
+ *	on multiple dependency lines.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	OP_HAS_COMMANDS may be set for the target.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseHasCommands(void *gnp)
+{
+    GNode *gn = (GNode *) gnp;
+    if (!Lst_IsEmpty(gn->commands)) {
+	gn->type |= OP_HAS_COMMANDS;
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Parse_AddIncludeDir --
+ *	Add a directory to the path searched for included makefiles
+ *	bracketed by double-quotes. Used by functions in main.c
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The directory is appended to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Parse_AddIncludeDir (char *dir)
+{
+    Dir_AddDir (parseIncPath, dir);
+}
+
+/*---------------------------------------------------------------------
+ * ParseDoError  --
+ *	Handle error directive
+ *
+ *	The input is the line minus the ".error".  We substitute variables,
+ *	print the message and exit(1) or just print a warning if the ".error"
+ *	directive is malformed.
+ *
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoError(char *errmsg)
+{
+	if (!isspace((unsigned char) *errmsg)) {
+		Parse_Error(PARSE_WARNING, "invalid syntax: .error%s", errmsg);
+		return;
+	}
+	
+	while (isspace((unsigned char) *errmsg))
+		errmsg++;
+	
+	errmsg = Var_Subst(NULL, errmsg, VAR_GLOBAL, FALSE);
+
+	/* use fprintf/exit instead of Parse_Error to terminate immediately */
+	fprintf(stderr, "\"%s\", line %d: %s\n", fname, lineno, errmsg);
+	exit(1);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoInclude  --
+ *	Push to another file.
+ *
+ *	The input is the line minus the #include. A file spec is a string
+ *	enclosed in <> or "". The former is looked for only in sysIncPath.
+ *	The latter in . and the directories specified by -I command line
+ *	options
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	A structure is added to the includes Lst and readProc, lineno,
+ *	fname and curFILE are altered for the new file
+ *---------------------------------------------------------------------
+ */
+static void
+ParseDoInclude (char *file)
+{
+    char          *fullname;	/* full pathname of file */
+    IFile         *oldFile;	/* state associated with current file */
+    char          endc;	    	/* the character which ends the file spec */
+    char          *cp;		/* current position in file spec */
+    Boolean 	  isSystem; 	/* TRUE if makefile is a system makefile */
+
+    /*
+     * Skip to delimiter character so we know where to look
+     */
+    while ((*file == ' ') || (*file == '\t')) {
+	file++;
+    }
+
+    if ((*file != '"') && (*file != '<')) {
+	Parse_Error (PARSE_FATAL,
+	    ".include filename must be delimited by '\"' or '<'");
+	return;
+    }
+
+    /*
+     * Set the search path on which to find the include file based on the
+     * characters which bracket its name. Angle-brackets imply it's
+     * a system Makefile while double-quotes imply it's a user makefile
+     */
+    if (*file == '<') {
+	isSystem = TRUE;
+	endc = '>';
+    } else {
+	isSystem = FALSE;
+	endc = '"';
+    }
+
+    /*
+     * Skip to matching delimiter
+     */
+    for (cp = ++file; *cp && *cp != endc; cp++) {
+	continue;
+    }
+
+    if (*cp != endc) {
+	Parse_Error (PARSE_FATAL,
+		     "Unclosed %cinclude filename. '%c' expected",
+		     '.', endc);
+	return;
+    }
+    *cp = '\0';
+
+    /*
+     * Substitute for any variables in the file name before trying to
+     * find the thing.
+     */
+    file = Var_Subst (NULL, file, VAR_CMD, FALSE);
+
+    /*
+     * Now we know the file's name and its search path, we attempt to
+     * find the durn thing. A return of NULL indicates the file don't
+     * exist.
+     */
+    if (!isSystem) {
+	/*
+	 * Include files contained in double-quotes are first searched for
+	 * relative to the including file's location. We don't want to
+	 * cd there, of course, so we just tack on the old file's
+	 * leading path components and call Dir_FindFile to see if
+	 * we can locate the beast.
+	 */
+	char	  *prefEnd, *Fname;
+
+	/* Make a temporary copy of this, to be safe. */
+	Fname = estrdup(fname);
+
+	prefEnd = strrchr (Fname, '/');
+	if (prefEnd != (char *)NULL) {
+	    char  	*newName;
+
+	    *prefEnd = '\0';
+	    if (file[0] == '/')
+		newName = estrdup(file);
+	    else
+		newName = str_concat (Fname, file, STR_ADDSLASH);
+	    fullname = Dir_FindFile (newName, parseIncPath);
+	    if (fullname == (char *)NULL) {
+		fullname = Dir_FindFile(newName, dirSearchPath);
+	    }
+	    free (newName);
+	    *prefEnd = '/';
+	} else {
+	    fullname = (char *)NULL;
+	}
+	free (Fname);
+    } else {
+	fullname = (char *)NULL;
+    }
+
+    if (fullname == (char *)NULL) {
+	/*
+	 * System makefile or makefile wasn't found in same directory as
+	 * included makefile. Search for it first on the -I search path,
+	 * then on the .PATH search path, if not found in a -I directory.
+	 * XXX: Suffix specific?
+	 */
+	fullname = Dir_FindFile (file, parseIncPath);
+	if (fullname == (char *)NULL) {
+	    fullname = Dir_FindFile(file, dirSearchPath);
+	}
+    }
+
+    if (fullname == (char *)NULL) {
+	/*
+	 * Still haven't found the makefile. Look for it on the system
+	 * path as a last resort.
+	 */
+	fullname = Dir_FindFile(file, sysIncPath);
+    }
+
+    if (fullname == (char *) NULL) {
+	*cp = endc;
+	Parse_Error (PARSE_FATAL, "Could not find %s", file);
+	return;
+    }
+
+    free(file);
+
+    /*
+     * Once we find the absolute path to the file, we get to save all the
+     * state from the current file before we can start reading this
+     * include file. The state is stored in an IFile structure which
+     * is placed on a list with other IFile structures. The list makes
+     * a very nice stack to track how we got here...
+     */
+    oldFile = (IFile *) emalloc (sizeof (IFile));
+    oldFile->fname = fname;
+
+    oldFile->F = curFILE;
+    oldFile->p = curPTR;
+    oldFile->lineno = lineno;
+
+    (void) Lst_AtFront (includes, (void *)oldFile);
+
+    /*
+     * Once the previous state has been saved, we can get down to reading
+     * the new file. We set up the name of the file to be the absolute
+     * name of the include file so error messages refer to the right
+     * place. Naturally enough, we start reading at line number 0.
+     */
+    fname = fullname;
+    lineno = 0;
+
+    curFILE = fopen (fullname, "r");
+    curPTR = NULL;
+    if (curFILE == (FILE * ) NULL) {
+	Parse_Error (PARSE_FATAL, "Cannot open %s", fullname);
+	/*
+	 * Pop to previous file
+	 */
+	(void) ParseEOF(0);
+    }
+}
+
+
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_FromString  --
+ *	Start Parsing from the given string
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	A structure is added to the includes Lst and readProc, lineno,
+ *	fname and curFILE are altered for the new file
+ *---------------------------------------------------------------------
+ */
+void
+Parse_FromString(char *str)
+{
+    IFile         *oldFile;	/* state associated with this file */
+
+    DEBUGF(FOR, ("%s\n----\n", str));
+
+    oldFile = (IFile *) emalloc (sizeof (IFile));
+    oldFile->lineno = lineno;
+    oldFile->fname = fname;
+    oldFile->F = curFILE;
+    oldFile->p = curPTR;
+
+    (void) Lst_AtFront (includes, (void *)oldFile);
+
+    curFILE = NULL;
+    curPTR = (PTR *) emalloc (sizeof (PTR));
+    curPTR->str = curPTR->ptr = str;
+    lineno = 0;
+    fname = estrdup(fname);
+}
+
+
+#ifdef SYSVINCLUDE
+/*-
+ *---------------------------------------------------------------------
+ * ParseTraditionalInclude  --
+ *	Push to another file.
+ *
+ *	The input is the line minus the "include".  The file name is
+ *	the string following the "include".
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	A structure is added to the includes Lst and readProc, lineno,
+ *	fname and curFILE are altered for the new file
+ *---------------------------------------------------------------------
+ */
+static void
+ParseTraditionalInclude (char *file)
+{
+    char          *fullname;	/* full pathname of file */
+    IFile         *oldFile;	/* state associated with current file */
+    char          *cp;		/* current position in file spec */
+    char	  *prefEnd;
+
+    /*
+     * Skip over whitespace
+     */
+    while ((*file == ' ') || (*file == '\t')) {
+	file++;
+    }
+
+    if (*file == '\0') {
+	Parse_Error (PARSE_FATAL,
+		     "Filename missing from \"include\"");
+	return;
+    }
+
+    /*
+     * Skip to end of line or next whitespace
+     */
+    for (cp = file; *cp && *cp != '\n' && *cp != '\t' && *cp != ' '; cp++) {
+	continue;
+    }
+
+    *cp = '\0';
+
+    /*
+     * Substitute for any variables in the file name before trying to
+     * find the thing.
+     */
+    file = Var_Subst (NULL, file, VAR_CMD, FALSE);
+
+    /*
+     * Now we know the file's name, we attempt to find the durn thing.
+     * A return of NULL indicates the file don't exist.
+     *
+     * Include files are first searched for relative to the including
+     * file's location. We don't want to cd there, of course, so we
+     * just tack on the old file's leading path components and call
+     * Dir_FindFile to see if we can locate the beast.
+     * XXX - this *does* search in the current directory, right?
+     */
+
+    prefEnd = strrchr (fname, '/');
+    if (prefEnd != (char *)NULL) {
+	char  	*newName;
+
+	*prefEnd = '\0';
+	newName = str_concat (fname, file, STR_ADDSLASH);
+	fullname = Dir_FindFile (newName, parseIncPath);
+	if (fullname == (char *)NULL) {
+	    fullname = Dir_FindFile(newName, dirSearchPath);
+	}
+	free (newName);
+	*prefEnd = '/';
+    } else {
+	fullname = (char *)NULL;
+    }
+
+    if (fullname == (char *)NULL) {
+	/*
+	 * System makefile or makefile wasn't found in same directory as
+	 * included makefile. Search for it first on the -I search path,
+	 * then on the .PATH search path, if not found in a -I directory.
+	 * XXX: Suffix specific?
+	 */
+	fullname = Dir_FindFile (file, parseIncPath);
+	if (fullname == (char *)NULL) {
+	    fullname = Dir_FindFile(file, dirSearchPath);
+	}
+    }
+
+    if (fullname == (char *)NULL) {
+	/*
+	 * Still haven't found the makefile. Look for it on the system
+	 * path as a last resort.
+	 */
+	fullname = Dir_FindFile(file, sysIncPath);
+    }
+
+    if (fullname == (char *) NULL) {
+	Parse_Error (PARSE_FATAL, "Could not find %s", file);
+	return;
+    }
+
+    /*
+     * Once we find the absolute path to the file, we get to save all the
+     * state from the current file before we can start reading this
+     * include file. The state is stored in an IFile structure which
+     * is placed on a list with other IFile structures. The list makes
+     * a very nice stack to track how we got here...
+     */
+    oldFile = (IFile *) emalloc (sizeof (IFile));
+    oldFile->fname = fname;
+
+    oldFile->F = curFILE;
+    oldFile->p = curPTR;
+    oldFile->lineno = lineno;
+
+    (void) Lst_AtFront (includes, (void *)oldFile);
+
+    /*
+     * Once the previous state has been saved, we can get down to reading
+     * the new file. We set up the name of the file to be the absolute
+     * name of the include file so error messages refer to the right
+     * place. Naturally enough, we start reading at line number 0.
+     */
+    fname = fullname;
+    lineno = 0;
+
+    curFILE = fopen (fullname, "r");
+    curPTR = NULL;
+    if (curFILE == (FILE * ) NULL) {
+	Parse_Error (PARSE_FATAL, "Cannot open %s", fullname);
+	/*
+	 * Pop to previous file
+	 */
+	(void) ParseEOF(1);
+    }
+}
+#endif
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseEOF  --
+ *	Called when EOF is reached in the current file. If we were reading
+ *	an include file, the includes stack is popped and things set up
+ *	to go back to reading the previous file at the previous location.
+ *
+ * Results:
+ *	CONTINUE if there's more to do. DONE if not.
+ *
+ * Side Effects:
+ *	The old curFILE, is closed. The includes list is shortened.
+ *	lineno, curFILE, and fname are changed if CONTINUE is returned.
+ *---------------------------------------------------------------------
+ */
+static int
+ParseEOF (int opened)
+{
+    IFile     *ifile;	/* the state on the top of the includes stack */
+
+    if (Lst_IsEmpty (includes)) {
+	return (DONE);
+    }
+
+    ifile = (IFile *) Lst_DeQueue (includes);
+    free (fname);
+    fname = ifile->fname;
+    lineno = ifile->lineno;
+    if (opened && curFILE)
+	(void) fclose (curFILE);
+    if (curPTR) {
+	free(curPTR->str);
+	free(curPTR);
+    }
+    curFILE = ifile->F;
+    curPTR = ifile->p;
+    free (ifile);
+    return (CONTINUE);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseReadc  --
+ *	Read a character from the current file
+ *
+ * Results:
+ *	The character that was read
+ *
+ * Side Effects:
+ *---------------------------------------------------------------------
+ */
+static int
+ParseReadc(void)
+{
+    if (curFILE)
+	return fgetc(curFILE);
+
+    if (curPTR && *curPTR->ptr)
+	return *curPTR->ptr++;
+    return EOF;
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseUnreadc  --
+ *	Put back a character to the current file
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *---------------------------------------------------------------------
+ */
+static void
+ParseUnreadc(int c)
+{
+    if (curFILE) {
+	ungetc(c, curFILE);
+	return;
+    }
+    if (curPTR) {
+	*--(curPTR->ptr) = c;
+	return;
+    }
+}
+
+
+/* ParseSkipLine():
+ *	Grab the next line unless it begins with a dot (`.') and we're told to
+ *	ignore such lines.
+ */
+static char *
+ParseSkipLine(int skip)
+{
+    char *line;
+    int c, lastc, lineLength = 0;
+    Buffer buf;
+
+    buf = Buf_Init(MAKE_BSIZE);
+
+    do {
+        Buf_Discard(buf, lineLength);
+        lastc = '\0';
+
+        while (((c = ParseReadc()) != '\n' || lastc == '\\')
+               && c != EOF) {
+            if (c == '\n') {
+                Buf_ReplaceLastByte(buf, (Byte)' ');
+                lineno++;
+
+                while ((c = ParseReadc()) == ' ' || c == '\t')
+		    continue;
+
+                if (c == EOF)
+                    break;
+            }
+
+            Buf_AddByte(buf, (Byte)c);
+            lastc = c;
+        }
+
+        if (c == EOF) {
+            Parse_Error(PARSE_FATAL, "Unclosed conditional/for loop");
+            Buf_Destroy(buf, TRUE);
+            return((char *)NULL);
+        }
+
+        lineno++;
+        Buf_AddByte(buf, (Byte)'\0');
+        line = (char *)Buf_GetAll(buf, &lineLength);
+    } while (skip == 1 && line[0] != '.');
+
+    Buf_Destroy(buf, FALSE);
+    return line;
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseReadLine --
+ *	Read an entire line from the input file. Called only by Parse_File.
+ *	To facilitate escaped newlines and what have you, a character is
+ *	buffered in 'lastc', which is '\0' when no characters have been
+ *	read. When we break out of the loop, c holds the terminating
+ *	character and lastc holds a character that should be added to
+ *	the line (unless we don't read anything but a terminator).
+ *
+ * Results:
+ *	A line w/o its newline
+ *
+ * Side Effects:
+ *	Only those associated with reading a character
+ *---------------------------------------------------------------------
+ */
+static char *
+ParseReadLine (void)
+{
+    Buffer  	  buf;	    	/* Buffer for current line */
+    int		  c;	      	/* the current character */
+    int		  lastc;    	/* The most-recent character */
+    Boolean	  semiNL;     	/* treat semi-colons as newlines */
+    Boolean	  ignDepOp;   	/* TRUE if should ignore dependency operators
+				 * for the purposes of setting semiNL */
+    Boolean 	  ignComment;	/* TRUE if should ignore comments (in a
+				 * shell command */
+    char 	  *line;    	/* Result */
+    char          *ep;		/* to strip trailing blanks */
+    int	    	  lineLength;	/* Length of result */
+
+    semiNL = FALSE;
+    ignDepOp = FALSE;
+    ignComment = FALSE;
+
+    /*
+     * Handle special-characters at the beginning of the line. Either a
+     * leading tab (shell command) or pound-sign (possible conditional)
+     * forces us to ignore comments and dependency operators and treat
+     * semi-colons as semi-colons (by leaving semiNL FALSE). This also
+     * discards completely blank lines.
+     */
+    for (;;) {
+	c = ParseReadc();
+
+	if (c == '\t') {
+	    ignComment = ignDepOp = TRUE;
+	    break;
+	} else if (c == '\n') {
+	    lineno++;
+	} else if (c == '#') {
+	    ParseUnreadc(c);
+	    break;
+	} else {
+	    /*
+	     * Anything else breaks out without doing anything
+	     */
+	    break;
+	}
+    }
+
+    if (c != EOF) {
+	lastc = c;
+	buf = Buf_Init(MAKE_BSIZE);
+
+	while (((c = ParseReadc ()) != '\n' || (lastc == '\\')) &&
+	       (c != EOF))
+	{
+test_char:
+	    switch(c) {
+	    case '\n':
+		/*
+		 * Escaped newline: read characters until a non-space or an
+		 * unescaped newline and replace them all by a single space.
+		 * This is done by storing the space over the backslash and
+		 * dropping through with the next nonspace. If it is a
+		 * semi-colon and semiNL is TRUE, it will be recognized as a
+		 * newline in the code below this...
+		 */
+		lineno++;
+		lastc = ' ';
+		while ((c = ParseReadc ()) == ' ' || c == '\t') {
+		    continue;
+		}
+		if (c == EOF || c == '\n') {
+		    goto line_read;
+		} else {
+		    /*
+		     * Check for comments, semiNL's, etc. -- easier than
+		     * ParseUnreadc(c); continue;
+		     */
+		    goto test_char;
+		}
+		/*NOTREACHED*/
+		break;
+
+	    case ';':
+		/*
+		 * Semi-colon: Need to see if it should be interpreted as a
+		 * newline
+		 */
+		if (semiNL) {
+		    /*
+		     * To make sure the command that may be following this
+		     * semi-colon begins with a tab, we push one back into the
+		     * input stream. This will overwrite the semi-colon in the
+		     * buffer. If there is no command following, this does no
+		     * harm, since the newline remains in the buffer and the
+		     * whole line is ignored.
+		     */
+		    ParseUnreadc('\t');
+		    goto line_read;
+		}
+		break;
+	    case '=':
+		if (!semiNL) {
+		    /*
+		     * Haven't seen a dependency operator before this, so this
+		     * must be a variable assignment -- don't pay attention to
+		     * dependency operators after this.
+		     */
+		    ignDepOp = TRUE;
+		} else if (lastc == ':' || lastc == '!') {
+		    /*
+		     * Well, we've seen a dependency operator already, but it
+		     * was the previous character, so this is really just an
+		     * expanded variable assignment. Revert semi-colons to
+		     * being just semi-colons again and ignore any more
+		     * dependency operators.
+		     *
+		     * XXX: Note that a line like "foo : a:=b" will blow up,
+		     * but who'd write a line like that anyway?
+		     */
+		    ignDepOp = TRUE; semiNL = FALSE;
+		}
+		break;
+	    case '#':
+		if (!ignComment) {
+		    if (lastc != '\\') {
+			/*
+			 * If the character is a hash mark and it isn't escaped
+			 * (or we're being compatible), the thing is a comment.
+			 * Skip to the end of the line.
+			 */
+			do {
+			    c = ParseReadc();
+			} while ((c != '\n') && (c != EOF));
+			goto line_read;
+		    } else {
+			/*
+			 * Don't add the backslash. Just let the # get copied
+			 * over.
+			 */
+			lastc = c;
+			continue;
+		    }
+		}
+		break;
+	    case ':':
+	    case '!':
+		if (!ignDepOp && (c == ':' || c == '!')) {
+		    /*
+		     * A semi-colon is recognized as a newline only on
+		     * dependency lines. Dependency lines are lines with a
+		     * colon or an exclamation point. Ergo...
+		     */
+		    semiNL = TRUE;
+		}
+		break;
+	    default:
+		break;
+	    }
+	    /*
+	     * Copy in the previous character and save this one in lastc.
+	     */
+	    Buf_AddByte (buf, (Byte)lastc);
+	    lastc = c;
+
+	}
+    line_read:
+	lineno++;
+
+	if (lastc != '\0') {
+	    Buf_AddByte (buf, (Byte)lastc);
+	}
+	Buf_AddByte (buf, (Byte)'\0');
+	line = (char *)Buf_GetAll (buf, &lineLength);
+	Buf_Destroy (buf, FALSE);
+
+	/*
+	 * Strip trailing blanks and tabs from the line.
+	 * Do not strip a blank or tab that is preceded by
+	 * a '\'
+	 */
+	ep = line;
+	while (*ep)
+	    ++ep;
+	while (ep > line + 1 && (ep[-1] == ' ' || ep[-1] == '\t')) {
+	    if (ep > line + 1 && ep[-2] == '\\')
+		break;
+	    --ep;
+	}
+	*ep = 0;
+
+	if (line[0] == '.') {
+	    /*
+	     * The line might be a conditional. Ask the conditional module
+	     * about it and act accordingly
+	     */
+	    switch (Cond_Eval (line)) {
+	    case COND_SKIP:
+		/*
+		 * Skip to next conditional that evaluates to COND_PARSE.
+		 */
+		do {
+		    free (line);
+		    line = ParseSkipLine(1);
+		} while (line && Cond_Eval(line) != COND_PARSE);
+		if (line == NULL)
+		    break;
+		/*FALLTHRU*/
+	    case COND_PARSE:
+		free (line);
+		line = ParseReadLine();
+		break;
+	    case COND_INVALID:
+		if (For_Eval(line)) {
+		    int ok;
+		    free(line);
+		    do {
+			/*
+			 * Skip after the matching end
+			 */
+			line = ParseSkipLine(0);
+			if (line == NULL) {
+			    Parse_Error (PARSE_FATAL,
+				     "Unexpected end of file in for loop.\n");
+			    break;
+			}
+			ok = For_Eval(line);
+			free(line);
+		    }
+		    while (ok);
+		    if (line != NULL)
+			For_Run();
+		    line = ParseReadLine();
+		}
+		break;
+	    default:
+		break;
+	    }
+	}
+	return (line);
+
+    } else {
+	/*
+	 * Hit end-of-file, so return a NULL line to indicate this.
+	 */
+	return((char *)NULL);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseFinishLine --
+ *	Handle the end of a dependency group.
+ *
+ * Results:
+ *	Nothing.
+ *
+ * Side Effects:
+ *	inLine set FALSE. 'targets' list destroyed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseFinishLine(void)
+{
+    if (inLine) {
+	Lst_ForEach(targets, Suff_EndTransform, (void *)NULL);
+	Lst_Destroy (targets, ParseHasCommands);
+	targets = NULL;
+	inLine = FALSE;
+    }
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_File --
+ *	Parse a file into its component parts, incorporating it into the
+ *	current dependency graph. This is the main function and controls
+ *	almost every other function in this module
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Loads. Nodes are added to the list of all targets, nodes and links
+ *	are added to the dependency graph. etc. etc. etc.
+ *---------------------------------------------------------------------
+ */
+void
+Parse_File(char *name, FILE *stream)
+{
+    char	  *cp,		/* pointer into the line */
+                  *line;	/* the line we're working on */
+
+    inLine = FALSE;
+    fname = name;
+    curFILE = stream;
+    lineno = 0;
+    fatals = 0;
+
+    do {
+	while ((line = ParseReadLine ()) != NULL) {
+	    if (*line == '.') {
+		/*
+		 * Lines that begin with the special character are either
+		 * include or undef directives.
+		 */
+		for (cp = line + 1; isspace ((unsigned char) *cp); cp++) {
+		    continue;
+		}
+		if (strncmp (cp, "include", 7) == 0) {
+		    ParseDoInclude (cp + 7);
+		    goto nextLine;
+		} else if (strncmp (cp, "error", 5) == 0) {
+		    ParseDoError(cp + 5);
+	            goto nextLine;	    
+		} else if (strncmp(cp, "undef", 5) == 0) {
+		    char *cp2;
+		    for (cp += 5; isspace((unsigned char) *cp); cp++) {
+			continue;
+		    }
+
+		    for (cp2 = cp; !isspace((unsigned char) *cp2) &&
+				   (*cp2 != '\0'); cp2++) {
+			continue;
+		    }
+
+		    *cp2 = '\0';
+
+		    Var_Delete(cp, VAR_GLOBAL);
+		    goto nextLine;
+		}
+	    }
+	    if (*line == '#') {
+		/* If we're this far, the line must be a comment. */
+		goto nextLine;
+	    }
+
+	    if (*line == '\t') {
+		/*
+		 * If a line starts with a tab, it can only hope to be
+		 * a creation command.
+		 */
+#ifndef POSIX
+	    shellCommand:
+#endif
+		for (cp = line + 1; isspace ((unsigned char) *cp); cp++) {
+		    continue;
+		}
+		if (*cp) {
+		    if (inLine) {
+			/*
+			 * So long as it's not a blank line and we're actually
+			 * in a dependency spec, add the command to the list of
+			 * commands of all targets in the dependency spec
+			 */
+			Lst_ForEach (targets, ParseAddCmd, cp);
+			Lst_AtEnd(targCmds, (void *) line);
+			continue;
+		    } else {
+			Parse_Error (PARSE_FATAL,
+				     "Unassociated shell command \"%s\"",
+				     cp);
+		    }
+		}
+#ifdef SYSVINCLUDE
+	    } else if (strncmp (line, "include", 7) == 0 &&
+		       isspace((unsigned char) line[7]) &&
+		       strchr(line, ':') == NULL) {
+		/*
+		 * It's an S3/S5-style "include".
+		 */
+		ParseTraditionalInclude (line + 7);
+		goto nextLine;
+#endif
+	    } else if (Parse_IsVar (line)) {
+		ParseFinishLine();
+		Parse_DoVar (line, VAR_GLOBAL);
+	    } else {
+		/*
+		 * We now know it's a dependency line so it needs to have all
+		 * variables expanded before being parsed. Tell the variable
+		 * module to complain if some variable is undefined...
+		 * To make life easier on novices, if the line is indented we
+		 * first make sure the line has a dependency operator in it.
+		 * If it doesn't have an operator and we're in a dependency
+		 * line's script, we assume it's actually a shell command
+		 * and add it to the current list of targets.
+		 */
+#ifndef POSIX
+		Boolean	nonSpace = FALSE;
+#endif
+
+		cp = line;
+		if (isspace((unsigned char) line[0])) {
+		    while ((*cp != '\0') && isspace((unsigned char) *cp)) {
+			cp++;
+		    }
+		    if (*cp == '\0') {
+			goto nextLine;
+		    }
+#ifndef POSIX
+		    while ((*cp != ':') && (*cp != '!') && (*cp != '\0')) {
+			nonSpace = TRUE;
+			cp++;
+		    }
+#endif
+		}
+
+#ifndef POSIX
+		if (*cp == '\0') {
+		    if (inLine) {
+			Parse_Error (PARSE_WARNING,
+				     "Shell command needs a leading tab");
+			goto shellCommand;
+		    } else if (nonSpace) {
+			Parse_Error (PARSE_FATAL, "Missing operator");
+		    }
+		} else {
+#endif
+		    ParseFinishLine();
+
+		    cp = Var_Subst (NULL, line, VAR_CMD, TRUE);
+		    free (line);
+		    line = cp;
+
+		    /*
+		     * Need a non-circular list for the target nodes
+		     */
+		    if (targets)
+			Lst_Destroy(targets, NOFREE);
+
+		    targets = Lst_Init (FALSE);
+		    inLine = TRUE;
+
+		    ParseDoDependency (line);
+#ifndef POSIX
+		}
+#endif
+	    }
+
+	    nextLine:
+
+	    free (line);
+	}
+	/*
+	 * Reached EOF, but it may be just EOF of an include file...
+	 */
+    } while (ParseEOF(1) == CONTINUE);
+
+    /*
+     * Make sure conditionals are clean
+     */
+    Cond_End();
+
+    if (fatals)
+	errx(1, "fatal errors encountered -- cannot continue");
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_Init --
+ *	initialize the parsing module
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	the parseIncPath list is initialized...
+ *---------------------------------------------------------------------
+ */
+void
+Parse_Init (void)
+{
+    mainNode = NULL;
+    parseIncPath = Lst_Init (FALSE);
+    sysIncPath = Lst_Init (FALSE);
+    includes = Lst_Init (FALSE);
+    targCmds = Lst_Init (FALSE);
+}
+
+void
+Parse_End (void)
+{
+    Lst_Destroy(targCmds, (void (*)(void *)) free);
+    if (targets)
+	Lst_Destroy(targets, NOFREE);
+    Lst_Destroy(sysIncPath, Dir_Destroy);
+    Lst_Destroy(parseIncPath, Dir_Destroy);
+    Lst_Destroy(includes, NOFREE);	/* Should be empty now */
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Parse_MainName --
+ *	Return a Lst of the main target to create for main()'s sake. If
+ *	no such target exists, we Punt with an obnoxious error message.
+ *
+ * Results:
+ *	A Lst of the single node to create.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Parse_MainName(void)
+{
+    Lst           listmain;	/* result list */
+
+    listmain = Lst_Init (FALSE);
+
+    if (mainNode == NULL) {
+	Punt ("no target to make.");
+    	/*NOTREACHED*/
+    } else if (mainNode->type & OP_DOUBLEDEP) {
+	(void) Lst_AtEnd (listmain, (void *)mainNode);
+	Lst_Concat(listmain, mainNode->cohorts, LST_CONCNEW);
+    }
+    else
+	(void) Lst_AtEnd (listmain, (void *)mainNode);
+    return (listmain);
+}
Index: trunk/src/kmk/pathnames.h
===================================================================
--- trunk/src/kmk/pathnames.h	(revision 9)
+++ trunk/src/kmk/pathnames.h	(revision 9)
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)pathnames.h	8.2 (Berkeley) 4/28/95
+ * $FreeBSD: src/usr.bin/make/pathnames.h,v 1.12 2002/05/24 15:51:27 ru Exp $
+ */
+
+#ifndef _PATH_OBJDIR
+#define	_PATH_OBJDIR		"obj"
+#endif /* ! _PATH_OBJDIR */
+#ifndef _PATH_OBJDIRPREFIX
+#define	_PATH_OBJDIRPREFIX	"/usr/obj"
+#endif /* ! _PATH_OBJDIRPREFIX */
+#ifndef _PATH_DEFSHELLDIR
+#define	_PATH_DEFSHELLDIR	"/bin"
+#endif /* ! _PATH_DEFSHELLDIR */
+#ifndef _PATH_DEFSYSMK
+#define	_PATH_DEFSYSMK		"sys.mk"
+#endif /* ! _PATH_DEFSYSMK */
+#ifndef _PATH_DEFSYSPATH
+#define	_PATH_DEFSYSPATH	"/usr/share/mk"
+#endif /* ! _PATH_DEFSYSPATH */
Index: trunk/src/kmk/sprite.h
===================================================================
--- trunk/src/kmk/sprite.h	(revision 9)
+++ trunk/src/kmk/sprite.h	(revision 9)
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)sprite.h	8.2 (Berkeley) 4/28/95
+ * $FreeBSD: src/usr.bin/make/sprite.h,v 1.13 2002/09/17 21:29:06 jmallett Exp $
+ */
+
+/*
+ * sprite.h --
+ *
+ * Common constants and type declarations for Sprite.
+ */
+
+#ifndef _SPRITE
+#define	_SPRITE
+
+/*
+ * A boolean type is defined as an integer, not an enum. This allows a
+ * boolean argument to be an expression that isn't strictly 0 or 1 valued.
+ */
+
+typedef int Boolean;
+#ifndef TRUE
+#define	TRUE	1
+#endif /* TRUE */
+#ifndef FALSE
+#define	FALSE	0
+#endif /* FALSE */
+
+typedef int  ReturnStatus;
+
+#define	SUCCESS		0
+#define	FAILURE		1
+
+#endif /* _SPRITE */
Index: trunk/src/kmk/str.c
===================================================================
--- trunk/src/kmk/str.c	(revision 9)
+++ trunk/src/kmk/str.c	(revision 9)
@@ -0,0 +1,487 @@
+/*-
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)str.c	5.8 (Berkeley) 6/1/90
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/str.c,v 1.25 2002/10/09 03:42:10 jmallett Exp $");
+
+#include "make.h"
+
+static char **argv, *buffer;
+static int argmax, curlen;
+
+/*
+ * str_init --
+ *	Initialize the strings package
+ *
+ */
+void
+str_init(void)
+{
+    char *p1;
+    argv = (char **)emalloc(((argmax = 50) + 1) * sizeof(char *));
+    argv[0] = Var_Value(".MAKE", VAR_GLOBAL, &p1);
+}
+
+
+/*
+ * str_end --
+ *	Cleanup the strings package
+ *
+ */
+void
+str_end(void)
+{
+    if (argv) {
+	if (argv[0])
+	    free(argv[0]);
+	free(argv);
+    }
+    if (buffer)
+	free(buffer);
+}
+
+/*-
+ * str_concat --
+ *	concatenate the two strings, inserting a space or slash between them,
+ *	freeing them if requested.
+ *
+ * returns --
+ *	the resulting string in allocated space.
+ */
+char *
+str_concat(char *s1, char *s2, int flags)
+{
+	int len1, len2;
+	char *result;
+
+	/* get the length of both strings */
+	len1 = strlen(s1);
+	len2 = strlen(s2);
+
+	/* allocate length plus separator plus EOS */
+	result = emalloc((u_int)(len1 + len2 + 2));
+
+	/* copy first string into place */
+	memcpy(result, s1, len1);
+
+	/* add separator character */
+	if (flags & STR_ADDSPACE) {
+		result[len1] = ' ';
+		++len1;
+	} else if (flags & STR_ADDSLASH) {
+		result[len1] = '/';
+		++len1;
+	}
+
+	/* copy second string plus EOS into place */
+	memcpy(result + len1, s2, len2 + 1);
+
+	/* free original strings */
+	if (flags & STR_DOFREE) {
+		(void)efree(s1);
+		(void)efree(s2);
+	}
+	return(result);
+}
+
+/*-
+ * brk_string --
+ *	Fracture a string into an array of words (as delineated by tabs or
+ *	spaces) taking quotation marks into account.  Leading tabs/spaces
+ *	are ignored.
+ *
+ * returns --
+ *	Pointer to the array of pointers to the words.  To make life easier,
+ *	the first word is always the value of the .MAKE variable.
+ */
+char **
+brk_string(char *str, int *store_argc, Boolean expand)
+{
+	int argc, ch;
+	char inquote, *p, *start, *t;
+	int len;
+
+	/* skip leading space chars. */
+	for (; *str == ' ' || *str == '\t'; ++str)
+		continue;
+
+	/* allocate room for a copy of the string */
+	if ((len = strlen(str) + 1) > curlen) {
+		if (buffer)
+		    free(buffer);
+		buffer = emalloc(curlen = len);
+	}
+
+	/*
+	 * copy the string; at the same time, parse backslashes,
+	 * quotes and build the argument list.
+	 */
+	argc = 1;
+	inquote = '\0';
+	for (p = str, start = t = buffer;; ++p) {
+		switch(ch = *p) {
+		case '"':
+		case '\'':
+			if (inquote) {
+				if (inquote == ch)
+					inquote = '\0';
+				else
+					break;
+			} else {
+				inquote = (char) ch;
+				/* Don't miss "" or '' */
+				if (start == NULL && p[1] == inquote) {
+					start = t + 1;
+					break;
+				}
+			}
+			if (!expand) {
+				if (!start)
+					start = t;
+				*t++ = ch;
+			}
+			continue;
+		case ' ':
+		case '\t':
+		case '\n':
+			if (inquote)
+				break;
+			if (!start)
+				continue;
+			/* FALLTHROUGH */
+		case '\0':
+			/*
+			 * end of a token -- make sure there's enough argv
+			 * space and save off a pointer.
+			 */
+			if (!start)
+			    goto done;
+
+			*t++ = '\0';
+			if (argc == argmax) {
+				argmax *= 2;		/* ramp up fast */
+				argv = (char **)erealloc(argv,
+				    (argmax + 1) * sizeof(char *));
+			}
+			argv[argc++] = start;
+			start = (char *)NULL;
+			if (ch == '\n' || ch == '\0')
+				goto done;
+			continue;
+		case '\\':
+			if (!expand) {
+				if (!start)
+					start = t;
+				*t++ = '\\';
+				ch = *++p;
+				break;
+			}
+
+			switch (ch = *++p) {
+			case '\0':
+			case '\n':
+				/* hmmm; fix it up as best we can */
+				ch = '\\';
+				--p;
+				break;
+			case 'b':
+				ch = '\b';
+				break;
+			case 'f':
+				ch = '\f';
+				break;
+			case 'n':
+				ch = '\n';
+				break;
+			case 'r':
+				ch = '\r';
+				break;
+			case 't':
+				ch = '\t';
+				break;
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+		if (!start)
+			start = t;
+		*t++ = (char) ch;
+	}
+done:	argv[argc] = (char *)NULL;
+	*store_argc = argc;
+	return(argv);
+}
+
+/*
+ * Str_FindSubstring -- See if a string contains a particular substring.
+ *
+ * Results: If string contains substring, the return value is the location of
+ * the first matching instance of substring in string.  If string doesn't
+ * contain substring, the return value is NULL.  Matching is done on an exact
+ * character-for-character basis with no wildcards or special characters.
+ *
+ * Side effects: None.
+ *
+ * XXX should be strstr(3).
+ */
+char *
+Str_FindSubstring(char *string, char *substring)
+{
+	char *a, *b;
+
+	/*
+	 * First scan quickly through the two strings looking for a single-
+	 * character match.  When it's found, then compare the rest of the
+	 * substring.
+	 */
+
+	for (b = substring; *string != 0; string += 1) {
+		if (*string != *b)
+			continue;
+		a = string;
+		for (;;) {
+			if (*b == 0)
+				return(string);
+			if (*a++ != *b++)
+				break;
+		}
+		b = substring;
+	}
+	return((char *) NULL);
+}
+
+/*
+ * Str_Match --
+ *
+ * See if a particular string matches a particular pattern.
+ *
+ * Results: Non-zero is returned if string matches pattern, 0 otherwise. The
+ * matching operation permits the following special characters in the
+ * pattern: *?\[] (see the man page for details on what these mean).
+ *
+ * Side effects: None.
+ */
+int
+Str_Match(char *string, char *pattern)
+{
+	char c2;
+
+	for (;;) {
+		/*
+		 * See if we're at the end of both the pattern and the
+		 * string. If, we succeeded.  If we're at the end of the
+		 * pattern but not at the end of the string, we failed.
+		 */
+		if (*pattern == 0)
+			return(!*string);
+		if (*string == 0 && *pattern != '*')
+			return(0);
+		/*
+		 * Check for a "*" as the next pattern character.  It matches
+		 * any substring.  We handle this by calling ourselves
+		 * recursively for each postfix of string, until either we
+		 * match or we reach the end of the string.
+		 */
+		if (*pattern == '*') {
+			pattern += 1;
+			if (*pattern == 0)
+				return(1);
+			while (*string != 0) {
+				if (Str_Match(string, pattern))
+					return(1);
+				++string;
+			}
+			return(0);
+		}
+		/*
+		 * Check for a "?" as the next pattern character.  It matches
+		 * any single character.
+		 */
+		if (*pattern == '?')
+			goto thisCharOK;
+		/*
+		 * Check for a "[" as the next pattern character.  It is
+		 * followed by a list of characters that are acceptable, or
+		 * by a range (two characters separated by "-").
+		 */
+		if (*pattern == '[') {
+			++pattern;
+			for (;;) {
+				if ((*pattern == ']') || (*pattern == 0))
+					return(0);
+				if (*pattern == *string)
+					break;
+				if (pattern[1] == '-') {
+					c2 = pattern[2];
+					if (c2 == 0)
+						return(0);
+					if ((*pattern <= *string) &&
+					    (c2 >= *string))
+						break;
+					if ((*pattern >= *string) &&
+					    (c2 <= *string))
+						break;
+					pattern += 2;
+				}
+				++pattern;
+			}
+			while ((*pattern != ']') && (*pattern != 0))
+				++pattern;
+			goto thisCharOK;
+		}
+		/*
+		 * If the next pattern character is '/', just strip off the
+		 * '/' so we do exact matching on the character that follows.
+		 */
+		if (*pattern == '\\') {
+			++pattern;
+			if (*pattern == 0)
+				return(0);
+		}
+		/*
+		 * There's no special character.  Just make sure that the
+		 * next characters of each string match.
+		 */
+		if (*pattern != *string)
+			return(0);
+thisCharOK:	++pattern;
+		++string;
+	}
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Str_SYSVMatch --
+ *	Check word against pattern for a match (% is wild),
+ *
+ * Results:
+ *	Returns the beginning position of a match or null. The number
+ *	of characters matched is returned in len.
+ *
+ * Side Effects:
+ *	None
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Str_SYSVMatch(char *word, char *pattern, int *len)
+{
+    char *p = pattern;
+    char *w = word;
+    char *m;
+
+    if (*w == '\0') {
+	/* Zero-length word cannot be matched against */
+	*len = 0;
+	return NULL;
+    }
+
+    if (*p == '\0') {
+	/* Null pattern is the whole string */
+	*len = strlen(w);
+	return w;
+    }
+
+    if ((m = strchr(p, '%')) != NULL) {
+	/* check that the prefix matches */
+	for (; p != m && *w && *w == *p; w++, p++)
+	     continue;
+
+	if (p != m)
+	    return NULL;	/* No match */
+
+	if (*++p == '\0') {
+	    /* No more pattern, return the rest of the string */
+	    *len = strlen(w);
+	    return w;
+	}
+    }
+
+    m = w;
+
+    /* Find a matching tail */
+    do
+	if (strcmp(p, w) == 0) {
+	    *len = w - m;
+	    return m;
+	}
+    while (*w++ != '\0');
+
+    return NULL;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Str_SYSVSubst --
+ *	Substitute '%' on the pattern with len characters from src.
+ *	If the pattern does not contain a '%' prepend len characters
+ *	from src.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Places result on buf
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Str_SYSVSubst(Buffer buf, char *pat, char *src, int len)
+{
+    char *m;
+
+    if ((m = strchr(pat, '%')) != NULL) {
+	/* Copy the prefix */
+	Buf_AddBytes(buf, m - pat, (Byte *) pat);
+	/* skip the % */
+	pat = m + 1;
+    }
+
+    /* Copy the pattern */
+    Buf_AddBytes(buf, len, (Byte *) src);
+
+    /* append the rest */
+    Buf_AddBytes(buf, strlen(pat), (Byte *) pat);
+}
Index: trunk/src/kmk/suff.c
===================================================================
--- trunk/src/kmk/suff.c	(revision 9)
+++ trunk/src/kmk/suff.c	(revision 9)
@@ -0,0 +1,2348 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)suff.c	8.4 (Berkeley) 3/21/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/suff.c,v 1.26 2002/10/09 03:42:10 jmallett Exp $");
+
+/*-
+ * suff.c --
+ *	Functions to maintain suffix lists and find implicit dependents
+ *	using suffix transformation rules
+ *
+ * Interface:
+ *	Suff_Init 	    	Initialize all things to do with suffixes.
+ *
+ *	Suff_End 	    	Cleanup the module
+ *
+ *	Suff_DoPaths	    	This function is used to make life easier
+ *	    	  	    	when searching for a file according to its
+ *	    	  	    	suffix. It takes the global search path,
+ *	    	  	    	as defined using the .PATH: target, and appends
+ *	    	  	    	its directories to the path of each of the
+ *	    	  	    	defined suffixes, as specified using
+ *	    	  	    	.PATH<suffix>: targets. In addition, all
+ *	    	  	    	directories given for suffixes labeled as
+ *	    	  	    	include files or libraries, using the .INCLUDES
+ *	    	  	    	or .LIBS targets, are played with using
+ *	    	  	    	Dir_MakeFlags to create the .INCLUDES and
+ *	    	  	    	.LIBS global variables.
+ *
+ *	Suff_ClearSuffixes  	Clear out all the suffixes and defined
+ *	    	  	    	transformations.
+ *
+ *	Suff_IsTransform    	Return TRUE if the passed string is the lhs
+ *	    	  	    	of a transformation rule.
+ *
+ *	Suff_AddSuffix	    	Add the passed string as another known suffix.
+ *
+ *	Suff_GetPath	    	Return the search path for the given suffix.
+ *
+ *	Suff_AddInclude	    	Mark the given suffix as denoting an include
+ *	    	  	    	file.
+ *
+ *	Suff_AddLib	    	Mark the given suffix as denoting a library.
+ *
+ *	Suff_AddTransform   	Add another transformation to the suffix
+ *	    	  	    	graph. Returns  GNode suitable for framing, I
+ *	    	  	    	mean, tacking commands, attributes, etc. on.
+ *
+ *	Suff_SetNull	    	Define the suffix to consider the suffix of
+ *	    	  	    	any file that doesn't have a known one.
+ *
+ *	Suff_FindDeps	    	Find implicit sources for and the location of
+ *	    	  	    	a target based on its suffix. Returns the
+ *	    	  	    	bottom-most node added to the graph or NULL
+ *	    	  	    	if the target had no implicit sources.
+ */
+
+#include    	  <stdio.h>
+#include	  "make.h"
+#include	  "hash.h"
+#include	  "dir.h"
+
+static Lst       sufflist;	/* Lst of suffixes */
+static Lst	 suffClean;	/* Lst of suffixes to be cleaned */
+static Lst	 srclist;	/* Lst of sources */
+static Lst       transforms;	/* Lst of transformation rules */
+
+static int        sNum = 0;	/* Counter for assigning suffix numbers */
+
+/*
+ * Structure describing an individual suffix.
+ */
+typedef struct _Suff {
+    char         *name;	    	/* The suffix itself */
+    int		 nameLen;	/* Length of the suffix */
+    short	 flags;      	/* Type of suffix */
+#define	SUFF_INCLUDE	  0x01	    /* One which is #include'd */
+#define	SUFF_LIBRARY	  0x02	    /* One which contains a library */
+#define	SUFF_NULL 	  0x04	    /* The empty suffix */
+    Lst    	 searchPath;	/* The path along which files of this suffix
+				 * may be found */
+    int          sNum;	      	/* The suffix number */
+    int		 refCount;	/* Reference count of list membership */
+    Lst          parents;	/* Suffixes we have a transformation to */
+    Lst          children;	/* Suffixes we have a transformation from */
+    Lst		 ref;		/* List of lists this suffix is referenced */
+} Suff;
+
+/*
+ * Structure used in the search for implied sources.
+ */
+typedef struct _Src {
+    char            *file;	/* The file to look for */
+    char    	    *pref;  	/* Prefix from which file was formed */
+    Suff            *suff;	/* The suffix on the file */
+    struct _Src     *parent;	/* The Src for which this is a source */
+    GNode           *node;	/* The node describing the file */
+    int	    	    children;	/* Count of existing children (so we don't free
+				 * this thing too early or never nuke it) */
+#ifdef DEBUG_SRC
+    Lst		    cp;		/* Debug; children list */
+#endif
+} Src;
+
+/*
+ * A structure for passing more than one argument to the Lst-library-invoked
+ * function...
+ */
+typedef struct {
+    Lst            l;
+    Src            *s;
+} LstSrc;
+
+static Suff 	    *suffNull;	/* The NULL suffix for this run */
+static Suff 	    *emptySuff;	/* The empty suffix required for POSIX
+				 * single-suffix transformation rules */
+
+
+static char *SuffStrIsPrefix(char *, char *);
+static char *SuffSuffIsSuffix(Suff *, char *);
+static int SuffSuffIsSuffixP(void *, void *);
+static int SuffSuffHasNameP(void *, void *);
+static int SuffSuffIsPrefix(void *, void *);
+static int SuffGNHasNameP(void *, void *);
+static void SuffFree(void *);
+static void SuffInsert(Lst, Suff *);
+static void SuffRemove(Lst, Suff *);
+static Boolean SuffParseTransform(char *, Suff **, Suff **);
+static int SuffRebuildGraph(void *, void *);
+static int SuffAddSrc(void *, void *);
+static int SuffRemoveSrc(Lst);
+static void SuffAddLevel(Lst, Src *);
+static Src *SuffFindThem(Lst, Lst);
+static Src *SuffFindCmds(Src *, Lst);
+static int SuffExpandChildren(void *, void *);
+static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *);
+static void SuffFindDeps(GNode *, Lst);
+static void SuffFindArchiveDeps(GNode *, Lst);
+static void SuffFindNormalDeps(GNode *, Lst);
+static int SuffPrintName(void *, void *);
+static int SuffPrintSuff(void *, void *);
+static int SuffPrintTrans(void *, void *);
+
+	/*************** Lst Predicates ****************/
+/*-
+ *-----------------------------------------------------------------------
+ * SuffStrIsPrefix  --
+ *	See if pref is a prefix of str.
+ *
+ * Results:
+ *	NULL if it ain't, pointer to character in str after prefix if so
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static char    *
+SuffStrIsPrefix (char *pref, char *str)
+{
+    while (*str && *pref == *str) {
+	pref++;
+	str++;
+    }
+
+    return (*pref ? NULL : str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsSuffix  --
+ *	See if suff is a suffix of str. Str should point to THE END of the
+ *	string to check. (THE END == the null byte)
+ *
+ * Results:
+ *	NULL if it ain't, pointer to character in str before suffix if
+ *	it is.
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static char *
+SuffSuffIsSuffix (Suff *s, char *str)
+{
+    char	   *p1;	    	/* Pointer into suffix name */
+    char	   *p2;	    	/* Pointer into string being examined */
+
+    p1 = s->name + s->nameLen;
+    p2 = str;
+
+    while (p1 >= s->name && *p1 == *p2) {
+	p1--;
+	p2--;
+    }
+
+    return (p1 == s->name - 1 ? p2 : NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsSuffixP --
+ *	Predicate form of SuffSuffIsSuffix. Passed as the callback function
+ *	to Lst_Find.
+ *
+ * Results:
+ *	0 if the suffix is the one desired, non-zero if not.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffIsSuffixP(void *s, void *str)
+{
+    return(!SuffSuffIsSuffix((Suff *) s, (char *) str));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffHasNameP --
+ *	Callback procedure for finding a suffix based on its name. Used by
+ *	Suff_GetPath.
+ *
+ * Results:
+ *	0 if the suffix is of the given name. non-zero otherwise.
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffHasNameP (void *s, void *sname)
+{
+    return (strcmp ((char *) sname, ((Suff *) s)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsPrefix  --
+ *	See if the suffix described by s is a prefix of the string. Care
+ *	must be taken when using this to search for transformations and
+ *	what-not, since there could well be two suffixes, one of which
+ *	is a prefix of the other...
+ *
+ * Results:
+ *	0 if s is a prefix of str. non-zero otherwise
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffIsPrefix (void *s, void *str)
+{
+    return (SuffStrIsPrefix (((Suff *) s)->name, (char *) str) == NULL ? 1 : 0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffGNHasNameP  --
+ *	See if the graph node has the desired name
+ *
+ * Results:
+ *	0 if it does. non-zero if it doesn't
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffGNHasNameP (void *gn, void *name)
+{
+    return (strcmp ((char *) name, ((GNode *) gn)->name));
+}
+
+ 	    /*********** Maintenance Functions ************/
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFree  --
+ *	Free up all memory associated with the given suffix structure.
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	the suffix entry is detroyed
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFree (void *sp)
+{
+    Suff           *s = (Suff *) sp;
+
+    if (s == suffNull)
+	suffNull = NULL;
+
+    if (s == emptySuff)
+	emptySuff = NULL;
+
+    Lst_Destroy (s->ref, NOFREE);
+    Lst_Destroy (s->children, NOFREE);
+    Lst_Destroy (s->parents, NOFREE);
+    Lst_Destroy (s->searchPath, Dir_Destroy);
+
+    free (s->name);
+    free (s);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffRemove  --
+ *	Remove the suffix into the list
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The reference count for the suffix is decremented
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffRemove(Lst l, Suff *s)
+{
+    LstNode ln = Lst_Member(l, (void *)s);
+    if (ln != NULL) {
+	Lst_Remove(l, ln);
+	s->refCount--;
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffInsert  --
+ *	Insert the suffix into the list keeping the list ordered by suffix
+ *	numbers.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The reference count of the suffix is incremented
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffInsert (Lst l, Suff *s)
+{
+    LstNode 	  ln;		/* current element in l we're examining */
+    Suff          *s2 = NULL;	/* the suffix descriptor in this element */
+
+    if (Lst_Open (l) == FAILURE) {
+	return;
+    }
+    while ((ln = Lst_Next (l)) != NULL) {
+	s2 = (Suff *) Lst_Datum (ln);
+	if (s2->sNum >= s->sNum) {
+	    break;
+	}
+    }
+    if (s2 == NULL) {
+	    DEBUGF(SUFF, ("inserting an empty list?..."));
+    }
+
+    Lst_Close (l);
+    DEBUGF(SUFF, ("inserting %s(%d)...", s->name, s->sNum));
+    if (ln == NULL) {
+	DEBUGF(SUFF, ("at end of list\n"));
+	(void)Lst_AtEnd (l, (void *)s);
+	s->refCount++;
+	(void)Lst_AtEnd(s->ref, (void *) l);
+    } else if (s2->sNum != s->sNum) {
+	DEBUGF(SUFF, ("before %s(%d)\n", s2->name, s2->sNum));
+	(void)Lst_Insert (l, ln, (void *)s);
+	s->refCount++;
+	(void)Lst_AtEnd(s->ref, (void *) l);
+    } else {
+	DEBUGF(SUFF, ("already there\n"));
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_ClearSuffixes --
+ *	This is gross. Nuke the list of suffixes but keep all transformation
+ *	rules around. The transformation graph is destroyed in this process,
+ *	but we leave the list of rules so when a new graph is formed the rules
+ *	will remain.
+ *	This function is called from the parse module when a
+ *	.SUFFIXES:\n line is encountered.
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	the sufflist and its graph nodes are destroyed
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_ClearSuffixes (void)
+{
+    Lst_Concat (suffClean, sufflist, LST_CONCLINK);
+    sufflist = Lst_Init(FALSE);
+    sNum = 1;
+    suffNull = emptySuff;
+    /*
+     * Clear suffNull's children list (the other suffixes are built new, but
+     * suffNull is used as is).
+     * NOFREE is used because all suffixes are are on the suffClean list.
+     * suffNull should not have parents.
+     */
+    Lst_Destroy(suffNull->children, NOFREE); 
+    suffNull->children = Lst_Init(FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffParseTransform --
+ *	Parse a transformation string to find its two component suffixes.
+ *
+ * Results:
+ *	TRUE if the string is a valid transformation and FALSE otherwise.
+ *
+ * Side Effects:
+ *	The passed pointers are overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+SuffParseTransform(char *str, Suff **srcPtr, Suff **targPtr)
+{
+    LstNode		srcLn;	    /* element in suffix list of trans source*/
+    Suff	    	*src;	    /* Source of transformation */
+    LstNode		targLn;	    /* element in suffix list of trans target*/
+    char	    	*str2;	    /* Extra pointer (maybe target suffix) */
+    LstNode 	    	singleLn;   /* element in suffix list of any suffix
+				     * that exactly matches str */
+    Suff    	    	*single = NULL;/* Source of possible transformation to
+				     * null suffix */
+
+    srcLn = NULL;
+    singleLn = NULL;
+
+    /*
+     * Loop looking first for a suffix that matches the start of the
+     * string and then for one that exactly matches the rest of it. If
+     * we can find two that meet these criteria, we've successfully
+     * parsed the string.
+     */
+    for (;;) {
+	if (srcLn == NULL) {
+	    srcLn = Lst_Find(sufflist, (void *)str, SuffSuffIsPrefix);
+	} else {
+	    srcLn = Lst_FindFrom (sufflist, Lst_Succ(srcLn), (void *)str,
+				  SuffSuffIsPrefix);
+	}
+	if (srcLn == NULL) {
+	    /*
+	     * Ran out of source suffixes -- no such rule
+	     */
+	    if (singleLn != NULL) {
+		/*
+		 * Not so fast Mr. Smith! There was a suffix that encompassed
+		 * the entire string, so we assume it was a transformation
+		 * to the null suffix (thank you POSIX). We still prefer to
+		 * find a double rule over a singleton, hence we leave this
+		 * check until the end.
+		 *
+		 * XXX: Use emptySuff over suffNull?
+		 */
+		*srcPtr = single;
+		*targPtr = suffNull;
+		return(TRUE);
+	    }
+	    return (FALSE);
+	}
+	src = (Suff *) Lst_Datum (srcLn);
+	str2 = str + src->nameLen;
+	if (*str2 == '\0') {
+	    single = src;
+	    singleLn = srcLn;
+	} else {
+	    targLn = Lst_Find(sufflist, (void *)str2, SuffSuffHasNameP);
+	    if (targLn != NULL) {
+		*srcPtr = src;
+		*targPtr = (Suff *)Lst_Datum(targLn);
+		return (TRUE);
+	    }
+	}
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_IsTransform  --
+ *	Return TRUE if the given string is a transformation rule
+ *
+ *
+ * Results:
+ *	TRUE if the string is a concatenation of two known suffixes.
+ *	FALSE otherwise
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Suff_IsTransform (char *str)
+{
+    Suff    	  *src, *targ;
+
+    return (SuffParseTransform(str, &src, &targ));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddTransform --
+ *	Add the transformation rule described by the line to the
+ *	list of rules and place the transformation itself in the graph
+ *
+ * Results:
+ *	The node created for the transformation in the transforms list
+ *
+ * Side Effects:
+ *	The node is placed on the end of the transforms Lst and links are
+ *	made between the two suffixes mentioned in the target name
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Suff_AddTransform (char *line)
+{
+    GNode         *gn;		/* GNode of transformation rule */
+    Suff          *s,		/* source suffix */
+                  *t;		/* target suffix */
+    LstNode 	  ln;	    	/* Node for existing transformation */
+
+    ln = Lst_Find (transforms, (void *)line, SuffGNHasNameP);
+    if (ln == NULL) {
+	/*
+	 * Make a new graph node for the transformation. It will be filled in
+	 * by the Parse module.
+	 */
+	gn = Targ_NewGN (line);
+	(void)Lst_AtEnd (transforms, (void *)gn);
+    } else {
+	/*
+	 * New specification for transformation rule. Just nuke the old list
+	 * of commands so they can be filled in again... We don't actually
+	 * free the commands themselves, because a given command can be
+	 * attached to several different transformations.
+	 */
+	gn = (GNode *) Lst_Datum (ln);
+	Lst_Destroy (gn->commands, NOFREE);
+	Lst_Destroy (gn->children, NOFREE);
+	gn->commands = Lst_Init (FALSE);
+	gn->children = Lst_Init (FALSE);
+    }
+
+    gn->type = OP_TRANSFORM;
+
+    (void)SuffParseTransform(line, &s, &t);
+
+    /*
+     * link the two together in the proper relationship and order
+     */
+    DEBUGF(SUFF, ("defining transformation from `%s' to `%s'\n",
+	   s->name, t->name));
+    SuffInsert (t->children, s);
+    SuffInsert (s->parents, t);
+
+    return (gn);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_EndTransform --
+ *	Handle the finish of a transformation definition, removing the
+ *	transformation from the graph if it has neither commands nor
+ *	sources. This is a callback procedure for the Parse module via
+ *	Lst_ForEach
+ *
+ * Results:
+ *	=== 0
+ *
+ * Side Effects:
+ *	If the node has no commands or children, the children and parents
+ *	lists of the affected suffices are altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Suff_EndTransform(void *gnp, void *dummy __unused)
+{
+    GNode *gn = (GNode *) gnp;
+
+    if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) &&
+	Lst_IsEmpty(gn->children))
+    {
+	Suff	*s, *t;
+
+	(void)SuffParseTransform(gn->name, &s, &t);
+
+	DEBUGF(SUFF, ("deleting transformation from `%s' to `%s'\n",
+	       s->name, t->name));
+
+	/*
+	 * Remove the source from the target's children list. We check for a
+	 * NULL return to handle a beanhead saying something like
+	 *  .c.o .c.o:
+	 *
+	 * We'll be called twice when the next target is seen, but .c and .o
+	 * are only linked once...
+	 */
+	SuffRemove(t->children, s);
+
+	/*
+	 * Remove the target from the source's parents list
+	 */
+	SuffRemove(s->parents, t);
+    } else if (gn->type & OP_TRANSFORM) {
+	DEBUGF(SUFF, ("transformation %s complete\n", gn->name));
+    }
+
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffRebuildGraph --
+ *	Called from Suff_AddSuffix via Lst_ForEach to search through the
+ *	list of existing transformation rules and rebuild the transformation
+ *	graph when it has been destroyed by Suff_ClearSuffixes. If the
+ *	given rule is a transformation involving this suffix and another,
+ *	existing suffix, the proper relationship is established between
+ *	the two.
+ *
+ * Results:
+ *	Always 0.
+ *
+ * Side Effects:
+ *	The appropriate links will be made between this suffix and
+ *	others if transformation rules exist for it.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffRebuildGraph(void *transformp, void *sp)
+{
+    GNode   	*transform = (GNode *) transformp;
+    Suff    	*s = (Suff *) sp;
+    char 	*cp;
+    LstNode	ln;
+    Suff  	*s2 = NULL;
+
+    /*
+     * First see if it is a transformation from this suffix.
+     */
+    cp = SuffStrIsPrefix(s->name, transform->name);
+    if (cp != (char *)NULL) {
+        if (cp[0] == '\0')  /* null rule */
+	    s2 = suffNull;
+	else {
+	    ln = Lst_Find(sufflist, (void *)cp, SuffSuffHasNameP);
+	    if (ln != NULL) 
+	        s2 = (Suff *)Lst_Datum(ln);
+	}
+	if (s2 != NULL) {
+	    /*
+	     * Found target. Link in and return, since it can't be anything
+	     * else.
+	     */
+	    SuffInsert(s2->children, s);
+	    SuffInsert(s->parents, s2);
+	    return(0);
+	}
+    }
+
+    /*
+     * Not from, maybe to?
+     */
+    cp = SuffSuffIsSuffix(s, transform->name + strlen(transform->name));
+    if (cp != (char *)NULL) {
+	/*
+	 * Null-terminate the source suffix in order to find it.
+	 */
+	cp[1] = '\0';
+	ln = Lst_Find(sufflist, (void *)transform->name, SuffSuffHasNameP);
+	/*
+	 * Replace the start of the target suffix
+	 */
+	cp[1] = s->name[0];
+	if (ln != NULL) {
+	    /*
+	     * Found it -- establish the proper relationship
+	     */
+	    s2 = (Suff *)Lst_Datum(ln);
+	    SuffInsert(s->children, s2);
+	    SuffInsert(s2->parents, s);
+	}
+    }
+    return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddSuffix --
+ *	Add the suffix in string to the end of the list of known suffixes.
+ *	Should we restructure the suffix graph? Make doesn't...
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	A GNode is created for the suffix and a Suff structure is created and
+ *	added to the suffixes list unless the suffix was already known.
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddSuffix (char *str)
+{
+    Suff          *s;	    /* new suffix descriptor */
+    LstNode 	  ln;
+
+    ln = Lst_Find (sufflist, (void *)str, SuffSuffHasNameP);
+    if (ln == NULL) {
+	s = (Suff *) emalloc (sizeof (Suff));
+
+	s->name =   	estrdup (str);
+	s->nameLen = 	strlen (s->name);
+	s->searchPath = Lst_Init (FALSE);
+	s->children = 	Lst_Init (FALSE);
+	s->parents = 	Lst_Init (FALSE);
+	s->ref = 	Lst_Init (FALSE);
+	s->sNum =   	sNum++;
+	s->flags =  	0;
+	s->refCount =	0;
+
+	(void)Lst_AtEnd (sufflist, (void *)s);
+	/*
+	 * Look for any existing transformations from or to this suffix.
+	 * XXX: Only do this after a Suff_ClearSuffixes?
+	 */
+	Lst_ForEach (transforms, SuffRebuildGraph, (void *)s);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_GetPath --
+ *	Return the search path for the given suffix, if it's defined.
+ *
+ * Results:
+ *	The searchPath for the desired suffix or NULL if the suffix isn't
+ *	defined.
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+Lst
+Suff_GetPath (char *sname)
+{
+    LstNode   	  ln;
+    Suff    	  *s;
+
+    ln = Lst_Find (sufflist, (void *)sname, SuffSuffHasNameP);
+    if (ln == NULL) {
+	return (NULL);
+    } else {
+	s = (Suff *) Lst_Datum (ln);
+	return (s->searchPath);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_DoPaths --
+ *	Extend the search paths for all suffixes to include the default
+ *	search path.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The searchPath field of all the suffixes is extended by the
+ *	directories in dirSearchPath. If paths were specified for the
+ *	".h" suffix, the directories are stuffed into a global variable
+ *	called ".INCLUDES" with each directory preceded by a -I. The same
+ *	is done for the ".a" suffix, except the variable is called
+ *	".LIBS" and the flag is -L.
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_DoPaths(void)
+{
+    Suff	   	*s;
+    LstNode	  	ln;
+    char		*ptr;
+    Lst	    	    	inIncludes; /* Cumulative .INCLUDES path */
+    Lst	    	    	inLibs;	    /* Cumulative .LIBS path */
+
+    if (Lst_Open (sufflist) == FAILURE) {
+	return;
+    }
+
+    inIncludes = Lst_Init(FALSE);
+    inLibs = Lst_Init(FALSE);
+
+    while ((ln = Lst_Next (sufflist)) != NULL) {
+	s = (Suff *) Lst_Datum (ln);
+	if (!Lst_IsEmpty (s->searchPath)) {
+#ifdef INCLUDES
+	    if (s->flags & SUFF_INCLUDE) {
+		Dir_Concat(inIncludes, s->searchPath);
+	    }
+#endif /* INCLUDES */
+#ifdef LIBRARIES
+	    if (s->flags & SUFF_LIBRARY) {
+		Dir_Concat(inLibs, s->searchPath);
+	    }
+#endif /* LIBRARIES */
+	    Dir_Concat(s->searchPath, dirSearchPath);
+	} else {
+	    Lst_Destroy (s->searchPath, Dir_Destroy);
+	    s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir);
+	}
+    }
+
+    Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL);
+    free(ptr);
+    Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL);
+    free(ptr);
+
+    Lst_Destroy(inIncludes, Dir_Destroy);
+    Lst_Destroy(inLibs, Dir_Destroy);
+
+    Lst_Close (sufflist);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddInclude --
+ *	Add the given suffix as a type of file which gets included.
+ *	Called from the parse module when a .INCLUDES line is parsed.
+ *	The suffix must have already been defined.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The SUFF_INCLUDE bit is set in the suffix's flags field
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddInclude (char *sname)
+{
+    LstNode	  ln;
+    Suff	  *s;
+
+    ln = Lst_Find (sufflist, (void *)sname, SuffSuffHasNameP);
+    if (ln != NULL) {
+	s = (Suff *) Lst_Datum (ln);
+	s->flags |= SUFF_INCLUDE;
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_AddLib --
+ *	Add the given suffix as a type of file which is a library.
+ *	Called from the parse module when parsing a .LIBS line. The
+ *	suffix must have been defined via .SUFFIXES before this is
+ *	called.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The SUFF_LIBRARY bit is set in the suffix's flags field
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddLib (char *sname)
+{
+    LstNode	  ln;
+    Suff	  *s;
+
+    ln = Lst_Find (sufflist, (void *)sname, SuffSuffHasNameP);
+    if (ln != NULL) {
+	s = (Suff *) Lst_Datum (ln);
+	s->flags |= SUFF_LIBRARY;
+    }
+}
+
+ 	  /********** Implicit Source Search Functions *********/
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffAddSrc  --
+ *	Add a suffix as a Src structure to the given list with its parent
+ *	being the given Src structure. If the suffix is the null suffix,
+ *	the prefix is used unaltered as the file name in the Src structure.
+ *
+ * Results:
+ *	always returns 0
+ *
+ * Side Effects:
+ *	A Src structure is created and tacked onto the end of the list
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffAddSrc (void *sp, void *lsp)
+{
+    Suff	*s = (Suff *) sp;
+    LstSrc      *ls = (LstSrc *) lsp;
+    Src         *s2;	    /* new Src structure */
+    Src    	*targ; 	    /* Target structure */
+
+    targ = ls->s;
+
+    if ((s->flags & SUFF_NULL) && (*s->name != '\0')) {
+	/*
+	 * If the suffix has been marked as the NULL suffix, also create a Src
+	 * structure for a file with no suffix attached. Two birds, and all
+	 * that...
+	 */
+	s2 = (Src *) emalloc (sizeof (Src));
+	s2->file =  	estrdup(targ->pref);
+	s2->pref =  	targ->pref;
+	s2->parent = 	targ;
+	s2->node =  	NULL;
+	s2->suff =  	s;
+	s->refCount++;
+	s2->children =	0;
+	targ->children += 1;
+	(void)Lst_AtEnd (ls->l, (void *)s2);
+#ifdef DEBUG_SRC
+	s2->cp = Lst_Init(FALSE);
+	Lst_AtEnd(targ->cp, (void *) s2);
+	printf("1 add %x %x to %x:", targ, s2, ls->l);
+	Lst_ForEach(ls->l, PrintAddr, (void *) 0);
+	printf("\n");
+#endif
+    }
+    s2 = (Src *) emalloc (sizeof (Src));
+    s2->file = 	    str_concat (targ->pref, s->name, 0);
+    s2->pref =	    targ->pref;
+    s2->parent =    targ;
+    s2->node = 	    NULL;
+    s2->suff = 	    s;
+    s->refCount++;
+    s2->children =  0;
+    targ->children += 1;
+    (void)Lst_AtEnd (ls->l, (void *)s2);
+#ifdef DEBUG_SRC
+    s2->cp = Lst_Init(FALSE);
+    Lst_AtEnd(targ->cp, (void *) s2);
+    printf("2 add %x %x to %x:", targ, s2, ls->l);
+    Lst_ForEach(ls->l, PrintAddr, (void *) 0);
+    printf("\n");
+#endif
+
+    return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffAddLevel  --
+ *	Add all the children of targ as Src structures to the given list
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ * 	Lots of structures are created and added to the list
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffAddLevel (Lst l, Src *targ)
+{
+    LstSrc         ls;
+
+    ls.s = targ;
+    ls.l = l;
+
+    Lst_ForEach (targ->suff->children, SuffAddSrc, (void *)&ls);
+}
+
+/*-
+ *----------------------------------------------------------------------
+ * SuffRemoveSrc --
+ *	Free all src structures in list that don't have a reference count
+ *
+ * Results:
+ *	Ture if an src was removed
+ *
+ * Side Effects:
+ *	The memory is free'd.
+ *----------------------------------------------------------------------
+ */
+static int
+SuffRemoveSrc (Lst l)
+{
+    LstNode ln;
+    Src *s;
+    int t = 0;
+
+    if (Lst_Open (l) == FAILURE) {
+	return 0;
+    }
+#ifdef DEBUG_SRC
+    printf("cleaning %lx: ", (unsigned long) l);
+    Lst_ForEach(l, PrintAddr, (void *) 0);
+    printf("\n");
+#endif
+
+
+    while ((ln = Lst_Next (l)) != NULL) {
+	s = (Src *) Lst_Datum (ln);
+	if (s->children == 0) {
+	    free (s->file);
+	    if (!s->parent)
+		free(s->pref);
+	    else {
+#ifdef DEBUG_SRC
+		LstNode ln = Lst_Member(s->parent->cp, (void *)s);
+		if (ln != NULL)
+		    Lst_Remove(s->parent->cp, ln);
+#endif
+		--s->parent->children;
+	    }
+#ifdef DEBUG_SRC
+	    printf("free: [l=%x] p=%x %d\n", l, s, s->children);
+	    Lst_Destroy(s->cp, NOFREE);
+#endif
+	    Lst_Remove(l, ln);
+	    free (s);
+	    t |= 1;
+	    Lst_Close(l);
+	    return TRUE;
+	}
+#ifdef DEBUG_SRC
+	else {
+	    printf("keep: [l=%x] p=%x %d: ", l, s, s->children);
+	    Lst_ForEach(s->cp, PrintAddr, (void *) 0);
+	    printf("\n");
+	}
+#endif
+    }
+
+    Lst_Close(l);
+
+    return t;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindThem --
+ *	Find the first existing file/target in the list srcs
+ *
+ * Results:
+ *	The lowest structure in the chain of transformations
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static Src *
+SuffFindThem (Lst srcs, Lst slst)
+{
+    Src            *s;		/* current Src */
+    Src		   *rs;		/* returned Src */
+    char	   *ptr;
+
+    rs = (Src *) NULL;
+
+    while (!Lst_IsEmpty (srcs)) {
+	s = (Src *) Lst_DeQueue (srcs);
+
+	DEBUGF(SUFF, ("\ttrying %s...", s->file));
+
+	/*
+	 * A file is considered to exist if either a node exists in the
+	 * graph for it or the file actually exists.
+	 */
+	if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) {
+#ifdef DEBUG_SRC
+	    printf("remove %x from %x\n", s, srcs);
+#endif
+	    rs = s;
+	    break;
+	}
+
+	if ((ptr = Dir_FindFile (s->file, s->suff->searchPath)) != NULL) {
+	    rs = s;
+#ifdef DEBUG_SRC
+	    printf("remove %x from %x\n", s, srcs);
+#endif
+	    free(ptr);
+	    break;
+	}
+
+	DEBUGF(SUFF, ("not there\n"));
+
+	SuffAddLevel (srcs, s);
+	Lst_AtEnd(slst, (void *) s);
+    }
+
+    if (rs) {
+	DEBUGF(SUFF, ("got it\n"));
+    }
+    return (rs);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindCmds --
+ *	See if any of the children of the target in the Src structure is
+ *	one from which the target can be transformed. If there is one,
+ *	a Src structure is put together for it and returned.
+ *
+ * Results:
+ *	The Src structure of the "winning" child, or NULL if no such beast.
+ *
+ * Side Effects:
+ *	A Src structure may be allocated.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Src *
+SuffFindCmds (Src *targ, Lst slst)
+{
+    LstNode 	  	ln; 	/* General-purpose list node */
+    GNode		*t, 	/* Target GNode */
+	    	  	*s; 	/* Source GNode */
+    int	    	  	prefLen;/* The length of the defined prefix */
+    Suff    	  	*suff;	/* Suffix on matching beastie */
+    Src	    	  	*ret;	/* Return value */
+    char    	  	*cp;
+
+    t = targ->node;
+    (void) Lst_Open (t->children);
+    prefLen = strlen (targ->pref);
+
+    while ((ln = Lst_Next (t->children)) != NULL) {
+	s = (GNode *)Lst_Datum (ln);
+
+	cp = strrchr (s->name, '/');
+	if (cp == (char *)NULL) {
+	    cp = s->name;
+	} else {
+	    cp++;
+	}
+	if (strncmp (cp, targ->pref, prefLen) == 0) {
+	    /*
+	     * The node matches the prefix ok, see if it has a known
+	     * suffix.
+	     */
+	    ln = Lst_Find (sufflist, (void *)&cp[prefLen],
+			   SuffSuffHasNameP);
+	    if (ln != NULL) {
+		/*
+		 * It even has a known suffix, see if there's a transformation
+		 * defined between the node's suffix and the target's suffix.
+		 *
+		 * XXX: Handle multi-stage transformations here, too.
+		 */
+		suff = (Suff *)Lst_Datum (ln);
+
+		if (Lst_Member (suff->parents,
+				(void *)targ->suff) != NULL)
+		{
+		    /*
+		     * Hot Damn! Create a new Src structure to describe
+		     * this transformation (making sure to duplicate the
+		     * source node's name so Suff_FindDeps can free it
+		     * again (ick)), and return the new structure.
+		     */
+		    ret = (Src *)emalloc (sizeof (Src));
+		    ret->file = estrdup(s->name);
+		    ret->pref = targ->pref;
+		    ret->suff = suff;
+		    suff->refCount++;
+		    ret->parent = targ;
+		    ret->node = s;
+		    ret->children = 0;
+		    targ->children += 1;
+#ifdef DEBUG_SRC
+		    ret->cp = Lst_Init(FALSE);
+		    printf("3 add %x %x\n", targ, ret);
+		    Lst_AtEnd(targ->cp, (void *) ret);
+#endif
+		    Lst_AtEnd(slst, (void *) ret);
+		    DEBUGF(SUFF, ("\tusing existing source %s\n", s->name));
+		    return (ret);
+		}
+	    }
+	}
+    }
+    Lst_Close (t->children);
+    return ((Src *)NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffExpandChildren --
+ *	Expand the names of any children of a given node that contain
+ *	variable invocations or file wildcards into actual targets.
+ *
+ * Results:
+ *	=== 0 (continue)
+ *
+ * Side Effects:
+ *	The expanded node is removed from the parent's list of children,
+ *	and the parent's unmade counter is decremented, but other nodes
+ * 	may be added.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffExpandChildren(void *cgnp, void *pgnp)
+{
+    GNode   	*cgn = (GNode *) cgnp;
+    GNode   	*pgn = (GNode *) pgnp;
+    GNode	*gn;	    /* New source 8) */
+    LstNode   	prevLN;    /* Node after which new source should be put */
+    LstNode	ln; 	    /* List element for old source */
+    char	*cp;	    /* Expanded value */
+
+    /*
+     * New nodes effectively take the place of the child, so place them
+     * after the child
+     */
+    prevLN = Lst_Member(pgn->children, (void *)cgn);
+
+    /*
+     * First do variable expansion -- this takes precedence over
+     * wildcard expansion. If the result contains wildcards, they'll be gotten
+     * to later since the resulting words are tacked on to the end of
+     * the children list.
+     */
+    if (strchr(cgn->name, '$') != (char *)NULL) {
+	DEBUGF(SUFF, ("Expanding \"%s\"...", cgn->name));
+	cp = Var_Subst(NULL, cgn->name, pgn, TRUE);
+
+	if (cp != (char *)NULL) {
+	    Lst	    members = Lst_Init(FALSE);
+
+	    if (cgn->type & OP_ARCHV) {
+		/*
+		 * Node was an archive(member) target, so we want to call
+		 * on the Arch module to find the nodes for us, expanding
+		 * variables in the parent's context.
+		 */
+		char	*sacrifice = cp;
+
+		(void)Arch_ParseArchive(&sacrifice, members, pgn);
+	    } else {
+		/*
+		 * Break the result into a vector of strings whose nodes
+		 * we can find, then add those nodes to the members list.
+		 * Unfortunately, we can't use brk_string b/c it
+		 * doesn't understand about variable specifications with
+		 * spaces in them...
+		 */
+		char	    *start;
+		char	    *initcp = cp;   /* For freeing... */
+
+		for (start = cp; *start == ' ' || *start == '\t'; start++)
+		    continue;
+		for (cp = start; *cp != '\0'; cp++) {
+		    if (*cp == ' ' || *cp == '\t') {
+			/*
+			 * White-space -- terminate element, find the node,
+			 * add it, skip any further spaces.
+			 */
+			*cp++ = '\0';
+			gn = Targ_FindNode(start, TARG_CREATE);
+			(void)Lst_AtEnd(members, (void *)gn);
+			while (*cp == ' ' || *cp == '\t') {
+			    cp++;
+			}
+			/*
+			 * Adjust cp for increment at start of loop, but
+			 * set start to first non-space.
+			 */
+			start = cp--;
+		    } else if (*cp == '$') {
+			/*
+			 * Start of a variable spec -- contact variable module
+			 * to find the end so we can skip over it.
+			 */
+			char	*junk;
+			int 	len;
+			Boolean	doFree;
+
+			junk = Var_Parse(cp, pgn, TRUE, &len, &doFree);
+			if (junk != var_Error) {
+			    cp += len - 1;
+			}
+
+			if (doFree) {
+			    free(junk);
+			}
+		    } else if (*cp == '\\' && *cp != '\0') {
+			/*
+			 * Escaped something -- skip over it
+			 */
+			cp++;
+		    }
+		}
+
+		if (cp != start) {
+		    /*
+		     * Stuff left over -- add it to the list too
+		     */
+		    gn = Targ_FindNode(start, TARG_CREATE);
+		    (void)Lst_AtEnd(members, (void *)gn);
+		}
+		/*
+		 * Point cp back at the beginning again so the variable value
+		 * can be freed.
+		 */
+		cp = initcp;
+	    }
+	    /*
+	     * Add all elements of the members list to the parent node.
+	     */
+	    while(!Lst_IsEmpty(members)) {
+		gn = (GNode *)Lst_DeQueue(members);
+
+		DEBUGF(SUFF, ("%s...", gn->name));
+		if (Lst_Member(pgn->children, (void *)gn) == NULL) {
+		    (void)Lst_Append(pgn->children, prevLN, (void *)gn);
+		    prevLN = Lst_Succ(prevLN);
+		    (void)Lst_AtEnd(gn->parents, (void *)pgn);
+		    pgn->unmade++;
+		}
+	    }
+	    Lst_Destroy(members, NOFREE);
+	    /*
+	     * Free the result
+	     */
+	    free((char *)cp);
+	}
+	/*
+	 * Now the source is expanded, remove it from the list of children to
+	 * keep it from being processed.
+	 */
+	ln = Lst_Member(pgn->children, (void *)cgn);
+	pgn->unmade--;
+	Lst_Remove(pgn->children, ln);
+	DEBUGF(SUFF, ("\n"));
+    } else if (Dir_HasWildcards(cgn->name)) {
+	Lst 	exp;	    /* List of expansions */
+	Lst 	path;	    /* Search path along which to expand */
+
+	/*
+	 * Find a path along which to expand the word.
+	 *
+	 * If the word has a known suffix, use that path.
+	 * If it has no known suffix and we're allowed to use the null
+	 *   suffix, use its path.
+	 * Else use the default system search path.
+	 */
+	cp = cgn->name + strlen(cgn->name);
+	ln = Lst_Find(sufflist, (void *)cp, SuffSuffIsSuffixP);
+
+	DEBUGF(SUFF, ("Wildcard expanding \"%s\"...", cgn->name));
+
+	if (ln != NULL) {
+	    Suff    *s = (Suff *)Lst_Datum(ln);
+
+	    DEBUGF(SUFF, ("suffix is \"%s\"...", s->name));
+	    path = s->searchPath;
+	} else {
+	    /*
+	     * Use default search path
+	     */
+	    path = dirSearchPath;
+	}
+
+	/*
+	 * Expand the word along the chosen path
+	 */
+	exp = Lst_Init(FALSE);
+	Dir_Expand(cgn->name, path, exp);
+
+	while (!Lst_IsEmpty(exp)) {
+	    /*
+	     * Fetch next expansion off the list and find its GNode
+	     */
+	    cp = (char *)Lst_DeQueue(exp);
+
+	    DEBUGF(SUFF, ("%s...", cp));
+	    gn = Targ_FindNode(cp, TARG_CREATE);
+
+	    /*
+	     * If gn isn't already a child of the parent, make it so and
+	     * up the parent's count of unmade children.
+	     */
+	    if (Lst_Member(pgn->children, (void *)gn) == NULL) {
+		(void)Lst_Append(pgn->children, prevLN, (void *)gn);
+		prevLN = Lst_Succ(prevLN);
+		(void)Lst_AtEnd(gn->parents, (void *)pgn);
+		pgn->unmade++;
+	    }
+	}
+
+	/*
+	 * Nuke what's left of the list
+	 */
+	Lst_Destroy(exp, NOFREE);
+
+	/*
+	 * Now the source is expanded, remove it from the list of children to
+	 * keep it from being processed.
+	 */
+	ln = Lst_Member(pgn->children, (void *)cgn);
+	pgn->unmade--;
+	Lst_Remove(pgn->children, ln);
+	DEBUGF(SUFF, ("\n"));
+    }
+
+    return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffApplyTransform --
+ *	Apply a transformation rule, given the source and target nodes
+ *	and suffixes.
+ *
+ * Results:
+ *	TRUE if successful, FALSE if not.
+ *
+ * Side Effects:
+ *	The source and target are linked and the commands from the
+ *	transformation are added to the target node's commands list.
+ *	All attributes but OP_DEPMASK and OP_TRANSFORM are applied
+ *	to the target. The target also inherits all the sources for
+ *	the transformation rule.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s)
+{
+    LstNode 	ln; 	    /* General node */
+    char    	*tname;	    /* Name of transformation rule */
+    GNode   	*gn;	    /* Node for same */
+
+    if (Lst_Member(tGn->children, (void *)sGn) == NULL) {
+	/*
+	 * Not already linked, so form the proper links between the
+	 * target and source.
+	 */
+	(void)Lst_AtEnd(tGn->children, (void *)sGn);
+	(void)Lst_AtEnd(sGn->parents, (void *)tGn);
+	tGn->unmade += 1;
+    }
+
+    if ((sGn->type & OP_OPMASK) == OP_DOUBLEDEP) {
+	/*
+	 * When a :: node is used as the implied source of a node, we have
+	 * to link all its cohorts in as sources as well. Only the initial
+	 * sGn gets the target in its iParents list, however, as that
+	 * will be sufficient to get the .IMPSRC variable set for tGn
+	 */
+	for (ln=Lst_First(sGn->cohorts); ln != NULL; ln=Lst_Succ(ln)) {
+	    gn = (GNode *)Lst_Datum(ln);
+
+	    if (Lst_Member(tGn->children, (void *)gn) == NULL) {
+		/*
+		 * Not already linked, so form the proper links between the
+		 * target and source.
+		 */
+		(void)Lst_AtEnd(tGn->children, (void *)gn);
+		(void)Lst_AtEnd(gn->parents, (void *)tGn);
+		tGn->unmade += 1;
+	    }
+	}
+    }
+    /*
+     * Locate the transformation rule itself
+     */
+    tname = str_concat(s->name, t->name, 0);
+    ln = Lst_Find(transforms, (void *)tname, SuffGNHasNameP);
+    free(tname);
+
+    if (ln == NULL) {
+	/*
+	 * Not really such a transformation rule (can happen when we're
+	 * called to link an OP_MEMBER and OP_ARCHV node), so return
+	 * FALSE.
+	 */
+	return(FALSE);
+    }
+
+    gn = (GNode *)Lst_Datum(ln);
+
+    DEBUGF(SUFF, ("\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name));
+
+    /*
+     * Record last child for expansion purposes
+     */
+    ln = Lst_Last(tGn->children);
+
+    /*
+     * Pass the buck to Make_HandleUse to apply the rule
+     */
+    (void)Make_HandleUse(gn, tGn);
+
+    /*
+     * Deal with wildcards and variables in any acquired sources
+     */
+    ln = Lst_Succ(ln);
+    if (ln != NULL) {
+	Lst_ForEachFrom(tGn->children, ln,
+			SuffExpandChildren, (void *)tGn);
+    }
+
+    /*
+     * Keep track of another parent to which this beast is transformed so
+     * the .IMPSRC variable can be set correctly for the parent.
+     */
+    (void)Lst_AtEnd(sGn->iParents, (void *)tGn);
+
+    return(TRUE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindArchiveDeps --
+ *	Locate dependencies for an OP_ARCHV node.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Same as Suff_FindDeps
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFindArchiveDeps(GNode *gn, Lst slst)
+{
+    char    	*eoarch;    /* End of archive portion */
+    char    	*eoname;    /* End of member portion */
+    GNode   	*mem;	    /* Node for member */
+    static char	*copy[] = { /* Variables to be copied from the member node */
+	TARGET,	    	    /* Must be first */
+	PREFIX,	    	    /* Must be second */
+    };
+    int	    	i;  	    /* Index into copy and vals */
+    Suff    	*ms;	    /* Suffix descriptor for member */
+    char    	*name;	    /* Start of member's name */
+
+    /*
+     * The node is an archive(member) pair. so we must find a
+     * suffix for both of them.
+     */
+    eoarch = strchr (gn->name, '(');
+    eoname = strchr (eoarch, ')');
+
+    *eoname = '\0';	  /* Nuke parentheses during suffix search */
+    *eoarch = '\0';	  /* So a suffix can be found */
+
+    name = eoarch + 1;
+
+    /*
+     * To simplify things, call Suff_FindDeps recursively on the member now,
+     * so we can simply compare the member's .PREFIX and .TARGET variables
+     * to locate its suffix. This allows us to figure out the suffix to
+     * use for the archive without having to do a quadratic search over the
+     * suffix list, backtracking for each one...
+     */
+    mem = Targ_FindNode(name, TARG_CREATE);
+    SuffFindDeps(mem, slst);
+
+    /*
+     * Create the link between the two nodes right off
+     */
+    if (Lst_Member(gn->children, (void *)mem) == NULL) {
+	(void)Lst_AtEnd(gn->children, (void *)mem);
+	(void)Lst_AtEnd(mem->parents, (void *)gn);
+	gn->unmade += 1;
+    }
+
+    /*
+     * Copy in the variables from the member node to this one.
+     */
+    for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) {
+	char *p1;
+	Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn);
+	efree(p1);
+
+    }
+
+    ms = mem->suffix;
+    if (ms == NULL) {
+	/*
+	 * Didn't know what it was -- use .NULL suffix if not in make mode
+	 */
+	DEBUGF(SUFF, ("using null suffix\n"));
+	ms = suffNull;
+    }
+
+
+    /*
+     * Set the other two local variables required for this target.
+     */
+    Var_Set (MEMBER, name, gn);
+    Var_Set (ARCHIVE, gn->name, gn);
+
+    if (ms != NULL) {
+	/*
+	 * Member has a known suffix, so look for a transformation rule from
+	 * it to a possible suffix of the archive. Rather than searching
+	 * through the entire list, we just look at suffixes to which the
+	 * member's suffix may be transformed...
+	 */
+	LstNode	    ln;
+
+	/*
+	 * Use first matching suffix...
+	 */
+	ln = Lst_Find(ms->parents, eoarch, SuffSuffIsSuffixP);
+
+	if (ln != NULL) {
+	    /*
+	     * Got one -- apply it
+	     */
+	    if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms)) {
+		DEBUGF(SUFF, ("\tNo transformation from %s -> %s\n",
+		       ms->name, ((Suff *)Lst_Datum(ln))->name));
+	    }
+	}
+    }
+
+    /*
+     * Replace the opening and closing parens now we've no need of the separate
+     * pieces.
+     */
+    *eoarch = '('; *eoname = ')';
+
+    /*
+     * Pretend gn appeared to the left of a dependency operator so
+     * the user needn't provide a transformation from the member to the
+     * archive.
+     */
+    if (OP_NOP(gn->type)) {
+	gn->type |= OP_DEPENDS;
+    }
+
+    /*
+     * Flag the member as such so we remember to look in the archive for
+     * its modification time.
+     */
+    mem->type |= OP_MEMBER;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindNormalDeps --
+ *	Locate implicit dependencies for regular targets.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	Same as Suff_FindDeps...
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffFindNormalDeps(GNode *gn, Lst slst)
+{
+    char    	*eoname;    /* End of name */
+    char    	*sopref;    /* Start of prefix */
+    LstNode 	ln; 	    /* Next suffix node to check */
+    Lst	    	srcs;	    /* List of sources at which to look */
+    Lst	    	targs;	    /* List of targets to which things can be
+			     * transformed. They all have the same file,
+			     * but different suff and pref fields */
+    Src	    	*bottom;    /* Start of found transformation path */
+    Src 	*src;	    /* General Src pointer */
+    char    	*pref;	    /* Prefix to use */
+    Src	    	*targ;	    /* General Src target pointer */
+
+
+    eoname = gn->name + strlen(gn->name);
+
+    sopref = gn->name;
+
+    /*
+     * Begin at the beginning...
+     */
+    ln = Lst_First(sufflist);
+    srcs = Lst_Init(FALSE);
+    targs = Lst_Init(FALSE);
+
+    /*
+     * We're caught in a catch-22 here. On the one hand, we want to use any
+     * transformation implied by the target's sources, but we can't examine
+     * the sources until we've expanded any variables/wildcards they may hold,
+     * and we can't do that until we've set up the target's local variables
+     * and we can't do that until we know what the proper suffix for the
+     * target is (in case there are two suffixes one of which is a suffix of
+     * the other) and we can't know that until we've found its implied
+     * source, which we may not want to use if there's an existing source
+     * that implies a different transformation.
+     *
+     * In an attempt to get around this, which may not work all the time,
+     * but should work most of the time, we look for implied sources first,
+     * checking transformations to all possible suffixes of the target,
+     * use what we find to set the target's local variables, expand the
+     * children, then look for any overriding transformations they imply.
+     * Should we find one, we discard the one we found before.
+     */
+
+    while (ln != NULL) {
+	/*
+	 * Look for next possible suffix...
+	 */
+	ln = Lst_FindFrom(sufflist, ln, eoname, SuffSuffIsSuffixP);
+
+	if (ln != NULL) {
+	    int	    prefLen;	    /* Length of the prefix */
+	    Src	    *target;
+
+	    /*
+	     * Allocate a Src structure to which things can be transformed
+	     */
+	    target = (Src *)emalloc(sizeof (Src));
+	    target->file = estrdup(gn->name);
+	    target->suff = (Suff *)Lst_Datum(ln);
+	    target->suff->refCount++;
+	    target->node = gn;
+	    target->parent = (Src *)NULL;
+	    target->children = 0;
+#ifdef DEBUG_SRC
+	    target->cp = Lst_Init(FALSE);
+#endif
+
+	    /*
+	     * Allocate room for the prefix, whose end is found by subtracting
+	     * the length of the suffix from the end of the name.
+	     */
+	    prefLen = (eoname - target->suff->nameLen) - sopref;
+	    target->pref = emalloc(prefLen + 1);
+	    memcpy(target->pref, sopref, prefLen);
+	    target->pref[prefLen] = '\0';
+
+	    /*
+	     * Add nodes from which the target can be made
+	     */
+	    SuffAddLevel(srcs, target);
+
+	    /*
+	     * Record the target so we can nuke it
+	     */
+	    (void)Lst_AtEnd(targs, (void *)target);
+
+	    /*
+	     * Search from this suffix's successor...
+	     */
+	    ln = Lst_Succ(ln);
+	}
+    }
+
+    /*
+     * Handle target of unknown suffix...
+     */
+    if (Lst_IsEmpty(targs) && suffNull != NULL) {
+	DEBUGF(SUFF, ("\tNo known suffix on %s. Using .NULL suffix\n", gn->name));
+
+	targ = (Src *)emalloc(sizeof (Src));
+	targ->file = estrdup(gn->name);
+	targ->suff = suffNull;
+	targ->suff->refCount++;
+	targ->node = gn;
+	targ->parent = (Src *)NULL;
+	targ->children = 0;
+	targ->pref = estrdup(sopref);
+#ifdef DEBUG_SRC
+	targ->cp = Lst_Init(FALSE);
+#endif
+
+	/*
+	 * Only use the default suffix rules if we don't have commands
+	 * or dependencies defined for this gnode
+	 */
+	if (Lst_IsEmpty(gn->commands) && Lst_IsEmpty(gn->children))
+	    SuffAddLevel(srcs, targ);
+	else {
+	    DEBUGF(SUFF, ("not "));
+	}
+
+	DEBUGF(SUFF, ("adding suffix rules\n"));
+
+	(void)Lst_AtEnd(targs, (void *)targ);
+    }
+
+    /*
+     * Using the list of possible sources built up from the target suffix(es),
+     * try and find an existing file/target that matches.
+     */
+    bottom = SuffFindThem(srcs, slst);
+
+    if (bottom == (Src *)NULL) {
+	/*
+	 * No known transformations -- use the first suffix found for setting
+	 * the local variables.
+	 */
+	if (!Lst_IsEmpty(targs)) {
+	    targ = (Src *)Lst_Datum(Lst_First(targs));
+	} else {
+	    targ = (Src *)NULL;
+	}
+    } else {
+	/*
+	 * Work up the transformation path to find the suffix of the
+	 * target to which the transformation was made.
+	 */
+	for (targ = bottom; targ->parent != NULL; targ = targ->parent)
+	    continue;
+    }
+
+    /*
+     * The .TARGET variable we always set to be the name at this point,
+     * since it's only set to the path if the thing is only a source and
+     * if it's only a source, it doesn't matter what we put here as far
+     * as expanding sources is concerned, since it has none...
+     */
+    Var_Set(TARGET, gn->name, gn);
+
+    pref = (targ != NULL) ? targ->pref : gn->name;
+    Var_Set(PREFIX, pref, gn);
+
+    /*
+     * Now we've got the important local variables set, expand any sources
+     * that still contain variables or wildcards in their names.
+     */
+    Lst_ForEach(gn->children, SuffExpandChildren, (void *)gn);
+
+    if (targ == NULL) {
+	DEBUGF(SUFF, ("\tNo valid suffix on %s\n", gn->name));
+
+sfnd_abort:
+	/*
+	 * Deal with finding the thing on the default search path if the
+	 * node is only a source (not on the lhs of a dependency operator
+	 * or [XXX] it has neither children or commands).
+	 */
+	if (OP_NOP(gn->type) ||
+	    (Lst_IsEmpty(gn->children) && Lst_IsEmpty(gn->commands)))
+	{
+	    gn->path = Dir_FindFile(gn->name,
+				    (targ == NULL ? dirSearchPath :
+				     targ->suff->searchPath));
+	    if (gn->path != NULL) {
+		char *ptr;
+		Var_Set(TARGET, gn->path, gn);
+
+		if (targ != NULL) {
+		    /*
+		     * Suffix known for the thing -- trim the suffix off
+		     * the path to form the proper .PREFIX variable.
+		     */
+		    int		savep = strlen(gn->path) - targ->suff->nameLen;
+		    char	savec;
+
+		    if (gn->suffix)
+			gn->suffix->refCount--;
+		    gn->suffix = targ->suff;
+		    gn->suffix->refCount++;
+
+		    savec = gn->path[savep];
+		    gn->path[savep] = '\0';
+
+		    if ((ptr = strrchr(gn->path, '/')) != NULL)
+			ptr++;
+		    else
+			ptr = gn->path;
+
+		    Var_Set(PREFIX, ptr, gn);
+
+		    gn->path[savep] = savec;
+		} else {
+		    /*
+		     * The .PREFIX gets the full path if the target has
+		     * no known suffix.
+		     */
+		    if (gn->suffix)
+			gn->suffix->refCount--;
+		    gn->suffix = NULL;
+
+		    if ((ptr = strrchr(gn->path, '/')) != NULL)
+			ptr++;
+		    else
+			ptr = gn->path;
+
+		    Var_Set(PREFIX, ptr, gn);
+		}
+	    }
+	} else {
+	    /*
+	     * Not appropriate to search for the thing -- set the
+	     * path to be the name so Dir_MTime won't go grovelling for
+	     * it.
+	     */
+	    if (gn->suffix)
+		gn->suffix->refCount--;
+	    gn->suffix = (targ == NULL) ? NULL : targ->suff;
+	    if (gn->suffix)
+		gn->suffix->refCount++;
+	    efree(gn->path);
+	    gn->path = estrdup(gn->name);
+	}
+
+	goto sfnd_return;
+    }
+
+    /*
+     * If the suffix indicates that the target is a library, mark that in
+     * the node's type field.
+     */
+    if (targ->suff->flags & SUFF_LIBRARY) {
+	gn->type |= OP_LIB;
+    }
+
+    /*
+     * Check for overriding transformation rule implied by sources
+     */
+    if (!Lst_IsEmpty(gn->children)) {
+	src = SuffFindCmds(targ, slst);
+
+	if (src != (Src *)NULL) {
+	    /*
+	     * Free up all the Src structures in the transformation path
+	     * up to, but not including, the parent node.
+	     */
+	    while (bottom && bottom->parent != NULL) {
+		if (Lst_Member(slst, (void *) bottom) == NULL) {
+		    Lst_AtEnd(slst, (void *) bottom);
+		}
+		bottom = bottom->parent;
+	    }
+	    bottom = src;
+	}
+    }
+
+    if (bottom == NULL) {
+	/*
+	 * No idea from where it can come -- return now.
+	 */
+	goto sfnd_abort;
+    }
+
+    /*
+     * We now have a list of Src structures headed by 'bottom' and linked via
+     * their 'parent' pointers. What we do next is create links between
+     * source and target nodes (which may or may not have been created)
+     * and set the necessary local variables in each target. The
+     * commands for each target are set from the commands of the
+     * transformation rule used to get from the src suffix to the targ
+     * suffix. Note that this causes the commands list of the original
+     * node, gn, to be replaced by the commands of the final
+     * transformation rule. Also, the unmade field of gn is incremented.
+     * Etc.
+     */
+    if (bottom->node == NULL) {
+	bottom->node = Targ_FindNode(bottom->file, TARG_CREATE);
+    }
+
+    for (src = bottom; src->parent != (Src *)NULL; src = src->parent) {
+	targ = src->parent;
+
+	if (src->node->suffix)
+	    src->node->suffix->refCount--;
+	src->node->suffix = src->suff;
+	src->node->suffix->refCount++;
+
+	if (targ->node == NULL) {
+	    targ->node = Targ_FindNode(targ->file, TARG_CREATE);
+	}
+
+	SuffApplyTransform(targ->node, src->node,
+			   targ->suff, src->suff);
+
+	if (targ->node != gn) {
+	    /*
+	     * Finish off the dependency-search process for any nodes
+	     * between bottom and gn (no point in questing around the
+	     * filesystem for their implicit source when it's already
+	     * known). Note that the node can't have any sources that
+	     * need expanding, since SuffFindThem will stop on an existing
+	     * node, so all we need to do is set the standard and System V
+	     * variables.
+	     */
+	    targ->node->type |= OP_DEPS_FOUND;
+
+	    Var_Set(PREFIX, targ->pref, targ->node);
+
+	    Var_Set(TARGET, targ->node->name, targ->node);
+	}
+    }
+
+    if (gn->suffix)
+	gn->suffix->refCount--;
+    gn->suffix = src->suff;
+    gn->suffix->refCount++;
+
+    /*
+     * So Dir_MTime doesn't go questing for it...
+     */
+    efree(gn->path);
+    gn->path = estrdup(gn->name);
+
+    /*
+     * Nuke the transformation path and the Src structures left over in the
+     * two lists.
+     */
+sfnd_return:
+    if (bottom)
+	if (Lst_Member(slst, (void *) bottom) == NULL)
+	    Lst_AtEnd(slst, (void *) bottom);
+
+    while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs))
+	continue;
+
+    Lst_Concat(slst, srcs, LST_CONCLINK);
+    Lst_Concat(slst, targs, LST_CONCLINK);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_FindDeps  --
+ *	Find implicit sources for the target described by the graph node
+ *	gn
+ *
+ * Results:
+ *	Nothing.
+ *
+ * Side Effects:
+ *	Nodes are added to the graph below the passed-in node. The nodes
+ *	are marked to have their IMPSRC variable filled in. The
+ *	PREFIX variable is set for the given node and all its
+ *	implied children.
+ *
+ * Notes:
+ *	The path found by this target is the shortest path in the
+ *	transformation graph, which may pass through non-existent targets,
+ *	to an existing target. The search continues on all paths from the
+ *	root suffix until a file is found. I.e. if there's a path
+ *	.o -> .c -> .l -> .l,v from the root and the .l,v file exists but
+ *	the .c and .l files don't, the search will branch out in
+ *	all directions from .o and again from all the nodes on the
+ *	next level until the .l,v node is encountered.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Suff_FindDeps(GNode *gn)
+{
+
+    SuffFindDeps(gn, srclist);
+    while (SuffRemoveSrc(srclist))
+	continue;
+}
+
+
+static void
+SuffFindDeps (GNode *gn, Lst slst)
+{
+    if (gn->type & OP_DEPS_FOUND) {
+	/*
+	 * If dependencies already found, no need to do it again...
+	 */
+	return;
+    } else {
+	gn->type |= OP_DEPS_FOUND;
+    }
+
+    DEBUGF(SUFF, ("SuffFindDeps (%s)\n", gn->name));
+
+    if (gn->type & OP_ARCHV) {
+	SuffFindArchiveDeps(gn, slst);
+    } else if (gn->type & OP_LIB) {
+	/*
+	 * If the node is a library, it is the arch module's job to find it
+	 * and set the TARGET variable accordingly. We merely provide the
+	 * search path, assuming all libraries end in ".a" (if the suffix
+	 * hasn't been defined, there's nothing we can do for it, so we just
+	 * set the TARGET variable to the node's name in order to give it a
+	 * value).
+	 */
+	LstNode	ln;
+	Suff	*s;
+
+	ln = Lst_Find (sufflist, (void *)LIBSUFF, SuffSuffHasNameP);
+	if (gn->suffix)
+	    gn->suffix->refCount--;
+	if (ln != NULL) {
+	    gn->suffix = s = (Suff *) Lst_Datum (ln);
+	    gn->suffix->refCount++;
+	    Arch_FindLib (gn, s->searchPath);
+	} else {
+	    gn->suffix = NULL;
+	    Var_Set (TARGET, gn->name, gn);
+	}
+	/*
+	 * Because a library (-lfoo) target doesn't follow the standard
+	 * filesystem conventions, we don't set the regular variables for
+	 * the thing. .PREFIX is simply made empty...
+	 */
+	Var_Set(PREFIX, "", gn);
+    } else {
+	SuffFindNormalDeps(gn, slst);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_SetNull --
+ *	Define which suffix is the null suffix.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	'suffNull' is altered.
+ *
+ * Notes:
+ *	Need to handle the changing of the null suffix gracefully so the
+ *	old transformation rules don't just go away.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_SetNull(char *name)
+{
+    Suff    *s;
+    LstNode ln;
+
+    ln = Lst_Find(sufflist, (void *)name, SuffSuffHasNameP);
+    if (ln != NULL) {
+	s = (Suff *)Lst_Datum(ln);
+	if (suffNull != (Suff *)NULL) {
+	    suffNull->flags &= ~SUFF_NULL;
+	}
+	s->flags |= SUFF_NULL;
+	/*
+	 * XXX: Here's where the transformation mangling would take place
+	 */
+	suffNull = s;
+    } else {
+	Parse_Error (PARSE_WARNING, "Desired null suffix %s not defined.",
+		     name);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_Init --
+ *	Initialize suffixes module
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	Many
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_Init (void)
+{
+    sufflist = Lst_Init (FALSE);
+    suffClean = Lst_Init(FALSE);
+    srclist = Lst_Init (FALSE);
+    transforms = Lst_Init (FALSE);
+
+    sNum = 0;
+    /*
+     * Create null suffix for single-suffix rules (POSIX). The thing doesn't
+     * actually go on the suffix list or everyone will think that's its
+     * suffix.
+     */
+    emptySuff = suffNull = (Suff *) emalloc (sizeof (Suff));
+
+    suffNull->name =   	    estrdup ("");
+    suffNull->nameLen =     0;
+    suffNull->searchPath =  Lst_Init (FALSE);
+    Dir_Concat(suffNull->searchPath, dirSearchPath);
+    suffNull->children =    Lst_Init (FALSE);
+    suffNull->parents =	    Lst_Init (FALSE);
+    suffNull->ref =	    Lst_Init (FALSE);
+    suffNull->sNum =   	    sNum++;
+    suffNull->flags =  	    SUFF_NULL;
+    suffNull->refCount =    1;
+
+}
+
+
+/*-
+ *----------------------------------------------------------------------
+ * Suff_End --
+ *	Cleanup the this module
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The memory is free'd.
+ *----------------------------------------------------------------------
+ */
+
+void
+Suff_End(void)
+{
+    Lst_Destroy(sufflist, SuffFree);
+    Lst_Destroy(suffClean, SuffFree);
+    if (suffNull)
+	SuffFree(suffNull);
+    Lst_Destroy(srclist, NOFREE);
+    Lst_Destroy(transforms, NOFREE);
+}
+
+
+/********************* DEBUGGING FUNCTIONS **********************/
+
+static int
+SuffPrintName(void *s, void *dummy __unused)
+{
+    printf ("`%s' ", ((Suff *) s)->name);
+    return (0);
+}
+
+static int
+SuffPrintSuff (void *sp, void *dummy __unused)
+{
+    Suff    *s = (Suff *) sp;
+    int	    flags;
+    int	    flag;
+
+    printf ("# `%s' [%d] ", s->name, s->refCount);
+
+    flags = s->flags;
+    if (flags) {
+	fputs (" (", stdout);
+	while (flags) {
+	    flag = 1 << (ffs(flags) - 1);
+	    flags &= ~flag;
+	    switch (flag) {
+		case SUFF_NULL:
+		    printf ("NULL");
+		    break;
+		case SUFF_INCLUDE:
+		    printf ("INCLUDE");
+		    break;
+		case SUFF_LIBRARY:
+		    printf ("LIBRARY");
+		    break;
+		default:
+		    break;
+	    }
+	    fputc(flags ? '|' : ')', stdout);
+	}
+    }
+    fputc ('\n', stdout);
+    printf ("#\tTo: ");
+    Lst_ForEach (s->parents, SuffPrintName, (void *)0);
+    fputc ('\n', stdout);
+    printf ("#\tFrom: ");
+    Lst_ForEach (s->children, SuffPrintName, (void *)0);
+    fputc ('\n', stdout);
+    printf ("#\tSearch Path: ");
+    Dir_PrintPath (s->searchPath);
+    fputc ('\n', stdout);
+    return (0);
+}
+
+static int
+SuffPrintTrans (void *tp, void *dummy __unused)
+{
+    GNode   *t = (GNode *) tp;
+
+    printf ("%-16s: ", t->name);
+    Targ_PrintType (t->type);
+    fputc ('\n', stdout);
+    Lst_ForEach (t->commands, Targ_PrintCmd, (void *)0);
+    fputc ('\n', stdout);
+    return (0);
+}
+
+void
+Suff_PrintAll(void)
+{
+    printf ("#*** Suffixes:\n");
+    Lst_ForEach (sufflist, SuffPrintSuff, (void *)0);
+
+    printf ("#*** Transformations:\n");
+    Lst_ForEach (transforms, SuffPrintTrans, (void *)0);
+}
Index: trunk/src/kmk/targ.c
===================================================================
--- trunk/src/kmk/targ.c	(revision 9)
+++ trunk/src/kmk/targ.c	(revision 9)
@@ -0,0 +1,626 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)targ.c	8.2 (Berkeley) 3/19/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/targ.c,v 1.25 2002/10/09 03:42:10 jmallett Exp $");
+
+/*-
+ * targ.c --
+ *	Functions for maintaining the Lst allTargets. Target nodes are
+ * kept in two structures: a Lst, maintained by the list library, and a
+ * hash table, maintained by the hash library.
+ *
+ * Interface:
+ *	Targ_Init 	    	Initialization procedure.
+ *
+ *	Targ_End 	    	Cleanup the module
+ *
+ *	Targ_NewGN	    	Create a new GNode for the passed target
+ *	    	  	    	(string). The node is *not* placed in the
+ *	    	  	    	hash table, though all its fields are
+ *	    	  	    	initialized.
+ *
+ *	Targ_FindNode	    	Find the node for a given target, creating
+ *	    	  	    	and storing it if it doesn't exist and the
+ *	    	  	    	flags are right (TARG_CREATE)
+ *
+ *	Targ_FindList	    	Given a list of names, find nodes for all
+ *	    	  	    	of them. If a name doesn't exist and the
+ *	    	  	    	TARG_NOCREATE flag was given, an error message
+ *	    	  	    	is printed. Else, if a name doesn't exist,
+ *	    	  	    	its node is created.
+ *
+ *	Targ_Ignore	    	Return TRUE if errors should be ignored when
+ *	    	  	    	creating the given target.
+ *
+ *	Targ_Silent	    	Return TRUE if we should be silent when
+ *	    	  	    	creating the given target.
+ *
+ *	Targ_Precious	    	Return TRUE if the target is precious and
+ *	    	  	    	should not be removed if we are interrupted.
+ *
+ * Debugging:
+ *	Targ_PrintGraph	    	Print out the entire graphm all variables
+ *	    	  	    	and statistics for the directory cache. Should
+ *	    	  	    	print something for suffixes, too, but...
+ */
+
+#include	  <stdio.h>
+#include	  <time.h>
+#include	  "make.h"
+#include	  "hash.h"
+#include	  "dir.h"
+
+static Lst        allTargets;	/* the list of all targets found so far */
+static Lst	  allGNs;	/* List of all the GNodes */
+static Hash_Table targets;	/* a hash table of same */
+
+#define	HTSIZE	191		/* initial size of hash table */
+
+static int TargPrintOnlySrc(void *, void *);
+static int TargPrintName(void *, void *);
+static int TargPrintNode(void *, void *);
+static void TargFreeGN(void *);
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Init --
+ *	Initialize this module
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The allTargets list and the targets hash table are initialized
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_Init (void)
+{
+    allTargets = Lst_Init (FALSE);
+    Hash_InitTable (&targets, HTSIZE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_End --
+ *	Finalize this module
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	All lists and gnodes are cleared
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_End (void)
+{
+    Lst_Destroy(allTargets, NOFREE);
+    if (allGNs)
+	Lst_Destroy(allGNs, TargFreeGN);
+    Hash_DeleteTable(&targets);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_NewGN  --
+ *	Create and initialize a new graph node
+ *
+ * Results:
+ *	An initialized graph node with the name field filled with a copy
+ *	of the passed name
+ *
+ * Side Effects:
+ *	The gnode is added to the list of all gnodes.
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Targ_NewGN (char *name)
+{
+    GNode *gn;
+
+    gn = (GNode *) emalloc (sizeof (GNode));
+    gn->name = estrdup (name);
+    gn->path = (char *) 0;
+    if (name[0] == '-' && name[1] == 'l') {
+	gn->type = OP_LIB;
+    } else {
+	gn->type = 0;
+    }
+    gn->unmade =    	0;
+    gn->make = 	    	FALSE;
+    gn->made = 	    	UNMADE;
+    gn->childMade = 	FALSE;
+    gn->order =		0;
+    gn->mtime = gn->cmtime = 0;
+    gn->iParents =  	Lst_Init (FALSE);
+    gn->cohorts =   	Lst_Init (FALSE);
+    gn->parents =   	Lst_Init (FALSE);
+    gn->children =  	Lst_Init (FALSE);
+    gn->successors = 	Lst_Init (FALSE);
+    gn->preds =     	Lst_Init (FALSE);
+    gn->context =   	Lst_Init (FALSE);
+    gn->commands =  	Lst_Init (FALSE);
+    gn->suffix =	NULL;
+
+    if (allGNs == NULL)
+	allGNs = Lst_Init(FALSE);
+    Lst_AtEnd(allGNs, (void *) gn);
+
+    return (gn);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargFreeGN  --
+ *	Destroy a GNode
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	None.
+ *-----------------------------------------------------------------------
+ */
+static void
+TargFreeGN (void *gnp)
+{
+    GNode *gn = (GNode *) gnp;
+
+
+    free(gn->name);
+    efree(gn->path);
+
+    Lst_Destroy(gn->iParents, NOFREE);
+    Lst_Destroy(gn->cohorts, NOFREE);
+    Lst_Destroy(gn->parents, NOFREE);
+    Lst_Destroy(gn->children, NOFREE);
+    Lst_Destroy(gn->successors, NOFREE);
+    Lst_Destroy(gn->preds, NOFREE);
+    Lst_Destroy(gn->context, NOFREE);
+    Lst_Destroy(gn->commands, NOFREE);
+    free(gn);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FindNode  --
+ *	Find a node in the list using the given name for matching
+ *
+ * Results:
+ *	The node in the list if it was. If it wasn't, return NULL of
+ *	flags was TARG_NOCREATE or the newly created and initialized node
+ *	if it was TARG_CREATE
+ *
+ * Side Effects:
+ *	Sometimes a node is created and added to the list
+ *-----------------------------------------------------------------------
+ */
+GNode *
+Targ_FindNode (char *name, int flags)
+{
+    GNode         *gn;	      /* node in that element */
+    Hash_Entry	  *he;	      /* New or used hash entry for node */
+    Boolean	  isNew;      /* Set TRUE if Hash_CreateEntry had to create */
+			      /* an entry for the node */
+
+
+    if (flags & TARG_CREATE) {
+	he = Hash_CreateEntry (&targets, name, &isNew);
+	if (isNew) {
+	    gn = Targ_NewGN (name);
+	    Hash_SetValue (he, gn);
+	    (void) Lst_AtEnd (allTargets, (void *)gn);
+	}
+    } else {
+	he = Hash_FindEntry (&targets, name);
+    }
+
+    if (he == (Hash_Entry *) NULL) {
+	return (NULL);
+    } else {
+	return ((GNode *) Hash_GetValue (he));
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FindList --
+ *	Make a complete list of GNodes from the given list of names
+ *
+ * Results:
+ *	A complete list of graph nodes corresponding to all instances of all
+ *	the names in names.
+ *
+ * Side Effects:
+ *	If flags is TARG_CREATE, nodes will be created for all names in
+ *	names which do not yet have graph nodes. If flags is TARG_NOCREATE,
+ *	an error message will be printed for each name which can't be found.
+ * -----------------------------------------------------------------------
+ */
+Lst
+Targ_FindList (Lst names, int flags)
+{
+    Lst            nodes;	/* result list */
+    LstNode	   ln;		/* name list element */
+    GNode	   *gn;		/* node in tLn */
+    char    	   *name;
+
+    nodes = Lst_Init (FALSE);
+
+    if (Lst_Open (names) == FAILURE) {
+	return (nodes);
+    }
+    while ((ln = Lst_Next (names)) != NULL) {
+	name = (char *)Lst_Datum(ln);
+	gn = Targ_FindNode (name, flags);
+	if (gn != NULL) {
+	    /*
+	     * Note: Lst_AtEnd must come before the Lst_Concat so the nodes
+	     * are added to the list in the order in which they were
+	     * encountered in the makefile.
+	     */
+	    (void) Lst_AtEnd (nodes, (void *)gn);
+	    if (gn->type & OP_DOUBLEDEP) {
+		(void)Lst_Concat (nodes, gn->cohorts, LST_CONCNEW);
+	    }
+	} else if (flags == TARG_NOCREATE) {
+	    Error ("\"%s\" -- target unknown.", name);
+	}
+    }
+    Lst_Close (names);
+    return (nodes);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Ignore  --
+ *	Return true if should ignore errors when creating gn
+ *
+ * Results:
+ *	TRUE if should ignore errors
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Ignore (GNode *gn)
+{
+    if (ignoreErrors || gn->type & OP_IGNORE) {
+	return (TRUE);
+    } else {
+	return (FALSE);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Silent  --
+ *	Return true if be silent when creating gn
+ *
+ * Results:
+ *	TRUE if should be silent
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Silent (GNode *gn)
+{
+    if (beSilent || gn->type & OP_SILENT) {
+	return (TRUE);
+    } else {
+	return (FALSE);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Precious --
+ *	See if the given target is precious
+ *
+ * Results:
+ *	TRUE if it is precious. FALSE otherwise
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Targ_Precious (GNode *gn)
+{
+    if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) {
+	return (TRUE);
+    } else {
+	return (FALSE);
+    }
+}
+
+/******************* DEBUG INFO PRINTING ****************/
+
+static GNode	  *mainTarg;	/* the main target, as set by Targ_SetMain */
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_SetMain --
+ *	Set our idea of the main target we'll be creating. Used for
+ *	debugging output.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	"mainTarg" is set to the main target's node.
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_SetMain (GNode *gn)
+{
+    mainTarg = gn;
+}
+
+static int
+TargPrintName (void *gnp, void *ppath)
+{
+    GNode *gn = (GNode *) gnp;
+    printf ("%s ", gn->name);
+#ifdef notdef
+    if (ppath) {
+	if (gn->path) {
+	    printf ("[%s]  ", gn->path);
+	}
+	if (gn == mainTarg) {
+	    printf ("(MAIN NAME)  ");
+	}
+    }
+#endif /* notdef */
+    return (ppath ? 0 : 0);
+}
+
+
+int
+Targ_PrintCmd (void *cmd, void *dummy __unused)
+{
+    printf ("\t%s\n", (char *) cmd);
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FmtTime --
+ *	Format a modification time in some reasonable way and return it.
+ *
+ * Results:
+ *	The time reformatted.
+ *
+ * Side Effects:
+ *	The time is placed in a static area, so it is overwritten
+ *	with each call.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Targ_FmtTime (time_t modtime)
+{
+    struct tm	  	*parts;
+    static char	  	buf[128];
+
+    parts = localtime(&modtime);
+
+    strftime(buf, sizeof buf, "%H:%M:%S %b %d, %Y", parts);
+    buf[sizeof(buf) - 1] = '\0';
+    return(buf);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_PrintType --
+ *	Print out a type field giving only those attributes the user can
+ *	set.
+ *
+ * Results:
+ *
+ * Side Effects:
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_PrintType (int type)
+{
+    int    tbit;
+
+#define	PRINTBIT(attr)	case CONCAT(OP_,attr): printf("." #attr " "); break
+#define	PRINTDBIT(attr) case CONCAT(OP_,attr): DEBUGF(TARG, ("." #attr " ")); break
+
+    type &= ~OP_OPMASK;
+
+    while (type) {
+	tbit = 1 << (ffs(type) - 1);
+	type &= ~tbit;
+
+	switch(tbit) {
+	    PRINTBIT(OPTIONAL);
+	    PRINTBIT(USE);
+	    PRINTBIT(EXEC);
+	    PRINTBIT(IGNORE);
+	    PRINTBIT(PRECIOUS);
+	    PRINTBIT(SILENT);
+	    PRINTBIT(MAKE);
+	    PRINTBIT(JOIN);
+	    PRINTBIT(INVISIBLE);
+	    PRINTBIT(NOTMAIN);
+	    PRINTDBIT(LIB);
+	    /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */
+	    case OP_MEMBER: DEBUGF(TARG, (".MEMBER ")); break;
+	    PRINTDBIT(ARCHV);
+	}
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPrintNode --
+ *	print the contents of a node
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPrintNode (void *gnp, void *passp)
+{
+    GNode         *gn = (GNode *) gnp;
+    int	    	  pass = *(int *) passp;
+    if (!OP_NOP(gn->type)) {
+	printf("#\n");
+	if (gn == mainTarg) {
+	    printf("# *** MAIN TARGET ***\n");
+	}
+	if (pass == 2) {
+	    if (gn->unmade) {
+		printf("# %d unmade children\n", gn->unmade);
+	    } else {
+		printf("# No unmade children\n");
+	    }
+	    if (! (gn->type & (OP_JOIN|OP_USE|OP_EXEC))) {
+		if (gn->mtime != 0) {
+		    printf("# last modified %s: %s\n",
+			      Targ_FmtTime(gn->mtime),
+			      (gn->made == UNMADE ? "unmade" :
+			       (gn->made == MADE ? "made" :
+				(gn->made == UPTODATE ? "up-to-date" :
+				 "error when made"))));
+		} else if (gn->made != UNMADE) {
+		    printf("# non-existent (maybe): %s\n",
+			      (gn->made == MADE ? "made" :
+			       (gn->made == UPTODATE ? "up-to-date" :
+				(gn->made == ERROR ? "error when made" :
+				 "aborted"))));
+		} else {
+		    printf("# unmade\n");
+		}
+	    }
+	    if (!Lst_IsEmpty (gn->iParents)) {
+		printf("# implicit parents: ");
+		Lst_ForEach (gn->iParents, TargPrintName, (void *)0);
+		fputc ('\n', stdout);
+	    }
+	}
+	if (!Lst_IsEmpty (gn->parents)) {
+	    printf("# parents: ");
+	    Lst_ForEach (gn->parents, TargPrintName, (void *)0);
+	    fputc ('\n', stdout);
+	}
+
+	printf("%-16s", gn->name);
+	switch (gn->type & OP_OPMASK) {
+	    case OP_DEPENDS:
+		printf(": "); break;
+	    case OP_FORCE:
+		printf("! "); break;
+	    case OP_DOUBLEDEP:
+		printf(":: "); break;
+	    default:
+		break;
+	}
+	Targ_PrintType (gn->type);
+	Lst_ForEach (gn->children, TargPrintName, (void *)0);
+	fputc ('\n', stdout);
+	Lst_ForEach (gn->commands, Targ_PrintCmd, (void *)0);
+	printf("\n\n");
+	if (gn->type & OP_DOUBLEDEP) {
+	    Lst_ForEach (gn->cohorts, TargPrintNode, (void *)&pass);
+	}
+    }
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPrintOnlySrc --
+ *	Print only those targets that are just a source.
+ *
+ * Results:
+ *	0.
+ *
+ * Side Effects:
+ *	The name of each file is printed preceded by #\t
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPrintOnlySrc(void *gnp, void *dummy __unused)
+{
+    GNode   	  *gn = (GNode *) gnp;
+    if (OP_NOP(gn->type))
+	printf("#\t%s [%s]\n", gn->name, gn->path ? gn->path : gn->name);
+
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_PrintGraph --
+ *	Print the entire graph.
+ *
+ * Results:
+ *	none
+ *
+ * Side Effects:
+ *	lots o' output
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_PrintGraph (int pass)
+{
+    printf("#*** Input graph:\n");
+    Lst_ForEach (allTargets, TargPrintNode, (void *)&pass);
+    printf("\n\n");
+    printf("#\n#   Files that are only sources:\n");
+    Lst_ForEach (allTargets, TargPrintOnlySrc, (void *) 0);
+    printf("#*** Global Variables:\n");
+    Var_Dump (VAR_GLOBAL);
+    printf("#*** Command-line Variables:\n");
+    Var_Dump (VAR_CMD);
+    printf("\n");
+    Dir_PrintDirectories();
+    printf("\n");
+    Suff_PrintAll();
+}
Index: trunk/src/kmk/util.c
===================================================================
--- trunk/src/kmk/util.c	(revision 9)
+++ trunk/src/kmk/util.c	(revision 9)
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2002 Juli Mallett.  All rights reserved.
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)main.c      8.3 (Berkeley) 3/19/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/util.c,v 1.12 2002/10/10 19:27:48 jmallett Exp $");
+
+/*-
+ * util.c --
+ *	General utilitarian routines for make(1).
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <err.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <sysexits.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+
+/*-
+ * Debug --
+ *	Print a debugging message given its format.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The message is printed.
+ */
+/* VARARGS */
+void
+Debug(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	(void)vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	(void)fflush(stderr);
+}
+
+/*-
+ * Error --
+ *	Print an error message given its format.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The message is printed.
+ */
+/* VARARGS */
+void
+Error(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	(void)vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	(void)fprintf(stderr, "\n");
+	(void)fflush(stderr);
+}
+
+/*-
+ * Fatal --
+ *	Produce a Fatal error message. If jobs are running, waits for them
+ *	to finish.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The program exits
+ */
+/* VARARGS */
+void
+Fatal(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	if (jobsRunning)
+		Job_Wait();
+
+	(void)vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	(void)fprintf(stderr, "\n");
+	(void)fflush(stderr);
+
+	if (DEBUG(GRAPH2))
+		Targ_PrintGraph(2);
+	exit(2);		/* Not 1 so -q can distinguish error */
+}
+
+/*
+ * Punt --
+ *	Major exception once jobs are being created. Kills all jobs, prints
+ *	a message and exits.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	All children are killed indiscriminately and the program Lib_Exits
+ */
+/* VARARGS */
+void
+Punt(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	(void)fprintf(stderr, "make: ");
+	(void)vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	(void)fprintf(stderr, "\n");
+	(void)fflush(stderr);
+
+	DieHorribly();
+}
+
+/*-
+ * DieHorribly --
+ *	Exit without giving a message.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	A big one...
+ */
+void
+DieHorribly(void)
+{
+	if (jobsRunning)
+		Job_AbortAll();
+	if (DEBUG(GRAPH2))
+		Targ_PrintGraph(2);
+	exit(2);		/* Not 1, so -q can distinguish error */
+}
+
+/*
+ * Finish --
+ *	Called when aborting due to errors in child shell to signal
+ *	abnormal exit, with the number of errors encountered in Make_Make.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The program exits
+ */
+void
+Finish(int errors)
+{
+	Fatal("%d error%s", errors, errors == 1 ? "" : "s");
+}
+
+/*
+ * emalloc --
+ *	malloc, but die on error.
+ */
+void *
+emalloc(size_t len)
+{
+	void *p;
+
+	if ((p = malloc(len)) == NULL)
+		enomem();
+	return(p);
+}
+
+/*
+ * estrdup --
+ *	strdup, but die on error.
+ */
+char *
+estrdup(const char *str)
+{
+	char *p;
+
+	if ((p = strdup(str)) == NULL)
+		enomem();
+	return(p);
+}
+
+/*
+ * erealloc --
+ *	realloc, but die on error.
+ */
+void *
+erealloc(void *ptr, size_t size)
+{
+	if ((ptr = realloc(ptr, size)) == NULL)
+		enomem();
+	return(ptr);
+}
+
+/*
+ * enomem --
+ *	die when out of memory.
+ */
+void
+enomem(void)
+{
+	err(2, NULL);
+}
+
+/*
+ * enunlink --
+ *	Remove a file carefully, avoiding directories.
+ */
+int
+eunlink(const char *file)
+{
+	struct stat st;
+
+	if (lstat(file, &st) == -1)
+		return -1;
+
+	if (S_ISDIR(st.st_mode)) {
+		errno = EISDIR;
+		return -1;
+	}
+	return unlink(file);
+}
+
+/*
+ * Printaddr --
+ * 	Print the address of a node, used as an interative function.
+ */
+int
+PrintAddr(void *a, void *b __unused)
+{
+    printf("%p ", a);
+    return 0;
+}
Index: trunk/src/kmk/var.c
===================================================================
--- trunk/src/kmk/var.c	(revision 9)
+++ trunk/src/kmk/var.c	(revision 9)
@@ -0,0 +1,2480 @@
+/*
+ * Copyright (c) 1988, 1989, 1990, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1989 by Berkeley Softworks
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Adam de Boor.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)var.c	8.3 (Berkeley) 3/19/94
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/make/var.c,v 1.35 2002/10/09 03:42:10 jmallett Exp $");
+
+/*-
+ * var.c --
+ *	Variable-handling functions
+ *
+ * Interface:
+ *	Var_Set	  	    Set the value of a variable in the given
+ *	    	  	    context. The variable is created if it doesn't
+ *	    	  	    yet exist. The value and variable name need not
+ *	    	  	    be preserved.
+ *
+ *	Var_Append	    Append more characters to an existing variable
+ *	    	  	    in the given context. The variable needn't
+ *	    	  	    exist already -- it will be created if it doesn't.
+ *	    	  	    A space is placed between the old value and the
+ *	    	  	    new one.
+ *
+ *	Var_Exists	    See if a variable exists.
+ *
+ *	Var_Value 	    Return the value of a variable in a context or
+ *	    	  	    NULL if the variable is undefined.
+ *
+ *	Var_Subst 	    Substitute named variable, or all variables if
+ *			    NULL in a string using
+ *	    	  	    the given context as the top-most one. If the
+ *	    	  	    third argument is non-zero, Parse_Error is
+ *	    	  	    called if any variables are undefined.
+ *
+ *	Var_Parse 	    Parse a variable expansion from a string and
+ *	    	  	    return the result and the number of characters
+ *	    	  	    consumed.
+ *
+ *	Var_Delete	    Delete a variable in a context.
+ *
+ *	Var_Init  	    Initialize this module.
+ *
+ * Debugging:
+ *	Var_Dump  	    Print out all variables defined in the given
+ *	    	  	    context.
+ *
+ * XXX: There's a lot of duplication in these functions.
+ */
+
+#include    <ctype.h>
+#include    <sys/types.h>
+#include    <regex.h>
+#include    <stdlib.h>
+#include    "make.h"
+#include    "buf.h"
+
+/*
+ * This is a harmless return value for Var_Parse that can be used by Var_Subst
+ * to determine if there was an error in parsing -- easier than returning
+ * a flag, as things outside this module don't give a hoot.
+ */
+char 	var_Error[] = "";
+
+/*
+ * Similar to var_Error, but returned when the 'err' flag for Var_Parse is
+ * set false. Why not just use a constant? Well, gcc likes to condense
+ * identical string instances...
+ */
+static char	varNoError[] = "";
+
+/*
+ * Internally, variables are contained in four different contexts.
+ *	1) the environment. They may not be changed. If an environment
+ *	    variable is appended-to, the result is placed in the global
+ *	    context.
+ *	2) the global context. Variables set in the Makefile are located in
+ *	    the global context. It is the penultimate context searched when
+ *	    substituting.
+ *	3) the command-line context. All variables set on the command line
+ *	   are placed in this context. They are UNALTERABLE once placed here.
+ *	4) the local context. Each target has associated with it a context
+ *	   list. On this list are located the structures describing such
+ *	   local variables as $(@) and $(*)
+ * The four contexts are searched in the reverse order from which they are
+ * listed.
+ */
+GNode          *VAR_GLOBAL;   /* variables from the makefile */
+GNode          *VAR_CMD;      /* variables defined on the command-line */
+
+static Lst	allVars;      /* List of all variables */
+
+#define	FIND_CMD	0x1   /* look in VAR_CMD when searching */
+#define	FIND_GLOBAL	0x2   /* look in VAR_GLOBAL as well */
+#define	FIND_ENV  	0x4   /* look in the environment also */
+
+typedef struct Var {
+    char          *name;	/* the variable's name */
+    Buffer	  val;	    	/* its value */
+    int	    	  flags;    	/* miscellaneous status flags */
+#define	VAR_IN_USE	1   	    /* Variable's value currently being used.
+				     * Used to avoid recursion */
+#define	VAR_FROM_ENV	2   	    /* Variable comes from the environment */
+#define	VAR_JUNK  	4   	    /* Variable is a junk variable that
+				     * should be destroyed when done with
+				     * it. Used by Var_Parse for undefined,
+				     * modified variables */
+}  Var;
+
+/* Var*Pattern flags */
+#define	VAR_SUB_GLOBAL	0x01	/* Apply substitution globally */
+#define	VAR_SUB_ONE	0x02	/* Apply substitution to one word */
+#define	VAR_SUB_MATCHED	0x04	/* There was a match */
+#define	VAR_MATCH_START	0x08	/* Match at start of word */
+#define	VAR_MATCH_END	0x10	/* Match at end of word */
+#define	VAR_NOSUBST	0x20	/* don't expand vars in VarGetPattern */
+
+typedef struct {
+    char    	  *lhs;	    /* String to match */
+    int	    	  leftLen;  /* Length of string */
+    char    	  *rhs;	    /* Replacement string (w/ &'s removed) */
+    int	    	  rightLen; /* Length of replacement */
+    int	    	  flags;
+} VarPattern;
+
+typedef struct { 
+    regex_t	   re; 
+    int		   nsub;
+    regmatch_t	  *matches;
+    char	  *replace;
+    int		   flags;
+} VarREPattern;
+
+static int VarCmp(void *, void *);
+static void VarPossiblyExpand(char **, GNode *);
+static Var *VarFind(char *, GNode *, int);
+static void VarAdd(char *, char *, GNode *);
+static void VarDelete(void *);
+static Boolean VarHead(char *, Boolean, Buffer, void *);
+static Boolean VarTail(char *, Boolean, Buffer, void *);
+static Boolean VarSuffix(char *, Boolean, Buffer, void *);
+static Boolean VarRoot(char *, Boolean, Buffer, void *);
+static Boolean VarMatch(char *, Boolean, Buffer, void *);
+#ifdef SYSVVARSUB
+static Boolean VarSYSVMatch(char *, Boolean, Buffer, void *);
+#endif
+static Boolean VarNoMatch(char *, Boolean, Buffer, void *);
+static void VarREError(int, regex_t *, const char *);
+static Boolean VarRESubstitute(char *, Boolean, Buffer, void *);
+static Boolean VarSubstitute(char *, Boolean, Buffer, void *);
+static char *VarGetPattern(GNode *, int, char **, int, int *, int *, 
+			   VarPattern *);
+static char *VarQuote(char *);
+static char *VarModify(char *, Boolean (*)(char *, Boolean, Buffer, void *),
+		       void *);
+static int VarPrintVar(void *, void *);
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarCmp  --
+ *	See if the given variable matches the named one. Called from
+ *	Lst_Find when searching for a variable of a given name.
+ *
+ * Results:
+ *	0 if they match. non-zero otherwise.
+ *
+ * Side Effects:
+ *	none
+ *-----------------------------------------------------------------------
+ */
+static int
+VarCmp (void *v, void *name)
+{
+    return (strcmp ((char *) name, ((Var *) v)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarPossiblyExpand --
+ *	Expand a variable name's embedded variables in the given context.
+ *
+ * Results:
+ *	The contents of name, possibly expanded.
+ *
+ * Side Effects:
+ *	The caller must free the new contents or old contents of name.
+ *-----------------------------------------------------------------------
+ */
+static void
+VarPossiblyExpand(char **name, GNode *ctxt)
+{
+    if (strchr(*name, '$') != NULL)
+        *name = Var_Subst(NULL, *name, ctxt, 0);
+    else
+        *name = estrdup(*name);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarFind --
+ *	Find the given variable in the given context and any other contexts
+ *	indicated.
+ *
+ *	Flags:
+ *		FIND_GLOBAL	set means look in the VAR_GLOBAL context too
+ *		FIND_CMD	set means to look in the VAR_CMD context too
+ *		FIND_ENV	set means to look in the environment
+ *
+ * Results:
+ *	A pointer to the structure describing the desired variable or
+ *	NULL if the variable does not exist.
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static Var *
+VarFind (char *name, GNode *ctxt, int flags)
+{
+    Boolean		localCheckEnvFirst;
+    LstNode         	var;
+    Var		  	*v;
+
+	/*
+	 * If the variable name begins with a '.', it could very well be one of
+	 * the local ones.  We check the name against all the local variables
+	 * and substitute the short version in for 'name' if it matches one of
+	 * them.
+	 */
+	if (*name == '.' && isupper((unsigned char) name[1]))
+		switch (name[1]) {
+		case 'A':
+			if (!strcmp(name, ".ALLSRC"))
+				name = ALLSRC;
+			if (!strcmp(name, ".ARCHIVE"))
+				name = ARCHIVE;
+			break;
+		case 'I':
+			if (!strcmp(name, ".IMPSRC"))
+				name = IMPSRC;
+			break;
+		case 'M':
+			if (!strcmp(name, ".MEMBER"))
+				name = MEMBER;
+			break;
+		case 'O':
+			if (!strcmp(name, ".OODATE"))
+				name = OODATE;
+			break;
+		case 'P':
+			if (!strcmp(name, ".PREFIX"))
+				name = PREFIX;
+			break;
+		case 'T':
+			if (!strcmp(name, ".TARGET"))
+				name = TARGET;
+			break;
+		}
+
+    /*
+     * Note whether this is one of the specific variables we were told through
+     * the -E flag to use environment-variable-override for.
+     */
+    if (Lst_Find (envFirstVars, (void *)name,
+		  (int (*)(void *, void *)) strcmp) != NULL)
+    {
+	localCheckEnvFirst = TRUE;
+    } else {
+	localCheckEnvFirst = FALSE;
+    }
+
+    /*
+     * First look for the variable in the given context. If it's not there,
+     * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order,
+     * depending on the FIND_* flags in 'flags'
+     */
+    var = Lst_Find (ctxt->context, (void *)name, VarCmp);
+
+    if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) {
+	var = Lst_Find (VAR_CMD->context, (void *)name, VarCmp);
+    }
+    if ((var == NULL) && (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL) &&
+	!checkEnvFirst && !localCheckEnvFirst)
+    {
+	var = Lst_Find (VAR_GLOBAL->context, (void *)name, VarCmp);
+    }
+    if ((var == NULL) && (flags & FIND_ENV)) {
+	char *env;
+
+	if ((env = getenv (name)) != NULL) {
+	    int	  	len;
+
+	    v = (Var *) emalloc(sizeof(Var));
+	    v->name = estrdup(name);
+
+	    len = strlen(env);
+
+	    v->val = Buf_Init(len);
+	    Buf_AddBytes(v->val, len, (Byte *)env);
+
+	    v->flags = VAR_FROM_ENV;
+	    return (v);
+	} else if ((checkEnvFirst || localCheckEnvFirst) &&
+		   (flags & FIND_GLOBAL) && (ctxt != VAR_GLOBAL))
+	{
+	    var = Lst_Find (VAR_GLOBAL->context, (void *)name, VarCmp);
+	    if (var == NULL) {
+		return ((Var *) NULL);
+	    } else {
+		return ((Var *)Lst_Datum(var));
+	    }
+	} else {
+	    return((Var *)NULL);
+	}
+    } else if (var == NULL) {
+	return ((Var *) NULL);
+    } else {
+	return ((Var *) Lst_Datum (var));
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarAdd  --
+ *	Add a new variable of name name and value val to the given context.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The new variable is placed at the front of the given context
+ *	The name and val arguments are duplicated so they may
+ *	safely be freed.
+ *-----------------------------------------------------------------------
+ */
+static void
+VarAdd (char *name, char *val, GNode *ctxt)
+{
+    Var		  *v;
+    int	    	  len;
+
+    v = (Var *) emalloc (sizeof (Var));
+
+    v->name = estrdup (name);
+
+    len = val ? strlen(val) : 0;
+    v->val = Buf_Init(len+1);
+    Buf_AddBytes(v->val, len, (Byte *)val);
+
+    v->flags = 0;
+
+    (void) Lst_AtFront (ctxt->context, (void *)v);
+    (void) Lst_AtEnd (allVars, (void *) v);
+    DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, val));
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarDelete  --
+ *	Delete a variable and all the space associated with it.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+static void
+VarDelete(void *vp)
+{
+    Var *v = (Var *) vp;
+    free(v->name);
+    Buf_Destroy(v->val, TRUE);
+    free(v);
+}
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Delete --
+ *	Remove a variable from a context.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	The Var structure is removed and freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Delete(char *name, GNode *ctxt)
+{
+    LstNode 	  ln;
+
+    DEBUGF(VAR, ("%s:delete %s\n", ctxt->name, name));
+    ln = Lst_Find(ctxt->context, (void *)name, VarCmp);
+    if (ln != NULL) {
+	Var 	  *v;
+
+	v = (Var *)Lst_Datum(ln);
+	Lst_Remove(ctxt->context, ln);
+	ln = Lst_Member(allVars, v);
+	Lst_Remove(allVars, ln);
+	VarDelete((void *) v);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Set --
+ *	Set the variable name to the value val in the given context.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	If the variable doesn't yet exist, a new record is created for it.
+ *	Else the old value is freed and the new one stuck in its place
+ *
+ * Notes:
+ *	The variable is searched for only in its context before being
+ *	created in that context. I.e. if the context is VAR_GLOBAL,
+ *	only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only
+ *	VAR_CMD->context is searched. This is done to avoid the literally
+ *	thousands of unnecessary strcmp's that used to be done to
+ *	set, say, $(@) or $(<).
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Set (char *name, char *val, GNode *ctxt)
+{
+    Var		   *v;
+
+    /*
+     * We only look for a variable in the given context since anything set
+     * here will override anything in a lower context, so there's not much
+     * point in searching them all just to save a bit of memory...
+     */
+    VarPossiblyExpand(&name, ctxt);
+    v = VarFind (name, ctxt, 0);
+    if (v == (Var *) NULL) {
+	VarAdd (name, val, ctxt);
+    } else {
+	Buf_Discard(v->val, Buf_Size(v->val));
+	Buf_AddBytes(v->val, strlen(val), (Byte *)val);
+
+	DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, val));
+    }
+    /*
+     * Any variables given on the command line are automatically exported
+     * to the environment (as per POSIX standard)
+     */
+    if (ctxt == VAR_CMD) {
+	setenv(name, val, 1);
+    }
+    free(name);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Append --
+ *	The variable of the given name has the given value appended to it in
+ *	the given context.
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	If the variable doesn't exist, it is created. Else the strings
+ *	are concatenated (with a space in between).
+ *
+ * Notes:
+ *	Only if the variable is being sought in the global context is the
+ *	environment searched.
+ *	XXX: Knows its calling circumstances in that if called with ctxt
+ *	an actual target, it will only search that context since only
+ *	a local variable could be being appended to. This is actually
+ *	a big win and must be tolerated.
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Append (char *name, char *val, GNode *ctxt)
+{
+    Var		   *v;
+
+    VarPossiblyExpand(&name, ctxt);
+    v = VarFind (name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0);
+
+    if (v == (Var *) NULL) {
+	VarAdd (name, val, ctxt);
+    } else {
+	Buf_AddByte(v->val, (Byte)' ');
+	Buf_AddBytes(v->val, strlen(val), (Byte *)val);
+
+	DEBUGF(VAR, ("%s:%s = %s\n", ctxt->name, name, 
+	       (char *) Buf_GetAll(v->val, (int *)NULL)));
+
+	if (v->flags & VAR_FROM_ENV) {
+	    /*
+	     * If the original variable came from the environment, we
+	     * have to install it in the global context (we could place
+	     * it in the environment, but then we should provide a way to
+	     * export other variables...)
+	     */
+	    v->flags &= ~VAR_FROM_ENV;
+	    Lst_AtFront(ctxt->context, (void *)v);
+	}
+    }
+    free(name);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Exists --
+ *	See if the given variable exists.
+ *
+ * Results:
+ *	TRUE if it does, FALSE if it doesn't
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Var_Exists(char *name, GNode *ctxt)
+{
+    Var	    	  *v;
+
+    VarPossiblyExpand(&name, ctxt);
+    v = VarFind(name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV);
+    free(name);
+
+    if (v == (Var *)NULL) {
+	return(FALSE);
+    } else if (v->flags & VAR_FROM_ENV) {
+	free(v->name);
+	Buf_Destroy(v->val, TRUE);
+	free((char *)v);
+    }
+    return(TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Value --
+ *	Return the value of the named variable in the given context
+ *
+ * Results:
+ *	The value if the variable exists, NULL if it doesn't
+ *
+ * Side Effects:
+ *	None
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Value (char *name, GNode *ctxt, char **frp)
+{
+    Var            *v;
+
+    VarPossiblyExpand(&name, ctxt);
+    v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+    free(name);
+    *frp = NULL;
+    if (v != (Var *) NULL) {
+	char *p = ((char *)Buf_GetAll(v->val, (int *)NULL));
+	if (v->flags & VAR_FROM_ENV) {
+	    Buf_Destroy(v->val, FALSE);
+	    free(v);
+	    *frp = p;
+	}
+	return p;
+    } else {
+	return ((char *) NULL);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarHead --
+ *	Remove the tail of the given word and place the result in the given
+ *	buffer.
+ *
+ * Results:
+ *	TRUE if characters were added to the buffer (a space needs to be
+ *	added to the buffer before the next word).
+ *
+ * Side Effects:
+ *	The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarHead (char *word, Boolean addSpace, Buffer buf, void *dummy __unused)
+{
+    char *slash;
+
+    slash = strrchr (word, '/');
+    if (slash != (char *)NULL) {
+	if (addSpace) {
+	    Buf_AddByte (buf, (Byte)' ');
+	}
+	*slash = '\0';
+	Buf_AddBytes (buf, strlen (word), (Byte *)word);
+	*slash = '/';
+	return (TRUE);
+    } else {
+	/*
+	 * If no directory part, give . (q.v. the POSIX standard)
+	 */
+	if (addSpace) {
+	    Buf_AddBytes(buf, 2, (Byte *)" .");
+	} else {
+	    Buf_AddByte(buf, (Byte)'.');
+	}
+    }
+    return (TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarTail --
+ *	Remove the head of the given word and place the result in the given
+ *	buffer.
+ *
+ * Results:
+ *	TRUE if characters were added to the buffer (a space needs to be
+ *	added to the buffer before the next word).
+ *
+ * Side Effects:
+ *	The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarTail (char *word, Boolean addSpace, Buffer buf, void *dummy __unused)
+{
+    char *slash;
+
+    if (addSpace) {
+	Buf_AddByte (buf, (Byte)' ');
+    }
+
+    slash = strrchr (word, '/');
+    if (slash != (char *)NULL) {
+	*slash++ = '\0';
+	Buf_AddBytes (buf, strlen(slash), (Byte *)slash);
+	slash[-1] = '/';
+    } else {
+	Buf_AddBytes (buf, strlen(word), (Byte *)word);
+    }
+    return (TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSuffix --
+ *	Place the suffix of the given word in the given buffer.
+ *
+ * Results:
+ *	TRUE if characters were added to the buffer (a space needs to be
+ *	added to the buffer before the next word).
+ *
+ * Side Effects:
+ *	The suffix from the word is placed in the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSuffix (char *word, Boolean addSpace, Buffer buf, void *dummy __unused)
+{
+    char *dot;
+
+    dot = strrchr (word, '.');
+    if (dot != (char *)NULL) {
+	if (addSpace) {
+	    Buf_AddByte (buf, (Byte)' ');
+	}
+	*dot++ = '\0';
+	Buf_AddBytes (buf, strlen (dot), (Byte *)dot);
+	dot[-1] = '.';
+	addSpace = TRUE;
+    }
+    return (addSpace);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRoot --
+ *	Remove the suffix of the given word and place the result in the
+ *	buffer.
+ *
+ * Results:
+ *	TRUE if characters were added to the buffer (a space needs to be
+ *	added to the buffer before the next word).
+ *
+ * Side Effects:
+ *	The trimmed word is added to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarRoot (char *word, Boolean addSpace, Buffer buf, void *dummy __unused)
+{
+    char *dot;
+
+    if (addSpace) {
+	Buf_AddByte (buf, (Byte)' ');
+    }
+
+    dot = strrchr (word, '.');
+    if (dot != (char *)NULL) {
+	*dot = '\0';
+	Buf_AddBytes (buf, strlen (word), (Byte *)word);
+	*dot = '.';
+    } else {
+	Buf_AddBytes (buf, strlen(word), (Byte *)word);
+    }
+    return (TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarMatch --
+ *	Place the word in the buffer if it matches the given pattern.
+ *	Callback function for VarModify to implement the :M modifier.
+ *	A space will be added if requested.  A pattern is supplied
+ *	which the word must match.
+ *
+ * Results:
+ *	TRUE if a space should be placed in the buffer before the next
+ *	word.
+ *
+ * Side Effects:
+ *	The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarMatch (char *word, Boolean addSpace, Buffer buf, void *pattern)
+{
+    if (Str_Match(word, (char *) pattern)) {
+	if (addSpace) {
+	    Buf_AddByte(buf, (Byte)' ');
+	}
+	addSpace = TRUE;
+	Buf_AddBytes(buf, strlen(word), (Byte *)word);
+    }
+    return(addSpace);
+}
+
+#ifdef SYSVVARSUB
+/*-
+ *-----------------------------------------------------------------------
+ * VarSYSVMatch --
+ *	Place the word in the buffer if it matches the given pattern.
+ *	Callback function for VarModify to implement the System V %
+ *	modifiers.  A space is added if requested.
+ *
+ * Results:
+ *	TRUE if a space should be placed in the buffer before the next
+ *	word.
+ *
+ * Side Effects:
+ *	The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSYSVMatch (char *word, Boolean addSpace, Buffer buf, void *patp)
+{
+    int len;
+    char *ptr;
+    VarPattern 	  *pat = (VarPattern *) patp;
+
+    if (addSpace)
+	Buf_AddByte(buf, (Byte)' ');
+
+    addSpace = TRUE;
+
+    if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL)
+	Str_SYSVSubst(buf, pat->rhs, ptr, len);
+    else
+	Buf_AddBytes(buf, strlen(word), (Byte *) word);
+
+    return(addSpace);
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarNoMatch --
+ *	Place the word in the buffer if it doesn't match the given pattern.
+ *	Callback function for VarModify to implement the :N modifier.  A
+ *	space is added if requested.
+ *
+ * Results:
+ *	TRUE if a space should be placed in the buffer before the next
+ *	word.
+ *
+ * Side Effects:
+ *	The word may be copied to the buffer.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarNoMatch (char *word, Boolean addSpace, Buffer buf, void *pattern)
+{
+    if (!Str_Match(word, (char *) pattern)) {
+	if (addSpace) {
+	    Buf_AddByte(buf, (Byte)' ');
+	}
+	addSpace = TRUE;
+	Buf_AddBytes(buf, strlen(word), (Byte *)word);
+    }
+    return(addSpace);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSubstitute --
+ *	Perform a string-substitution on the given word, placing the
+ *	result in the passed buffer.  A space is added if requested.
+ *
+ * Results:
+ *	TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSubstitute (char *word, Boolean addSpace, Buffer buf, void *patternp)
+{
+    int		  	wordLen;    /* Length of word */
+    char	 	*cp;	    /* General pointer */
+    VarPattern	*pattern = (VarPattern *) patternp;
+
+    wordLen = strlen(word);
+    if (1) { /* substitute in each word of the variable */
+	/*
+	 * Break substitution down into simple anchored cases
+	 * and if none of them fits, perform the general substitution case.
+	 */
+	if ((pattern->flags & VAR_MATCH_START) &&
+	    (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) {
+		/*
+		 * Anchored at start and beginning of word matches pattern
+		 */
+		if ((pattern->flags & VAR_MATCH_END) &&
+		    (wordLen == pattern->leftLen)) {
+			/*
+			 * Also anchored at end and matches to the end (word
+			 * is same length as pattern) add space and rhs only
+			 * if rhs is non-null.
+			 */
+			if (pattern->rightLen != 0) {
+			    if (addSpace) {
+				Buf_AddByte(buf, (Byte)' ');
+			    }
+			    addSpace = TRUE;
+			    Buf_AddBytes(buf, pattern->rightLen,
+					 (Byte *)pattern->rhs);
+			}
+		} else if (pattern->flags & VAR_MATCH_END) {
+		    /*
+		     * Doesn't match to end -- copy word wholesale
+		     */
+		    goto nosub;
+		} else {
+		    /*
+		     * Matches at start but need to copy in trailing characters
+		     */
+		    if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){
+			if (addSpace) {
+			    Buf_AddByte(buf, (Byte)' ');
+			}
+			addSpace = TRUE;
+		    }
+		    Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs);
+		    Buf_AddBytes(buf, wordLen - pattern->leftLen,
+				 (Byte *)(word + pattern->leftLen));
+		}
+	} else if (pattern->flags & VAR_MATCH_START) {
+	    /*
+	     * Had to match at start of word and didn't -- copy whole word.
+	     */
+	    goto nosub;
+	} else if (pattern->flags & VAR_MATCH_END) {
+	    /*
+	     * Anchored at end, Find only place match could occur (leftLen
+	     * characters from the end of the word) and see if it does. Note
+	     * that because the $ will be left at the end of the lhs, we have
+	     * to use strncmp.
+	     */
+	    cp = word + (wordLen - pattern->leftLen);
+	    if ((cp >= word) &&
+		(strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) {
+		/*
+		 * Match found. If we will place characters in the buffer,
+		 * add a space before hand as indicated by addSpace, then
+		 * stuff in the initial, unmatched part of the word followed
+		 * by the right-hand-side.
+		 */
+		if (((cp - word) + pattern->rightLen) != 0) {
+		    if (addSpace) {
+			Buf_AddByte(buf, (Byte)' ');
+		    }
+		    addSpace = TRUE;
+		}
+		Buf_AddBytes(buf, cp - word, (Byte *)word);
+		Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs);
+	    } else {
+		/*
+		 * Had to match at end and didn't. Copy entire word.
+		 */
+		goto nosub;
+	    }
+	} else {
+	    /*
+	     * Pattern is unanchored: search for the pattern in the word using
+	     * String_FindSubstring, copying unmatched portions and the
+	     * right-hand-side for each match found, handling non-global
+	     * substitutions correctly, etc. When the loop is done, any
+	     * remaining part of the word (word and wordLen are adjusted
+	     * accordingly through the loop) is copied straight into the
+	     * buffer.
+	     * addSpace is set FALSE as soon as a space is added to the
+	     * buffer.
+	     */
+	    Boolean done;
+	    int origSize;
+
+	    done = FALSE;
+	    origSize = Buf_Size(buf);
+	    while (!done) {
+		cp = Str_FindSubstring(word, pattern->lhs);
+		if (cp != (char *)NULL) {
+		    if (addSpace && (((cp - word) + pattern->rightLen) != 0)){
+			Buf_AddByte(buf, (Byte)' ');
+			addSpace = FALSE;
+		    }
+		    Buf_AddBytes(buf, cp-word, (Byte *)word);
+		    Buf_AddBytes(buf, pattern->rightLen, (Byte *)pattern->rhs);
+		    wordLen -= (cp - word) + pattern->leftLen;
+		    word = cp + pattern->leftLen;
+		    if (wordLen == 0 || (pattern->flags & VAR_SUB_GLOBAL) == 0){
+			done = TRUE;
+		    }
+		} else {
+		    done = TRUE;
+		}
+	    }
+	    if (wordLen != 0) {
+		if (addSpace) {
+		    Buf_AddByte(buf, (Byte)' ');
+		}
+		Buf_AddBytes(buf, wordLen, (Byte *)word);
+	    }
+	    /*
+	     * If added characters to the buffer, need to add a space
+	     * before we add any more. If we didn't add any, just return
+	     * the previous value of addSpace.
+	     */
+	    return ((Buf_Size(buf) != origSize) || addSpace);
+	}
+	/*
+	 * Common code for anchored substitutions:
+	 * addSpace was set TRUE if characters were added to the buffer.
+	 */
+	return (addSpace);
+    }
+ nosub:
+    if (addSpace) {
+	Buf_AddByte(buf, (Byte)' ');
+    }
+    Buf_AddBytes(buf, wordLen, (Byte *)word);
+    return(TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarREError --
+ *	Print the error caused by a regcomp or regexec call.
+ *
+ * Results:
+ *	None.
+ *
+ * Side Effects:
+ *	An error gets printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+VarREError(int err, regex_t *pat, const char *str)
+{
+    char *errbuf;
+    int errlen;
+
+    errlen = regerror(err, pat, 0, 0);
+    errbuf = emalloc(errlen);
+    regerror(err, pat, errbuf, errlen);
+    Error("%s: %s", str, errbuf);
+    free(errbuf);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRESubstitute --
+ *	Perform a regex substitution on the given word, placing the
+ *	result in the passed buffer.  A space is added if requested.
+ *
+ * Results:
+ *	TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarRESubstitute(char *word, Boolean addSpace, Buffer buf, void *patternp)
+{
+    VarREPattern *pat;
+    int xrv;
+    char *wp;
+    char *rp;
+    int added;
+    int flags = 0;
+
+#define	MAYBE_ADD_SPACE()		\
+	if (addSpace && !added)		\
+	    Buf_AddByte(buf, ' ');	\
+	added = 1
+
+    added = 0;
+    wp = word;
+    pat = patternp;
+
+    if ((pat->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) ==
+	(VAR_SUB_ONE|VAR_SUB_MATCHED))
+	xrv = REG_NOMATCH;
+    else {
+    tryagain:
+	xrv = regexec(&pat->re, wp, pat->nsub, pat->matches, flags);
+    }
+
+    switch (xrv) {
+    case 0:
+	pat->flags |= VAR_SUB_MATCHED;
+	if (pat->matches[0].rm_so > 0) {
+	    MAYBE_ADD_SPACE();
+	    Buf_AddBytes(buf, pat->matches[0].rm_so, wp);
+	}
+
+	for (rp = pat->replace; *rp; rp++) {
+	    if ((*rp == '\\') && ((rp[1] == '&') || (rp[1] == '\\'))) {
+		MAYBE_ADD_SPACE();
+		Buf_AddByte(buf,rp[1]);
+		rp++;
+	    }
+	    else if ((*rp == '&') ||
+		((*rp == '\\') && isdigit((unsigned char)rp[1]))) {
+		int n;
+		char *subbuf;
+		int sublen;
+		char errstr[3];
+
+		if (*rp == '&') {
+		    n = 0;
+		    errstr[0] = '&';
+		    errstr[1] = '\0';
+		} else {
+		    n = rp[1] - '0';
+		    errstr[0] = '\\';
+		    errstr[1] = rp[1];
+		    errstr[2] = '\0';
+		    rp++;
+		}
+
+		if (n > pat->nsub) {
+		    Error("No subexpression %s", &errstr[0]);
+		    subbuf = "";
+		    sublen = 0;
+		} else if ((pat->matches[n].rm_so == -1) &&
+			   (pat->matches[n].rm_eo == -1)) {
+		    Error("No match for subexpression %s", &errstr[0]);
+		    subbuf = "";
+		    sublen = 0;
+		} else {
+		    subbuf = wp + pat->matches[n].rm_so;
+		    sublen = pat->matches[n].rm_eo - pat->matches[n].rm_so;
+		}
+
+		if (sublen > 0) {
+		    MAYBE_ADD_SPACE();
+		    Buf_AddBytes(buf, sublen, subbuf);
+		}
+	    } else {
+		MAYBE_ADD_SPACE();
+		Buf_AddByte(buf, *rp);
+	    }
+	}
+	wp += pat->matches[0].rm_eo;
+	if (pat->flags & VAR_SUB_GLOBAL) {
+	    flags |= REG_NOTBOL;
+	    if (pat->matches[0].rm_so == 0 && pat->matches[0].rm_eo == 0) {
+		MAYBE_ADD_SPACE();
+		Buf_AddByte(buf, *wp);
+		wp++;
+
+	    }
+	    if (*wp)
+		goto tryagain;
+	}
+	if (*wp) {
+	    MAYBE_ADD_SPACE();
+	    Buf_AddBytes(buf, strlen(wp), wp);
+	}
+	break;
+    default:
+	VarREError(xrv, &pat->re, "Unexpected regex error");
+       /* fall through */
+    case REG_NOMATCH:
+	if (*wp) {
+	    MAYBE_ADD_SPACE();
+	    Buf_AddBytes(buf,strlen(wp),wp);
+	}
+	break;
+    }
+    return(addSpace||added);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarModify --
+ *	Modify each of the words of the passed string using the given
+ *	function. Used to implement all modifiers.
+ *
+ * Results:
+ *	A string of all the words modified appropriately.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarModify (char *str, Boolean (*modProc)(char *, Boolean, Buffer, void *),
+    void *datum)
+{
+    Buffer  	  buf;	    	    /* Buffer for the new string */
+    Boolean 	  addSpace; 	    /* TRUE if need to add a space to the
+				     * buffer before adding the trimmed
+				     * word */
+    char **av;			    /* word list [first word does not count] */
+    int ac, i;
+
+    buf = Buf_Init (0);
+    addSpace = FALSE;
+
+    av = brk_string(str, &ac, FALSE);
+
+    for (i = 1; i < ac; i++)
+	addSpace = (*modProc)(av[i], addSpace, buf, datum);
+
+    Buf_AddByte (buf, '\0');
+    str = (char *)Buf_GetAll (buf, (int *)NULL);
+    Buf_Destroy (buf, FALSE);
+    return (str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarGetPattern --
+ *	Pass through the tstr looking for 1) escaped delimiters,
+ *	'$'s and backslashes (place the escaped character in
+ *	uninterpreted) and 2) unescaped $'s that aren't before
+ *	the delimiter (expand the variable substitution unless flags
+ *	has VAR_NOSUBST set).
+ *	Return the expanded string or NULL if the delimiter was missing
+ *	If pattern is specified, handle escaped ampersands, and replace
+ *	unescaped ampersands with the lhs of the pattern.
+ *
+ * Results:
+ *	A string of all the words modified appropriately.
+ *	If length is specified, return the string length of the buffer
+ *	If flags is specified and the last character of the pattern is a
+ *	$ set the VAR_MATCH_END bit of flags.
+ *
+ * Side Effects:
+ *	None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarGetPattern(GNode *ctxt, int err, char **tstr, int delim, int *flags,
+    int *length, VarPattern *pattern)
+{
+    char *cp;
+    Buffer buf = Buf_Init(0);
+    int junk;
+    if (length == NULL)
+	length = &junk;
+
+#define	IS_A_MATCH(cp, delim) \
+    ((cp[0] == '\\') && ((cp[1] == delim) ||  \
+     (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&'))))
+
+    /*
+     * Skim through until the matching delimiter is found;
+     * pick up variable substitutions on the way. Also allow
+     * backslashes to quote the delimiter, $, and \, but don't
+     * touch other backslashes.
+     */
+    for (cp = *tstr; *cp && (*cp != delim); cp++) {
+	if (IS_A_MATCH(cp, delim)) {
+	    Buf_AddByte(buf, (Byte) cp[1]);
+	    cp++;
+	} else if (*cp == '$') {
+	    if (cp[1] == delim) {
+		if (flags == NULL)
+		    Buf_AddByte(buf, (Byte) *cp);
+		else
+		    /*
+		     * Unescaped $ at end of pattern => anchor
+		     * pattern at end.
+		     */
+		    *flags |= VAR_MATCH_END;
+	    } else {
+		if (flags == NULL || (*flags & VAR_NOSUBST) == 0) {
+		    char   *cp2;
+		    int	    len;
+		    Boolean freeIt;
+
+		    /*
+		     * If unescaped dollar sign not before the
+		     * delimiter, assume it's a variable
+		     * substitution and recurse.
+		     */
+		    cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt);
+		    Buf_AddBytes(buf, strlen(cp2), (Byte *) cp2);
+		    if (freeIt)
+			free(cp2);
+		    cp += len - 1;
+		} else {
+		    char *cp2 = &cp[1];
+
+		    if (*cp2 == '(' || *cp2 == '{') {
+			/*
+			 * Find the end of this variable reference
+			 * and suck it in without further ado.
+			 * It will be interperated later.
+			 */
+			int have = *cp2;
+			int want = (*cp2 == '(') ? ')' : '}';
+			int depth = 1;
+
+			for (++cp2; *cp2 != '\0' && depth > 0; ++cp2) {
+			    if (cp2[-1] != '\\') {
+				if (*cp2 == have)
+				    ++depth;
+				if (*cp2 == want)
+				    --depth;
+			    }
+			}
+			Buf_AddBytes(buf, cp2 - cp, (Byte *)cp);
+			cp = --cp2;
+		    } else
+			Buf_AddByte(buf, (Byte) *cp);
+		}
+	    }
+	}
+	else if (pattern && *cp == '&')
+	    Buf_AddBytes(buf, pattern->leftLen, (Byte *)pattern->lhs);
+	else
+	    Buf_AddByte(buf, (Byte) *cp);
+    }
+
+    Buf_AddByte(buf, (Byte) '\0');
+
+    if (*cp != delim) {
+	*tstr = cp;
+	*length = 0;
+	return NULL;
+    }
+    else {
+	*tstr = ++cp;
+	cp = (char *) Buf_GetAll(buf, length);
+	*length -= 1;	/* Don't count the NULL */
+	Buf_Destroy(buf, FALSE);
+	return cp;
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarQuote --
+ *	Quote shell meta-characters in the string
+ *
+ * Results:
+ *	The quoted string
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarQuote(char *str)
+{
+
+    Buffer  	  buf;
+    /* This should cover most shells :-( */
+    static char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~";
+
+    buf = Buf_Init (MAKE_BSIZE);
+    for (; *str; str++) {
+	if (strchr(meta, *str) != NULL)
+	    Buf_AddByte(buf, (Byte)'\\');
+	Buf_AddByte(buf, (Byte)*str);
+    }
+    Buf_AddByte(buf, (Byte) '\0');
+    str = (char *)Buf_GetAll (buf, (int *)NULL);
+    Buf_Destroy (buf, FALSE);
+    return str;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Parse --
+ *	Given the start of a variable invocation, extract the variable
+ *	name and find its value, then modify it according to the
+ *	specification.
+ *
+ * Results:
+ *	The (possibly-modified) value of the variable or var_Error if the
+ *	specification is invalid. The length of the specification is
+ *	placed in *lengthPtr (for invalid specifications, this is just
+ *	2...?).
+ *	A Boolean in *freePtr telling whether the returned string should
+ *	be freed by the caller.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Parse(char *str, GNode *ctxt, Boolean err, int *lengthPtr, Boolean *freePtr)
+{
+    char	    *tstr;    	/* Pointer into str */
+    Var	    	    *v;	    	/* Variable in invocation */
+    char	    *cp;    	/* Secondary pointer into str (place marker
+				 * for tstr) */
+    Boolean 	    haveModifier;/* TRUE if have modifiers for the variable */
+    char	    endc;    	/* Ending character when variable in parens
+				 * or braces */
+    char	    startc=0;	/* Starting character when variable in parens
+				 * or braces */
+    int             cnt;	/* Used to count brace pairs when variable in
+				 * in parens or braces */
+    char    	    *start;
+    char	     delim;
+    Boolean 	    dynamic;	/* TRUE if the variable is local and we're
+				 * expanding it in a non-local context. This
+				 * is done to support dynamic sources. The
+				 * result is just the invocation, unaltered */
+    int		vlen;		/* length of variable name, after embedded variable
+				 * expansion */
+
+    *freePtr = FALSE;
+    dynamic = FALSE;
+    start = str;
+
+    if (str[1] != '(' && str[1] != '{') {
+	/*
+	 * If it's not bounded by braces of some sort, life is much simpler.
+	 * We just need to check for the first character and return the
+	 * value if it exists.
+	 */
+	char	  name[2];
+
+	name[0] = str[1];
+	name[1] = '\0';
+
+	v = VarFind (name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+	if (v == (Var *)NULL) {
+	    *lengthPtr = 2;
+
+	    if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) {
+		/*
+		 * If substituting a local variable in a non-local context,
+		 * assume it's for dynamic source stuff. We have to handle
+		 * this specially and return the longhand for the variable
+		 * with the dollar sign escaped so it makes it back to the
+		 * caller. Only four of the local variables are treated
+		 * specially as they are the only four that will be set
+		 * when dynamic sources are expanded.
+		 */
+		/* XXX: It looks like $% and $! are reversed here */
+		switch (str[1]) {
+		    case '@':
+			return("$(.TARGET)");
+		    case '%':
+			return("$(.ARCHIVE)");
+		    case '*':
+			return("$(.PREFIX)");
+		    case '!':
+			return("$(.MEMBER)");
+		    default:
+			break;
+		}
+	    }
+	    /*
+	     * Error
+	     */
+	    return (err ? var_Error : varNoError);
+	} else {
+	    haveModifier = FALSE;
+	    tstr = &str[1];
+	    endc = str[1];
+	}
+    } else {
+	/* build up expanded variable name in this buffer */
+	Buffer	buf = Buf_Init(MAKE_BSIZE);
+
+	startc = str[1];
+	endc = startc == '(' ? ')' : '}';
+
+	/*
+	 * Skip to the end character or a colon, whichever comes first,
+	 * replacing embedded variables as we go.
+	 */
+	for (tstr = str + 2; *tstr != '\0' && *tstr != endc && *tstr != ':'; tstr++)
+		if (*tstr == '$') {
+			int	rlen;
+			Boolean	rfree;
+			char*	rval = Var_Parse(tstr, ctxt, err, &rlen, &rfree);
+                
+			if (rval == var_Error) {
+				Fatal("Error expanding embedded variable.");
+			} else if (rval != NULL) {
+				Buf_AddBytes(buf, strlen(rval), (Byte *) rval);
+				if (rfree)
+					free(rval);
+			}
+			tstr += rlen - 1;
+		} else
+			Buf_AddByte(buf, (Byte) *tstr);
+	
+	if (*tstr == '\0') {
+	    /*
+	     * If we never did find the end character, return NULL
+	     * right now, setting the length to be the distance to
+	     * the end of the string, since that's what make does.
+	     */
+	    *lengthPtr = tstr - str;
+	    return (var_Error);
+	}
+	
+	haveModifier = (*tstr == ':');
+	*tstr = '\0';
+
+	Buf_AddByte(buf, (Byte) '\0');
+	str = Buf_GetAll(buf, NULL);
+	vlen = strlen(str);
+
+	v = VarFind (str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+	if ((v == (Var *)NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) &&
+	    (vlen == 2) && (str[1] == 'F' || str[1] == 'D'))
+	{
+	    /*
+	     * Check for bogus D and F forms of local variables since we're
+	     * in a local context and the name is the right length.
+	     */
+	    switch(str[0]) {
+		case '@':
+		case '%':
+		case '*':
+		case '!':
+		case '>':
+		case '<':
+		{
+		    char    vname[2];
+		    char    *val;
+
+		    /*
+		     * Well, it's local -- go look for it.
+		     */
+		    vname[0] = str[0];
+		    vname[1] = '\0';
+		    v = VarFind(vname, ctxt, 0);
+
+		    if (v != (Var *)NULL && !haveModifier) {
+			/*
+			 * No need for nested expansion or anything, as we're
+			 * the only one who sets these things and we sure don't
+			 * put nested invocations in them...
+			 */
+			val = (char *)Buf_GetAll(v->val, (int *)NULL);
+
+			if (str[1] == 'D') {
+			    val = VarModify(val, VarHead, (void *)0);
+			} else {
+			    val = VarModify(val, VarTail, (void *)0);
+			}
+			/*
+			 * Resulting string is dynamically allocated, so
+			 * tell caller to free it.
+			 */
+			*freePtr = TRUE;
+			*lengthPtr = tstr-start+1;
+			*tstr = endc;
+			Buf_Destroy(buf, TRUE);
+			return(val);
+		    }
+		    break;
+		default:
+		    break;
+		}
+	    }
+	}
+
+	if (v == (Var *)NULL) {
+	    if (((vlen == 1) ||
+		 (((vlen == 2) && (str[1] == 'F' ||
+					 str[1] == 'D')))) &&
+		((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+	    {
+		/*
+		 * If substituting a local variable in a non-local context,
+		 * assume it's for dynamic source stuff. We have to handle
+		 * this specially and return the longhand for the variable
+		 * with the dollar sign escaped so it makes it back to the
+		 * caller. Only four of the local variables are treated
+		 * specially as they are the only four that will be set
+		 * when dynamic sources are expanded.
+		 */
+		switch (str[0]) {
+		    case '@':
+		    case '%':
+		    case '*':
+		    case '!':
+			dynamic = TRUE;
+			break;
+		    default:
+			break;
+		}
+	    } else if ((vlen > 2) && (str[0] == '.') &&
+		       isupper((unsigned char) str[1]) &&
+		       ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)))
+	    {
+		int	len;
+
+		len = vlen - 1;
+		if ((strncmp(str, ".TARGET", len) == 0) ||
+		    (strncmp(str, ".ARCHIVE", len) == 0) ||
+		    (strncmp(str, ".PREFIX", len) == 0) ||
+		    (strncmp(str, ".MEMBER", len) == 0))
+		{
+		    dynamic = TRUE;
+		}
+	    }
+
+	    if (!haveModifier) {
+		/*
+		 * No modifiers -- have specification length so we can return
+		 * now.
+		 */
+		*lengthPtr = tstr - start + 1;
+		*tstr = endc;
+		if (dynamic) {
+		    str = emalloc(*lengthPtr + 1);
+		    strncpy(str, start, *lengthPtr);
+		    str[*lengthPtr] = '\0';
+		    *freePtr = TRUE;
+		    Buf_Destroy(buf, TRUE);
+		    return(str);
+		} else {
+		    Buf_Destroy(buf, TRUE);
+		    return (err ? var_Error : varNoError);
+		}
+	    } else {
+		/*
+		 * Still need to get to the end of the variable specification,
+		 * so kludge up a Var structure for the modifications
+		 */
+		v = (Var *) emalloc(sizeof(Var));
+		v->name = &str[1];
+		v->val = Buf_Init(1);
+		v->flags = VAR_JUNK;
+	    }
+	}
+	Buf_Destroy(buf, TRUE);
+    }
+
+    if (v->flags & VAR_IN_USE) {
+	Fatal("Variable %s is recursive.", v->name);
+	/*NOTREACHED*/
+    } else {
+	v->flags |= VAR_IN_USE;
+    }
+    /*
+     * Before doing any modification, we have to make sure the value
+     * has been fully expanded. If it looks like recursion might be
+     * necessary (there's a dollar sign somewhere in the variable's value)
+     * we just call Var_Subst to do any other substitutions that are
+     * necessary. Note that the value returned by Var_Subst will have
+     * been dynamically-allocated, so it will need freeing when we
+     * return.
+     */
+    str = (char *)Buf_GetAll(v->val, (int *)NULL);
+    if (strchr (str, '$') != (char *)NULL) {
+	str = Var_Subst(NULL, str, ctxt, err);
+	*freePtr = TRUE;
+    }
+
+    v->flags &= ~VAR_IN_USE;
+
+    /*
+     * Now we need to apply any modifiers the user wants applied.
+     * These are:
+     *  	  :M<pattern>	words which match the given <pattern>.
+     *  	  	    	<pattern> is of the standard file
+     *  	  	    	wildcarding form.
+     *  	  :S<d><pat1><d><pat2><d>[g]
+     *  	  	    	Substitute <pat2> for <pat1> in the value
+     *		  :C<d><pat1><d><pat2><d>[g]
+     *				Substitute <pat2> for regex <pat1> in the value
+     *  	  :H	    	Substitute the head of each word
+     *  	  :T	    	Substitute the tail of each word
+     *  	  :E	    	Substitute the extension (minus '.') of
+     *  	  	    	each word
+     *  	  :R	    	Substitute the root of each word
+     *  	  	    	(pathname minus the suffix).
+     *	    	  :lhs=rhs  	Like :S, but the rhs goes to the end of
+     *	    	    	    	the invocation.
+     *		  :U		Converts variable to upper-case.
+     *		  :L		Converts variable to lower-case.
+     */
+    if ((str != (char *)NULL) && haveModifier) {
+	/*
+	 * Skip initial colon while putting it back.
+	 */
+	*tstr++ = ':';
+	while (*tstr != endc) {
+	    char	*newStr;    /* New value to return */
+	    char	termc;	    /* Character which terminated scan */
+
+	    DEBUGF(VAR, ("Applying :%c to \"%s\"\n", *tstr, str));
+	    switch (*tstr) {
+	        case 'U':
+			if (tstr[1] == endc || tstr[1] == ':') {
+				Buffer buf;
+				buf = Buf_Init(MAKE_BSIZE);
+				for (cp = str; *cp ; cp++)
+					Buf_AddByte(buf, (Byte) toupper(*cp));
+
+				Buf_AddByte(buf, (Byte) '\0');
+				newStr = (char *) Buf_GetAll(buf, (int *) NULL);
+				Buf_Destroy(buf, FALSE);
+
+				cp = tstr + 1;
+				termc = *cp;
+				break;
+			}
+			/* FALLTHROUGH */
+		case 'L':
+			if (tstr[1] == endc || tstr[1] == ':') {
+				Buffer buf;
+				buf = Buf_Init(MAKE_BSIZE);
+				for (cp = str; *cp ; cp++)
+					Buf_AddByte(buf, (Byte) tolower(*cp));
+
+				Buf_AddByte(buf, (Byte) '\0');
+				newStr = (char *) Buf_GetAll(buf, (int *) NULL);
+				Buf_Destroy(buf, FALSE);
+
+				cp = tstr + 1;
+				termc = *cp;
+				break;
+			}
+			/* FALLTHROUGH */
+		case 'N':
+		case 'M':
+		{
+		    char    *pattern;
+		    char    *cp2;
+		    Boolean copy;
+
+		    copy = FALSE;
+		    for (cp = tstr + 1;
+			 *cp != '\0' && *cp != ':' && *cp != endc;
+			 cp++)
+		    {
+			if (*cp == '\\' && (cp[1] == ':' || cp[1] == endc)){
+			    copy = TRUE;
+			    cp++;
+			}
+		    }
+		    termc = *cp;
+		    *cp = '\0';
+		    if (copy) {
+			/*
+			 * Need to compress the \:'s out of the pattern, so
+			 * allocate enough room to hold the uncompressed
+			 * pattern (note that cp started at tstr+1, so
+			 * cp - tstr takes the null byte into account) and
+			 * compress the pattern into the space.
+			 */
+			pattern = emalloc(cp - tstr);
+			for (cp2 = pattern, cp = tstr + 1;
+			     *cp != '\0';
+			     cp++, cp2++)
+			{
+			    if ((*cp == '\\') &&
+				(cp[1] == ':' || cp[1] == endc)) {
+				    cp++;
+			    }
+			    *cp2 = *cp;
+			}
+			*cp2 = '\0';
+		    } else {
+			pattern = &tstr[1];
+		    }
+		    if (*tstr == 'M' || *tstr == 'm') {
+			newStr = VarModify(str, VarMatch, (void *)pattern);
+		    } else {
+			newStr = VarModify(str, VarNoMatch,
+					   (void *)pattern);
+		    }
+		    if (copy) {
+			free(pattern);
+		    }
+		    break;
+		}
+		case 'S':
+		{
+		    VarPattern 	    pattern;
+		    char	    del;
+		    Buffer  	    buf;    	/* Buffer for patterns */
+
+		    pattern.flags = 0;
+		    del = tstr[1];
+		    tstr += 2;
+
+		    /*
+		     * If pattern begins with '^', it is anchored to the
+		     * start of the word -- skip over it and flag pattern.
+		     */
+		    if (*tstr == '^') {
+			pattern.flags |= VAR_MATCH_START;
+			tstr += 1;
+		    }
+
+		    buf = Buf_Init(0);
+
+		    /*
+		     * Pass through the lhs looking for 1) escaped delimiters,
+		     * '$'s and backslashes (place the escaped character in
+		     * uninterpreted) and 2) unescaped $'s that aren't before
+		     * the delimiter (expand the variable substitution).
+		     * The result is left in the Buffer buf.
+		     */
+		    for (cp = tstr; *cp != '\0' && *cp != del; cp++) {
+			if ((*cp == '\\') &&
+			    ((cp[1] == del) ||
+			     (cp[1] == '$') ||
+			     (cp[1] == '\\')))
+			{
+			    Buf_AddByte(buf, (Byte)cp[1]);
+			    cp++;
+			} else if (*cp == '$') {
+			    if (cp[1] != del) {
+				/*
+				 * If unescaped dollar sign not before the
+				 * delimiter, assume it's a variable
+				 * substitution and recurse.
+				 */
+				char	    *cp2;
+				int	    len;
+				Boolean	    freeIt;
+
+				cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt);
+				Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2);
+				if (freeIt) {
+				    free(cp2);
+				}
+				cp += len - 1;
+			    } else {
+				/*
+				 * Unescaped $ at end of pattern => anchor
+				 * pattern at end.
+				 */
+				pattern.flags |= VAR_MATCH_END;
+			    }
+			} else {
+			    Buf_AddByte(buf, (Byte)*cp);
+			}
+		    }
+
+		    Buf_AddByte(buf, (Byte)'\0');
+
+		    /*
+		     * If lhs didn't end with the delimiter, complain and
+		     * return NULL
+		     */
+		    if (*cp != del) {
+			*lengthPtr = cp - start + 1;
+			if (*freePtr) {
+			    free(str);
+			}
+			Buf_Destroy(buf, TRUE);
+			Error("Unclosed substitution for %s (%c missing)",
+			      v->name, del);
+			return (var_Error);
+		    }
+
+		    /*
+		     * Fetch pattern and destroy buffer, but preserve the data
+		     * in it, since that's our lhs. Note that Buf_GetAll
+		     * will return the actual number of bytes, which includes
+		     * the null byte, so we have to decrement the length by
+		     * one.
+		     */
+		    pattern.lhs = (char *)Buf_GetAll(buf, &pattern.leftLen);
+		    pattern.leftLen--;
+		    Buf_Destroy(buf, FALSE);
+
+		    /*
+		     * Now comes the replacement string. Three things need to
+		     * be done here: 1) need to compress escaped delimiters and
+		     * ampersands and 2) need to replace unescaped ampersands
+		     * with the l.h.s. (since this isn't regexp, we can do
+		     * it right here) and 3) expand any variable substitutions.
+		     */
+		    buf = Buf_Init(0);
+
+		    tstr = cp + 1;
+		    for (cp = tstr; *cp != '\0' && *cp != del; cp++) {
+			if ((*cp == '\\') &&
+			    ((cp[1] == del) ||
+			     (cp[1] == '&') ||
+			     (cp[1] == '\\') ||
+			     (cp[1] == '$')))
+			{
+			    Buf_AddByte(buf, (Byte)cp[1]);
+			    cp++;
+			} else if ((*cp == '$') && (cp[1] != del)) {
+			    char    *cp2;
+			    int	    len;
+			    Boolean freeIt;
+
+			    cp2 = Var_Parse(cp, ctxt, err, &len, &freeIt);
+			    Buf_AddBytes(buf, strlen(cp2), (Byte *)cp2);
+			    cp += len - 1;
+			    if (freeIt) {
+				free(cp2);
+			    }
+			} else if (*cp == '&') {
+			    Buf_AddBytes(buf, pattern.leftLen,
+					 (Byte *)pattern.lhs);
+			} else {
+			    Buf_AddByte(buf, (Byte)*cp);
+			}
+		    }
+
+		    Buf_AddByte(buf, (Byte)'\0');
+
+		    /*
+		     * If didn't end in delimiter character, complain
+		     */
+		    if (*cp != del) {
+			*lengthPtr = cp - start + 1;
+			if (*freePtr) {
+			    free(str);
+			}
+			Buf_Destroy(buf, TRUE);
+			Error("Unclosed substitution for %s (%c missing)",
+			      v->name, del);
+			return (var_Error);
+		    }
+
+		    pattern.rhs = (char *)Buf_GetAll(buf, &pattern.rightLen);
+		    pattern.rightLen--;
+		    Buf_Destroy(buf, FALSE);
+
+		    /*
+		     * Check for global substitution. If 'g' after the final
+		     * delimiter, substitution is global and is marked that
+		     * way.
+		     */
+		    cp++;
+		    if (*cp == 'g') {
+			pattern.flags |= VAR_SUB_GLOBAL;
+			cp++;
+		    }
+
+		    termc = *cp;
+		    newStr = VarModify(str, VarSubstitute,
+				       (void *)&pattern);
+		    /*
+		     * Free the two strings.
+		     */
+		    free(pattern.lhs);
+		    free(pattern.rhs);
+		    break;
+		}
+		case 'C':
+		{
+		    VarREPattern    pattern;
+		    char	   *re;
+		    int		    error;
+
+		    pattern.flags = 0;
+		    delim = tstr[1];
+		    tstr += 2;
+
+		    cp = tstr;
+
+		    if ((re = VarGetPattern(ctxt, err, &cp, delim, NULL,
+			NULL, NULL)) == NULL) {
+			/* was: goto cleanup */
+			*lengthPtr = cp - start + 1;
+			if (*freePtr)
+			    free(str);
+			if (delim != '\0')
+			    Error("Unclosed substitution for %s (%c missing)",
+				  v->name, delim);
+			return (var_Error);
+		    }
+
+		    if ((pattern.replace = VarGetPattern(ctxt, err, &cp,
+			delim, NULL, NULL, NULL)) == NULL){
+			free(re);
+
+			/* was: goto cleanup */
+			*lengthPtr = cp - start + 1;
+			if (*freePtr)
+			    free(str);
+			if (delim != '\0')
+			    Error("Unclosed substitution for %s (%c missing)",
+				  v->name, delim);
+			return (var_Error);
+		    }
+
+		    for (;; cp++) {
+			switch (*cp) {
+			case 'g':
+			    pattern.flags |= VAR_SUB_GLOBAL;
+			    continue;
+			case '1':
+			    pattern.flags |= VAR_SUB_ONE;
+			    continue;
+			default:
+			    break;
+			}
+			break;
+		    }
+
+		    termc = *cp;
+
+		    error = regcomp(&pattern.re, re, REG_EXTENDED);
+		    free(re);
+		    if (error)	{
+			*lengthPtr = cp - start + 1;
+			VarREError(error, &pattern.re, "RE substitution error");
+			free(pattern.replace);
+			return (var_Error);
+		    }
+
+		    pattern.nsub = pattern.re.re_nsub + 1;
+		    if (pattern.nsub < 1)
+			pattern.nsub = 1;
+		    if (pattern.nsub > 10)
+			pattern.nsub = 10;
+		    pattern.matches = emalloc(pattern.nsub *
+					      sizeof(regmatch_t));
+		    newStr = VarModify(str, VarRESubstitute,
+				       (void *) &pattern);
+		    regfree(&pattern.re);
+		    free(pattern.replace);
+		    free(pattern.matches);
+		    break;
+		}
+		case 'Q':
+		    if (tstr[1] == endc || tstr[1] == ':') {
+			newStr = VarQuote (str);
+			cp = tstr + 1;
+			termc = *cp;
+			break;
+		    }
+		    /*FALLTHRU*/
+		case 'T':
+		    if (tstr[1] == endc || tstr[1] == ':') {
+			newStr = VarModify (str, VarTail, (void *)0);
+			cp = tstr + 1;
+			termc = *cp;
+			break;
+		    }
+		    /*FALLTHRU*/
+		case 'H':
+		    if (tstr[1] == endc || tstr[1] == ':') {
+			newStr = VarModify (str, VarHead, (void *)0);
+			cp = tstr + 1;
+			termc = *cp;
+			break;
+		    }
+		    /*FALLTHRU*/
+		case 'E':
+		    if (tstr[1] == endc || tstr[1] == ':') {
+			newStr = VarModify (str, VarSuffix, (void *)0);
+			cp = tstr + 1;
+			termc = *cp;
+			break;
+		    }
+		    /*FALLTHRU*/
+		case 'R':
+		    if (tstr[1] == endc || tstr[1] == ':') {
+			newStr = VarModify (str, VarRoot, (void *)0);
+			cp = tstr + 1;
+			termc = *cp;
+			break;
+		    }
+		    /*FALLTHRU*/
+#ifdef SUNSHCMD
+		case 's':
+		    if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) {
+			char *error;
+			newStr = Cmd_Exec (str, &error);
+			if (error)
+			    Error (error, str);
+			cp = tstr + 2;
+			termc = *cp;
+			break;
+		    }
+		    /*FALLTHRU*/
+#endif
+		default:
+		{
+#ifdef SYSVVARSUB
+		    /*
+		     * This can either be a bogus modifier or a System-V
+		     * substitution command.
+		     */
+		    VarPattern      pattern;
+		    Boolean         eqFound;
+
+		    pattern.flags = 0;
+		    eqFound = FALSE;
+		    /*
+		     * First we make a pass through the string trying
+		     * to verify it is a SYSV-make-style translation:
+		     * it must be: <string1>=<string2>)
+		     */
+		    cp = tstr;
+		    cnt = 1;
+		    while (*cp != '\0' && cnt) {
+			if (*cp == '=') {
+			    eqFound = TRUE;
+			    /* continue looking for endc */
+			}
+			else if (*cp == endc)
+			    cnt--;
+			else if (*cp == startc)
+			    cnt++;
+			if (cnt)
+			    cp++;
+		    }
+		    if (*cp == endc && eqFound) {
+
+			/*
+			 * Now we break this sucker into the lhs and
+			 * rhs. We must null terminate them of course.
+			 */
+			for (cp = tstr; *cp != '='; cp++)
+			    continue;
+			pattern.lhs = tstr;
+			pattern.leftLen = cp - tstr;
+			*cp++ = '\0';
+
+			pattern.rhs = cp;
+			cnt = 1;
+			while (cnt) {
+			    if (*cp == endc)
+				cnt--;
+			    else if (*cp == startc)
+				cnt++;
+			    if (cnt)
+				cp++;
+			}
+			pattern.rightLen = cp - pattern.rhs;
+			*cp = '\0';
+
+			/*
+			 * SYSV modifications happen through the whole
+			 * string. Note the pattern is anchored at the end.
+			 */
+			newStr = VarModify(str, VarSYSVMatch,
+					   (void *)&pattern);
+
+			/*
+			 * Restore the nulled characters
+			 */
+			pattern.lhs[pattern.leftLen] = '=';
+			pattern.rhs[pattern.rightLen] = endc;
+			termc = endc;
+		    } else
+#endif
+		    {
+			Error ("Unknown modifier '%c'\n", *tstr);
+			for (cp = tstr+1;
+			     *cp != ':' && *cp != endc && *cp != '\0';
+			     cp++)
+				 continue;
+			termc = *cp;
+			newStr = var_Error;
+		    }
+		}
+	    }
+	    DEBUGF(VAR, ("Result is \"%s\"\n", newStr));
+
+	    if (*freePtr) {
+		free (str);
+	    }
+	    str = newStr;
+	    if (str != var_Error) {
+		*freePtr = TRUE;
+	    } else {
+		*freePtr = FALSE;
+	    }
+	    if (termc == '\0') {
+		Error("Unclosed variable specification for %s", v->name);
+	    } else if (termc == ':') {
+		*cp++ = termc;
+	    } else {
+		*cp = termc;
+	    }
+	    tstr = cp;
+	}
+	*lengthPtr = tstr - start + 1;
+    } else {
+	*lengthPtr = tstr - start + 1;
+	*tstr = endc;
+    }
+
+    if (v->flags & VAR_FROM_ENV) {
+	Boolean	  destroy = FALSE;
+
+	if (str != (char *)Buf_GetAll(v->val, (int *)NULL)) {
+	    destroy = TRUE;
+	} else {
+	    /*
+	     * Returning the value unmodified, so tell the caller to free
+	     * the thing.
+	     */
+	    *freePtr = TRUE;
+	}
+	Buf_Destroy(v->val, destroy);
+	free(v);
+    } else if (v->flags & VAR_JUNK) {
+	/*
+	 * Perform any free'ing needed and set *freePtr to FALSE so the caller
+	 * doesn't try to free a static pointer.
+	 */
+	if (*freePtr) {
+	    free(str);
+	}
+	*freePtr = FALSE;
+	Buf_Destroy(v->val, TRUE);
+	free(v);
+	if (dynamic) {
+	    str = emalloc(*lengthPtr + 1);
+	    strncpy(str, start, *lengthPtr);
+	    str[*lengthPtr] = '\0';
+	    *freePtr = TRUE;
+	} else {
+	    str = err ? var_Error : varNoError;
+	}
+    }
+    return (str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Subst  --
+ *	Substitute for all variables in the given string in the given context
+ *	If undefErr is TRUE, Parse_Error will be called when an undefined
+ *	variable is encountered.
+ *
+ * Results:
+ *	The resulting string.
+ *
+ * Side Effects:
+ *	None. The old string must be freed by the caller
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Subst (char *var, char *str, GNode *ctxt, Boolean undefErr)
+{
+    Buffer  	  buf;	    	    /* Buffer for forming things */
+    char    	  *val;		    /* Value to substitute for a variable */
+    int	    	  length;   	    /* Length of the variable invocation */
+    Boolean 	  doFree;   	    /* Set true if val should be freed */
+    static Boolean errorReported;   /* Set true if an error has already
+				     * been reported to prevent a plethora
+				     * of messages when recursing */
+
+    buf = Buf_Init (MAKE_BSIZE);
+    errorReported = FALSE;
+
+    while (*str) {
+	if (var == NULL && (*str == '$') && (str[1] == '$')) {
+	    /*
+	     * A dollar sign may be escaped either with another dollar sign.
+	     * In such a case, we skip over the escape character and store the
+	     * dollar sign into the buffer directly.
+	     */
+	    str++;
+	    Buf_AddByte(buf, (Byte)*str);
+	    str++;
+	} else if (*str != '$') {
+	    /*
+	     * Skip as many characters as possible -- either to the end of
+	     * the string or to the next dollar sign (variable invocation).
+	     */
+	    char  *cp;
+
+	    for (cp = str++; *str != '$' && *str != '\0'; str++)
+		continue;
+	    Buf_AddBytes(buf, str - cp, (Byte *)cp);
+	} else {
+	    if (var != NULL) {
+		int expand;
+		for (;;) {
+		    if (str[1] != '(' && str[1] != '{') {
+			if (str[1] != *var) {
+			    Buf_AddBytes(buf, 2, (Byte *) str);
+			    str += 2;
+			    expand = FALSE;
+			}
+			else
+			    expand = TRUE;
+			break;
+		    }
+		    else {
+			char *p;
+
+			/*
+			 * Scan up to the end of the variable name.
+			 */
+			for (p = &str[2]; *p &&
+			     *p != ':' && *p != ')' && *p != '}'; p++)
+			    if (*p == '$')
+				break;
+			/*
+			 * A variable inside the variable. We cannot expand
+			 * the external variable yet, so we try again with
+			 * the nested one
+			 */
+			if (*p == '$') {
+			    Buf_AddBytes(buf, p - str, (Byte *) str);
+			    str = p;
+			    continue;
+			}
+
+			if (strncmp(var, str + 2, p - str - 2) != 0 ||
+			    var[p - str - 2] != '\0') {
+			    /*
+			     * Not the variable we want to expand, scan
+			     * until the next variable
+			     */
+			    for (;*p != '$' && *p != '\0'; p++)
+				continue;
+			    Buf_AddBytes(buf, p - str, (Byte *) str);
+			    str = p;
+			    expand = FALSE;
+			}
+			else
+			    expand = TRUE;
+			break;
+		    }
+		}
+		if (!expand)
+		    continue;
+	    }
+
+	    val = Var_Parse (str, ctxt, undefErr, &length, &doFree);
+
+	    /*
+	     * When we come down here, val should either point to the
+	     * value of this variable, suitably modified, or be NULL.
+	     * Length should be the total length of the potential
+	     * variable invocation (from $ to end character...)
+	     */
+	    if (val == var_Error || val == varNoError) {
+		/*
+		 * If performing old-time variable substitution, skip over
+		 * the variable and continue with the substitution. Otherwise,
+		 * store the dollar sign and advance str so we continue with
+		 * the string...
+		 */
+		if (oldVars) {
+		    str += length;
+		} else if (undefErr) {
+		    /*
+		     * If variable is undefined, complain and skip the
+		     * variable. The complaint will stop us from doing anything
+		     * when the file is parsed.
+		     */
+		    if (!errorReported) {
+			Parse_Error (PARSE_FATAL,
+				     "Undefined variable \"%.*s\"",length,str);
+		    }
+		    str += length;
+		    errorReported = TRUE;
+		} else {
+		    Buf_AddByte (buf, (Byte)*str);
+		    str += 1;
+		}
+	    } else {
+		/*
+		 * We've now got a variable structure to store in. But first,
+		 * advance the string pointer.
+		 */
+		str += length;
+
+		/*
+		 * Copy all the characters from the variable value straight
+		 * into the new string.
+		 */
+		Buf_AddBytes (buf, strlen (val), (Byte *)val);
+		if (doFree) {
+		    free (val);
+		}
+	    }
+	}
+    }
+
+    Buf_AddByte (buf, '\0');
+    str = (char *)Buf_GetAll (buf, (int *)NULL);
+    Buf_Destroy (buf, FALSE);
+    return (str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_GetTail --
+ *	Return the tail from each of a list of words. Used to set the
+ *	System V local variables.
+ *
+ * Results:
+ *	The resulting string.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_GetTail(char *file)
+{
+    return(VarModify(file, VarTail, (void *)0));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_GetHead --
+ *	Find the leading components of a (list of) filename(s).
+ *	XXX: VarHead does not replace foo by ., as (sun) System V make
+ *	does.
+ *
+ * Results:
+ *	The leading components.
+ *
+ * Side Effects:
+ *	None.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_GetHead(char *file)
+{
+    return(VarModify(file, VarHead, (void *)0));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Init --
+ *	Initialize the module
+ *
+ * Results:
+ *	None
+ *
+ * Side Effects:
+ *	The VAR_CMD and VAR_GLOBAL contexts are created
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Init (void)
+{
+    VAR_GLOBAL = Targ_NewGN ("Global");
+    VAR_CMD = Targ_NewGN ("Command");
+    allVars = Lst_Init(FALSE);
+
+}
+
+
+void
+Var_End (void)
+{
+    Lst_Destroy(allVars, VarDelete);
+}
+
+
+/****************** PRINT DEBUGGING INFO *****************/
+static int
+VarPrintVar (void *vp, void *dummy __unused)
+{
+    Var    *v = (Var *) vp;
+    printf ("%-16s = %s\n", v->name, (char *) Buf_GetAll(v->val, (int *)NULL));
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Dump --
+ *	print all variables in a context
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Dump (GNode *ctxt)
+{
+    Lst_ForEach (ctxt->context, VarPrintVar, (void *) 0);
+}
